[
  {
    "path": ".captain/config.yaml",
    "content": "test-suites:\n  detectors:\n    command: gotestsum --jsonfile tmp/go-test.json --raw-command -- go test -tags=detectors -timeout=15m -json -count=1 -vet=off ./pkg/detectors/...\n    results:\n      path: tmp/go-test.json\n    output:\n      print-summary: true\n    ## No retries right now\n    # retries:\n    #   attempts: 3\n    #   command: gotestsum --raw-command --jsonfile tmp/go-test.json -- go test -tags=detectors -timeout=15m -json -count=1 -vet=off {{ package }} -run '{{ run }}'\n"
  },
  {
    "path": ".gitattributes",
    "content": "*.go text eol=lf\n*.md text eol=lf"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"\"\nlabels: bug, needs triage\nassignees: trufflesecurity/product-eng\n---\n\nPlease review the [Community Note](https://github.com/trufflesecurity/trufflehog/blob/main/.github/community_note.md) before submitting\n\n### TruffleHog Version\n<!--- Please run `trufflehog --version` to show the version. If you are not running the latest version, please upgrade because your issue may have already been fixed. --->\n\n### Trace Output\n\n<!---\nPlease provide a link to a GitHub Gist containing the complete debug output. Please do NOT paste the debug output in the issue; just paste a link to the Gist.\n\nTo obtain the trace output, run trufflehog with the --log-level=5 flag.\n--->\n\n### Expected Behavior\n\n<!--- What should have happened? --->\n\n### Actual Behavior\n\n<!--- What actually happened? --->\n\n### Steps to Reproduce\n\n<!--- Please list the steps required to reproduce the issue. --->\n 1. Go to '...'\n 2. Click on '....'\n 3. Scroll down to '....'\n 4. See error\n\n## Environment\n * OS: [e.g. iOS]\n * Version [e.g. 22]\n\n## Additional Context\n<!--- Add any other context about the problem here. --->\n\n### References\n\n<!---\nInformation about referencing Github Issues: https://help.github.com/articles/basic-writing-and-formatting-syntax/#referencing-issues-and-pull-requests\n\nAre there any other GitHub issues (open or closed) or pull requests that should be linked here? Vendor documentation? For example:\n--->\n\n* #0000\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"\"\nlabels: enhancement, needs triage\nassignees: trufflesecurity/product-eng\n---\n\nPlease review the [Community Note](https://github.com/trufflesecurity/trufflehog/blob/main/.github/community_note.md) before submitting\n\n## Description\n<!--- Please leave a helpful description of the feature request here. --->\n\n### Preferred Solution\n<!--- A clear and concise description of what you want to happen. What\ninformation may be required and what would be the preferred way to provide it?\nWhat should the output include? --->\n\n### Additional Context\n<!--- Add any other context or screenshots about the feature request here. --->\n\n#### References\n\n<!---\nInformation about referencing Github Issues: https://help.github.com/articles/basic-writing-and-formatting-syntax/#referencing-issues-and-pull-requests\n\nAre there any other GitHub issues (open or closed) or pull requests that should be linked here? Vendor blog posts or documentation? For example:\n\n* #0000\n--->\n\n\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\nPlease create an issue to collect feedback prior to feature additions. Please also reference that issue in any PRs.\nIf possible try to keep PRs scoped to one feature, and add tests for new features.\n-->\n\n### Description:\nExplain the purpose of the PR.\n\n### Checklist:\n* [ ] Tests passing (`make test-community`)?\n* [ ] Lint passing (`make lint` this requires [golangci-lint](https://golangci-lint.run/welcome/install/#local-installation))?\n"
  },
  {
    "path": ".github/community_note.md",
    "content": "## Community Note\n\nPlease vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request.\n\nPlease 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.\n\nIf you are interested in working on this issue or have submitted a pull request, please leave a comment.\n"
  },
  {
    "path": ".github/renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"config:base\"\n  ],\n  \"prConcurrentLimit\": 3,\n  \"prHourlyLimit\": 2\n}\n"
  },
  {
    "path": ".github/workflows/README.md",
    "content": "# GitHub Workflows\n\nThis directory contains GitHub Actions workflows for the TruffleHog repository.\n\n## PR Approval Check (`pr-approval-check.yml`)\n\nThis 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.\n\n### How it works:\n\n1. **Triggers**: The workflow runs on:\n   - `pull_request_review` events when a review is submitted (`submitted` type)\n   - `pull_request` events when a PR is opened, reopened, or synchronized (`opened`, `reopened`, `synchronize` types)\n\n2. **Approval Check Process**: The workflow:\n   - Fetches all reviews for the PR using the GitHub API\n   - Filters for reviews with state `APPROVED`\n   - Gets all child teams of `@trufflesecurity/product-eng` using `listChildInOrg` API\n   - Checks if any approver is an **active** member (not pending) of either:\n     - The parent `@trufflesecurity/product-eng` team, OR\n     - Any of its child teams\n   - Sets a commit status accordingly\n\n3. **Status Check**: Creates a commit status named `product-eng-approval` with:\n   - ✅ **Success**: When at least one approver is an active member of `@trufflesecurity/product-eng` or any child team\n   - ❌ **Failure**: When there are no approvals or there are approvals but none from active `@trufflesecurity/product-eng` members\n\n### Error Handling\n\nIf there are errors listing reviews or checking team membership, the workflow reports a failure status and also fails itself.\n\n### Branch Protection\n\nTo make this check required:\n\n1. Go to Settings → Branches\n2. Add or edit a branch protection rule for your main branch\n3. Enable \"Require status checks to pass before merging\"\n4. Add `pr-approval-check` to the required status checks\n\n### Permissions\n\nThe workflow uses the default `GITHUB_TOKEN` which has sufficient permissions to:\n- Read PR reviews\n- List child teams and check team membership (for public teams)\n- Create commit statuses\n\n**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."
  },
  {
    "path": ".github/workflows/TESTING.md",
    "content": "# Testing\n\nMost testing is handled automatically by our GitHub Actions workflows.\n\n## Local GitHub Action Testing\n\nIn 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.\n\nFortunately [nektos/act](https://github.com/nektos/act) enables local testing of GitHub Actions.\n\n### Instructions\n\n1. Please follow [the installation instructions](http://https://github.com/nektos/act#installation) for your OS.\n2. The first run of `act` will ask you to specify an image. `Medium` should suffice.\n3. You'll need to configure a personal-access-token(PAT) with: `repo:status`, `repo_deployment`, and `public_repo` permissions.\n4. Set an environment variable named `GITHUB_TOKEN` with the PAT from the previous step as the value: `$ export GITHUB_TOKEN=<your_PAT>`\n5. Run the following command from the repository root: `act pull_request -j test -W .github/workflows/secrets.yml -s GITHUB_TOKEN --defaultbranch main`\n6. If the job was successful, you should expect to see output from the scanner showing several detected secrets.\n7. 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`\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: \"CodeQL\"\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [main]\n  schedule:\n    - cron: \"35 11 * * 2\"\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [\"go\"]\n        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]\n        # Learn more about CodeQL language support at https://git.io/codeql-language-support\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n      - name: Install Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: \"1.24\"\n      # Initializes the CodeQL tools for scanning.\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v3\n        with:\n          languages: ${{ matrix.language }}\n          # If you wish to specify custom queries, you can do so here or in a config file.\n          # By default, queries listed here will override any specified in a config file.\n          # Prefix the list here with \"+\" to use these queries and those in the config file.\n          # queries: ./path/to/local/query, your-org/your-repo/queries@main\n      - name: Smoke\n        run: |\n          go build .\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v3\n"
  },
  {
    "path": ".github/workflows/detector-tests.yml",
    "content": "name: Detectors Aggregation\n\non:\n  workflow_dispatch:\n  schedule:\n    - cron: \"0 8 * * *\"\n\njobs:\n  test-detectors:\n    if: ${{ github.repository == 'trufflesecurity/trufflehog' }}\n    runs-on: ubuntu-latest\n    permissions:\n      actions: \"read\"\n      contents: \"read\"\n      id-token: \"write\"\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-go@v5\n      - name: Install gotestsum\n        uses: jaxxstorm/action-install-gh-release@v1.14.0\n        with:\n          repo: gotestyourself/gotestsum\n      - uses: rwx-research/setup-captain@v1\n      - name: Test Go\n        run: |\n          export CGO_ENABLED=1\n          captain run detectors\n        env:\n          RWX_ACCESS_TOKEN: ${{ secrets.RWX_ACCESS_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Lint\n\non:\n  push:\n    tags:\n      - v*\n  pull_request:\n\npermissions:\n  contents: read\n  pull-requests: read\n\njobs:\n  golangci-lint:\n    name: golangci-lint\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-go@v5\n        with:\n          go-version: \"1.24\"\n      - name: golangci-lint\n        uses: golangci/golangci-lint-action@v6\n        with:\n          # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version\n          version: latest\n          # Optional: working directory, useful for monorepos\n          # working-directory: somedir\n\n          # Optional: golangci-lint command line arguments.\n          args: --enable bodyclose --enable copyloopvar --enable misspell --timeout 10m\n\n          # Optional: if set to true then the action don't cache or restore ~/go/pkg.\n          # skip-pkg-cache: true\n\n          # Optional: if set to true then the action don't cache or restore ~/.cache/go-build.\n          # skip-build-cache: true\n  semgrep:\n    name: semgrep\n    runs-on: ubuntu-latest\n    container:\n      image: returntocorp/semgrep\n    if: (github.actor != 'dependabot[bot]')\n    steps:\n      - uses: actions/checkout@v4\n      - run: semgrep --config=hack/semgrep-rules/detectors.yaml pkg/detectors/\n"
  },
  {
    "path": ".github/workflows/performance.yml",
    "content": "name: Performance Test\n\non: [pull_request]\n\njobs:\n  speed:\n    #   skip if PR is from a fork.\n    # TODO: this could probabaly be refactored a bit so that it runs on forks\n    if: ${{ ! github.event.pull_request.head.repo.fork }}\n\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n          ref: ${{ github.head_ref }}\n\n      - name: Install Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: \"1.24\"\n\n      - name: Run Head\n        run: |\n          go build -o current .\n          repo_tmp=$(mktemp -d)\n          git clone https://github.com/trufflesecurity/trufflehog.git $repo_tmp\n          cd $repo_tmp\n          git checkout v3.75.1\n\n          user_time_sum=0\n\n          for i in {1..5}\n          do\n            tmpfile=$(mktemp)\n            /usr/bin/time -o $tmpfile $GITHUB_WORKSPACE/current filesystem \"$repo_tmp\" --no-verification --no-update > out.txt\n            cat $tmpfile\n            time_output=$(cat $tmpfile)\n            rm $tmpfile\n            user_time=$(echo $time_output | awk '{print $1}' | sed 's/user//')\n\n            # Add the user time to the sum\n            user_time_sum=$(echo \"$user_time_sum + $user_time\" | bc)\n          done\n\n          average_user_time=$(echo \"scale=3; $user_time_sum / 5\" | bc)\n          echo HEAD_TIME=$average_user_time >> $GITHUB_ENV\n\n      - name: Figure out previous tag\n        run: |\n          git fetch --tags\n          git tag -l --sort=-v:refname | head -n 1 > previous_tag.txt\n          echo PREVIOUS_TAG=$(cat previous_tag.txt) >> $GITHUB_ENV\n\n      - name: Checkout code\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n          ref: ${{ env.PREVIOUS_TAG }}\n\n      - name: Run Previous\n        run: |\n          go build -o previous .\n          repo_tmp=$(mktemp -d)\n          git clone https://github.com/trufflesecurity/trufflehog.git $repo_tmp\n          cd $repo_tmp\n          git checkout v3.75.1\n\n          user_time_sum=0\n\n          for i in {1..5}\n          do\n            tmpfile=$(mktemp)\n            /usr/bin/time -o $tmpfile $GITHUB_WORKSPACE/previous filesystem \"$repo_tmp\" --no-verification --no-update > out.txt\n            cat $tmpfile\n            time_output=$(cat $tmpfile)\n            rm $tmpfile\n            user_time=$(echo $time_output | awk '{print $1}' | sed 's/user//')\n\n            # Add the user time to the sum\n            user_time_sum=$(echo \"$user_time_sum + $user_time\" | bc)\n          done\n\n          average_user_time=$(echo \"scale=3; $user_time_sum / 5\" | bc)\n          echo PREVIOUS_TIME=$average_user_time >> $GITHUB_ENV\n\n      - name: Compare Results\n        run: |\n          echo \"head ($GITHUB_SHA) avg time (n=5): $HEAD_TIME\"\n          echo \"$PREVIOUS_TAG avg time (n=5): $PREVIOUS_TIME\"\n          if [ $(echo \"$HEAD_TIME > $PREVIOUS_TIME * 1.5\" | bc) -eq 1 ]\n          then\n            echo \"HEAD run time is at least 10% slower than PREVIOUS run time\"\n            exit 1\n          fi\n"
  },
  {
    "path": ".github/workflows/release-guard.yml",
    "content": "name: Release Guard\non:\n  release:\n    types: [created]\n\npermissions:\n  contents: write\n\njobs:\n  unset-latest:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Restore previous release as latest if needed\n        run: |\n          LATEST_TAG=$(gh release list --json tagName,isLatest -q '.[] | select(.isLatest) | .tagName')\n          if [ \"$LATEST_TAG\" != \"${{ github.event.release.tag_name }}\" ]; then\n            echo \"Release is not marked as latest (latest is $LATEST_TAG), skipping.\"\n            exit 0\n          fi\n\n          echo \"Release ${{ github.event.release.tag_name }} is marked as latest, finding previous release...\"\n\n          # Get the second release in the list (sorted by date, excluding drafts/prereleases by default)\n          # The first one is the current release, so we want the second one\n          PREVIOUS_TAG=$(gh release list --exclude-drafts --exclude-pre-releases --json tagName -q '.[1].tagName')\n\n          if [ -z \"$PREVIOUS_TAG\" ]; then\n            echo \"No previous release found, cannot restore. Exiting.\"\n            exit 0\n          fi\n\n          echo \"Restoring $PREVIOUS_TAG as latest...\"\n          gh release edit \"$PREVIOUS_TAG\" --latest\n        env:\n          GH_TOKEN: ${{ github.token }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags:\n      - v*\n\npermissions:\n  contents: write\n  packages: write\n  id-token: write\n\njobs:\n  Release:\n    runs-on: ubuntu-latest\n    env:\n      DOCKER_CLI_EXPERIMENTAL: \"enabled\"\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n      - name: Docker Login to DockerHub\n        uses: docker/login-action@v3\n        with:\n          username: ${{ secrets.DOCKERHUB_USERNAME }}\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n      - name: Docker Login to GitHub Container Registry\n        uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.repository_owner }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n      - name: Set up Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: \"1.24\"\n      - name: Cosign install\n        uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2\n      - name: Install UPX\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y upx\n      - name: Run GoReleaser\n        uses: goreleaser/goreleaser-action@v6\n        with:\n          distribution: goreleaser-pro\n          version: latest\n          args: release --clean\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}\n          GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}\n      - name: Mark release as latest\n        run: gh release edit ${{ github.ref_name }} --latest\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/secrets.yml",
    "content": "name: Scan for secrets\n\non:\n  push:\n    tags:\n      - v*\n    branches:\n      - main\n  pull_request:\n  workflow_dispatch:\n\njobs:\n  test:\n    if: ${{ github.repository == 'trufflesecurity/trufflehog' && !github.event.pull_request.head.repo.fork }}\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n          ref: ${{ github.head_ref }}\n      - name: Dogfood\n        uses: ./\n        id: dogfood\n        with:\n          extra_args: --results=verified\n"
  },
  {
    "path": ".github/workflows/smoke.yml",
    "content": "name: Smoke\n\non:\n  pull_request:\n\njobs:\n  smoke:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n      - name: Install Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: \"1.24\"\n      - name: Smoke\n        run: |\n          set -e\n          go run . git https://github.com/dustin-decker/secretsandstuff.git > /dev/null\n          go run . github --repo https://github.com/dustin-decker/secretsandstuff.git > /dev/null\n  zombies:\n    runs-on: ubuntu-latest\n    timeout-minutes: 5\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n      - name: Install Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: \"1.24\"\n      - name: Run trufflehog\n        run: |\n          set -e\n          go run . git --no-verification file://. > /dev/null\n          # This case previously had a deadlock issue and left zombies after trufflehog exited #3379\n          go run . git --no-verification https://github.com/git-test-fixtures/binary.git > /dev/null\n      - name: Check for running git processes and zombies\n        run: |\n          if pgrep -x \"git\" > /dev/null\n          then\n            echo \"Git processes are still running\"\n            exit 1\n          else\n            echo \"No git processes found\"\n          fi\n\n          if ps -A -ostat,ppid | grep -e '[zZ]' > /dev/null\n          then\n            echo \"Zombie processes found\"\n            exit 1\n          else\n            echo \"No zombie processes found\"\n          fi\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\n\non:\n  push:\n    tags:\n      - v*\n    branches:\n      - main\n  pull_request:\n\njobs:\n  test:\n    if: ${{ github.repository == 'trufflesecurity/trufflehog' && !github.event.pull_request.head.repo.fork }}\n    runs-on: ubuntu-latest\n    permissions:\n      actions: \"read\"\n      contents: \"read\"\n      id-token: \"write\"\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n      - name: Install Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: \"1.24\"\n      - id: \"auth\"\n        uses: \"google-github-actions/auth@v2\"\n        with:\n          workload_identity_provider: \"projects/811013774421/locations/global/workloadIdentityPools/github-pool/providers/github-provider\"\n          service_account: \"github-ci-external@trufflehog-testing.iam.gserviceaccount.com\"\n      - name: Set up gotestsum\n        run: |\n          go install gotest.tools/gotestsum@latest\n          mkdir -p tmp/test-results\n      - name: Test\n        run: |\n          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)\n        if: ${{ success() || failure() }} # always run this step, even if there were previous errors\n      - name: Upload test results to BuildPulse for flaky test detection\n        if: ${{ !cancelled() }} # Run this step even when the tests fail. Skip if the workflow is cancelled.\n        uses: buildpulse/buildpulse-action@main\n        with:\n          account: 79229934\n          repository: 77726177\n          path: |\n            tmp/test-results/*.xml\n          key: ${{ secrets.BUILDPULSE_ACCESS_KEY_ID }}\n          secret: ${{ secrets.BUILDPULSE_SECRET_ACCESS_KEY }}\n          tags: integration\n      - name: Annotate test results\n        uses: mikepenz/action-junit-report@v5\n        if: success() || failure() # always run even if the previous step fails\n        with:\n          report_paths: \"tmp/test-results/*.xml\"\n  test-community:\n    if: ${{ github.event.pull_request.head.repo.fork }}\n    runs-on: ubuntu-latest\n    permissions:\n      actions: \"read\"\n      contents: \"read\"\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n      - name: Install Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: \"1.24\"\n      - name: Test\n        run: make test-community\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea\ndist\n.env\n*.test\n\n# binary\ntrufflehog\ntmp/go-test.json\n.captain/detectors/timings.yaml\n.captain/detectors/quarantines.yaml\n.captain/detectors/flakes.yaml\n.vscode\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "version: 2\nrelease:\n  make_latest: false\nbuilds:\n  - id: trufflehog-upx\n    binary: trufflehog\n    ldflags:\n      - -s -w -X 'github.com/trufflesecurity/trufflehog/v3/pkg/version.BuildVersion={{ .Version }}'\n    env: [CGO_ENABLED=0]\n    goos:\n    - linux\n    goarch:\n    - amd64\n    - arm64\n    hooks:\n      post:\n        - upx -q \"{{ .Path }}\"\n  - id: trufflehog\n    binary: trufflehog\n    ldflags:\n      - -X 'github.com/trufflesecurity/trufflehog/v3/pkg/version.BuildVersion={{ .Version }}'\n    env: [CGO_ENABLED=0]\n    goos:\n    - darwin\n    - windows\n    goarch:\n    - amd64\n    - arm64\ndockers:\n  - image_templates: [\"trufflesecurity/{{ .ProjectName }}:{{ .Version }}-amd64\"]\n    dockerfile: Dockerfile.goreleaser\n    extra_files:\n    - entrypoint.sh\n    use: buildx\n    build_flag_templates:\n    - --platform=linux/amd64\n    - --label=org.opencontainers.image.title={{ .ProjectName }}\n    - --label=org.opencontainers.image.description={{ .ProjectName }}\n    - --label=org.opencontainers.image.url=https://github.com/trufflesecurity/{{ .ProjectName }}\n    - --label=org.opencontainers.image.source=https://github.com/trufflesecurity/{{ .ProjectName }}\n    - --label=org.opencontainers.image.version={{ .Version }}\n    - --label=org.opencontainers.image.revision={{ .FullCommit }}\n    - --label=org.opencontainers.image.licenses=AGPL-3.0\n    - --provenance=false\n  - image_templates: [\"trufflesecurity/{{ .ProjectName }}:{{ .Version }}-arm64v8\"]\n    goarch: arm64\n    dockerfile: Dockerfile.goreleaser\n    extra_files:\n    - entrypoint.sh\n    use: buildx\n    build_flag_templates:\n    - --platform=linux/arm64/v8\n    - --label=org.opencontainers.image.title={{ .ProjectName }}\n    - --label=org.opencontainers.image.description={{ .ProjectName }}\n    - --label=org.opencontainers.image.url=https://github.com/trufflesecurity/{{ .ProjectName }}\n    - --label=org.opencontainers.image.source=https://github.com/trufflesecurity/{{ .ProjectName }}\n    - --label=org.opencontainers.image.version={{ .Version }}\n    - --label=org.opencontainers.image.revision={{ .FullCommit }}\n    - --label=org.opencontainers.image.licenses=AGPL-3.0\n    - --provenance=false\n  - image_templates: [\"ghcr.io/trufflesecurity/{{ .ProjectName }}:{{ .Version }}-amd64\"]\n    dockerfile: Dockerfile.goreleaser\n    extra_files:\n    - entrypoint.sh\n    use: buildx\n    build_flag_templates:\n    - --platform=linux/amd64\n    - --label=org.opencontainers.image.title={{ .ProjectName }}\n    - --label=org.opencontainers.image.description={{ .ProjectName }}\n    - --label=org.opencontainers.image.url=https://github.com/trufflesecurity/{{ .ProjectName }}\n    - --label=org.opencontainers.image.source=https://github.com/trufflesecurity/{{ .ProjectName }}\n    - --label=org.opencontainers.image.version={{ .Version }}\n    - --label=org.opencontainers.image.revision={{ .FullCommit }}\n    - --label=org.opencontainers.image.licenses=AGPL-3.0\n    - --provenance=false\n  - image_templates: [\"ghcr.io/trufflesecurity/{{ .ProjectName }}:{{ .Version }}-arm64v8\"]\n    goarch: arm64\n    dockerfile: Dockerfile.goreleaser\n    extra_files:\n    - entrypoint.sh\n    use: buildx\n    build_flag_templates:\n    - --platform=linux/arm64/v8\n    - --label=org.opencontainers.image.title={{ .ProjectName }}\n    - --label=org.opencontainers.image.description={{ .ProjectName }}\n    - --label=org.opencontainers.image.url=https://github.com/trufflesecurity/{{ .ProjectName }}\n    - --label=org.opencontainers.image.source=https://github.com/trufflesecurity/{{ .ProjectName }}\n    - --label=org.opencontainers.image.version={{ .Version }}\n    - --label=org.opencontainers.image.revision={{ .FullCommit }}\n    - --label=org.opencontainers.image.licenses=AGPL-3.0\n    - --provenance=false\ndocker_manifests:\n  - name_template: trufflesecurity/{{ .ProjectName }}:{{ .Version }}\n    image_templates:\n    - trufflesecurity/{{ .ProjectName }}:{{ .Version }}-amd64\n    - trufflesecurity/{{ .ProjectName }}:{{ .Version }}-arm64v8\n  - name_template: trufflesecurity/{{ .ProjectName }}:latest\n    image_templates:\n    - trufflesecurity/{{ .ProjectName }}:{{ .Version }}-amd64\n    - trufflesecurity/{{ .ProjectName }}:{{ .Version }}-arm64v8\n  - name_template: ghcr.io/trufflesecurity/{{ .ProjectName }}:{{ .Version }}\n    image_templates:\n    - ghcr.io/trufflesecurity/{{ .ProjectName }}:{{ .Version }}-amd64\n    - ghcr.io/trufflesecurity/{{ .ProjectName }}:{{ .Version }}-arm64v8\n  - name_template: ghcr.io/trufflesecurity/{{ .ProjectName }}:latest\n    image_templates:\n    - ghcr.io/trufflesecurity/{{ .ProjectName }}:{{ .Version }}-amd64\n    - ghcr.io/trufflesecurity/{{ .ProjectName }}:{{ .Version }}-arm64v8\nbrews:\n  - repository:\n      owner: trufflesecurity\n      name: homebrew-trufflehog\n      token: \"{{ .Env.HOMEBREW_TAP_TOKEN }}\"\n    description: \"Find credentials all over the place\"\n    name: \"trufflehog\"\n    homepage: \"https://github.com/trufflesecurity/trufflehog\"\n    install: |\n      bin.install \"trufflehog\"\nsigns:\n  - cmd: cosign\n    signature: \"${artifact}.sig\"\n    certificate: \"${artifact}.pem\"\n    args: \n      - \"sign-blob\"\n      - \"--oidc-issuer=https://token.actions.githubusercontent.com\"\n      - \"--output-certificate=${certificate}\"\n      - \"--output-signature=${signature}\"\n      - \"${artifact}\"\n      - \"--yes\"\n    artifacts: checksum\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/rhysd/actionlint\n    rev: v1.6.24\n    hooks:\n      - id: actionlint\n\n  - repo: https://github.com/mpalmer/action-validator\n    rev: v0.5.1\n    hooks:\n      - id: action-validator\n"
  },
  {
    "path": ".pre-commit-hooks.yaml",
    "content": "- id: trufflehog\n  name: TruffleHog\n  description: Detect secrets in your data with TruffleHog.\n  entry: trufflehog git file://. --since-commit HEAD --results=verified --fail --trust-local-git-config\n  language: golang\n  pass_filenames: false\n"
  },
  {
    "path": "CODEOWNERS",
    "content": "# catch-all\n* @trufflesecurity/product-eng\n\n# Scanning\npkg/sources/ @trufflesecurity/Scanning\npkg/writers/ @trufflesecurity/Scanning\n\n# Integrations\npkg/sources/circleci/ @trufflesecurity/Integrations\npkg/sources/docker/ @trufflesecurity/Integrations\npkg/sources/elasticsearch/ @trufflesecurity/Integrations\npkg/sources/filesystem/ @trufflesecurity/Integrations\npkg/sources/gcs/ @trufflesecurity/Integrations\npkg/sources/git/ @trufflesecurity/Integrations\npkg/sources/github/ @trufflesecurity/Integrations\npkg/sources/gitlab/ @trufflesecurity/Integrations\npkg/sources/jenkins/ @trufflesecurity/Integrations\npkg/sources/postman/ @trufflesecurity/Integrations\npkg/sources/s3/ @trufflesecurity/Integrations\npkg/sources/travisci/ @trufflesecurity/Integrations\n\n# Shared\npkg/decoders/ @trufflesecurity/Scanning @trufflesecurity/OSS\npkg/engine/ @trufflesecurity/Scanning  @trufflesecurity/OSS\npkg/gitparse/ @trufflesecurity/Scanning  @trufflesecurity/OSS\npkg/giturl/ @trufflesecurity/Scanning  @trufflesecurity/OSS\npkg/handlers/ @trufflesecurity/Scanning  @trufflesecurity/OSS\npkg/iobuf/ @trufflesecurity/Scanning  @trufflesecurity/OSS\npkg/sanitizer/ @trufflesecurity/Scanning  @trufflesecurity/OSS\nproto/ @trufflesecurity/Scanning  @trufflesecurity/Integrations\n\n# OSS\npkg/detectors/ @trufflesecurity/OSS\npkg/common/ @trufflesecurity/OSS\npkg/custom_detectors/ @trufflesecurity/OSS\npkg/analyzer/ @trufflesecurity/OSS\npkg/engine/defaults/defaults.go @trufflesecurity/OSS\npkg/engine/defaults/defaults_test.go @trufflesecurity/OSS\n\n# critical detectors\npkg/detectors/aws/ @trufflesecurity/backend\npkg/detectors/gcp/ @trufflesecurity/backend\npkg/detectors/azure/ @trufflesecurity/backend\npkg/detectors/okta/ @trufflesecurity/backend\npkg/detectors/privatekey/ @trufflesecurity/backend\npkg/detectors/slack/ @trufflesecurity/backend\npkg/detectors/slackwebhook/ @trufflesecurity/backend\npkg/detectors/microsoftteamswebhook/ @trufflesecurity/backend\npkg/detectors/twilio/ @trufflesecurity/backend\npkg/detectors/sendgrid/ @trufflesecurity/backend\npkg/detectors/gitlab/ @trufflesecurity/backend\npkg/detectors/gitlabv2/ @trufflesecurity/backend\npkg/detectors/github/ @trufflesecurity/backend\npkg/detectors/github_old/ @trufflesecurity/backend\npkg/detectors/githubapp/ @trufflesecurity/backend\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at community@trufflesec.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contribution guidelines\n\nPlease 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.)\n\nWhen 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.\n\nContributors need to [sign our CLA](https://cla-assistant.io/trufflesecurity/trufflehog) before we are able to accept contributions.\n\n# Resources\n\n## How things work\n\nIt 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:\n* [Process Flow](docs/process_flow.md)\n* [Concurrency Overview](docs/concurrency.md)\n\n## Adding new secret detectors\n\nWe 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!\n\n## Logging in TruffleHog\n\n**Use fields over format strings**. For structured logging, fields allow us to better filter and search through logs than embedding data in the message.\n\n**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.\n\nLimit log levels to _**info**_ (indicate normal or expected operation) and _**error**_ (functionality is impeded and should be checked by an engineer)\n\n**Choose an appropriate verbosity level**\n```\n0. — logs we always want to see\n1. — logs we could possibly want to turn off\n2. — logs that are useful for debugging\n3. — frequently called logs that may produce a lot of output\n4. — extremely verbose logs or logs containing sensitive information\n5. — ultimate verbosity\n```\nExample: `Logger().V(2).Info(\"skipping file: extension is ignored\", \"ext\", mimeExt)`\n\n**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.\n\n**Log contextual information**. Every log emitted should contain this context via fields to easily filter and search.\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM --platform=${BUILDPLATFORM} golang:bullseye as builder\n\nWORKDIR /build\nCOPY . . \nENV CGO_ENABLED=0\nARG TARGETOS TARGETARCH\nRUN  --mount=type=cache,target=/go/pkg/mod \\\n     --mount=type=cache,target=/root/.cache/go-build \\\n     GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o trufflehog .\n\nFROM alpine:3.22\nRUN apk add --no-cache bash git openssh-client ca-certificates rpm2cpio binutils cpio \\\n    && rm -rf /var/cache/apk/* && update-ca-certificates\nCOPY --from=builder /build/trufflehog /usr/bin/trufflehog\nCOPY entrypoint.sh /etc/entrypoint.sh\nRUN chmod +x /etc/entrypoint.sh\nENTRYPOINT [\"/etc/entrypoint.sh\"]\n"
  },
  {
    "path": "Dockerfile.goreleaser",
    "content": "FROM alpine:3.22\n\nRUN apk add --no-cache bash git openssh-client ca-certificates \\\n    && rm -rf /var/cache/apk/* && update-ca-certificates\nWORKDIR /usr/bin/\nCOPY trufflehog .\nCOPY entrypoint.sh /etc/entrypoint.sh\nRUN chmod +x /etc/entrypoint.sh\nENTRYPOINT [\"/etc/entrypoint.sh\"]\n"
  },
  {
    "path": "LICENSE",
    "content": " GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published\n    by the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<https://www.gnu.org/licenses/>.\nCopy license text to clipboard\nSuggest this license\nMake 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.\n\nEnter GitHub repository URL\nHow to apply this license\nCreate 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.\n\nOptional steps\nThe 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.\n\nAdd 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.\n\n Source\n"
  },
  {
    "path": "Makefile",
    "content": "PROTOS_IMAGE ?= trufflesecurity/protos:1.22\n\n.PHONY: check\n.PHONY: lint\n.PHONY: test\n.PHONY: test-race\n.PHONY: run\n.PHONY: install\n.PHONY: protos\n.PHONY: protos-windows\n.PHONY: vendor\n.PHONY: dogfood\n\ndogfood:\n\tCGO_ENABLED=0 go run . git file://. --json --log-level=2\n\ninstall:\n\tCGO_ENABLED=0 go install .\n\ncheck:\n\tgo fmt $(shell go list ./... | grep -v /vendor/)\n\tgo vet $(shell go list ./... | grep -v /vendor/)\n\nlint:\n\tgolangci-lint run --enable bodyclose --enable copyloopvar --enable misspell --out-format=colored-line-number --timeout 10m\n\ntest-failing:\n\tCGO_ENABLED=0 go test -timeout=5m $(shell go list ./... | grep -v /vendor/) | grep FAIL\n\ntest:\n\tCGO_ENABLED=0 go test -timeout=5m $(shell go list ./... | grep -v /vendor/)\n\ntest-integration:\n\tCGO_ENABLED=0 go test -timeout=5m -tags=integration $(shell go list ./... | grep -v /vendor/)\n\ntest-race:\n\tCGO_ENABLED=1 go test -timeout=5m -race $(shell go list ./... | grep -v /vendor/)\n\ntest-detectors:\n\tCGO_ENABLED=0 go test -tags=detectors -timeout=5m $(shell go list ./... | grep pkg/detectors)\n\ntest-community:\n\tCGO_ENABLED=0 go test -timeout=5m $(shell go list ./... | grep -v /vendor/ | grep -v pkg/sources | grep -v pkg/analyzer/analyzers)\n\nbench:\n\tCGO_ENABLED=0 go test $(shell go list ./pkg/secrets/... | grep -v /vendor/) -benchmem -run=xxx -bench .\n\nrun:\n\tCGO_ENABLED=0 go run . git file://. --json\n\nrun-debug:\n\tCGO_ENABLED=0 go run . git file://. --json --log-level=2\n\nprotos:\n\tdocker 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\"\n\nprotos-windows:\n\tdocker run --rm -v \"$(shell cygpath -w $(shell pwd))\":/pwd \"${PROTOS_IMAGE}\" bash -c \"cd /pwd; ./scripts/gen_proto.sh\"\n\nrelease-protos-image:\n\tdocker buildx build --push --platform=linux/amd64,linux/arm64 \\\n\t-t ${PROTOS_IMAGE} -f hack/Dockerfile.protos .\n\ntest-release:\n\tgoreleaser release --clean --skip-publish --snapshot\n"
  },
  {
    "path": "PreCommit.md",
    "content": "# TruffleHog Pre-Commit Hooks\n\nPre-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.\n\nThis guide covers how to set up TruffleHog as a pre-commit hook using two popular frameworks:\n\n1. [Git's hooksPath feature](#global-setup-using-gits-hookspath-feature) - A built-in Git feature for managing hooks globally\n2. [Using Pre-commit framework](#using-the-pre-commit-framework) - A language-agnostic framework for managing pre-commit hooks\n3. [Using Husky](#using-husky) - A Git hooks manager for JavaScript/Node.js projects\n\n## Prerequisites\n\nAll of the methods require TruffleHog to be installed.\n\n1. Install TruffleHog:\n\n```bash\n# Using Homebrew (macOS)\nbrew install trufflehog\n\n# Using installation script for Linux, macOS, and Windows (and WSL)\ncurl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin\n```\n\n## Global setup using Git's hooksPath feature\n\nThis approach uses Git's `core.hooksPath` to apply hooks to all repositories without requiring any per-repository setup:\n\n1. Create a global hooks directory:\n\n```bash\nmkdir -p ~/.git-hooks\n```\n\n2. Create a pre-commit hook file:\n\n```bash\ntouch ~/.git-hooks/pre-commit\nchmod +x ~/.git-hooks/pre-commit\n```\n\n3. Configure Git Hook Script\n\n### **Standard Installation**\n#### **Option A: Auto-configured (Recommended)**\n\nTruffleHog automatically detects the `TRUFFLEHOG_PRE_COMMIT` environment variable and applies optimal pre-commit settings.\n\n```bash\n#!/bin/sh\nexport TRUFFLEHOG_PRE_COMMIT=1\ntrufflehog git file://.\n```\n\n#### **Option B: Manual-configuration**\n\nManual configuration (only if you need custom behavior). Do NOT set `TRUFFLEHOG_PRE_COMMIT` if using manual configuration.\n```bash\n#!bin/sh\ntrufflehog git file://. --since-commit HEAD --results=verified,unknown --fail --trust-local-git-config\n```\n\n### **Docker Installation**\n\n#### **Option A: Auto-configured (Recommended)**\n```bash\n#!/bin/sh\n# Set environment variable inside container (recommended)\ndocker run --rm \\\n  -v \"$(pwd):/workdir\" \\\n  -e \"TRUFFLEHOG_PRE_COMMIT=1\" \\\n  trufflesecurity/trufflehog:latest \\\n  git file:///workdir\n```\n\n#### **Option B: Manual-configuration**\n```bash\n#!/bin/sh\n\ndocker run --rm -v \"$(pwd):/workdir\" -i --rm trufflesecurity/trufflehog:latest git file:///workdir --since-commit HEAD --results=verified,unknown --fail\n```\n\n4. Configure Git to use this hooks directory globally:\n\n```bash\ngit config --global core.hooksPath ~/.git-hooks\n```\n\nNow all your repositories will automatically use this pre-commit hook without any additional setup.\n\n## Using the Pre-commit Framework\n\nThe [pre-commit framework](https://pre-commit.com) is a powerful, language-agnostic tool for managing Git hooks.\n\n### Installation of Pre-commit\n\n1. Install the pre-commit framework:\n\n```bash\n# Using pip (Python)\npip install pre-commit\n\n# Using Homebrew (macOS)\nbrew install pre-commit\n\n# Using conda\nconda install -c conda-forge pre-commit\n```\n\n### Repository-Specific Setup\n\nTo set up TruffleHog as a pre-commit hook for a specific repository:\n\n1. Create a `.pre-commit-config.yaml` file in the root of your repository:\n\nTruffleHog automatically detects when running under the pre-commit.com framework and applies optimal settings. No additional configuration is needed.\n```yaml\nrepos:\n  - repo: local\n    hooks:\n      - id: trufflehog\n        name: TruffleHog\n        description: Detect secrets in your data.\n        entry: bash -c 'trufflehog git file://.'\n        language: system\n        stages: [\"pre-commit\", \"pre-push\"]\n```\n\nIf TruffleHog doesn't auto-detect your pre-commit.com environment, you can manually specify the recommended pre-commit settings:\n```yaml\nrepos:\n  - repo: local\n    hooks:\n      - id: trufflehog\n        name: TruffleHog\n        description: Detect secrets in your data.\n        entry: bash -c 'trufflehog git file://. --since-commit HEAD --results=verified,unknown --fail --trust-local-git-config'\n        language: system\n        stages: [\"pre-commit\", \"pre-push\"]\n```\n\n2. Install the pre-commit hook:\n\n```bash\npre-commit install\n```\n\n## Using Husky\n\n[Husky](https://typicode.github.io/husky/) is a popular tool for managing Git hooks in JavaScript/Node.js projects.\n\n### Installation of Husky\n\n1. Install Husky in your project:\n\n```bash\n# npm\nnpm install husky --save-dev\n\n# yarn\nyarn add husky --dev\n```\n\n2. Enable Git hooks:\n\n```bash\n# npm\nnpx husky init\n```\n\n### Setting Up TruffleHog with Husky\n\n1. Add the following content to `.husky/pre-commit`:\n\nTruffleHog automatically detects when running under the Husky framework and applies optimal settings. No additional configuration is needed.\n```bash\necho \"trufflehog git file://.\" > .husky/pre-commit\n```\n\nIf TruffleHog doesn't auto-detect your husky framework, you can manually specify the recommended pre-commit settings:\n```bash\necho \"trufflehog git file://. --since-commit HEAD --results=verified,unknown --fail --trust-local-git-config\" > .husky/pre-commit\n```\n\n2. For Docker users, use this content instead:\n\n```bash\necho 'docker run --rm -v \"$(pwd):/workdir\" -i --rm trufflesecurity/trufflehog:latest git file:///workdir' > .husky/pre-commit\n```\n\n## Best Practices\n\n### Commit Process\n\nFor optimal hook efficacy:\n\n1. Execute `git add` followed by `git commit` separately. This ensures TruffleHog analyzes all intended changes.\n2. Avoid using `git commit -am`, as it might bypass pre-commit hook execution for unstaged modifications.\n\n### Skipping Hooks\n\nIn rare cases, you may need to bypass pre-commit hooks:\n\n```bash\ngit commit --no-verify -m \"Your commit message\"\n```\n\n### Running in Audit Mode (Without TRUFFLEHOG_PRE_COMMIT env variable)\n\nYou can run the TruffleHog pre-commit hook in an \"audit\" or \"non-enforcement\" mode to test the git hook with the following commands:\n\nLocal Binary Version:\n```bash\ntrufflehog git file://. --since-commit HEAD --results=verified,unknown 2>/dev/null\n```\n\nDocker Container Version:\n```bash\ndocker run --rm -v \"$(pwd):/workdir\" -i --rm trufflesecurity/trufflehog:latest git file:///workdir --since-commit HEAD --results=verified,unknown 2>/dev/null\n```\n\nThis 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.\n\n**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.**\n\n## Troubleshooting\n\n### Hook Not Running\n\nIf your pre-commit hook isn't running:\n\n1. Ensure the hook is executable:\n\n   ```bash\n   chmod +x .git/hooks/pre-commit\n   ```\n\n2. Check if hooks are enabled:\n\n   ```bash\n   git config --get core.hooksPath\n   ```\n\n### False Positives\n\nIf you're getting false positives:\n\n1. Use the `--results=verified` flag to only show verified secrets\n2. Add `trufflehog:ignore` comments on lines with known false positives or risk-accepted findings\n\n## Conclusion\n\nBy 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.\n\nFor more information on TruffleHog's capabilities, refer to the [main documentation](README.md).\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img alt=\"GoReleaser Logo\" src=\"https://storage.googleapis.com/trufflehog-static-sources/pixel_pig.png\" height=\"140\" />\n  <h2 align=\"center\">TruffleHog</h2>\n  <p align=\"center\">Find leaked credentials.</p>\n</p>\n\n---\n\n<div align=\"center\">\n\n[![Go Report Card](https://goreportcard.com/badge/github.com/trufflesecurity/trufflehog/v3)](https://goreportcard.com/report/github.com/trufflesecurity/trufflehog/v3)\n[![License](https://img.shields.io/badge/license-AGPL--3.0-brightgreen)](/LICENSE)\n[![Total Detectors](https://img.shields.io/github/directory-file-count/trufflesecurity/truffleHog/pkg/detectors?label=Total%20Detectors&type=dir)](/pkg/detectors)\n\n</div>\n\n---\n\n# :mag_right: _Now Scanning_\n\n<div align=\"center\">\n\n<img src=\"assets/scanning_logos.svg\">\n\n**...and more**\n\nTo learn more about TruffleHog and its features and capabilities, visit our [product page](https://trufflesecurity.com/trufflehog?gclid=CjwKCAjwouexBhAuEiwAtW_Zx5IW87JNj97Ci7heFnA5ar6-DuNzT2Y5nIl9DuZ-FOUqx0Qg3vb9nxoClcEQAvD_BwE).\n\n</div>\n\n# :globe_with_meridians: TruffleHog Enterprise\n\nAre 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 <https://trufflesecurity.com/trufflehog-enterprise>.\n\nWe take the revenue from the enterprise product to fund more awesome open source projects that the whole community can benefit from.\n\n</div>\n\n# What is TruffleHog 🐽\n\nTruffleHog 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.\n\n## Discovery 🔍\n\nTruffleHog can look for secrets in many places including Git, chats, wikis, logs, API testing platforms, object stores, filesystems and more.\n\n## Classification 📁\n\nTruffleHog 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.\n\n## Validation ✅\n\nFor 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.\n\n## Analysis 🔬\n\nFor 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?\n\n# :loudspeaker: Join Our Community\n\nHave questions? Feedback? Jump into Slack or Discord and hang out with us.\n\nJoin our [Slack Community](https://join.slack.com/t/trufflehog-community/shared_invite/zt-pw2qbi43-Aa86hkiimstfdKH9UCpPzQ)\n\nJoin the [Secret Scanning Discord](https://discord.gg/8Hzbrnkr7E)\n\n# :tv: Demo\n\n![GitHub scanning demo](https://storage.googleapis.com/truffle-demos/non-interactive.svg)\n\n```bash\ndocker run --rm -it -v \"$PWD:/pwd\" trufflesecurity/trufflehog:latest github --org=trufflesecurity\n```\n\n# :floppy_disk: Installation\n\nSeveral options are available for you:\n\n### MacOS users\n\n```bash\nbrew install trufflehog\n```\n\n### Docker:\n\n<sub><i>_Ensure Docker engine is running before executing the following commands:_</i></sub>\n\n#### &nbsp;&nbsp;&nbsp;&nbsp;Unix\n\n```bash\ndocker run --rm -it -v \"$PWD:/pwd\" trufflesecurity/trufflehog:latest github --repo https://github.com/trufflesecurity/test_keys\n```\n\n#### &nbsp;&nbsp;&nbsp;&nbsp;Windows Command Prompt\n\n```bash\ndocker run --rm -it -v \"%cd:/=\\%:/pwd\" trufflesecurity/trufflehog:latest github --repo https://github.com/trufflesecurity/test_keys\n```\n\n#### &nbsp;&nbsp;&nbsp;&nbsp;Windows PowerShell\n\n```bash\ndocker run --rm -it -v \"${PWD}:/pwd\" trufflesecurity/trufflehog github --repo https://github.com/trufflesecurity/test_keys\n```\n\n#### &nbsp;&nbsp;&nbsp;&nbsp;M1 and M2 Mac\n\n```bash\ndocker run --platform linux/arm64 --rm -it -v \"$PWD:/pwd\" trufflesecurity/trufflehog:latest github --repo https://github.com/trufflesecurity/test_keys\n```\n\n### Binary releases\n\n```bash\nDownload and unpack from https://github.com/trufflesecurity/trufflehog/releases\n```\n\n### Compile from source\n\n```bash\ngit clone https://github.com/trufflesecurity/trufflehog.git\ncd trufflehog; go install\n```\n\n### Using installation script\n\n```bash\ncurl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin\n```\n\n### Using installation script, verify checksum signature (requires cosign to be installed)\n\n```bash\ncurl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -v -b /usr/local/bin\n```\n\n### Using installation script to install a specific version\n\n```bash\ncurl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin <ReleaseTag like v3.56.0>\n```\n\n# :closed_lock_with_key: Verifying the artifacts\n\nChecksums are applied to all artifacts, and the resulting checksum file is signed using cosign.\n\nYou need the following tool to verify signature:\n\n- [Cosign](https://docs.sigstore.dev/cosign/system_config/installation/)\n\nVerification steps are as follows:\n\n1. Download the artifact files you want, and the following files from the [releases](https://github.com/trufflesecurity/trufflehog/releases) page.\n\n   - trufflehog\\_{version}\\_checksums.txt\n   - trufflehog\\_{version}\\_checksums.txt.pem\n   - trufflehog\\_{version}\\_checksums.txt.sig\n\n2. Verify the signature:\n\n   ```shell\n   cosign verify-blob <path to trufflehog_{version}_checksums.txt> \\\n   --certificate <path to trufflehog_{version}_checksums.txt.pem> \\\n   --signature <path to trufflehog_{version}_checksums.txt.sig> \\\n   --certificate-identity-regexp 'https://github\\.com/trufflesecurity/trufflehog/\\.github/workflows/.+' \\\n   --certificate-oidc-issuer \"https://token.actions.githubusercontent.com\"\n   ```\n\n3. Once the signature is confirmed as valid, you can proceed to validate that the SHA256 sums align with the downloaded artifact:\n\n   ```shell\n   sha256sum --ignore-missing -c trufflehog_{version}_checksums.txt\n   ```\n\nReplace `{version}` with the downloaded files version\n\nAlternatively, if you are using the installation script, pass `-v` option to perform signature verification.\nThis requires Cosign binary to be installed prior to running the installation script.\n\n# :rocket: Quick Start\n\n## 1: Scan a repo for only verified secrets\n\nCommand:\n\n```bash\ntrufflehog git https://github.com/trufflesecurity/test_keys --results=verified\n```\n\nExpected output:\n\n```\n🐷🔑🐷  TruffleHog. Unearth your secrets. 🐷🔑🐷\n\nFound verified result 🐷🔑\nDetector Type: AWS\nDecoder Type: PLAIN\nRaw result: AKIAYVP4CIPPERUVIFXG\nLine: 4\nCommit: fbc14303ffbf8fb1c2c1914e8dda7d0121633aca\nFile: keys\nEmail: counter <counter@counters-MacBook-Air.local>\nRepository: https://github.com/trufflesecurity/test_keys\nTimestamp: 2022-06-16 10:17:40 -0700 PDT\n...\n```\n\n## 2: Scan a GitHub Org for only verified secrets\n\n```bash\ntrufflehog github --org=trufflesecurity --results=verified\n```\n\n## 3: Scan a GitHub Repo for only verified secrets and get JSON output\n\nCommand:\n\n```bash\ntrufflehog git https://github.com/trufflesecurity/test_keys --results=verified --json\n```\n\nExpected output:\n\n```\n{\"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}\n...\n```\n\n## 4: Scan a GitHub Repo + its Issues and Pull Requests\n\n```bash\ntrufflehog github --repo=https://github.com/trufflesecurity/test_keys --issue-comments --pr-comments\n```\n\n## 5: Scan an S3 bucket for high-confidence results (verified + unknown)\n\n```bash\ntrufflehog s3 --bucket=<bucket name> --results=verified,unknown\n```\n\n## 6: Scan S3 buckets using IAM Roles\n\n```bash\ntrufflehog s3 --role-arn=<iam role arn>\n```\n\n## 7: Scan a Github Repo using SSH authentication in Docker\n\n```bash\ndocker run --rm -v \"$HOME/.ssh:/root/.ssh:ro\" trufflesecurity/trufflehog:latest git ssh://github.com/trufflesecurity/test_keys\n```\n\n## 8: Scan individual files or directories\n\n```bash\ntrufflehog filesystem path/to/file1.txt path/to/file2.txt path/to/dir\n```\n\n## 9: Scan a local git repo\n\nClone the git repo. For example [test keys](git@github.com:trufflesecurity/test_keys.git) repo.\n```bash\ngit clone git@github.com:trufflesecurity/test_keys.git\n```\n\nRun trufflehog from the parent directory (outside the git repo).\n```bash\ntrufflehog git file://test_keys --results=verified,unknown\n```\n\nTo 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.\n\n## 10: Scan GCS buckets for only verified secrets\n\n```bash\ntrufflehog gcs --project-id=<project-ID> --cloud-environment --results=verified\n```\n\n## 11: Scan a Docker image for only verified secrets\n\nUse the `--image` flag multiple times to scan multiple images.\n\n```bash\n# to scan from a remote registry\ntrufflehog docker --image trufflesecurity/secrets --results=verified\n\n# to scan from the local docker daemon\ntrufflehog docker --image docker://new_image:tag --results=verified\n\n# to scan from an image saved as a tarball\ntrufflehog docker --image file://path_to_image.tar --results=verified\n```\n\n## 12: Scan in CI\n\nSet 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.\n\n```bash\ntrufflehog git file://. --since-commit main --branch feature-1 --results=verified,unknown --fail\n```\n\n## 13: Scan a Postman workspace\n\nUse the `--workspace-id`, `--collection-id`, `--environment` flags multiple times to scan multiple targets.\n\n```bash\ntrufflehog postman --token=<postman api token> --workspace-id=<workspace id>\n```\n\n## 14: Scan a Jenkins server\n\n```bash\ntrufflehog jenkins --url https://jenkins.example.com --username admin --password admin\n```\n\n## 15: Scan an Elasticsearch server\n\n### Scan a Local Cluster\n\nThere are two ways to authenticate to a local cluster with TruffleHog: (1) username and password, (2) service token.\n\n#### Connect to a local cluster with username and password\n\n```bash\ntrufflehog elasticsearch --nodes 192.168.14.3 192.168.14.4 --username truffle --password hog\n```\n\n#### Connect to a local cluster with a service token\n\n```bash\ntrufflehog elasticsearch --nodes 192.168.14.3 192.168.14.4 --service-token ‘AAEWVaWM...Rva2VuaSDZ’\n```\n\n### Scan an Elastic Cloud Cluster\n\nTo scan a cluster on Elastic Cloud, you’ll need a Cloud ID and API key.\n\n```bash\ntrufflehog elasticsearch \\\n  --cloud-id 'search-prod:dXMtY2Vx...YjM1ODNlOWFiZGRlNjI0NA==' \\\n  --api-key 'MlVtVjBZ...ZSYlduYnF1djh3NG5FQQ=='\n```\n\n## 16. Scan a GitHub Repository for Cross Fork Object References and Deleted Commits\n\nThe following command will enumerate deleted and hidden commits on a GitHub repository and then scan them for secrets. This is an alpha release feature.\n\n```bash\ntrufflehog github-experimental --repo https://github.com/<USER>/<REPO>.git --object-discovery\n```\n\nIn 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`.\n\n**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.\n\nFor 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).\n\n## 17. Scan Hugging Face\n\n### Scan a Hugging Face Model, Dataset or Space\n\n```bash\ntrufflehog huggingface --model <model_id> --space <space_id> --dataset <dataset_id>\n```\n\n### Scan all Models, Datasets and Spaces belonging to a Hugging Face Organization or User\n\n```bash\ntrufflehog huggingface --org <orgname> --user <username>\n```\n\n(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 <model_id>`, `--ignore-datasets <dataset_id>`, `--ignore-spaces <space_id>`.\n\n### Scan Discussion and PR Comments\n\n```bash\ntrufflehog huggingface --model <model_id> --include-discussions --include-prs\n```\n\n## 18. Scan stdin Input\n\n```bash\naws s3 cp s3://example/gzipped/data.gz - | gunzip -c | trufflehog stdin\n```\n\n# :question: FAQ\n\n- All I see is `🐷🔑🐷  TruffleHog. Unearth your secrets. 🐷🔑🐷` and the program exits, what gives?\n  - That means no secrets were detected\n- Why is the scan taking a long time when I scan a GitHub org\n  - Unauthenticated GitHub scans have rate limits. To improve your rate limits, include the `--token` flag with a personal access token\n- It says a private key was verified, what does that mean?\n  - 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/)\n- Is there an easy way to ignore specific secrets?\n  - 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.\n\n# :newspaper: What's new in v3?\n\nTruffleHog v3 is a complete rewrite in Go with many new powerful features.\n\n- We've **added over 700 credential detectors that support active verification against their respective APIs**.\n- We've also added native **support for scanning GitHub, GitLab, Docker, filesystems, S3, GCS, Circle CI and Travis CI**.\n- **Instantly verify private keys** against millions of github users and **billions** of TLS certificates using our [Driftwood](https://trufflesecurity.com/blog/driftwood) technology.\n- Scan binaries, documents, and other file formats\n- Available as a GitHub Action and a pre-commit hook\n\n## What is credential verification?\n\nFor 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:\n\n- **verified**: Credential confirmed as valid and active by API testing\n- **unverified**: Credential detected but not confirmed valid (may be invalid, expired, or verification disabled)  \n- **unknown**: Verification attempted but failed due to errors, such as a network or API failure\n\nFor 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.\n\n# :memo: Usage\n\nTruffleHog has a sub-command for each source of data that you may want to scan:\n\n- git\n- github\n- gitlab\n- docker\n- s3\n- filesystem (files and directories)\n- syslog\n- circleci\n- travisci\n- gcs (Google Cloud Storage)\n- postman\n- jenkins\n- elasticsearch\n- stdin\n- multi-scan\n\nEach subcommand can have options that you can see with the `--help` flag provided to the sub command:\n\n```\n$ trufflehog git --help\nusage: TruffleHog [<flags>] <command> [<args> ...]\n\nTruffleHog is a tool for finding credentials.\n\n\nFlags:\n  -h, --[no-]help                Show context-sensitive help (also try --help-long and --help-man).\n      --log-level=0              Logging verbosity on a scale of 0 (info) to 5 (trace). Can be\n                                 disabled with \"-1\".\n      --[no-]profile             Enables profiling and sets a pprof and fgprof server on :18066.\n  -j, --[no-]json                Output in JSON format.\n      --[no-]json-legacy         Use the pre-v3.0 JSON format. Only works with git, gitlab,\n                                 and github sources.\n      --[no-]github-actions      Output in GitHub Actions format.\n      --concurrency=12           Number of concurrent workers.\n      --[no-]no-verification     Don't verify the results.\n      --results=RESULTS          Specifies which type(s) of results to output: verified (confirmed\n                                 valid by API), unknown (verification failed due to error),\n                                 unverified (detected but not verified), filtered_unverified\n                                 (unverified but would have been filtered out). Defaults to\n                                 verified,unverified,unknown.\n      --[no-]no-color            Disable colorized output\n      --[no-]allow-verification-overlap\n                                 Allow verification of similar credentials across detectors\n      --[no-]filter-unverified   Only output first unverified result per chunk per detector if there\n                                 are more than one results.\n      --filter-entropy=FILTER-ENTROPY\n                                 Filter unverified results with Shannon entropy. Start with 3.0.\n      --config=CONFIG            Path to configuration file.\n      --[no-]print-avg-detector-time\n                                 Print the average time spent on each detector.\n      --[no-]no-update           Don't check for updates.\n      --[no-]fail                Exit with code 183 if results are found.\n      --[no-]fail-on-scan-errors\n                                 Exit with non-zero error code if an error occurs during the scan.\n      --verifier=VERIFIER ...    Set custom verification endpoints.\n      --[no-]custom-verifiers-only\n                                 Only use custom verification endpoints.\n      --detector-timeout=DETECTOR-TIMEOUT\n                                 Maximum time to spend scanning chunks per detector (e.g., 30s).\n      --archive-max-size=ARCHIVE-MAX-SIZE\n                                 Maximum size of archive to scan. (Byte units eg. 512B, 2KB, 4MB)\n      --archive-max-depth=ARCHIVE-MAX-DEPTH\n                                 Maximum depth of archive to scan.\n      --archive-timeout=ARCHIVE-TIMEOUT\n                                 Maximum time to spend extracting an archive.\n      --include-detectors=\"all\"  Comma separated list of detector types to include. Protobuf name or\n                                 IDs may be used, as well as ranges.\n      --exclude-detectors=EXCLUDE-DETECTORS\n                                 Comma separated list of detector types to exclude. Protobuf name\n                                 or IDs may be used, as well as ranges. IDs defined here take\n                                 precedence over the include list.\n      --[no-]no-verification-cache\n                                 Disable verification caching\n      --[no-]force-skip-binaries\n                                 Force skipping binaries.\n      --[no-]force-skip-archives\n                                 Force skipping archives.\n      --[no-]skip-additional-refs\n                                 Skip additional references.\n      --user-agent-suffix=USER-AGENT-SUFFIX\n                                 Suffix to add to User-Agent.\n      --[no-]version             Show application version.\n\nCommands:\nhelp [<command>...]\n    Show help.\n\ngit [<flags>] <uri>\n\n    Find credentials in git repositories.\n\ngithub [<flags>]\n    Find credentials in GitHub repositories.\n\ngithub-experimental --repo=REPO [<flags>]\n    Run an experimental GitHub scan. Must specify at least one experimental sub-module to run:\n    object-discovery.\n\ngitlab --token=TOKEN [<flags>]\n    Find credentials in GitLab repositories.\n\nfilesystem [<flags>] [<path>...]\n    Find credentials in a filesystem.\n\ns3 [<flags>]\n    Find credentials in S3 buckets.\n\ngcs [<flags>]\n    Find credentials in GCS buckets.\n\nsyslog --format=FORMAT [<flags>]\n    Scan syslog\n\ncircleci --token=TOKEN\n    Scan CircleCI\n\ndocker [<flags>]\n    Scan Docker Image\n\n\ntravisci --token=TOKEN\n    Scan TravisCI\n\npostman [<flags>]\n    Scan Postman\n\nelasticsearch [<flags>]\n    Scan Elasticsearch\n\njenkins --url=URL [<flags>]\n    Scan Jenkins\n\nhuggingface [<flags>]\n    Find credentials in HuggingFace datasets, models and spaces.\n\nstdin\n    Find credentials from stdin.\n\nmulti-scan\n    Find credentials in multiple sources defined in configuration.\n\njson-enumerator [<path>...]\n    Find credentials from a JSON enumerator input.\n\nanalyze\n    Analyze API keys for fine-grained permissions information.\n```\n\nFor example, to scan a `git` repository, start with\n\n```\ntrufflehog git https://github.com/trufflesecurity/trufflehog.git\n```\n\n## Configuration\n\nTruffleHog supports defining [custom regex detectors](#custom-regex-detector-alpha)\nand multiple sources in a configuration file provided via the `--config` flag.\nThe regex detectors can be used with any subcommand, while the sources defined\nin configuration are only for the `multi-scan` subcommand.\n\nThe configuration format for sources can be found on Truffle Security's\n[source configuration documentation page](https://docs.trufflesecurity.com/scan-data-for-secrets).\n\nExample GitHub source configuration and [options reference](https://docs.trufflesecurity.com/github#Fvm1I):\n\n```yaml\nsources:\n- connection:\n    '@type': type.googleapis.com/sources.GitHub\n    repositories:\n    - https://github.com/trufflesecurity/test_keys.git\n    unauthenticated: {}\n  name: example config scan\n  type: SOURCE_TYPE_GITHUB\n  verify: true\n```\n\nYou may define multiple connections under the `sources` key (see above), and\nTruffleHog will scan all of the sources concurrently.\n\n## S3\n\nThe 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.\n\nThe 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.\n\nTo scan a specific bucket using locally set credentials or instance metadata if on an EC2 instance:\n\n```bash\ntrufflehog s3 --bucket=<bucket-name>\n```\n\nTo scan a specific bucket using an assumed role:\n\n```bash\ntrufflehog s3 --bucket=<bucket-name> --role-arn=<iam-role-arn>\n```\n\nMultiple 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:\n\n```bash\ntrufflehog s3 --role-arn=<iam-role-arn-1> --role-arn=<iam-role-arn-2>\n```\n\nExit Codes:\n\n- 0: No errors and no results were found.\n- 1: An error was encountered. Sources may not have completed scans.\n- 183: No errors were encountered, but results were found. Will only be returned if `--fail` flag is used.\n\n## :octocat: TruffleHog Github Action\n\n### General Usage\n\n```\non:\n  push:\n    branches:\n      - main\n  pull_request:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n      with:\n        fetch-depth: 0\n    - name: Secret Scanning\n      uses: trufflesecurity/trufflehog@main\n      with:\n        extra_args: --results=verified,unknown\n```\n\nIn 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.\n\n### Shallow Cloning\n\nIf 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:\n\n```\n...\n      - shell: bash\n        run: |\n          if [ \"${{ github.event_name }}\" == \"push\" ]; then\n            echo \"depth=$(($(jq length <<< '${{ toJson(github.event.commits) }}') + 2))\" >> $GITHUB_ENV\n            echo \"branch=${{ github.ref_name }}\" >> $GITHUB_ENV\n          fi\n          if [ \"${{ github.event_name }}\" == \"pull_request\" ]; then\n            echo \"depth=$((${{ github.event.pull_request.commits }}+2))\" >> $GITHUB_ENV\n            echo \"branch=${{ github.event.pull_request.head.ref }}\" >> $GITHUB_ENV\n          fi\n      - uses: actions/checkout@v3\n        with:\n          ref: ${{env.branch}}\n          fetch-depth: ${{env.depth}}\n      - uses: trufflesecurity/trufflehog@main\n        with:\n          extra_args: --results=verified,unknown\n...\n```\n\nDepending 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.\n\n### Canary detection\n\nTruffleHog statically detects [https://canarytokens.org/](https://canarytokens.org/).\n\n![image](https://github.com/trufflesecurity/trufflehog/assets/52866392/74ace530-08c5-4eaf-a169-84a73e328f6f)\n\n### Advanced Usage\n\n```yaml\n- name: TruffleHog\n  uses: trufflesecurity/trufflehog@main\n  with:\n    # Repository path\n    path:\n    # Start scanning from here (usually main branch).\n    base:\n    # Scan commits until here (usually dev branch).\n    head: # optional\n    # Extra args to be passed to the trufflehog cli.\n    extra_args: --log-level=2 --results=verified,unknown\n```\n\nIf 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.\n\n#### Advanced Usage: Scan entire branch\n\n```\n- name: scan-push\n        uses: trufflesecurity/trufflehog@main\n        with:\n          base: \"\"\n          head: ${{ github.ref_name }}\n          extra_args: --results=verified,unknown\n```\n\n## TruffleHog GitLab CI\n\n### Example Usage\n\n```yaml\nstages:\n  - security\n\nsecurity-secrets:\n  stage: security\n  allow_failure: false\n  image: alpine:latest\n  variables:\n    SCAN_PATH: \".\" # Set the relative path in the repo to scan\n  before_script:\n    - apk add --no-cache git curl jq\n    - curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin\n  script:\n    - trufflehog filesystem \"$SCAN_PATH\" --results=verified,unknown --fail --json | jq\n  rules:\n    - if: '$CI_PIPELINE_SOURCE == \"merge_request_event\"'\n```\n\nIn 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.\n\n## Pre-commit Hook\n\nTruffleHog can be used in a pre-commit hook to prevent credentials from leaking before they ever leave your computer.\n\nSee the [pre-commit hook documentation](PreCommit.md) for more information.\n\n## Custom Regex Detector (alpha)\n\nTruffleHog supports detection and verification of custom regular expressions.\nFor detection, at least one **regular expression** and **keyword** is required.\nA **keyword** is a fixed literal string identifier that appears in or around\nthe regex to be detected. To allow maximum flexibility for verification, a\nwebhook is used containing the regular expression matches.\n\nTruffleHog will send a JSON POST request containing the regex matches to a\nconfigured webhook endpoint. If the endpoint responds with a `200 OK` response\nstatus code, the secret is considered verified. If verification fails due to network/API errors, the result is marked as unknown.\n\nCustom Detectors support a few different filtering mechanisms: entropy, regex targeting the entire match, regex targeting the captured secret,\nand excluded word lists checked against the secret (captured group if present, entire match if capture group is not present). Note that if\nyour 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.\n\n**NB:** This feature is alpha and subject to change.\n\n### Regex Detector Example\n[Here](/pkg/custom_detectors/CUSTOM_DETECTORS.md) is how to setup a custom regex detector with verification server.\n\n## Generic JWT Detection\n\nTruffleHog supports detection and verification of a subset of generic JWTs it finds.\nSpecifically, 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.\n\n## :mag: Analyze\n\nTruffleHog supports running a deeper analysis of a credential to view its permissions and the resources it has access to.\n\n```bash\ntrufflehog analyze\n```\n\n# :heart: Contributors\n\nThis project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].\n\n<a href=\"https://github.com/trufflesecurity/trufflehog/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=trufflesecurity/trufflehog\" />\n</a>\n\n# :computer: Contributing\n\nContributions are very welcome! Please see our [contribution guidelines first](CONTRIBUTING.md).\n\nWe no longer accept contributions to TruffleHog v2, but that code is available in the `v2` branch.\n\n## Adding new secret detectors\n\nWe 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!\n\n# Use as a library\n\nCurrently, trufflehog is in heavy development and no guarantees can be made on\nthe stability of the public APIs at this time.\n\n# License Change\n\nSince 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.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "Please report security issues to security@trufflesec.com and include `trufflehog` in the subject line. If your vulnerability involves SSRF or outbound requests, please see our policy for that specific class of vulnerability below.\n\n## Blind SSRF & Outbound Request Policy\nTruffle Security treats blind SSRF (the ability to induce outbound requests without data retrieval) as a hardening opportunity rather than a vulnerability. We do not issue CVEs or formal advisories for reports showing outbound interactions unless they demonstrate a tangible security risk to users.\n\n#### Policy Criteria\n**Vulnerability (CVE Issued):** We will issue a CVE if a researcher demonstrates a clear exploit chain. For example:\n- Credential Exfiltration: Forcing TruffleHog to send third-party secrets (discovered during a scan) or the host's own environment credentials (e.g., IAM metadata) to an attacker-controlled endpoint.\n- Internal Exploitation: Using a blind request to trigger secondary vulnerabilities (e.g. RCE) on restricted internal services configured for defense-in-depth.\n\n**Hardening (No CVE):** We generally will not issue a CVE for:\n- Reflected Payloads: Inducing a request to an attacker-controlled URL that was already present in the scanned source code (i.e., the attacker receiving their own data back).\n- Basic Outbound Control: Demonstrating control over the request URL, Path, or Body, without demonstrating a path to credential leakage or internal system exploitation.\n- Service Probing: Simple open/closed port verification or basic interaction with internal services (e.g., triggering a GET request to a local web server) without a demonstrated compromise of data or system integrity.\n- Secondary Vulnerability Dependencies: Where the impact relies entirely on the pre-existing lack of authentication, misconfiguration, or known vulnerabilities of a third-party internal service.\n\n### Submission Guidelines\nTo help us evaluate your report, please specify:\n- Level of Control: Which request components are controllable (Method, Host, Path, Headers, or Body)?\n- Secret Context: Can you prove that a legitimate secret (not the attacker's payload) is attached to or contained within the outbound request?\n- Target Reach: Can the request reach restricted internal IPs (e.g., 127.0.0.1 or 169.254.169.254)?\n- Demonstrated Impact: What is the specific risk to a user or environment beyond a simple DNS/HTTP interaction?\n"
  },
  {
    "path": "action.yml",
    "content": "name: 'TruffleHog OSS'\ndescription: 'Find and verify leaked credentials in your source code.'\nauthor: Truffle Security Co. <support@trufflesec.com>\n\ninputs:\n  path:\n    description: Repository path\n    required: false\n    default: \"./\"\n  base:\n    description: Start scanning from here (usually main branch).\n    required: false\n    default: \"\"\n  head:\n    description: Scan commits until here (usually dev branch).\n    required: false\n  extra_args:\n    default: \"\"\n    description: Extra args to be passed to the trufflehog cli.\n    required: false\n  version:\n    default: \"latest\"\n    description: Scan with this trufflehog cli version.\n    required: false\nbranding:\n  icon: \"shield\"\n  color: \"green\"\n\nruns:\n  using: \"composite\"\n  steps:\n    - shell: bash\n      working-directory: ${{ inputs.path }}\n      env:\n        BASE: ${{ inputs.base }}\n        HEAD: ${{ inputs.head }}\n        ARGS: ${{ inputs.extra_args }}\n        COMMIT_IDS: ${{ toJson(github.event.commits.*.id) }}\n        VERSION: ${{ inputs.version }}\n      run: |\n        ##########################################\n        ## ADVANCED USAGE                       ##\n        ## Scan by BASE & HEAD user inputs      ##\n        ## If BASE == HEAD, exit with error     ##\n        ##########################################\n        # Check if jq is installed, if not, install it\n        if ! command -v jq &> /dev/null\n        then\n          echo \"jq could not be found, installing...\"\n          apt-get -y update && apt-get install -y jq\n        fi\n\n        git status >/dev/null  # make sure we are in a git repository\n        if [ -n \"$BASE\" ] || [ -n \"$HEAD\" ]; then\n          if [ -n \"$BASE\" ]; then\n            base_commit=$(git rev-parse \"$BASE\" 2>/dev/null) || true\n          else\n            base_commit=\"\"\n          fi\n          if [ -n \"$HEAD\" ]; then\n            head_commit=$(git rev-parse \"$HEAD\" 2>/dev/null) || true\n          else\n            head_commit=\"\"\n          fi\n          if [ \"$base_commit\" == \"$head_commit\" ] ; then\n            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).\"\n            exit 1\n          fi\n        ##########################################\n        ## Scan commits based on event type     ##\n        ##########################################\n        else\n          if [ \"${{ github.event_name }}\" == \"push\" ]; then\n            COMMIT_LENGTH=$(printenv COMMIT_IDS | jq length)\n            if [ $COMMIT_LENGTH == \"0\" ]; then\n              echo \"No commits to scan\"\n              exit 0\n            fi\n            HEAD=${{ github.event.after }}\n            if [ ${{ github.event.before }} == \"0000000000000000000000000000000000000000\" ]; then\n              BASE=\"\"\n            else\n              BASE=${{ github.event.before }}\n            fi\n          elif [ \"${{ github.event_name }}\" == \"workflow_dispatch\" ] || [ \"${{ github.event_name }}\" == \"schedule\" ]; then\n            BASE=\"\"\n            HEAD=\"\"\n          elif [ \"${{ github.event_name }}\" == \"pull_request\" ]; then\n            BASE=${{github.event.pull_request.base.sha}}\n            HEAD=${{github.event.pull_request.head.sha}}\n          fi\n        fi\n        ##########################################\n        ##          Run TruffleHog              ##\n        ##########################################\n        docker run --rm -v .:/tmp -w /tmp \\\n        ghcr.io/trufflesecurity/trufflehog:${VERSION} \\\n        git file:///tmp/ \\\n        --since-commit \\\n        ${BASE:-''} \\\n        --branch \\\n        ${HEAD:-''} \\\n        --fail \\\n        --no-update \\\n        --github-actions \\\n        ${ARGS:-''}\n"
  },
  {
    "path": "docs/concurrency.md",
    "content": "\n\n## Concurrency\n\n```mermaid\nsequenceDiagram\n    %% Setup the workers\n    participant Main\n    Note over Main: e.startWorkers()<br />kicks off some number<br />of threads per worker type\n    create participant ScannerWorkers\n    Main->>ScannerWorkers: e.startScannerWorkers()\n    Note over ScannerWorkers: ScannerWorkers are primarily<br />responsible for enumerating<br />and chunking a source\n    create participant VerificationOverlapWorkers\n    Main->>VerificationOverlapWorkers: e.startVerificationOverlapWorkers()\n    Note over VerificationOverlapWorkers: VerificationOverlapWorkers<br />handles chunks<br />matched to multiple<br />detectors\n    create participant DetectorWorkers\n    Main->>DetectorWorkers: e.startDetectorWorkers()\n    Note over DetectorWorkers: DetectorWorkers are primarily<br />responsible for running<br />detectors on chunks\n    create participant NotifierWorkers\n    Main->>NotifierWorkers: e.startNotifierWorkers()\n    Note over NotifierWorkers: Primarily responsible for reporting<br />results (typically to the cmd line)\n    \n    %% Set up the parallelism\n    par\n        Note over Main,ScannerWorkers: Depending on the type of<br />scan requested, calls one of<br />engine.(ScanGit|ScanGitHub|ScanFileSystem|etc)\n        Main->>ScannerWorkers:  e.ChunksChan()<br /><- chunk\n    and \n        Note over ScannerWorkers: Decode chunks and find matching detectors\n        ScannerWorkers->>DetectorWorkers: e.detectableChunksChan<br /><- detectableChunk\n        Note over ScannerWorkers: When multiple detectors match on the<br />same chunk we have to decided _which_<br />detector will verify found secrets\n        ScannerWorkers->>VerificationOverlapWorkers: e.verificationOverlapChunksChan<br /><- verificationOverlapChunk\n    and\n        Note over VerificationOverlapWorkers: Decide which detectors to run on that chunk\n        VerificationOverlapWorkers->>DetectorWorkers:  e.detectableChunksChan<br /><- detectableChunk\n    and\n        Note over DetectorWorkers: Run detection (finding secrets),<br />optionally verify them<br />do filtering and enrichment\n        DetectorWorkers->>NotifierWorkers: e.ResultsChan()|e.results<br /><-detectors.ResultWithMetadata\n    and\n        Note over NotifierWorkers: Write results to output\n    end\n        \n    \n```"
  },
  {
    "path": "docs/iterative_decoding_performance.md",
    "content": "# Iterative Decoding Performance\n\nPerformance characteristics of the `--max-decode-depth` feature, which enables\nchained decoding (e.g., base64 inside UTF-16, double-encoded base64).\n\n## How it works\n\nAt depth 0, all decoders run on the original chunk (identical to pre-existing\nbehavior). When a decoder produces new output, that output is fed back through\nall decoders at the next depth level. The loop exits early when no decoder\nproduces new data, so unused depth levels are effectively free.\n\nThe PLAIN (UTF-8) decoder is skipped at depth > 0 since it's a passthrough\nthat never transforms data produced by other decoders (their output is already\nvalid UTF-8/ASCII).\n\n## Filesystem scan benchmark\n\nScanned the trufflehog repository (~4,500 files) with `--no-verification`\nand `--concurrency=1` for deterministic comparison.\n\n| Depth | Wall time | Unique results | Delta vs depth=1 |\n|-------|-----------|----------------|-------------------|\n| 1     | 8.05s     | 924            | —                 |\n| 2     | 8.18s     | 927            | +3, +1.6%         |\n| 3     | 8.09s     | 928            | +4, +0.5%         |\n| 5     | 8.19s     | 928            | +4, +1.7%         |\n| 10    | 8.35s     | 932            | +8, +3.7%         |\n\nResults converge by depth 3. Depths 4–5 produce no additional decoded data in\nthis corpus, so they add only a single `len() == 0` check per chunk per extra\ndepth level.\n\nThe small unique-result variance at depth 10 is from pre-existing\nnondeterminism in the concurrent detector workers' dedup ordering, not from the\ndecoding itself.\n\n## Per-decoder microbenchmarks\n\nIndividual decoder cost is unchanged by this feature (decoders are not\nmodified). For reference, base64 decoder latency on random data:\n\n| Input size | Latency/op | Allocs    |\n|------------|------------|-----------|\n| 100 B      | ~250 ns    | 96 B / 2  |\n| 1 KB       | ~2.25 µs   | 96 B / 2  |\n| 10 KB      | ~44 ns     | 96 B / 2  |\n\nThe 10 KB case is fast because random bytes rarely form valid base64 substrings\n(the 20-character minimum threshold is never met), so the decoder exits after a\nsingle O(n) character scan.\n\n## Memory overhead\n\nEach depth level that produces new decoded data stores one copy of the output\n(typically smaller than the input, since base64 decoding shrinks by ~25%).\nA `seen` list (slice of byte slices) prevents reprocessing identical data.\nAt depth 5 on a typical chunk, this list has 0–3 entries. No hashing or maps\nare used.\n\n## Choosing a depth\n\n| Depth | Use case |\n|-------|----------|\n| 1     | Legacy behavior, no chaining |\n| 2     | Covers base64-in-base64, base64-in-UTF-16, base64-in-escaped-unicode |\n| 5     | Default. Handles deeply nested configs with no measurable cost over depth 2 |\n"
  },
  {
    "path": "docs/process_flow.md",
    "content": "# TruffleHog Process Flows\n\n## Scans\n\n## Data Flow\n\n```mermaid\nflowchart LR\n    SourceDecomposition[\"`**Source Decomposition**\n\nBreaking up the locations that we are looking _for_ secrets into small chunks`\"]\n\n    DetectorMatching{Chunk<br/>to<br/>Detector<br/>Matching}\n    \n    SecretDetection[\"`**Secret Detection**\n\nFinding secrets in these chunks and (optionally) verifying whether they are live`\"]\n\n    ResultNotification[\"`**Result Notification**\n\nEnriching results with metadata and (usually) printing to console`\"]\n    \n    SourceDecomposition -- chunks --> DetectorMatching\n    DetectorMatching -- matched chunks --> SecretDetection\n    SecretDetection -- results --> ResultNotification\n```\n\n#### Source Decomposition\n\n```mermaid\nflowchart TD\n    subgraph Source\n        direction TB\n        SourceDescription(\"`**(1)** Sources are top level places we find data/files/text to _scan_`\")\n        GitSource[\"git Source\"]\n        GitHubSource[\"GitHub Source\"]\n        FilesystemSource[\"File System Source\"]\n        PostmanSource[\"Postman Source\"]\n    end\n\n    subgraph Unit\n        direction TB\n        UnitDescription(\"`**(2)** Units are natural subdivisions of Sources, but still quite large`\")\n        FilesystemUnit[Directory]\n        GitUnit[Git Repository]\n    end\n\n    subgraph Chunk\n        direction TB\n        ChunkDescription(\"`**(3)** Chunks are the smallest units that we decompose our chunks into, and are subsequent passed on to detection`\")\n        FilesystemChunk[file contents]\n        GitRepositoryChunk[\"`git log diff hunks`\"]\n        PostmanChunk[data chunk]\n    end\n\n\n    SourceDescription -- decomposed into --> UnitDescription\n    UnitDescription -- further decomposed into --> ChunkDescription\n\n\n    GitSource -- cloned locally<br />if not already local --> GitUnit\n    GitHubSource -- cloned locally --> GitUnit\n    PostmanSource -- Most sources\\ndon't use units --> PostmanChunk\n    FilesystemSource --> FilesystemUnit\n\n    GitUnit -- git log -p --> GitRepositoryChunk\n    FilesystemUnit --> FilesystemChunk\n\n    style SourceDescription fill:#89553e\n    style UnitDescription fill:#89553e\n    style ChunkDescription fill:#89553e\n```\n\n#### Chunk to Detector Matching\n\n```mermaid\nflowchart LR\n\n\n    KeywordMatching[\"`**Keyword Matching**\n_(Aho-Corasick)_\n\nMatch chunks to detectors based on the presence of specific keywords in the chunk`\"]\n    \n    chunks --> KeywordMatching --> detectors\n```\n\n#### Secret Detection\n\n```mermaid\nflowchart LR\n\nsubgraph Detector\n    direction RL\n    subgraph DetectorDescription[\"  \"]\n        DetectorDescriptionText[\"`Detectors are the bits that actually check for the existence of a secret in a chunk, and (optionally) verify it`\"]\n        ExampleDetectors[\"`Example Detectors:\n                    * AWS\n                    * Azure\n                    * Twilio`\"]\n    end\n    \n    subgraph DetectorResponsibility[\" \"]\n        direction LR\n\n        De-Dupe-Detectors[\"`**De-Dupe-Detectors**\n\nIf 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)`\"]\n\n        CollectMatches[\"`**Collect Matches**\n\nDetector specific regexes are run against the matched chunks, resulting in unverified secrets`\"]\n        VerifyMatches[\"`**Verify Matches**\n\nOptionally, observed unverified secrets are verified by attempting to use them against live services`\"]\n        \n        De-Dupe-Detectors -- deduped detectors --> CollectMatches\n        CollectMatches -- regex matched chunks --> VerifyMatches\n    end\n    \n    style DetectorDescription fill:#89553e\n    style DetectorDescriptionText fill:#89553e\nend\n```\n\n#### Result Notification\n\n```mermaid\nflowchart LR\n\n    Dispatcher[\"`**Dispatcher**\n\nResults, verified or otherwise, are sent to a dispatcher to be sent to whichever place we're updating about the \nresults -- usually the command line.`\"]\n    \n    results --> Dispatcher --> output\n```\n\n"
  },
  {
    "path": "entrypoint.sh",
    "content": "#!/usr/bin/env bash\n\n# Parse the last argument into an array of extra_args.\nmapfile -t extra_args < <(bash -c \"for arg in ${*: -1}; do echo \\$arg; done\")\n\n# Directories might be owned by a user other than root\ngit config --global --add safe.directory '*'\n\nif [[ $# -eq 0 ]]; then\n  /usr/bin/trufflehog --help\nelse\n  /usr/bin/trufflehog \"${@: 1: $#-1}\" \"${extra_args[@]}\"\nfi\n"
  },
  {
    "path": "examples/README.md",
    "content": "# Examples\nThis folder contains various examples like custom detectors, scripts, etc. Feel free to contribute!\n\n### Generic Detector\nAn 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. \n\n#### Try it out:\n```\nwget https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/examples/generic.yml\ntrufflehog filesystem --config=$PWD/generic.yml $PWD\n\n# to filter so that _only_ generic credentials are logged:\ntrufflehog filesystem --config=$PWD/generic.yml --json --no-verification $PWD | awk '/generic-api-key/{print $0}'\n```\n"
  },
  {
    "path": "examples/generic.yml",
    "content": "detectors:\n- name: generic-api-key\n  keywords:\n  - key\n  - api\n  - token\n  - secret\n  - client\n  - passwd\n  - password\n  - auth\n  - access\n  regex:\n    # borrowing the gitleaks generic-api-key regex\n    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|;]|$)\"\n"
  },
  {
    "path": "examples/generic_with_filters.yml",
    "content": "detectors:\n- name: generic-password\n  keywords:\n  - pass\n  - access\n  - auth\n  - credential\n  - cred\n  - secret\n  - token\n  regex:\n    secret: |-\n      (?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]|$)\n  validations:\n    secret: # name of the regex to apply these validations to\n      contains_digit: true\n      contains_special_char: true\n  entropy: 3.5\n  # exclude_regexes_capture:\n  #   - |-\n  #     (?i)(?:ignore)\n  exclude_regexes_match:\n    - |-\n      (?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)))\n  exclude_words:\n    - \"exclude\"\n    - \"000000\"\n    - \"aaaaaa\"\n    - \"about\"\n    - \"abstract\"\n    - \"academy\"\n    - \"acces\"\n    - \"account\"\n    - \"act-\"\n    - \"act.\"\n    - \"act_\"\n    - \"action\"\n    - \"active\"\n    - \"actively\"\n    - \"activity\"\n    - \"adapter\"\n    - \"add-\"\n    - \"add.\"\n    - \"add_\"\n    - \"add-on\"\n    - \"addon\"\n    - \"addres\"\n    - \"admin\"\n    - \"adobe\"\n    - \"advanced\"\n    - \"adventure\"\n    - \"agent\"\n    - \"agile\"\n    - \"air-\"\n    - \"air.\"\n    - \"air_\"\n    - \"ajax\"\n    - \"akka\"\n    - \"alert\"\n    - \"alfred\"\n    - \"algorithm\"\n    - \"all-\"\n    - \"all.\"\n    - \"all_\"\n    - \"alloy\"\n    - \"alpha\"\n    - \"amazon\"\n    - \"amqp\"\n    - \"analysi\"\n    - \"analytic\"\n    - \"analyzer\"\n    - \"android\"\n    - \"angular\"\n    - \"angularj\"\n    - \"animate\"\n    - \"animation\"\n    - \"another\"\n    - \"ansible\"\n    - \"answer\"\n    - \"ant-\"\n    - \"ant.\"\n    - \"ant_\"\n    - \"any-\"\n    - \"any.\"\n    - \"any_\"\n    - \"apache\"\n    - \"app-\"\n    - \"app-\"\n    - \"app.\"\n    - \"app.\"\n    - \"app_\"\n    - \"app_\"\n    - \"apple\"\n    - \"arch\"\n    - \"archive\"\n    - \"archived\"\n    - \"arduino\"\n    - \"array\"\n    - \"art-\"\n    - \"art.\"\n    - \"art_\"\n    - \"article\"\n    - \"asp-\"\n    - \"asp.\"\n    - \"asp_\"\n    - \"asset\"\n    - \"async\"\n    - \"atom\"\n    - \"attention\"\n    - \"audio\"\n    - \"audit\"\n    - \"aura\"\n    - \"auth\"\n    - \"author\"\n    - \"author\"\n    - \"authorize\"\n    - \"auto\"\n    - \"automated\"\n    - \"automatic\"\n    - \"awesome\"\n    - \"aws_\"\n    - \"azure\"\n    - \"back\"\n    - \"backbone\"\n    - \"backend\"\n    - \"backup\"\n    - \"bar-\"\n    - \"bar.\"\n    - \"bar_\"\n    - \"base\"\n    - \"based\"\n    - \"bash\"\n    - \"basic\"\n    - \"batch\"\n    - \"been\"\n    - \"beer\"\n    - \"behavior\"\n    - \"being\"\n    - \"benchmark\"\n    - \"best\"\n    - \"beta\"\n    - \"better\"\n    - \"big-\"\n    - \"big.\"\n    - \"big_\"\n    - \"binary\"\n    - \"binding\"\n    - \"bit-\"\n    - \"bit.\"\n    - \"bit_\"\n    - \"bitcoin\"\n    - \"block\"\n    - \"blog\"\n    - \"board\"\n    - \"book\"\n    - \"bookmark\"\n    - \"boost\"\n    - \"boot\"\n    - \"bootstrap\"\n    - \"bosh\"\n    - \"bot-\"\n    - \"bot.\"\n    - \"bot_\"\n    - \"bower\"\n    - \"box-\"\n    - \"box.\"\n    - \"box_\"\n    - \"boxen\"\n    - \"bracket\"\n    - \"branch\"\n    - \"bridge\"\n    - \"browser\"\n    - \"brunch\"\n    - \"buffer\"\n    - \"bug-\"\n    - \"bug.\"\n    - \"bug_\"\n    - \"build\"\n    - \"builder\"\n    - \"building\"\n    - \"buildout\"\n    - \"buildpack\"\n    - \"built\"\n    - \"bundle\"\n    - \"busines\"\n    - \"but-\"\n    - \"but.\"\n    - \"but_\"\n    - \"button\"\n    - \"cache\"\n    - \"caching\"\n    - \"cakephp\"\n    - \"calendar\"\n    - \"call\"\n    - \"camera\"\n    - \"campfire\"\n    - \"can-\"\n    - \"can.\"\n    - \"can_\"\n    - \"canva\"\n    - \"captcha\"\n    - \"capture\"\n    - \"card\"\n    - \"carousel\"\n    - \"case\"\n    - \"cassandra\"\n    - \"cat-\"\n    - \"cat.\"\n    - \"cat_\"\n    - \"category\"\n    - \"center\"\n    - \"cento\"\n    - \"challenge\"\n    - \"change\"\n    - \"changelog\"\n    - \"channel\"\n    - \"chart\"\n    - \"chat\"\n    - \"cheat\"\n    - \"check\"\n    - \"checker\"\n    - \"chef\"\n    - \"ches\"\n    - \"chinese\"\n    - \"chosen\"\n    - \"chrome\"\n    - \"ckeditor\"\n    - \"clas\"\n    - \"classe\"\n    - \"classic\"\n    - \"clean\"\n    - \"cli-\"\n    - \"cli.\"\n    - \"cli_\"\n    - \"client\"\n    - \"client\"\n    - \"clojure\"\n    - \"clone\"\n    - \"closure\"\n    - \"cloud\"\n    - \"club\"\n    - \"cluster\"\n    - \"cms-\"\n    - \"cms_\"\n    - \"coco\"\n    - \"code\"\n    - \"coding\"\n    - \"coffee\"\n    - \"color\"\n    - \"combination\"\n    - \"combo\"\n    - \"command\"\n    - \"commander\"\n    - \"comment\"\n    - \"commit\"\n    - \"common\"\n    - \"community\"\n    - \"compas\"\n    - \"compiler\"\n    - \"complete\"\n    - \"component\"\n    - \"composer\"\n    - \"computer\"\n    - \"computing\"\n    - \"con-\"\n    - \"con.\"\n    - \"con_\"\n    - \"concept\"\n    - \"conf\"\n    - \"config\"\n    - \"config\"\n    - \"connect\"\n    - \"connector\"\n    - \"console\"\n    - \"contact\"\n    - \"container\"\n    - \"contao\"\n    - \"content\"\n    - \"contest\"\n    - \"context\"\n    - \"control\"\n    - \"convert\"\n    - \"converter\"\n    - \"conway'\"\n    - \"cookbook\"\n    - \"cookie\"\n    - \"cool\"\n    - \"copy\"\n    - \"cordova\"\n    - \"core\"\n    - \"couchbase\"\n    - \"couchdb\"\n    - \"countdown\"\n    - \"counter\"\n    - \"course\"\n    - \"craft\"\n    - \"crawler\"\n    - \"create\"\n    - \"creating\"\n    - \"creator\"\n    - \"credential\"\n    - \"crm-\"\n    - \"crm.\"\n    - \"crm_\"\n    - \"cros\"\n    - \"crud\"\n    - \"csv-\"\n    - \"csv.\"\n    - \"csv_\"\n    - \"cube\"\n    - \"cucumber\"\n    - \"cuda\"\n    - \"current\"\n    - \"currently\"\n    - \"custom\"\n    - \"daemon\"\n    - \"dark\"\n    - \"dart\"\n    - \"dash\"\n    - \"dashboard\"\n    - \"data\"\n    - \"database\"\n    - \"date\"\n    - \"day-\"\n    - \"day.\"\n    - \"day_\"\n    - \"dead\"\n    - \"debian\"\n    - \"debug\"\n    - \"debug\"\n    - \"debugger\"\n    - \"deck\"\n    - \"define\"\n    - \"del-\"\n    - \"del.\"\n    - \"del_\"\n    - \"delete\"\n    - \"demo\"\n    - \"deploy\"\n    - \"design\"\n    - \"designer\"\n    - \"desktop\"\n    - \"detection\"\n    - \"detector\"\n    - \"dev-\"\n    - \"dev.\"\n    - \"dev_\"\n    - \"develop\"\n    - \"developer\"\n    - \"device\"\n    - \"devise\"\n    - \"diff\"\n    - \"digital\"\n    - \"directive\"\n    - \"directory\"\n    - \"discovery\"\n    - \"display\"\n    - \"django\"\n    - \"dns-\"\n    - \"dns_\"\n    - \"doc-\"\n    - \"doc-\"\n    - \"doc.\"\n    - \"doc.\"\n    - \"doc_\"\n    - \"doc_\"\n    - \"docker\"\n    - \"docpad\"\n    - \"doctrine\"\n    - \"document\"\n    - \"doe-\"\n    - \"doe.\"\n    - \"doe_\"\n    - \"dojo\"\n    - \"dom-\"\n    - \"dom.\"\n    - \"dom_\"\n    - \"domain\"\n    - \"done\"\n    - \"don't\"\n    - \"dot-\"\n    - \"dot.\"\n    - \"dot_\"\n    - \"dotfile\"\n    - \"download\"\n    - \"draft\"\n    - \"drag\"\n    - \"drill\"\n    - \"drive\"\n    - \"driven\"\n    - \"driver\"\n    - \"drop\"\n    - \"dropbox\"\n    - \"drupal\"\n    - \"dsl-\"\n    - \"dsl.\"\n    - \"dsl_\"\n    - \"dynamic\"\n    - \"easy\"\n    - \"_ec2_\"\n    - \"ecdsa\"\n    - \"eclipse\"\n    - \"edit\"\n    - \"editing\"\n    - \"edition\"\n    - \"editor\"\n    - \"element\"\n    - \"emac\"\n    - \"email\"\n    - \"embed\"\n    - \"embedded\"\n    - \"ember\"\n    - \"emitter\"\n    - \"emulator\"\n    - \"encoding\"\n    - \"endpoint\"\n    - \"engine\"\n    - \"english\"\n    - \"enhanced\"\n    - \"entity\"\n    - \"entry\"\n    - \"env_\"\n    - \"episode\"\n    - \"erlang\"\n    - \"error\"\n    - \"espresso\"\n    - \"event\"\n    - \"evented\"\n    - \"example\"\n    - \"example\"\n    - \"exchange\"\n    - \"exercise\"\n    - \"experiment\"\n    - \"expire\"\n    - \"exploit\"\n    - \"explorer\"\n    - \"export\"\n    - \"exporter\"\n    - \"expres\"\n    - \"ext-\"\n    - \"ext.\"\n    - \"ext_\"\n    - \"extended\"\n    - \"extension\"\n    - \"external\"\n    - \"extra\"\n    - \"extractor\"\n    - \"fabric\"\n    - \"facebook\"\n    - \"factory\"\n    - \"fake\"\n    - \"fast\"\n    - \"feature\"\n    - \"feed\"\n    - \"fewfwef\"\n    - \"ffmpeg\"\n    - \"field\"\n    - \"file\"\n    - \"filter\"\n    - \"find\"\n    - \"finder\"\n    - \"firefox\"\n    - \"firmware\"\n    - \"first\"\n    - \"fish\"\n    - \"fix-\"\n    - \"fix_\"\n    - \"flash\"\n    - \"flask\"\n    - \"flat\"\n    - \"flex\"\n    - \"flexible\"\n    - \"flickr\"\n    - \"flow\"\n    - \"fluent\"\n    - \"fluentd\"\n    - \"fluid\"\n    - \"folder\"\n    - \"font\"\n    - \"force\"\n    - \"foreman\"\n    - \"fork\"\n    - \"form\"\n    - \"format\"\n    - \"formatter\"\n    - \"forum\"\n    - \"foundry\"\n    - \"framework\"\n    - \"free\"\n    - \"friend\"\n    - \"friendly\"\n    - \"front-end\"\n    - \"frontend\"\n    - \"ftp-\"\n    - \"ftp.\"\n    - \"ftp_\"\n    - \"fuel\"\n    - \"full\"\n    - \"fun-\"\n    - \"fun.\"\n    - \"fun_\"\n    - \"func\"\n    - \"future\"\n    - \"gaia\"\n    - \"gallery\"\n    - \"game\"\n    - \"gateway\"\n    - \"gem-\"\n    - \"gem.\"\n    - \"gem_\"\n    - \"gen-\"\n    - \"gen.\"\n    - \"gen_\"\n    - \"general\"\n    - \"generator\"\n    - \"generic\"\n    - \"genetic\"\n    - \"get-\"\n    - \"get.\"\n    - \"get_\"\n    - \"getenv\"\n    - \"getting\"\n    - \"ghost\"\n    - \"gist\"\n    - \"git-\"\n    - \"git.\"\n    - \"git_\"\n    - \"github\"\n    - \"gitignore\"\n    - \"gitlab\"\n    - \"glas\"\n    - \"gmail\"\n    - \"gnome\"\n    - \"gnu-\"\n    - \"gnu.\"\n    - \"gnu_\"\n    - \"goal\"\n    - \"golang\"\n    - \"gollum\"\n    - \"good\"\n    - \"google\"\n    - \"gpu-\"\n    - \"gpu.\"\n    - \"gpu_\"\n    - \"gradle\"\n    - \"grail\"\n    - \"graph\"\n    - \"graphic\"\n    - \"great\"\n    - \"grid\"\n    - \"groovy\"\n    - \"group\"\n    - \"grunt\"\n    - \"guard\"\n    - \"gui-\"\n    - \"gui.\"\n    - \"gui_\"\n    - \"guide\"\n    - \"guideline\"\n    - \"gulp\"\n    - \"gwt-\"\n    - \"gwt.\"\n    - \"gwt_\"\n    - \"hack\"\n    - \"hackathon\"\n    - \"hacker\"\n    - \"hacking\"\n    - \"hadoop\"\n    - \"haml\"\n    - \"handler\"\n    - \"hardware\"\n    - \"has-\"\n    - \"has_\"\n    - \"hash\"\n    - \"haskell\"\n    - \"have\"\n    - \"haxe\"\n    - \"hello\"\n    - \"help\"\n    - \"helper\"\n    - \"here\"\n    - \"hero\"\n    - \"heroku\"\n    - \"high\"\n    - \"hipchat\"\n    - \"history\"\n    - \"home\"\n    - \"homebrew\"\n    - \"homepage\"\n    - \"hook\"\n    - \"host\"\n    - \"hosting\"\n    - \"hot-\"\n    - \"hot.\"\n    - \"hot_\"\n    - \"house\"\n    - \"how-\"\n    - \"how.\"\n    - \"how_\"\n    - \"html\"\n    - \"http\"\n    - \"hub-\"\n    - \"hub.\"\n    - \"hub_\"\n    - \"hubot\"\n    - \"human\"\n    - \"icon\"\n    - \"ide-\"\n    - \"ide.\"\n    - \"ide_\"\n    - \"idea\"\n    - \"identity\"\n    - \"idiomatic\"\n    - \"image\"\n    - \"impact\"\n    - \"import\"\n    - \"important\"\n    - \"importer\"\n    - \"impres\"\n    - \"index\"\n    - \"infinite\"\n    - \"info\"\n    - \"injection\"\n    - \"inline\"\n    - \"input\"\n    - \"inside\"\n    - \"inspector\"\n    - \"instagram\"\n    - \"install\"\n    - \"installer\"\n    - \"instant\"\n    - \"intellij\"\n    - \"interface\"\n    - \"internet\"\n    - \"interview\"\n    - \"into\"\n    - \"intro\"\n    - \"ionic\"\n    - \"iphone\"\n    - \"ipython\"\n    - \"irc-\"\n    - \"irc_\"\n    - \"iso-\"\n    - \"iso.\"\n    - \"iso_\"\n    - \"issue\"\n    - \"jade\"\n    - \"jasmine\"\n    - \"java\"\n    - \"jbos\"\n    - \"jekyll\"\n    - \"jenkin\"\n    - \"jetbrains\"\n    - \"job-\"\n    - \"job.\"\n    - \"job_\"\n    - \"joomla\"\n    - \"jpa-\"\n    - \"jpa.\"\n    - \"jpa_\"\n    - \"jquery\"\n    - \"json\"\n    - \"just\"\n    - \"kafka\"\n    - \"karma\"\n    - \"kata\"\n    - \"kernel\"\n    - \"keyboard\"\n    - \"kindle\"\n    - \"kit-\"\n    - \"kit.\"\n    - \"kit_\"\n    - \"kitchen\"\n    - \"knife\"\n    - \"koan\"\n    - \"kohana\"\n    - \"lab-\"\n    - \"lab-\"\n    - \"lab.\"\n    - \"lab.\"\n    - \"lab_\"\n    - \"lab_\"\n    - \"lambda\"\n    - \"lamp\"\n    - \"language\"\n    - \"laravel\"\n    - \"last\"\n    - \"latest\"\n    - \"latex\"\n    - \"launcher\"\n    - \"layer\"\n    - \"layout\"\n    - \"lazy\"\n    - \"ldap\"\n    - \"leaflet\"\n    - \"league\"\n    - \"learn\"\n    - \"learning\"\n    - \"led-\"\n    - \"led.\"\n    - \"led_\"\n    - \"leetcode\"\n    - \"les-\"\n    - \"les.\"\n    - \"les_\"\n    - \"level\"\n    - \"leveldb\"\n    - \"lib-\"\n    - \"lib.\"\n    - \"lib_\"\n    - \"librarie\"\n    - \"library\"\n    - \"license\"\n    - \"life\"\n    - \"liferay\"\n    - \"light\"\n    - \"lightbox\"\n    - \"like\"\n    - \"line\"\n    - \"link\"\n    - \"linked\"\n    - \"linkedin\"\n    - \"linux\"\n    - \"lisp\"\n    - \"list\"\n    - \"lite\"\n    - \"little\"\n    - \"load\"\n    - \"loader\"\n    - \"local\"\n    - \"location\"\n    - \"lock\"\n    - \"log-\"\n    - \"log.\"\n    - \"log_\"\n    - \"logger\"\n    - \"logging\"\n    - \"logic\"\n    - \"login\"\n    - \"logstash\"\n    - \"longer\"\n    - \"look\"\n    - \"love\"\n    - \"lua-\"\n    - \"lua.\"\n    - \"lua_\"\n    - \"mac-\"\n    - \"mac.\"\n    - \"mac_\"\n    - \"machine\"\n    - \"made\"\n    - \"magento\"\n    - \"magic\"\n    - \"mail\"\n    - \"make\"\n    - \"maker\"\n    - \"making\"\n    - \"man-\"\n    - \"man.\"\n    - \"man_\"\n    - \"manage\"\n    - \"manager\"\n    - \"manifest\"\n    - \"manual\"\n    - \"map-\"\n    - \"map-\"\n    - \"map.\"\n    - \"map.\"\n    - \"map_\"\n    - \"map_\"\n    - \"mapper\"\n    - \"mapping\"\n    - \"markdown\"\n    - \"markup\"\n    - \"master\"\n    - \"math\"\n    - \"matrix\"\n    - \"maven\"\n    - \"md5\"\n    - \"mean\"\n    - \"media\"\n    - \"mediawiki\"\n    - \"meetup\"\n    - \"memcached\"\n    - \"memory\"\n    - \"menu\"\n    - \"merchant\"\n    - \"message\"\n    - \"messaging\"\n    - \"meta\"\n    - \"metadata\"\n    - \"meteor\"\n    - \"method\"\n    - \"metric\"\n    - \"micro\"\n    - \"middleman\"\n    - \"migration\"\n    - \"minecraft\"\n    - \"miner\"\n    - \"mini\"\n    - \"minimal\"\n    - \"mirror\"\n    - \"mit-\"\n    - \"mit.\"\n    - \"mit_\"\n    - \"mobile\"\n    - \"mocha\"\n    - \"mock\"\n    - \"mod-\"\n    - \"mod.\"\n    - \"mod_\"\n    - \"mode\"\n    - \"model\"\n    - \"modern\"\n    - \"modular\"\n    - \"module\"\n    - \"modx\"\n    - \"money\"\n    - \"mongo\"\n    - \"mongodb\"\n    - \"mongoid\"\n    - \"mongoose\"\n    - \"monitor\"\n    - \"monkey\"\n    - \"more\"\n    - \"motion\"\n    - \"moved\"\n    - \"movie\"\n    - \"mozilla\"\n    - \"mqtt\"\n    - \"mule\"\n    - \"multi\"\n    - \"multiple\"\n    - \"music\"\n    - \"mustache\"\n    - \"mvc-\"\n    - \"mvc.\"\n    - \"mvc_\"\n    - \"mysql\"\n    - \"nagio\"\n    - \"name\"\n    - \"native\"\n    - \"need\"\n    - \"neo-\"\n    - \"neo.\"\n    - \"neo_\"\n    - \"nest\"\n    - \"nested\"\n    - \"net-\"\n    - \"net.\"\n    - \"net_\"\n    - \"nette\"\n    - \"network\"\n    - \"new-\"\n    - \"new-\"\n    - \"new.\"\n    - \"new.\"\n    - \"new_\"\n    - \"new_\"\n    - \"next\"\n    - \"nginx\"\n    - \"ninja\"\n    - \"nlp-\"\n    - \"nlp.\"\n    - \"nlp_\"\n    - \"node\"\n    - \"nodej\"\n    - \"nosql\"\n    - \"not-\"\n    - \"not.\"\n    - \"not_\"\n    - \"note\"\n    - \"notebook\"\n    - \"notepad\"\n    - \"notice\"\n    - \"notifier\"\n    - \"now-\"\n    - \"now.\"\n    - \"now_\"\n    - \"number\"\n    - \"oauth\"\n    - \"object\"\n    - \"objective\"\n    - \"obsolete\"\n    - \"ocaml\"\n    - \"octopres\"\n    - \"official\"\n    - \"old-\"\n    - \"old.\"\n    - \"old_\"\n    - \"onboard\"\n    - \"online\"\n    - \"only\"\n    - \"open\"\n    - \"opencv\"\n    - \"opengl\"\n    - \"openshift\"\n    - \"openwrt\"\n    - \"option\"\n    - \"oracle\"\n    - \"org-\"\n    - \"org.\"\n    - \"org_\"\n    - \"origin\"\n    - \"original\"\n    - \"orm-\"\n    - \"orm.\"\n    - \"orm_\"\n    - \"osx-\"\n    - \"osx_\"\n    - \"our-\"\n    - \"our.\"\n    - \"our_\"\n    - \"out-\"\n    - \"out.\"\n    - \"out_\"\n    - \"output\"\n    - \"over\"\n    - \"overview\"\n    - \"own-\"\n    - \"own.\"\n    - \"own_\"\n    - \"pack\"\n    - \"package\"\n    - \"packet\"\n    - \"page\"\n    - \"page\"\n    - \"panel\"\n    - \"paper\"\n    - \"paperclip\"\n    - \"para\"\n    - \"parallax\"\n    - \"parallel\"\n    - \"parse\"\n    - \"parser\"\n    - \"parsing\"\n    - \"particle\"\n    - \"party\"\n    - \"password\"\n    - \"patch\"\n    - \"path\"\n    - \"pattern\"\n    - \"payment\"\n    - \"paypal\"\n    - \"pdf-\"\n    - \"pdf.\"\n    - \"pdf_\"\n    - \"pebble\"\n    - \"people\"\n    - \"perl\"\n    - \"personal\"\n    - \"phalcon\"\n    - \"phoenix\"\n    - \"phone\"\n    - \"phonegap\"\n    - \"photo\"\n    - \"php-\"\n    - \"php.\"\n    - \"php_\"\n    - \"physic\"\n    - \"picker\"\n    - \"pipeline\"\n    - \"platform\"\n    - \"play\"\n    - \"player\"\n    - \"please\"\n    - \"plu-\"\n    - \"plu.\"\n    - \"plu_\"\n    - \"plug-in\"\n    - \"plugin\"\n    - \"plupload\"\n    - \"png-\"\n    - \"png.\"\n    - \"png_\"\n    - \"poker\"\n    - \"polyfill\"\n    - \"polymer\"\n    - \"pool\"\n    - \"pop-\"\n    - \"pop.\"\n    - \"pop_\"\n    - \"popcorn\"\n    - \"popup\"\n    - \"port\"\n    - \"portable\"\n    - \"portal\"\n    - \"portfolio\"\n    - \"post\"\n    - \"power\"\n    - \"powered\"\n    - \"powerful\"\n    - \"prelude\"\n    - \"pretty\"\n    - \"preview\"\n    - \"principle\"\n    - \"print\"\n    - \"pro-\"\n    - \"pro.\"\n    - \"pro_\"\n    - \"problem\"\n    - \"proc\"\n    - \"product\"\n    - \"profile\"\n    - \"profiler\"\n    - \"program\"\n    - \"progres\"\n    - \"project\"\n    - \"protocol\"\n    - \"prototype\"\n    - \"provider\"\n    - \"proxy\"\n    - \"public\"\n    - \"pull\"\n    - \"puppet\"\n    - \"pure\"\n    - \"purpose\"\n    - \"push\"\n    - \"pusher\"\n    - \"pyramid\"\n    - \"python\"\n    - \"quality\"\n    - \"query\"\n    - \"queue\"\n    - \"quick\"\n    - \"rabbitmq\"\n    - \"rack\"\n    - \"radio\"\n    - \"rail\"\n    - \"railscast\"\n    - \"random\"\n    - \"range\"\n    - \"raspberry\"\n    - \"rdf-\"\n    - \"rdf.\"\n    - \"rdf_\"\n    - \"react\"\n    - \"reactive\"\n    - \"read\"\n    - \"reader\"\n    - \"readme\"\n    - \"ready\"\n    - \"real\"\n    - \"reality\"\n    - \"real-time\"\n    - \"realtime\"\n    - \"recipe\"\n    - \"recorder\"\n    - \"red-\"\n    - \"red.\"\n    - \"red_\"\n    - \"reddit\"\n    - \"redi\"\n    - \"redmine\"\n    - \"reference\"\n    - \"refinery\"\n    - \"refresh\"\n    - \"registry\"\n    - \"related\"\n    - \"release\"\n    - \"remote\"\n    - \"rendering\"\n    - \"repo\"\n    - \"report\"\n    - \"request\"\n    - \"require\"\n    - \"required\"\n    - \"requirej\"\n    - \"research\"\n    - \"resource\"\n    - \"response\"\n    - \"resque\"\n    - \"rest\"\n    - \"restful\"\n    - \"resume\"\n    - \"reveal\"\n    - \"reverse\"\n    - \"review\"\n    - \"riak\"\n    - \"rich\"\n    - \"right\"\n    - \"ring\"\n    - \"robot\"\n    - \"role\"\n    - \"room\"\n    - \"router\"\n    - \"routing\"\n    - \"rpc-\"\n    - \"rpc.\"\n    - \"rpc_\"\n    - \"rpg-\"\n    - \"rpg.\"\n    - \"rpg_\"\n    - \"rspec\"\n    - \"ruby-\"\n    - \"ruby.\"\n    - \"ruby_\"\n    - \"rule\"\n    - \"run-\"\n    - \"run.\"\n    - \"run_\"\n    - \"runner\"\n    - \"running\"\n    - \"runtime\"\n    - \"rust\"\n    - \"rvm-\"\n    - \"rvm.\"\n    - \"rvm_\"\n    - \"salt\"\n    - \"sample\"\n    - \"sample\"\n    - \"sandbox\"\n    - \"sas-\"\n    - \"sas.\"\n    - \"sas_\"\n    - \"sbt-\"\n    - \"sbt.\"\n    - \"sbt_\"\n    - \"scala\"\n    - \"scalable\"\n    - \"scanner\"\n    - \"schema\"\n    - \"scheme\"\n    - \"school\"\n    - \"science\"\n    - \"scraper\"\n    - \"scratch\"\n    - \"screen\"\n    - \"script\"\n    - \"scroll\"\n    - \"scs-\"\n    - \"scs.\"\n    - \"scs_\"\n    - \"sdk-\"\n    - \"sdk.\"\n    - \"sdk_\"\n    - \"sdl-\"\n    - \"sdl.\"\n    - \"sdl_\"\n    - \"search\"\n    - \"secure\"\n    - \"security\"\n    - \"see-\"\n    - \"see.\"\n    - \"see_\"\n    - \"seed\"\n    - \"select\"\n    - \"selector\"\n    - \"selenium\"\n    - \"semantic\"\n    - \"sencha\"\n    - \"send\"\n    - \"sentiment\"\n    - \"serie\"\n    - \"server\"\n    - \"service\"\n    - \"session\"\n    - \"set-\"\n    - \"set.\"\n    - \"set_\"\n    - \"setting\"\n    - \"setting\"\n    - \"setup\"\n    - \"sha1\"\n    - \"sha2\"\n    - \"sha256\"\n    - \"share\"\n    - \"shared\"\n    - \"sharing\"\n    - \"sheet\"\n    - \"shell\"\n    - \"shield\"\n    - \"shipping\"\n    - \"shop\"\n    - \"shopify\"\n    - \"shortener\"\n    - \"should\"\n    - \"show\"\n    - \"showcase\"\n    - \"side\"\n    - \"silex\"\n    - \"simple\"\n    - \"simulator\"\n    - \"single\"\n    - \"site\"\n    - \"skeleton\"\n    - \"sketch\"\n    - \"skin\"\n    - \"slack\"\n    - \"slide\"\n    - \"slider\"\n    - \"slim\"\n    - \"small\"\n    - \"smart\"\n    - \"smtp\"\n    - \"snake\"\n    - \"snapshot\"\n    - \"snippet\"\n    - \"soap\"\n    - \"social\"\n    - \"socket\"\n    - \"software\"\n    - \"solarized\"\n    - \"solr\"\n    - \"solution\"\n    - \"solver\"\n    - \"some\"\n    - \"soon\"\n    - \"source\"\n    - \"space\"\n    - \"spark\"\n    - \"spatial\"\n    - \"spec\"\n    - \"sphinx\"\n    - \"spine\"\n    - \"spotify\"\n    - \"spree\"\n    - \"spring\"\n    - \"sprite\"\n    - \"sql-\"\n    - \"sql.\"\n    - \"sql_\"\n    - \"sqlite\"\n    - \"ssh-\"\n    - \"ssh.\"\n    - \"ssh_\"\n    - \"stack\"\n    - \"staging\"\n    - \"standard\"\n    - \"stanford\"\n    - \"start\"\n    - \"started\"\n    - \"starter\"\n    - \"startup\"\n    - \"stat\"\n    - \"statamic\"\n    - \"state\"\n    - \"static\"\n    - \"statistic\"\n    - \"statsd\"\n    - \"statu\"\n    - \"steam\"\n    - \"step\"\n    - \"still\"\n    - \"stm-\"\n    - \"stm.\"\n    - \"stm_\"\n    - \"storage\"\n    - \"store\"\n    - \"storm\"\n    - \"story\"\n    - \"strategy\"\n    - \"stream\"\n    - \"streaming\"\n    - \"string\"\n    - \"stripe\"\n    - \"structure\"\n    - \"studio\"\n    - \"study\"\n    - \"stuff\"\n    - \"style\"\n    - \"sublime\"\n    - \"sugar\"\n    - \"suite\"\n    - \"summary\"\n    - \"super\"\n    - \"support\"\n    - \"supported\"\n    - \"svg-\"\n    - \"svg.\"\n    - \"svg_\"\n    - \"svn-\"\n    - \"svn.\"\n    - \"svn_\"\n    - \"swagger\"\n    - \"swift\"\n    - \"switch\"\n    - \"switcher\"\n    - \"symfony\"\n    - \"symphony\"\n    - \"sync\"\n    - \"synopsi\"\n    - \"syntax\"\n    - \"system\"\n    - \"system\"\n    - \"tab-\"\n    - \"tab-\"\n    - \"tab.\"\n    - \"tab.\"\n    - \"tab_\"\n    - \"tab_\"\n    - \"table\"\n    - \"tag-\"\n    - \"tag-\"\n    - \"tag.\"\n    - \"tag.\"\n    - \"tag_\"\n    - \"tag_\"\n    - \"talk\"\n    - \"target\"\n    - \"task\"\n    - \"tcp-\"\n    - \"tcp.\"\n    - \"tcp_\"\n    - \"tdd-\"\n    - \"tdd.\"\n    - \"tdd_\"\n    - \"team\"\n    - \"tech\"\n    - \"template\"\n    - \"term\"\n    - \"terminal\"\n    - \"testing\"\n    - \"tetri\"\n    - \"text\"\n    - \"textmate\"\n    - \"theme\"\n    - \"theory\"\n    - \"three\"\n    - \"thrift\"\n    - \"time\"\n    - \"timeline\"\n    - \"timer\"\n    - \"tiny\"\n    - \"tinymce\"\n    - \"tip-\"\n    - \"tip.\"\n    - \"tip_\"\n    - \"title\"\n    - \"todo\"\n    - \"todomvc\"\n    - \"token\"\n    - \"tool\"\n    - \"toolbox\"\n    - \"toolkit\"\n    - \"top-\"\n    - \"top.\"\n    - \"top_\"\n    - \"tornado\"\n    - \"touch\"\n    - \"tower\"\n    - \"tracker\"\n    - \"tracking\"\n    - \"traffic\"\n    - \"training\"\n    - \"transfer\"\n    - \"translate\"\n    - \"transport\"\n    - \"tree\"\n    - \"trello\"\n    - \"try-\"\n    - \"try.\"\n    - \"try_\"\n    - \"tumblr\"\n    - \"tut-\"\n    - \"tut.\"\n    - \"tut_\"\n    - \"tutorial\"\n    - \"tweet\"\n    - \"twig\"\n    - \"twitter\"\n    - \"type\"\n    - \"typo\"\n    - \"ubuntu\"\n    - \"uiview\"\n    - \"ultimate\"\n    - \"under\"\n    - \"unit\"\n    - \"unity\"\n    - \"universal\"\n    - \"unix\"\n    - \"update\"\n    - \"updated\"\n    - \"upgrade\"\n    - \"upload\"\n    - \"uploader\"\n    - \"uri-\"\n    - \"uri.\"\n    - \"uri_\"\n    - \"url-\"\n    - \"url.\"\n    - \"url_\"\n    - \"usage\"\n    - \"usb-\"\n    - \"usb.\"\n    - \"usb_\"\n    - \"use-\"\n    - \"use.\"\n    - \"use_\"\n    - \"used\"\n    - \"useful\"\n    - \"user\"\n    - \"using\"\n    - \"util\"\n    - \"utilitie\"\n    - \"utility\"\n    - \"vagrant\"\n    - \"validator\"\n    - \"value\"\n    - \"variou\"\n    - \"varnish\"\n    - \"version\"\n    - \"via-\"\n    - \"via.\"\n    - \"via_\"\n    - \"video\"\n    - \"view\"\n    - \"viewer\"\n    - \"vim-\"\n    - \"vim.\"\n    - \"vim_\"\n    - \"vimrc\"\n    - \"virtual\"\n    - \"vision\"\n    - \"visual\"\n    - \"vpn\"\n    - \"want\"\n    - \"warning\"\n    - \"watch\"\n    - \"watcher\"\n    - \"wave\"\n    - \"way-\"\n    - \"way.\"\n    - \"way_\"\n    - \"weather\"\n    - \"web-\"\n    - \"web_\"\n    - \"webapp\"\n    - \"webgl\"\n    - \"webhook\"\n    - \"webkit\"\n    - \"webrtc\"\n    - \"website\"\n    - \"websocket\"\n    - \"welcome\"\n    - \"welcome\"\n    - \"what\"\n    - \"what'\"\n    - \"when\"\n    - \"where\"\n    - \"which\"\n    - \"why-\"\n    - \"why.\"\n    - \"why_\"\n    - \"widget\"\n    - \"wifi\"\n    - \"wiki\"\n    - \"win-\"\n    - \"win.\"\n    - \"win_\"\n    - \"window\"\n    - \"wip-\"\n    - \"wip.\"\n    - \"wip_\"\n    - \"within\"\n    - \"without\"\n    - \"wizard\"\n    - \"word\"\n    - \"wordpres\"\n    - \"work\"\n    - \"worker\"\n    - \"workflow\"\n    - \"working\"\n    - \"workshop\"\n    - \"world\"\n    - \"wrapper\"\n    - \"write\"\n    - \"writer\"\n    - \"writing\"\n    - \"written\"\n    - \"www-\"\n    - \"www.\"\n    - \"www_\"\n    - \"xamarin\"\n    - \"xcode\"\n    - \"xml-\"\n    - \"xml.\"\n    - \"xml_\"\n    - \"xmpp\"\n    - \"xxxxxx\"\n    - \"yahoo\"\n    - \"yaml\"\n    - \"yandex\"\n    - \"yeoman\"\n    - \"yet-\"\n    - \"yet.\"\n    - \"yet_\"\n    - \"yii-\"\n    - \"yii.\"\n    - \"yii_\"\n    - \"youtube\"\n    - \"yui-\"\n    - \"yui.\"\n    - \"yui_\"\n    - \"zend\"\n    - \"zero\"\n    - \"zip-\"\n    - \"zip.\"\n    - \"zip_\"\n    - \"zsh-\"\n    - \"zsh.\"\n    - \"zsh_\""
  },
  {
    "path": "go.mod",
    "content": "module github.com/trufflesecurity/trufflehog/v3\n\ngo 1.24.0\n\ntoolchain go1.24.5\n\nreplace github.com/jpillora/overseer => github.com/trufflesecurity/overseer v1.2.8\n\n// Coinbase archived this library and it has some vulnerable dependencies so we've forked.\nreplace github.com/coinbase/waas-client-library-go => github.com/trufflesecurity/waas-client-library-go v1.0.9\n\nrequire (\n\tcloud.google.com/go/secretmanager v1.16.0\n\tcloud.google.com/go/storage v1.56.1\n\tgithub.com/BobuSumisu/aho-corasick v1.0.3\n\tgithub.com/TheZeroSlave/zapsentry v1.23.0\n\tgithub.com/adrg/strutil v0.3.1\n\tgithub.com/alecthomas/kingpin/v2 v2.4.0\n\tgithub.com/avast/apkparser v0.0.0-20250626104540-d53391f4d69d\n\tgithub.com/aws/aws-sdk-go-v2 v1.39.0\n\tgithub.com/aws/aws-sdk-go-v2/config v1.31.7\n\tgithub.com/aws/aws-sdk-go-v2/credentials v1.18.11\n\tgithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.5\n\tgithub.com/aws/aws-sdk-go-v2/service/s3 v1.88.0\n\tgithub.com/aws/aws-sdk-go-v2/service/sns v1.38.2\n\tgithub.com/aws/aws-sdk-go-v2/service/sts v1.38.3\n\tgithub.com/aws/smithy-go v1.23.0\n\tgithub.com/aymanbagabas/go-osc52 v1.2.1\n\tgithub.com/bill-rich/go-syslog v0.0.0-20220413021637-49edb52a574c\n\tgithub.com/bradleyfalzon/ghinstallation/v2 v2.16.0\n\tgithub.com/brianvoe/gofakeit/v7 v7.6.0\n\tgithub.com/charmbracelet/bubbles v0.18.0\n\tgithub.com/charmbracelet/bubbletea v1.3.6\n\tgithub.com/charmbracelet/glamour v0.10.0\n\tgithub.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834\n\tgithub.com/couchbase/gocb/v2 v2.11.0\n\tgithub.com/crewjam/rfc5424 v0.1.0\n\tgithub.com/csnewman/dextk v0.3.0\n\tgithub.com/docker/docker v28.3.3+incompatible\n\tgithub.com/dustin/go-humanize v1.0.1\n\tgithub.com/elastic/go-elasticsearch/v8 v8.17.1\n\tgithub.com/envoyproxy/protoc-gen-validate v1.2.1\n\tgithub.com/fatih/color v1.18.0\n\tgithub.com/felixge/fgprof v0.9.5\n\tgithub.com/gabriel-vasile/mimetype v1.4.10\n\tgithub.com/getsentry/sentry-go v0.32.0\n\tgithub.com/go-errors/errors v1.5.1\n\tgithub.com/go-git/go-git/v5 v5.13.2\n\tgithub.com/go-logr/logr v1.4.3\n\tgithub.com/go-logr/zapr v1.3.0\n\tgithub.com/go-redis/redis v6.15.9+incompatible\n\tgithub.com/go-sql-driver/mysql v1.8.1\n\tgithub.com/gobwas/glob v0.2.3\n\tgithub.com/golang-jwt/jwt/v5 v5.2.3\n\tgithub.com/google/go-cmp v0.7.0\n\tgithub.com/google/go-containerregistry v0.20.6\n\tgithub.com/google/go-github/v67 v67.0.0\n\tgithub.com/google/uuid v1.6.0\n\tgithub.com/googleapis/gax-go/v2 v2.16.0\n\tgithub.com/hashicorp/go-retryablehttp v0.7.8\n\tgithub.com/hashicorp/golang-lru/v2 v2.0.7\n\tgithub.com/jedib0t/go-pretty/v6 v6.6.8\n\tgithub.com/jlaffaye/ftp v0.2.0\n\tgithub.com/joho/godotenv v1.5.1\n\tgithub.com/jpillora/overseer v1.1.6\n\tgithub.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213\n\tgithub.com/klauspost/pgzip v1.2.6\n\tgithub.com/kylelemons/godebug v1.1.0\n\tgithub.com/lestrrat-go/jwx/v3 v3.0.12\n\tgithub.com/lib/pq v1.10.9\n\tgithub.com/lrstanley/bubblezone v0.0.0-20250404061050-e13639e27357\n\tgithub.com/mariduv/ldap-verify v0.0.2\n\tgithub.com/marusama/semaphore/v2 v2.5.0\n\tgithub.com/mattn/go-isatty v0.0.20\n\tgithub.com/mholt/archives v0.0.0-20241216060121-23e0af8fe73d\n\tgithub.com/microsoft/go-mssqldb v1.8.2\n\tgithub.com/mitchellh/go-ps v1.0.0\n\tgithub.com/muesli/reflow v0.3.0\n\tgithub.com/patrickmn/go-cache v2.1.0+incompatible\n\tgithub.com/paulbellamy/ratecounter v0.2.0\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/prometheus/client_golang v1.20.5\n\tgithub.com/rabbitmq/amqp091-go v1.10.0\n\tgithub.com/repeale/fp-go v0.11.1\n\tgithub.com/sassoftware/go-rpmutils v0.4.0\n\tgithub.com/schollz/progressbar/v3 v3.17.1\n\tgithub.com/sendgrid/sendgrid-go v3.16.1+incompatible\n\tgithub.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3\n\tgithub.com/shuheiktgw/go-travis v0.3.1\n\tgithub.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7\n\tgithub.com/stretchr/testify v1.11.1\n\tgithub.com/testcontainers/testcontainers-go v0.34.0\n\tgithub.com/testcontainers/testcontainers-go/modules/elasticsearch v0.34.0\n\tgithub.com/testcontainers/testcontainers-go/modules/mongodb v0.34.0\n\tgithub.com/testcontainers/testcontainers-go/modules/mssql v0.34.0\n\tgithub.com/testcontainers/testcontainers-go/modules/mysql v0.34.0\n\tgithub.com/testcontainers/testcontainers-go/modules/postgres v0.34.0\n\tgithub.com/trufflesecurity/disk-buffer-reader v0.2.1\n\tgithub.com/wasilibs/go-re2 v1.9.0\n\tgithub.com/xo/dburl v0.23.8\n\tgitlab.com/gitlab-org/api/client-go v1.12.0\n\tgo.mongodb.org/mongo-driver v1.17.4\n\tgo.uber.org/automaxprocs v1.6.0\n\tgo.uber.org/mock v0.6.0\n\tgo.uber.org/zap v1.27.0\n\tgolang.org/x/crypto v0.46.0\n\tgolang.org/x/net v0.48.0\n\tgolang.org/x/oauth2 v0.34.0\n\tgolang.org/x/sync v0.19.0\n\tgolang.org/x/text v0.32.0\n\tgolang.org/x/time v0.14.0\n\tgoogle.golang.org/api v0.259.0\n\tgoogle.golang.org/protobuf v1.36.11\n\tgopkg.in/h2non/gock.v1 v1.1.2\n\tgopkg.in/yaml.v2 v2.4.0\n\tgopkg.in/yaml.v3 v3.0.1\n\tpault.ag/go/debian v0.18.0\n\tpgregory.net/rapid v1.1.0\n\tsigs.k8s.io/yaml v1.4.0\n)\n\nrequire (\n\tcel.dev/expr v0.25.1 // indirect\n\tcloud.google.com/go v0.121.6 // indirect\n\tcloud.google.com/go/auth v0.18.0 // indirect\n\tcloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect\n\tcloud.google.com/go/compute/metadata v0.9.0 // indirect\n\tcloud.google.com/go/iam v1.5.3 // indirect\n\tcloud.google.com/go/monitoring v1.24.3 // indirect\n\tdario.cat/mergo v1.0.0 // indirect\n\tfilippo.io/edwards25519 v1.1.0 // indirect\n\tgithub.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect\n\tgithub.com/Azure/go-ntlmssp v0.1.0 // indirect\n\tgithub.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect\n\tgithub.com/DataDog/zstd v1.5.5 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect\n\tgithub.com/Microsoft/go-winio v0.6.2 // indirect\n\tgithub.com/ProtonMail/go-crypto v1.1.5 // indirect\n\tgithub.com/STARRY-S/zip v0.2.1 // indirect\n\tgithub.com/alecthomas/chroma/v2 v2.14.0 // indirect\n\tgithub.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect\n\tgithub.com/andybalholm/brotli v1.1.1 // indirect\n\tgithub.com/atotto/clipboard v0.1.4 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sso v1.29.2 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.3 // indirect\n\tgithub.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect\n\tgithub.com/aymerick/douceur v0.2.0 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/bodgit/plumbing v1.3.0 // indirect\n\tgithub.com/bodgit/sevenzip v1.6.0 // indirect\n\tgithub.com/bodgit/windows v1.0.1 // indirect\n\tgithub.com/cenkalti/backoff/v4 v4.2.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect\n\tgithub.com/charmbracelet/x/ansi v0.9.3 // indirect\n\tgithub.com/charmbracelet/x/cellbuf v0.0.13 // indirect\n\tgithub.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect\n\tgithub.com/charmbracelet/x/term v0.2.1 // indirect\n\tgithub.com/cloudflare/circl v1.6.1 // indirect\n\tgithub.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f // indirect\n\tgithub.com/containerd/errdefs v1.0.0 // indirect\n\tgithub.com/containerd/errdefs/pkg v0.3.0 // indirect\n\tgithub.com/containerd/log v0.1.0 // indirect\n\tgithub.com/containerd/platforms v0.2.1 // indirect\n\tgithub.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect\n\tgithub.com/couchbase/gocbcore/v10 v10.8.0 // indirect\n\tgithub.com/couchbase/gocbcoreps v0.1.3 // indirect\n\tgithub.com/couchbase/goprotostellar v1.0.2 // indirect\n\tgithub.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20240607131231-fb385523de28 // indirect\n\tgithub.com/cpuguy83/dockercfg v0.3.2 // indirect\n\tgithub.com/cyphar/filepath-securejoin v0.3.6 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect\n\tgithub.com/distribution/reference v0.6.0 // indirect\n\tgithub.com/dlclark/regexp2 v1.11.0 // indirect\n\tgithub.com/docker/cli v28.2.2+incompatible // indirect\n\tgithub.com/docker/distribution v2.8.3+incompatible // indirect\n\tgithub.com/docker/docker-credential-helpers v0.9.3 // indirect\n\tgithub.com/docker/go-connections v0.5.0 // indirect\n\tgithub.com/docker/go-units v0.5.0 // indirect\n\tgithub.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect\n\tgithub.com/elastic/elastic-transport-go/v8 v8.6.1 // indirect\n\tgithub.com/emirpasic/gods v1.18.1 // indirect\n\tgithub.com/envoyproxy/go-control-plane/envoy v1.35.0 // indirect\n\tgithub.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/fsnotify/fsnotify v1.6.0 // indirect\n\tgithub.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect\n\tgithub.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect\n\tgithub.com/go-git/go-billy/v5 v5.6.2 // indirect\n\tgithub.com/go-jose/go-jose/v4 v4.1.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-ole/go-ole v1.2.6 // indirect\n\tgithub.com/goccy/go-json v0.10.3 // indirect\n\tgithub.com/gofrs/flock v0.12.1 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang-jwt/jwt/v4 v4.5.2 // indirect\n\tgithub.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect\n\tgithub.com/golang-sql/sqlexp v0.1.0 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect\n\tgithub.com/golang/snappy v1.0.0 // indirect\n\tgithub.com/google/go-github/v72 v72.0.0 // indirect\n\tgithub.com/google/go-querystring v1.2.0 // indirect\n\tgithub.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect\n\tgithub.com/google/s2a-go v0.1.9 // indirect\n\tgithub.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect\n\tgithub.com/gorilla/css v1.0.1 // indirect\n\tgithub.com/gorilla/websocket v1.5.3 // indirect\n\tgithub.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect\n\tgithub.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/hashicorp/go-cleanhttp v0.5.2 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.1 // indirect\n\tgithub.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect\n\tgithub.com/jpillora/s3 v1.1.4 // indirect\n\tgithub.com/kevinburke/ssh_config v1.2.0 // indirect\n\tgithub.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d // indirect\n\tgithub.com/klauspost/compress v1.18.0 // indirect\n\tgithub.com/lestrrat-go/blackmagic v1.0.4 // indirect\n\tgithub.com/lestrrat-go/httpcc v1.0.1 // indirect\n\tgithub.com/lestrrat-go/httprc/v3 v3.0.1 // indirect\n\tgithub.com/lestrrat-go/option v1.0.1 // indirect\n\tgithub.com/lestrrat-go/option/v2 v2.0.0 // indirect\n\tgithub.com/lucasb-eyer/go-colorful v1.2.0 // indirect\n\tgithub.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect\n\tgithub.com/magiconair/properties v1.8.7 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-localereader v0.0.1 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.16 // indirect\n\tgithub.com/microcosm-cc/bluemonday v1.0.27 // indirect\n\tgithub.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect\n\tgithub.com/mitchellh/go-homedir v1.1.0 // indirect\n\tgithub.com/moby/docker-image-spec v1.3.1 // indirect\n\tgithub.com/moby/go-archive v0.1.0 // indirect\n\tgithub.com/moby/patternmatcher v0.6.0 // indirect\n\tgithub.com/moby/sys/sequential v0.6.0 // indirect\n\tgithub.com/moby/sys/user v0.4.0 // indirect\n\tgithub.com/moby/sys/userns v0.1.0 // indirect\n\tgithub.com/moby/term v0.5.0 // indirect\n\tgithub.com/montanaflynn/stats v0.7.1 // indirect\n\tgithub.com/morikuni/aec v1.0.0 // indirect\n\tgithub.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect\n\tgithub.com/muesli/cancelreader v0.2.2 // indirect\n\tgithub.com/muesli/termenv v0.16.0 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/nwaples/rardecode/v2 v2.2.1 // indirect\n\tgithub.com/onsi/ginkgo v1.16.5 // indirect\n\tgithub.com/opencontainers/go-digest v1.0.0 // indirect\n\tgithub.com/opencontainers/image-spec v1.1.1 // indirect\n\tgithub.com/pierrec/lz4/v4 v4.1.21 // indirect\n\tgithub.com/pjbgf/sha1cd v0.3.2 // indirect\n\tgithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.55.0 // indirect\n\tgithub.com/prometheus/procfs v0.15.1 // indirect\n\tgithub.com/rivo/uniseg v0.4.7 // indirect\n\tgithub.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect\n\tgithub.com/segmentio/asm v1.2.1 // indirect\n\tgithub.com/sendgrid/rest v2.6.9+incompatible // indirect\n\tgithub.com/shirou/gopsutil/v3 v3.23.12 // indirect\n\tgithub.com/shoenig/go-m1cpu v0.1.6 // indirect\n\tgithub.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect\n\tgithub.com/sirupsen/logrus v1.9.3 // indirect\n\tgithub.com/skeema/knownhosts v1.3.0 // indirect\n\tgithub.com/sorairolake/lzip-go v0.3.5 // indirect\n\tgithub.com/spiffe/go-spiffe/v2 v2.6.0 // indirect\n\tgithub.com/stretchr/objx v0.5.2 // indirect\n\tgithub.com/tetratelabs/wazero v1.9.0 // indirect\n\tgithub.com/therootcompany/xz v1.0.1 // indirect\n\tgithub.com/tklauser/go-sysconf v0.3.12 // indirect\n\tgithub.com/tklauser/numcpus v0.6.1 // indirect\n\tgithub.com/trufflesecurity/touchfile v0.1.1 // indirect\n\tgithub.com/ulikunitz/xz v0.5.12 // indirect\n\tgithub.com/vbatts/tar-split v0.12.1 // indirect\n\tgithub.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect\n\tgithub.com/xanzy/ssh-agent v0.3.3 // indirect\n\tgithub.com/xdg-go/pbkdf2 v1.0.0 // indirect\n\tgithub.com/xdg-go/scram v1.1.2 // indirect\n\tgithub.com/xdg-go/stringprep v1.0.4 // indirect\n\tgithub.com/xhit/go-str2duration/v2 v2.1.0 // indirect\n\tgithub.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect\n\tgithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect\n\tgithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect\n\tgithub.com/yuin/goldmark v1.7.8 // indirect\n\tgithub.com/yuin/goldmark-emoji v1.0.5 // indirect\n\tgithub.com/yusufpapurcu/wmi v1.2.3 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect\n\tgo.opentelemetry.io/otel v1.38.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.38.0 // indirect\n\tgo.opentelemetry.io/otel/sdk v1.38.0 // indirect\n\tgo.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.38.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo4.org v0.0.0-20230225012048-214862532bf5 // indirect\n\tgolang.org/x/mod v0.30.0 // indirect\n\tgolang.org/x/sys v0.39.0 // indirect\n\tgolang.org/x/term v0.38.0 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect\n\tgoogle.golang.org/grpc v1.78.0 // indirect\n\tgopkg.in/warnings.v0 v0.1.2 // indirect\n\tpault.ag/go/topsort v0.1.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=\ncel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=\ncloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=\ncloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=\ncloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0=\ncloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo=\ncloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=\ncloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=\ncloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=\ncloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=\ncloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY=\ncloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw=\ncloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=\ncloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=\ncloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=\ncloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/secretmanager v1.16.0 h1:19QT7ZsLJ8FSP1k+4esQvuCD7npMJml6hYzilxVyT+k=\ncloud.google.com/go/secretmanager v1.16.0/go.mod h1://C/e4I8D26SDTz1f3TQcddhcmiC3rMEl0S1Cakvs3Q=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ncloud.google.com/go/storage v1.56.1 h1:n6gy+yLnHn0hTwBFzNn8zJ1kqWfR91wzdM8hjRF4wP0=\ncloud.google.com/go/storage v1.56.1/go.mod h1:C9xuCZgFl3buo2HZU/1FncgvvOgTAs/rnh4gF4lMg0s=\ncloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=\ncloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=\ndario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=\ndario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\nfilippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=\nfilippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=\ngithub.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=\ngithub.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=\ngithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=\ngithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY=\ngithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=\ngithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=\ngithub.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=\ngithub.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=\ngithub.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A=\ngithub.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=\ngithub.com/BobuSumisu/aho-corasick v1.0.3 h1:uuf+JHwU9CHP2Vx+wAy6jcksJThhJS9ehR8a+4nPE9g=\ngithub.com/BobuSumisu/aho-corasick v1.0.3/go.mod h1:hm4jLcvZKI2vRF2WDU1N4p/jpWtpOzp3nLmi9AzX/XE=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=\ngithub.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo=\ngithub.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=\ngithub.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=\ngithub.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=\ngithub.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=\ngithub.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=\ngithub.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg=\ngithub.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4=\ngithub.com/TheZeroSlave/zapsentry v1.23.0 h1:TKyzfEL7LRlRr+7AvkukVLZ+jZPC++ebCUv7ZJHl1AU=\ngithub.com/TheZeroSlave/zapsentry v1.23.0/go.mod h1:3DRFLu4gIpnCTD4V9HMCBSaqYP8gYU7mZickrs2/rIY=\ngithub.com/adrg/strutil v0.3.1 h1:OLvSS7CSJO8lBii4YmBt8jiK9QOtB9CzCzwl4Ic/Fz4=\ngithub.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA=\ngithub.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=\ngithub.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=\ngithub.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=\ngithub.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=\ngithub.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=\ngithub.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=\ngithub.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=\ngithub.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=\ngithub.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=\ngithub.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=\ngithub.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=\ngithub.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=\ngithub.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=\ngithub.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=\ngithub.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=\ngithub.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=\ngithub.com/avast/apkparser v0.0.0-20250626104540-d53391f4d69d h1:PGSn2pnK/u5ZBompy83R6Wo4BqLYp3dX43QWDoPv7TA=\ngithub.com/avast/apkparser v0.0.0-20250626104540-d53391f4d69d/go.mod h1:3F9A8btIerUcuy7Fmno+g/nIk4ELKJ6NCs2/KK1bvLs=\ngithub.com/aws/aws-sdk-go-v2 v1.39.0 h1:xm5WV/2L4emMRmMjHFykqiA4M/ra0DJVSWUkDyBjbg4=\ngithub.com/aws/aws-sdk-go-v2 v1.39.0/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00=\ngithub.com/aws/aws-sdk-go-v2/config v1.31.7 h1:zS1O6hr6t0nZdBCMFc/c9OyZFyLhXhf/B2IZ9Y0lRQE=\ngithub.com/aws/aws-sdk-go-v2/config v1.31.7/go.mod h1:GpHmi1PQDdL5pP4JaB00pU0ek4EXVcYH7IkjkUadQmM=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.18.11 h1:1Fnb+7Dk96/VYx/uYfzk5sU2V0b0y2RWZROiMZCN/Io=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.18.11/go.mod h1:iuvn9v10dkxU4sDgtTXGWY0MrtkEcmkUmjv4clxhuTc=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7 h1:Is2tPmieqGS2edBnmOJIbdvOA6Op+rRpaYR60iBAwXM=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.7/go.mod h1:F1i5V5421EGci570yABvpIXgRIBPb5JM+lSkHF6Dq5w=\ngithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.5 h1:fSuJX/VBJKufwJG/szWgUdRJVyRiEQDDXNh/6NPrTBg=\ngithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.5/go.mod h1:LvN0noQuST+3Su55Wl++BkITpptnfN9g6Ohkv4zs9To=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7 h1:UCxq0X9O3xrlENdKf1r9eRJoKz/b0AfGkpp3a7FPlhg=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.7/go.mod h1:rHRoJUNUASj5Z/0eqI4w32vKvC7atoWR0jC+IkmVH8k=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7 h1:Y6DTZUn7ZUC4th9FMBbo8LVE+1fyq3ofw+tRwkUd3PY=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.7/go.mod h1:x3XE6vMnU9QvHN/Wrx2s44kwzV2o2g5x/siw4ZUJ9g8=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=\ngithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7 h1:BszAktdUo2xlzmYHjWMq70DqJ7cROM8iBd3f6hrpuMQ=\ngithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.7/go.mod h1:XJ1yHki/P7ZPuG4fd3f0Pg/dSGA2cTQBCLw82MH2H48=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=\ngithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7 h1:zmZ8qvtE9chfhBPuKB2aQFxW5F/rpwXUgmcVCgQzqRw=\ngithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.7/go.mod h1:vVYfbpd2l+pKqlSIDIOgouxNsGu5il9uDp0ooWb0jys=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7 h1:mLgc5QIgOy26qyh5bvW+nDoAppxgn3J2WV3m9ewq7+8=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.7/go.mod h1:wXb/eQnqt8mDQIQTTmcw58B5mYGxzLGZGK8PWNFZ0BA=\ngithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7 h1:u3VbDKUCWarWiU+aIUK4gjTr/wQFXV17y3hgNno9fcA=\ngithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.7/go.mod h1:/OuMQwhSyRapYxq6ZNpPer8juGNrB4P5Oz8bZ2cgjQE=\ngithub.com/aws/aws-sdk-go-v2/service/s3 v1.88.0 h1:k5JXPr+2SrPDwM3PdygZUenn0lVPLa3KOs7cCYqinFs=\ngithub.com/aws/aws-sdk-go-v2/service/s3 v1.88.0/go.mod h1:xajPTguLoeQMAOE44AAP2RQoUhF8ey1g5IFHARv71po=\ngithub.com/aws/aws-sdk-go-v2/service/sns v1.38.2 h1:Djc2m7mTPuizL1iMxJfMc209PDy2AqiN1AXrtq/rBdY=\ngithub.com/aws/aws-sdk-go-v2/service/sns v1.38.2/go.mod h1:kHMCS+JDWKuKSDP9J/v3dlV2S9zNBKbXzaLy/kHSdEE=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.29.2 h1:rcoTaYOhGE/zfxE1uR6X5fvj+uKkqeCNRE0rBbiQM34=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.29.2/go.mod h1:Ql6jE9kyyWI5JHn+61UT/Y5Z0oyVJGmgmJbZD5g4unY=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.3 h1:BSIfeFtU9tlSt8vEYS7KzurMoAuYzYPWhcZiMtxVf2M=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.3/go.mod h1:XclEty74bsGBCr1s0VSaA11hQ4ZidK4viWK7rRfO88I=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.38.3 h1:yEiZ0ztgji2GsCb/6uQSITXcGdtmWMfLRys0jJFiUkc=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.38.3/go.mod h1:Z+Gd23v97pX9zK97+tX4ppAgqCt3Z2dIXB02CtBncK8=\ngithub.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=\ngithub.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=\ngithub.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E=\ngithub.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=\ngithub.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=\ngithub.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=\ngithub.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=\ngithub.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=\ngithub.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bill-rich/go-syslog v0.0.0-20220413021637-49edb52a574c h1:tSME5FDS02qQll3JYodI6RZR/g4EKOHApGv1wMZT+Z0=\ngithub.com/bill-rich/go-syslog v0.0.0-20220413021637-49edb52a574c/go.mod h1:+sCc6hztur+oZCLOsNk6wCCy+GLrnSNHSRmTnnL+8iQ=\ngithub.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=\ngithub.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=\ngithub.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A=\ngithub.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc=\ngithub.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=\ngithub.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=\ngithub.com/bradleyfalzon/ghinstallation/v2 v2.16.0 h1:B91r9bHtXp/+XRgS5aZm6ZzTdz3ahgJYmkt4xZkgDz8=\ngithub.com/bradleyfalzon/ghinstallation/v2 v2.16.0/go.mod h1:OeVe5ggFzoBnmgitZe/A+BqGOnv1DvU/0uiLQi1wutM=\ngithub.com/brianvoe/gofakeit/v7 v7.6.0 h1:M3RUb5CuS2IZmF/cP+O+NdLxJEuDAZxNQBwPbbqR6h4=\ngithub.com/brianvoe/gofakeit/v7 v7.6.0/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA=\ngithub.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=\ngithub.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=\ngithub.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=\ngithub.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=\ngithub.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=\ngithub.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=\ngithub.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=\ngithub.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY=\ngithub.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk=\ngithub.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=\ngithub.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=\ngithub.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=\ngithub.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=\ngithub.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=\ngithub.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=\ngithub.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=\ngithub.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=\ngithub.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI=\ngithub.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU=\ngithub.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=\ngithub.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=\ngithub.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=\ngithub.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=\ngithub.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=\ngithub.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=\ngithub.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=\ngithub.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0=\ngithub.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4=\ngithub.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=\ngithub.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=\ngithub.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=\ngithub.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=\ngithub.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=\ngithub.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=\ngithub.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=\ngithub.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=\ngithub.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8=\ngithub.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU=\ngithub.com/couchbase/gocb/v2 v2.11.0 h1:OVB+KlVeXlKVtziKx/LWZT7DClLsoQHQFrI4wan5Ijc=\ngithub.com/couchbase/gocb/v2 v2.11.0/go.mod h1:Y+lODSgyVzDSaf0Sy8sIzIa0RTAw3vlZUsjY6+FUq9Y=\ngithub.com/couchbase/gocbcore/v10 v10.8.0 h1:zDcJyYqOirFyC8T/aVvNL4N9oj6GI4qtaBuTGGWCDb4=\ngithub.com/couchbase/gocbcore/v10 v10.8.0/go.mod h1:OWKfU9R5Nm5V3QZBtfdZl5qCfgxtxTqOgXiNr4pn9/c=\ngithub.com/couchbase/gocbcoreps v0.1.3 h1:fILaKGCjxFIeCgAUG8FGmRDSpdrRggohOMKEgO9CUpg=\ngithub.com/couchbase/gocbcoreps v0.1.3/go.mod h1:hBFpDNPnRno6HH5cRXExhqXYRmTsFJlFHQx7vztcXPk=\ngithub.com/couchbase/goprotostellar v1.0.2 h1:yoPbAL9sCtcyZ5e/DcU5PRMOEFaJrF9awXYu3VPfGls=\ngithub.com/couchbase/goprotostellar v1.0.2/go.mod h1:5/yqVnZlW2/NSbAWu1hPJCFBEwjxgpe0PFFOlRixnp4=\ngithub.com/couchbaselabs/gocaves/client v0.0.0-20250107114554-f96479220ae8 h1:MQfvw4BiLTuyR69FuA5Kex+tXUeLkH+/ucJfVL1/hkM=\ngithub.com/couchbaselabs/gocaves/client v0.0.0-20250107114554-f96479220ae8/go.mod h1:AVekAZwIY2stsJOMWLAS/0uA/+qdp7pjO8EHnl61QkY=\ngithub.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20240607131231-fb385523de28 h1:lhGOw8rNG6RAadmmaJAF3PJ7MNt7rFuWG7BHCYMgnGE=\ngithub.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20240607131231-fb385523de28/go.mod h1:o7T431UOfFVHDNvMBUmUxpHnhivwv7BziUao/nMl81E=\ngithub.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=\ngithub.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=\ngithub.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=\ngithub.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=\ngithub.com/crewjam/rfc5424 v0.1.0 h1:MSeXJm22oKovLzWj44AHwaItjIMUMugYGkEzfa831H8=\ngithub.com/crewjam/rfc5424 v0.1.0/go.mod h1:RCi9M3xHVOeerf6ULZzqv2xOGRO/zYaVUeRyPnBW3gQ=\ngithub.com/csnewman/dextk v0.3.0 h1:gigNZlZRNfCuARV7depunRlafEAzGhyvgBQo1FT3/0M=\ngithub.com/csnewman/dextk v0.3.0/go.mod h1:FcDoI3258ea0KPQogyv4iazQRGcLFNOW+I4pHBUfNO0=\ngithub.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=\ngithub.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=\ngithub.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=\ngithub.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=\ngithub.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=\ngithub.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=\ngithub.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=\ngithub.com/docker/cli v28.2.2+incompatible h1:qzx5BNUDFqlvyq4AHzdNB7gSyVTmU4cgsyN9SdInc1A=\ngithub.com/docker/cli v28.2.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=\ngithub.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=\ngithub.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=\ngithub.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=\ngithub.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=\ngithub.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=\ngithub.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=\ngithub.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=\ngithub.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=\ngithub.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=\ngithub.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=\ngithub.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=\ngithub.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=\ngithub.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=\ngithub.com/elastic/elastic-transport-go/v8 v8.6.1 h1:h2jQRqH6eLGiBSN4eZbQnJLtL4bC5b4lfVFRjw2R4e4=\ngithub.com/elastic/elastic-transport-go/v8 v8.6.1/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=\ngithub.com/elastic/go-elasticsearch/v8 v8.17.1 h1:bOXChDoCMB4TIwwGqKd031U8OXssmWLT3UrAr9EGs3Q=\ngithub.com/elastic/go-elasticsearch/v8 v8.17.1/go.mod h1:MVJCtL+gJJ7x5jFeUmA20O7rvipX8GcQmo5iBcmaJn4=\ngithub.com/elazarl/goproxy v1.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM=\ngithub.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ=\ngithub.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=\ngithub.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM=\ngithub.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329/go.mod h1:Alz8LEClvR7xKsrq3qzoc4N0guvVNSS8KmSChGYr9hs=\ngithub.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo=\ngithub.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs=\ngithub.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=\ngithub.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=\ngithub.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=\ngithub.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=\ngithub.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=\ngithub.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=\ngithub.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=\ngithub.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=\ngithub.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=\ngithub.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=\ngithub.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=\ngithub.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY=\ngithub.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY=\ngithub.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=\ngithub.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=\ngithub.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=\ngithub.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=\ngithub.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=\ngithub.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=\ngithub.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=\ngithub.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=\ngithub.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=\ngithub.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=\ngithub.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=\ngithub.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=\ngithub.com/go-git/go-git/v5 v5.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0=\ngithub.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=\ngithub.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=\ngithub.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=\ngithub.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=\ngithub.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=\ngithub.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=\ngithub.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=\ngithub.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=\ngithub.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=\ngithub.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=\ngithub.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=\ngithub.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=\ngithub.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=\ngithub.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=\ngithub.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=\ngithub.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=\ngithub.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=\ngithub.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=\ngithub.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=\ngithub.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=\ngithub.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=\ngithub.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=\ngithub.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=\ngithub.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=\ngithub.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=\ngithub.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=\ngithub.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=\ngithub.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU=\ngithub.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y=\ngithub.com/google/go-github/v67 v67.0.0 h1:g11NDAmfaBaCO8qYdI9fsmbaRipHNWRIU/2YGvlh4rg=\ngithub.com/google/go-github/v67 v67.0.0/go.mod h1:zH3K7BxjFndr9QSeFibx4lTKkYS3K9nDanoI1NjaOtY=\ngithub.com/google/go-github/v72 v72.0.0 h1:FcIO37BLoVPBO9igQQ6tStsv2asG4IPcYFi655PPvBM=\ngithub.com/google/go-github/v72 v72.0.0/go.mod h1:WWtw8GMRiL62mvIquf1kO3onRHeWWKmK01qdCY8c5fg=\ngithub.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=\ngithub.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=\ngithub.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=\ngithub.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=\ngithub.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=\ngithub.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=\ngithub.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y=\ngithub.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14=\ngithub.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=\ngithub.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=\ngithub.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=\ngithub.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=\ngithub.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=\ngithub.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=\ngithub.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=\ngithub.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=\ngithub.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=\ngithub.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=\ngithub.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=\ngithub.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=\ngithub.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=\ngithub.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=\ngithub.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=\ngithub.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=\ngithub.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8=\ngithub.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=\ngithub.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=\ngithub.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=\ngithub.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=\ngithub.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=\ngithub.com/jedib0t/go-pretty/v6 v6.6.8 h1:JnnzQeRz2bACBobIaa/r+nqjvws4yEhcmaZ4n1QzsEc=\ngithub.com/jedib0t/go-pretty/v6 v6.6.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=\ngithub.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg=\ngithub.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI=\ngithub.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=\ngithub.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/jpillora/s3 v1.1.4 h1:YCCKDWzb/Ye9EBNd83ATRF/8wPEy0xd43Rezb6u6fzc=\ngithub.com/jpillora/s3 v1.1.4/go.mod h1:yedE603V+crlFi1Kl/5vZJaBu9pUzE9wvKegU/lF2zs=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=\ngithub.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=\ngithub.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=\ngithub.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d h1:RnWZeH8N8KXfbwMTex/KKMYMj0FJRCF6tQubUuQ02GM=\ngithub.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d/go.mod h1:phT/jsRPBAEqjAibu1BurrabCBNTYiVI+zbmyCZJY6Q=\ngithub.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=\ngithub.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=\ngithub.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=\ngithub.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=\ngithub.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=\ngithub.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=\ngithub.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38=\ngithub.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo=\ngithub.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY=\ngithub.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU=\ngithub.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=\ngithub.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=\ngithub.com/lestrrat-go/httprc/v3 v3.0.1 h1:3n7Es68YYGZb2Jf+k//llA4FTZMl3yCwIjFIk4ubevI=\ngithub.com/lestrrat-go/httprc/v3 v3.0.1/go.mod h1:2uAvmbXE4Xq8kAUjVrZOq1tZVYYYs5iP62Cmtru00xk=\ngithub.com/lestrrat-go/jwx/v3 v3.0.12 h1:p25r68Y4KrbBdYjIsQweYxq794CtGCzcrc5dGzJIRjg=\ngithub.com/lestrrat-go/jwx/v3 v3.0.12/go.mod h1:HiUSaNmMLXgZ08OmGBaPVvoZQgJVOQphSrGr5zMamS8=\ngithub.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=\ngithub.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=\ngithub.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=\ngithub.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg=\ngithub.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=\ngithub.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=\ngithub.com/lrstanley/bubblezone v0.0.0-20250404061050-e13639e27357 h1:DxFncLGTrDh5v0z+DE7h+qjD5tPCGR+3knGVVfT3YLI=\ngithub.com/lrstanley/bubblezone v0.0.0-20250404061050-e13639e27357/go.mod h1:S5etECMx+sZnW0Gm100Ma9J1PgVCTgNyFaqGu2b08b4=\ngithub.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=\ngithub.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=\ngithub.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=\ngithub.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=\ngithub.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/mariduv/ldap-verify v0.0.2 h1:NBdDTYyWDr71CONVcizasqL/AA9tQ2RNgLhTgnyfquI=\ngithub.com/mariduv/ldap-verify v0.0.2/go.mod h1:d/7+kkMBGDs9LPZ/7hmduYqtOkRIJcgpa8dL+9CsveE=\ngithub.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM=\ngithub.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=\ngithub.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=\ngithub.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=\ngithub.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=\ngithub.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/mholt/archives v0.0.0-20241216060121-23e0af8fe73d h1:Vw3f39TqFSQLA+OyW+8SouppHTYzX8/fDv6Ao8uj3Ho=\ngithub.com/mholt/archives v0.0.0-20241216060121-23e0af8fe73d/go.mod h1:j/Ire/jm42GN7h90F5kzj6hf6ZFzEH66de+hmjEKu+I=\ngithub.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=\ngithub.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=\ngithub.com/microsoft/go-mssqldb v1.8.2 h1:236sewazvC8FvG6Dr3bszrVhMkAl4KYImryLkRMCd0I=\ngithub.com/microsoft/go-mssqldb v1.8.2/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo=\ngithub.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=\ngithub.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=\ngithub.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=\ngithub.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=\ngithub.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=\ngithub.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=\ngithub.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=\ngithub.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=\ngithub.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=\ngithub.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=\ngithub.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=\ngithub.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=\ngithub.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=\ngithub.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=\ngithub.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=\ngithub.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=\ngithub.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=\ngithub.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=\ngithub.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=\ngithub.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=\ngithub.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=\ngithub.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=\ngithub.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=\ngithub.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=\ngithub.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=\ngithub.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=\ngithub.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=\ngithub.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=\ngithub.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=\ngithub.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=\ngithub.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=\ngithub.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=\ngithub.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/nwaples/rardecode/v2 v2.2.1 h1:DgHK/O/fkTQEKBJxBMC5d9IU8IgauifbpG78+rZJMnI=\ngithub.com/nwaples/rardecode/v2 v2.2.1/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=\ngithub.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=\ngithub.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=\ngithub.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=\ngithub.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=\ngithub.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=\ngithub.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=\ngithub.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=\ngithub.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=\ngithub.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=\ngithub.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=\ngithub.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=\ngithub.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=\ngithub.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=\ngithub.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=\ngithub.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=\ngithub.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=\ngithub.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=\ngithub.com/paulbellamy/ratecounter v0.2.0 h1:2L/RhJq+HA8gBQImDXtLPrDXK5qAj6ozWVK/zFXVJGs=\ngithub.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE=\ngithub.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=\ngithub.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=\ngithub.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=\ngithub.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=\ngithub.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=\ngithub.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=\ngithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=\ngithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=\ngithub.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=\ngithub.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=\ngithub.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=\ngithub.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=\ngithub.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=\ngithub.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=\ngithub.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=\ngithub.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=\ngithub.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=\ngithub.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=\ngithub.com/repeale/fp-go v0.11.1 h1:Q/e+gNyyHaxKAyfdbBqvip3DxhVWH453R+kthvSr9Mk=\ngithub.com/repeale/fp-go v0.11.1/go.mod h1:4KrwQJB1VRY+06CA+jTc4baZetr6o2PeuqnKr5ybQUc=\ngithub.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=\ngithub.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y=\ngithub.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=\ngithub.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=\ngithub.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=\ngithub.com/schollz/progressbar/v3 v3.17.1 h1:bI1MTaoQO+v5kzklBjYNRQLoVpe0zbyRZNK6DFkVC5U=\ngithub.com/schollz/progressbar/v3 v3.17.1/go.mod h1:RzqpnsPQNjUyIgdglUjRLgD7sVnxN1wpmBMV+UiEbL4=\ngithub.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=\ngithub.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=\ngithub.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=\ngithub.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=\ngithub.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1GjJohAA0p6hVEaDtHWWs=\ngithub.com/sendgrid/sendgrid-go v3.16.1+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=\ngithub.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=\ngithub.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=\ngithub.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=\ngithub.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=\ngithub.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=\ngithub.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=\ngithub.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=\ngithub.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=\ngithub.com/shuheiktgw/go-travis v0.3.1 h1:SAT16mi77ccqogOslnXxBXzXbpeyChaIYUwi2aJpVZY=\ngithub.com/shuheiktgw/go-travis v0.3.1/go.mod h1:avnFFDqJDdRHwlF9tgqvYi3asQCm/HGL8aLxYiKa4Yg=\ngithub.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 h1:cYCy18SHPKRkvclm+pWm1Lk4YrREb4IOIb/YdFO0p2M=\ngithub.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8=\ngithub.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0=\ngithub.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=\ngithub.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=\ngithub.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=\ngithub.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=\ngithub.com/smartystreets/gunit v1.1.3 h1:32x+htJCu3aMswhPw3teoJ+PnWPONqdNgaGs6Qt8ZaU=\ngithub.com/smartystreets/gunit v1.1.3/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ=\ngithub.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg=\ngithub.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk=\ngithub.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=\ngithub.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/testcontainers/testcontainers-go v0.34.0 h1:5fbgF0vIN5u+nD3IWabQwRybuB4GY8G2HHgCkbMzMHo=\ngithub.com/testcontainers/testcontainers-go v0.34.0/go.mod h1:6P/kMkQe8yqPHfPWNulFGdFHTD8HB2vLq/231xY2iPQ=\ngithub.com/testcontainers/testcontainers-go/modules/elasticsearch v0.34.0 h1:BBwJUs9xBpt1uOfO+yAr2pYW75MsyzuO/o70HTPnhe4=\ngithub.com/testcontainers/testcontainers-go/modules/elasticsearch v0.34.0/go.mod h1:OqhRGYR+5VG0Dw506F6Ho9I4YG1kB+o9uPTKC0uPUA8=\ngithub.com/testcontainers/testcontainers-go/modules/mongodb v0.34.0 h1:o3bgcECyBFfMwqexCH/6vIJ8XzbCffCP/Euesu33rgY=\ngithub.com/testcontainers/testcontainers-go/modules/mongodb v0.34.0/go.mod h1:ljLR42dN7k40CX0dp30R8BRIB3OOdvr7rBANEpfmMs4=\ngithub.com/testcontainers/testcontainers-go/modules/mssql v0.34.0 h1:4Pf7ILuLnxhpeTgQfKzEMPuMQhasU3VaYer9l5HrQ3s=\ngithub.com/testcontainers/testcontainers-go/modules/mssql v0.34.0/go.mod h1:L2eMWZ49X0XjewabzJ6TXSY5z4SAWM/2WOBqlIxYFD8=\ngithub.com/testcontainers/testcontainers-go/modules/mysql v0.34.0 h1:Tqz17mGXjPORHFS/oBUGdeJyIsZXLsVVHRhaBqhewGI=\ngithub.com/testcontainers/testcontainers-go/modules/mysql v0.34.0/go.mod h1:hDpm3DLfjo7rd6232wWflEBDGr6Ow9ys43mJTiJwWx8=\ngithub.com/testcontainers/testcontainers-go/modules/postgres v0.34.0 h1:c51aBXT3v2HEBVarmaBnsKzvgZjC5amn0qsj8Naqi50=\ngithub.com/testcontainers/testcontainers-go/modules/postgres v0.34.0/go.mod h1:EWP75ogLQU4M4L8U+20mFipjV4WIR9WtlMXSB6/wiuc=\ngithub.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=\ngithub.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=\ngithub.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=\ngithub.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=\ngithub.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=\ngithub.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=\ngithub.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=\ngithub.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=\ngithub.com/trufflesecurity/disk-buffer-reader v0.2.1 h1:K9nNpX3xeWT2E6YRjlcc1X5c1NjgV9JS5T9aw2FjA8Q=\ngithub.com/trufflesecurity/disk-buffer-reader v0.2.1/go.mod h1:uYwTCdxzV0o+qaeBMxflOsq4eu2WjrE46qGR2e80O9Y=\ngithub.com/trufflesecurity/overseer v1.2.8 h1:VXlWPiwYaQmwNxY2W1rVulEAG9O6iF1S0LX3wionWYM=\ngithub.com/trufflesecurity/overseer v1.2.8/go.mod h1:Dt6Y9LFpM+C/3rRWpy4//4iS5qrbb0pL3XvZqMd4zhg=\ngithub.com/trufflesecurity/touchfile v0.1.1 h1:Snhd5VEa8Cxd+D60nvLEj2kVeb1omY2tWwnhDhjTqdo=\ngithub.com/trufflesecurity/touchfile v0.1.1/go.mod h1:Yg/AUMrxAk+dWDUjIig0OyGgFOHFuWNw+t2S/GvO6Mk=\ngithub.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=\ngithub.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=\ngithub.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=\ngithub.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=\ngithub.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=\ngithub.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=\ngithub.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=\ngithub.com/wasilibs/go-re2 v1.9.0 h1:kjAd8qbNvV4Ve2Uf+zrpTCrDHtqH4dlsRXktywo73JQ=\ngithub.com/wasilibs/go-re2 v1.9.0/go.mod h1:0sRtscWgpUdNA137bmr1IUgrRX0Su4dcn9AEe61y+yI=\ngithub.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8SqJnZ6P+mjlzc2K7PM22rRUPE1x32G9DTPrC4=\ngithub.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY=\ngithub.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=\ngithub.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=\ngithub.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=\ngithub.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=\ngithub.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=\ngithub.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=\ngithub.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=\ngithub.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=\ngithub.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=\ngithub.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=\ngithub.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=\ngithub.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=\ngithub.com/xo/dburl v0.23.8 h1:NwFghJfjaUW7tp+WE5mTLQQCfgseRsvgXjlSvk7x4t4=\ngithub.com/xo/dburl v0.23.8/go.mod h1:uazlaAQxj4gkshhfuuYyvwCBouOmNnG2aDxTCFZpmL4=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=\ngithub.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=\ngithub.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngithub.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=\ngithub.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=\ngithub.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=\ngithub.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=\ngithub.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=\ngithub.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=\ngithub.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=\ngitlab.com/gitlab-org/api/client-go v1.12.0 h1:vYeraq+4eC/5Scir5nWve3LPxLoY5rgW2qRNUgjKS2k=\ngitlab.com/gitlab-org/api/client-go v1.12.0/go.mod h1:adtVJ4zSTEJ2fP5Pb1zF4Ox1OKFg0MH43yxpb0T0248=\ngo.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=\ngo.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY6LiIY9I8cUfm+pJs=\ngo.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 h1:rbRJ8BBoVMsQShESYZ0FkvcITu8X8QNwJogcLUmDNNw=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0/go.mod h1:ru6KHrNtNHxM4nD/vd6QrLVWgKhxPYgblq4VAtNawTQ=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=\ngo.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=\ngo.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=\ngo.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=\ngo.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=\ngo.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=\ngo.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=\ngo.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=\ngo.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=\ngo.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=\ngo.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=\ngo.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=\ngo.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=\ngo.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=\ngo.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=\ngo.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=\ngo.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=\ngo.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=\ngo4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=\ngolang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE=\ngolang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=\ngolang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=\ngolang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=\ngolang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=\ngolang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=\ngolang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=\ngolang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=\ngolang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.259.0 h1:90TaGVIxScrh1Vn/XI2426kRpBqHwWIzVBzJsVZ5XrQ=\ngoogle.golang.org/api v0.259.0/go.mod h1:LC2ISWGWbRoyQVpxGntWwLWN/vLNxxKBK9KuJRI8Te4=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934=\ngoogle.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=\ngoogle.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=\ngopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=\ngopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=\ngotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\npault.ag/go/debian v0.18.0 h1:nr0iiyOU5QlG1VPnhZLNhnCcHx58kukvBJp+dvaM6CQ=\npault.ag/go/debian v0.18.0/go.mod h1:JFl0XWRCv9hWBrB5MDDZjA5GSEs1X3zcFK/9kCNIUmE=\npault.ag/go/topsort v0.1.1 h1:L0QnhUly6LmTv0e3DEzbN2q6/FGgAcQvaEw65S53Bg4=\npault.ag/go/topsort v0.1.1/go.mod h1:r1kc/L0/FZ3HhjezBIPaNVhkqv8L0UJ9bxRuHRVZ0q4=\npgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=\npgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\nsigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=\nsigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=\n"
  },
  {
    "path": "hack/Dockerfile.protos",
    "content": "# trufflesecurity/protos:1.23\n\nFROM golang:1.24-bullseye\n\nARG TARGETARCH\nARG TARGETOS\n\nENV PROTOC_VER=25.3\nENV PROTOC_GEN_GO_VER=v1.5.4\nENV PROTOC_GEN_VALIDATE_VER=v1.0.4\nENV GORELEASER_VERSION=1.19.2\n\nRUN echo \"building $TARGETARCH\"\nRUN go install github.com/dustin-decker/quill/cmd/quill@v0.5.1\nRUN apt-get update; apt-get install apt-transport-https ca-certificates curl gnupg lsb-release -y; \\\n    curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg; \\\n    echo \"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \\\n    $(lsb_release -cs) stable\" | tee /etc/apt/sources.list.d/docker.list > /dev/null; \\\n    apt-get update; apt-get install -y --no-install-recommends python3-pip docker-ce-cli docker-buildx-plugin docker-compose-plugin \\\n    git netbase wget upx unzip && rm -rf /var/lib/apt/lists/*\nRUN pip3 install --upgrade setuptools pip\nRUN set -e; \\\n\tarch=$(echo $TARGETARCH | sed -e s/amd64/x86_64/ -e s/arm64/aarch_64/); \\\n\twget -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\nRUN 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\nRUN echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | tee /etc/apt/sources.list.d/goreleaser.list; \\\n    apt-get update; apt-get install -y goreleaser=${GORELEASER_VERSION} && rm -rf /var/lib/apt/lists/*\nRUN go install \"github.com/golang/protobuf/protoc-gen-go@${PROTOC_GEN_GO_VER}\"\nRUN go install gotest.tools/gotestsum@latest\nRUN git clone https://github.com/envoyproxy/protoc-gen-validate $GOPATH/src/github.com/envoyproxy/protoc-gen-validate && \\\n    cd $GOPATH/src/github.com/envoyproxy/protoc-gen-validate && \\\n    git checkout ${PROTOC_GEN_VALIDATE_VER} && \\\n    ln -s /usr/local/protoc/include/google google && \\\n    make build\nCMD [\"bash\"]\n"
  },
  {
    "path": "hack/bench/plot.gp",
    "content": "set terminal png size 800,600\nset output \"hack/bench/versions.png\"\n\nset title \"User Time vs. Version\"\nset xlabel \"Version\"\nset ylabel \"Average User Time (s)\"\n\nset xtics rotate by -45\n\nplot \"hack/bench/plot.txt\" using 2:xtic(1) with linespoints linestyle 1 notitle\n"
  },
  {
    "path": "hack/bench/plot.sh",
    "content": "#!/bin/bash\n\nif [ $# -ne 2 ]; then\n  echo \"Usage: $0 <repository to clone> <number_of_versions_back_to_test>\"\n  exit 1\nfi\n\n# Get the number of versions back to test from command line argument\nnum_versions=\"$2\"\n\ntest_repo=\"$1\"\n\nbash hack/bench/versions.sh $test_repo $num_versions | tee hack/bench/plot.txt\n\ngnuplot hack/bench/plot.gp\n"
  },
  {
    "path": "hack/bench/plot.txt",
    "content": "v3.33.0: 1.402\nv3.32.2: 1.298\nv3.32.1: 1.332\nv3.32.0: 1.348\nv3.31.6: 2.470\nv3.31.5: 2.462\nv3.31.4: 2.460\nv3.31.3: 2.418\nv3.31.2: 1.384\nv3.31.1: 1.344\nv3.31.0: 1.354\nv3.30.0: 1.392\nv3.29.1: 1.382\nv3.29.0: 1.340\nv3.28.7: 1.380\nv3.28.6: 1.308\nv3.28.5: 2.596\nv3.28.4: 2.554\nv3.28.3: 2.582\nv3.28.1: 2.578\nv3.28.2: 2.566\nv3.28.0: 2.552\nv3.27.1: 2.574\nv3.26.0: 2.538\n"
  },
  {
    "path": "hack/bench/versions.sh",
    "content": "#!/bin/bash\n\nif [ $# -ne 2 ]; then\n  echo \"Usage: $0 <repository to clone> <number_of_versions_back_to_test>\"\n  exit 1\nfi\n\n# Get the number of versions back to test from command line argument\nnum_versions=\"$2\"\n\ntest_repo=\"$1\"\n\nnum_iterations=5\n\n# Create a temporary folder to clone the repository\nrepo_tmp=$(mktemp -d)\n# Set up a trap to remove the temporary folder on exit or failure\ntrap \"rm -rf $repo_tmp\" EXIT\n# Clone the test repository to a temporary folder\ngit clone --quiet \"$test_repo\" $repo_tmp\n\n\n# Get list of git tags, sorted from newest to oldest\ntags=$(echo $(git describe --tags --always --dirty --match='v*') $(git tag --sort=-creatordate))\n\n# Counter to keep track of number of tags checked out\ncount=0\n\n\n# Loop over tags and checkout each one in turn, up to the specified number of versions\nfor tag in $tags\ndo\n  if [[ $count -eq $num_versions ]]; then\n      break\n  fi\n\n  # Skip RC tags\n  if [[ $tag == *\"rc\"* ]]; then\n    continue\n  fi\n\n  # Skip alpha tags\n  if [[ $tag == *\"alpha\"* ]];  then\n    continue\n  fi\n\n  # Use git checkout with the quiet flag to suppress output\n  git checkout $tag --quiet\n\n  # Run make install with suppressed output\n  make install > /dev/null\n\n  # Initialize the variable to store the sum of user times\n  user_time_sum=0\n\n  # Run each iteration 5 times and calculate the average user time\n  for i in {1..$num_iterations}\n  do\n    # Run trufflehog with suppressed output and capture user time with /usr/bin/time\n    tmpfile=$(mktemp)\n    /usr/bin/time -o $tmpfile trufflehog git \"file://$repo_tmp\" --no-verification --no-update >/dev/null 2>&1\n    time_output=$(cat $tmpfile)\n    rm $tmpfile\n\n    # Extract the user time from the output\n    user_time=$(echo $time_output | awk '{print $3}')\n\n    # Add the user time to the sum\n    user_time_sum=$(echo \"$user_time_sum + $user_time\" | bc)\n  done\n\n  # Calculate the average user time\n  average_user_time=$(echo \"scale=3; $user_time_sum / $num_iterations\" | bc)\n\n  # Print the average user time output for this iteration in the specified format\n  echo \"$tag: $average_user_time\"\n\n  # Increment the counter\n  count=$((count+1))\ndone\n"
  },
  {
    "path": "hack/docs/Adding_Detectors_Internal.md",
    "content": "# Secret Detectors\n\nSecret Detectors have these two major functions:\n\n1. Given some bytes, extract possible secrets, typically using a regex.\n2. Validate the secrets against the target API, typically using a HTTP client.\n\nThe purpose of Secret Detectors is to discover secrets with exceptionally high signal. High rates of false positives are not accepted.\n\n## Table of Contents\n\n- [Secret Detectors](#secret-detectors)\n  - [Table of Contents](#table-of-contents)\n  - [Getting Started](#getting-started)\n    - [Sourcing Guidelines](#sourcing-guidelines)\n    - [Development Guidelines](#development-guidelines)\n    - [Development Dependencies](#development-dependencies)\n    - [Creating a new Secret Scanner](#creating-a-new-secret-detector)\n  - [Addendum](#addendum)\n    - [Managing Test Secrets](#managing-test-secrets)\n    - [Setting up Google Cloud SDK](#setting-up-google-cloud-sdk)\n\n## Getting Started\n\n### Sourcing Guidelines\n\nWe are interested in detectors for services that meet at least one of these criteria\n- host data (they store any sort of data provided)\n- have paid services (having a free or trial tier is okay though)\n\nIf you think that something should be included outside of these guidelines, please let us know.\n\n### Development Guidelines\n\n- When reasonable, favor using the `net/http` library to make requests instead of bringing in another library.\n- Use the [`common.SaneHttpClient`](pkg/common/http.go) for the `http.Client` whenever possible.\n- We recommend an editor with gopls integration (such as Vscode with Go plugin) for benefits like easily running tests, autocompletion, linting, type checking, etc.\n\n### Development Dependencies\n\n- A GitLab account\n- A Google account\n- [Google Cloud SDK installed](#setting-up-google-cloud-sdk)\n- Go 1.17+\n- Make\n\n### Adding New Token Formats to an Existing Scanner\n\nIn 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.\n\n1. Create two new directories `v1` and `v2`. Move the existing detector and tests into `v1`, and add new files to `v2`.\nEx: `<packagename>/<old_files>` -> `<packagename>/v1/<old_files>`, `<packagename>/v2/<new_files>`\n\nNote: Be sure to update the tests to reference the new secret values in GSM, or the tests will fail.\n\n2. Implement the `Versioner` interface. [GitHub example implementation.](/pkg/detectors/github/v1/github_old.go#L23)\n\n3. Add a 'version' field in ExtraData for both existing and new detector versions.\n\n4. Update the existing detector in DefaultDetectors in `/pkg/engine/defaults/defaults.go`\n\n5. Proceed from step 3 of [Creating a new Secret Scanner](#creating-a-new-secret-scanner)\n\n### Creating a new Secret Scanner\n\n1. Identify the Secret Detector name from the [/proto/detectors.proto](/proto/detectors.proto) `DetectorType` enum.\n\n2. Generate the Secret Detector\n\n   ```bash\n   go run hack/generate/generate.go detector <DetectorType enum name>\n   ```\n\n3. Complete the secret detector.\n\n   The previous step templated a boilerplate + some example code as a package in the `pkg/detectors` folder for you to work on.\n   The secret detector can be completed with these general steps:\n\n   1. Add the test secret to GCP Secrets. See [managing test secrets](#managing-test-secrets)\n   2. Update the pattern regex and keywords. Try iterating with [regex101.com](http://regex101.com/).\n   3. Update the verifier code to use a non-destructive API call that can determine whether the secret is valid or not.\n      * Make sure you understand [verification indeterminacy](#verification-indeterminacy).\n   4. Update the tests with these test cases at minimum:\n      1. Found and verified (using a credential loaded from GCP Secrets)\n      2. Found and unverified (determinately, i.e. the secret is invalid)\n      3. Found and unverified (indeterminately due to timeout)\n      4. Found and unverified (indeterminately due to an unexpected API response)\n      5. Not found\n      6. Any false positive cases that you come across\n   5. Add your new detector to DefaultDetectors in `/pkg/engine/defaults/defaults.go`\n   6. Create a merge request for review. CI tests must be passing.\n\n## Addendum\n\n### Verification indeterminacy\n\nThere are two types of reasons that secret verification can fail:\n* The candidate secret is not actually a valid secret.\n* Something went wrong in the process unrelated to the candidate secret, such as a transient network error or an unexpected API response.\n\nIn 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.\n\nFor 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:\n* A `200` response would indicate that verification succeeded. (Or maybe any `2xx` response.)\n* A `403` response would indicate that verification failed **determinately** and no error object should be returned.\n* Any other response would indicate that verification failed **indeterminately** and an error object should be returned.\n\n### Managing Test Secrets\n\nDo not embed test credentials in the test code. Instead, use GCP Secrets Manager.\n\n1. Access the latest secret version for modification.\n\n   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).\n\n   ```bash\n   gcloud secrets versions access --project trufflehog-testing --secret detectors5 latest > /tmp/s\n   ```\n\n2. Add the secret that you need for testing.\n\n   The command above saved it to `/tmp/s`.\n\n   The format is standard env file format,\n\n   ```bash\n   SECRET_TYPE_ONE=value\n   SECRET_TYPE_ONE_INACTIVE=v@lue\n   ```\n\n3. Update the secret version with your modification.\n\n   ```bash\n   gcloud secrets versions add --project trufflehog-testing detectors5 --data-file /tmp/s\n   ```\n   Note: We increment the detectors file name `detectors(n+1)` once the previous one exceeds the max size allowed by GSM (65kb).\n\n4. Access the secret value as shown in the [example code](pkg/detectors/heroku/heroku_test.go).\n\n### Setting up Google Cloud SDK\n\n1. Install the Google Cloud SDK: https://cloud.google.com/sdk/docs/install\n2. Authenticate with `gcloud auth login --update-adc` using your Google account\n\n### Adding Protos in Windows\n\n1. Install Ubuntu App in Microsoft Store https://www.microsoft.com/en-us/p/ubuntu/9nblggh4msv6.\n2. 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.\n3. Open Ubuntu cli and install `dos2unix`.\n   ```bash\n   sudo apt install dos2unix\n   ```\n4. Identify the `trufflehog` local directory and convert `scripts/gen_proto.sh` file in Unix format.\n   ```bash\n   dos2unix ./scripts/gen_proto.sh\n   ```\n5. 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.\n   ```bash\n   make protos\n   ```\n### Testing a detector\n```bash\n   go test ./pkg/detectors/<detector> -tags=detectors\n   ```\n"
  },
  {
    "path": "hack/docs/Adding_Detectors_external.md",
    "content": "# Secret Detectors\n\nSecret Detectors have these two major functions:\n\n1. Given some bytes, extract possible secrets, typically using a regex.\n2. Validate the secrets against the target API, typically using a HTTP client.\n\nThe purpose of Secret Detectors is to discover secrets with exceptionally high signal. High rates of false positives are not accepted.\n\n## Table of Contents\n\n- [Secret Detectors](#secret-detectors)\n  * [Table of Contents](#table-of-contents)\n  * [Getting Started](#getting-started)\n    + [Sourcing Guidelines](#sourcing-guidelines)\n    + [Development Guidelines](#development-guidelines)\n    + [Development Dependencies](#development-dependencies)\n    + [Creating a new Secret Detector](#creating-a-new-secret-detector)\n    + [Testing the Detector](#testing-the-detector)\n  * [Addendum](#addendum)\n    + [Adding Protos in Windows](#adding-protos-in-windows)\n\n## Getting Started\n\n### Sourcing Guidelines\n\nWe are interested in detectors for services that meet at least one of these criteria\n- host data (they store any sort of data provided)\n- have paid services (having a free or trial tier is okay though)\n\nIf you think that something should be included outside of these guidelines, please let us know.\n\n### Development Guidelines\n\n- When reasonable, favor using the `net/http` library to make requests instead of bringing in another library.\n- Use the [`common.SaneHttpClient`](/pkg/common/http.go) for the `http.Client` whenever possible.\n\n### Development Dependencies\n\n- Go 1.17+\n- Make\n\n### Adding New Token Formats to an Existing Scanner\n\nIn 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.\n\n1. Create two new directories `v1` and `v2`. Move the existing detector and tests into `v1`, and add new files to `v2`.\nEx: `<packagename>/<old_files>` -> `<packagename>/v1/<old_files>`, `<packagename>/v2/<new_files>`\n\nNote: Be sure to update the tests to reference the new secret values in GSM, or the tests will fail.\n\n2. Implement the `Versioner` interface. [GitHub example implementation.](https://github.com/trufflesecurity/trufflehog/blob/2964b3b2d2edf2b60b1f71443338c6534720b67a/pkg/detectors/github/v1/github_old.go#L23))\n\n3. Add a 'version' field in ExtraData for both existing and new detector versions.\n\n4. Update the existing detector in DefaultDetectors in `/pkg/engine/defaults/defaults.go`\n\n5. Proceed from step 3 of [Creating a new Secret Scanner](#creating-a-new-secret-scanner)\n\n### Creating a new Secret Detector\n\n1. Add a new Secret Detector enum to the [`DetectorType` list here](/proto/detectors.proto).\n\n2. Run `make protos` to update the `.pb` files.\n\n3. Generate the Secret Detector\n\n   ```bash\n   go run hack/generate/generate.go detector <DetectorType enum name>\n   example: go run hack/generate/generate.go detector SampleAPI\n   ```\n4. Add the Secret Detector to TruffleHog's Default Detectors\n\n   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/<detector_name>`](https://github.com/trufflesecurity/trufflehog/blob/b71ea27a696bdf1c3141f637fda4ee4936c2f2d6/pkg/engine/defaults/defaults.go#L9) and \n   [`<detector_name>.Scanner{}`](https://github.com/trufflesecurity/trufflehog/blob/b71ea27a696bdf1c3141f637fda4ee4936c2f2d6/pkg/engine/defaults/defaults.go#L1546)\n\n5. Complete the Secret Detector.\n\n   The previous step templated a boilerplate + some example code as a package in the `pkg/detectors` folder for you to work on.\n   The Secret Detector can be completed with these general steps:\n\n   1. Update the pattern regex and keywords. Try iterating with [regex101.com](http://regex101.com/).\n   2. Update the verifier code to use a non-destructive API call that can determine whether the secret is valid or not.\n      * Make sure you understand [verification indeterminacy](#verification-indeterminacy).\n   3. Create a [test for the detector](#testing-the-detector).\n   4. Add your new detector to DefaultDetectors in `/pkg/engine/defaults/defaults.go`.\n   5. Create a pull request for review.\n\n### Testing the Detector\nTo ensure the quality of your PR, make sure your tests are passing with verified credentials.\n\n1. Create a file called `.env` with this env file format:\n\n   ```bash\n   SECRET_TYPE_ONE=value\n   SECRET_TYPE_ONE_INACTIVE=v@lue\n   ```\n\n2. Export the `TEST_SECRET_FILE` variable, pointing to the env file:\n\n   ```bash\n   export TEST_SECRET_FILE=\".env\"\n   ```\n   The `.env` file should be in the new detector's directory like this:\n   ```\n   ├── tailscale\n   │   ├── .env\n   │   ├── tailscale.go\n   │   └── tailscale_test.go\n   ```\n   Now that a `.env` file is present, the test file can load secrets locally.\n\n3. 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:\n   1. Found and verified (using a credential loaded from the .env file)\n   2. Found and unverified (determinately, i.e. the secret is invalid)\n   3. Found and unverified (indeterminately due to timeout)\n   4. Found and unverified (indeterminately due to an unexpected API response)\n   5. Not found\n\n    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. \n   [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).\n\n4. Now run the tests and check to make sure they are passing ✔️!\n```bash\n   go test ./pkg/detectors/<detector> -tags=detectors\n   ```\n\nIf the tests are passing, feel free to open a PR! \n\n\n\n\n## Addendum\n\n### Verification indeterminacy\n\nThere are two types of reasons that secret verification can fail:\n* The candidate secret is not actually a valid secret.\n* Something went wrong in the process unrelated to the candidate secret, such as a transient network error or an unexpected API response.\n\nIn 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.\n\nFor 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:\n* A `200` response would indicate that verification succeeded. (Or maybe any `2xx` response.)\n* A `403` response would indicate that verification failed **determinately** and no error object should be returned.\n* Any other response would indicate that verification failed **indeterminately** and an error object should be returned.\n\n### Adding Protos in Windows\n\n1. Install Ubuntu App in Microsoft Store https://www.microsoft.com/en-us/p/ubuntu/9nblggh4msv6.\n2. 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.\n3. Open Ubuntu cli and install `dos2unix`.\n   ```bash\n   sudo apt install dos2unix\n   ```\n4. Identify the `trufflehog` local directory and convert `scripts/gen_proto.sh` file in Unix format.\n   ```bash\n   dos2unix ./scripts/gen_proto.sh\n   ```\n5. 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.\n   ```bash\n   make protos\n   ```\n\n"
  },
  {
    "path": "hack/generate/generate.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/alecthomas/kingpin/v2\"\n\t\"github.com/go-errors/errors\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n)\n\nvar (\n\tapp                             = kingpin.New(\"generate\", \"Generate is used to write new features.\")\n\tkind                            = app.Arg(\"kind\", \"Kind of thing to generate.\").Required().Enum(\"detector\")\n\tname                            = app.Arg(\"name\", \"Name of the Source/Detector to generate.\").Required().String()\n\tnameTitle, nameLower, nameUpper string\n)\n\nfunc main() {\n\tlog.SetFlags(log.Lmsgprefix)\n\tlog.SetPrefix(\"😲 [generate] \")\n\n\tkingpin.MustParse(app.Parse(os.Args[1:]))\n\tnameTitle = cases.Title(language.AmericanEnglish).String(*name)\n\tnameLower = strings.ToLower(*name)\n\tnameUpper = strings.ToUpper(*name)\n\n\tswitch *kind {\n\tcase \"detector\":\n\t\tmustWriteTemplates([]templateJob{\n\t\t\t{\n\t\t\t\tTemplatePath:  \"pkg/detectors/alchemy/alchemy.go\",\n\t\t\t\tWritePath:     filepath.Join(folderPath(), nameLower+\".go\"),\n\t\t\t\tReplaceString: []string{\"alchemy\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tTemplatePath:  \"pkg/detectors/alchemy/alchemy_test.go\",\n\t\t\t\tWritePath:     filepath.Join(folderPath(), nameLower+\"_test.go\"),\n\t\t\t\tReplaceString: []string{\"alchemy\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tTemplatePath:  \"pkg/detectors/alchemy/alchemy_integration_test.go\",\n\t\t\t\tWritePath:     filepath.Join(folderPath(), nameLower+\"_integration_test.go\"),\n\t\t\t\tReplaceString: []string{\"alchemy\"},\n\t\t\t},\n\t\t})\n\t\t// case \"source\":\n\t\t// \tmustWriteTemplates([]templateJob{\n\t\t// \t\t{\n\t\t// \t\t\tTemplatePath:  \"pkg/sources/filesystem/filesystem.go\",\n\t\t// \t\t\tWritePath:     filepath.Join(folderPath(), nameLower+\".go\"),\n\t\t// \t\t\tReplaceString: []string{\"filesystem\"},\n\t\t// \t\t},\n\t\t// \t\t{\n\t\t// \t\t\tTemplatePath:  \"pkg/sources/filesystem/filesystem_test.go\",\n\t\t// \t\t\tWritePath:     filepath.Join(folderPath(), nameLower+\"_test.go\"),\n\t\t// \t\t\tReplaceString: []string{\"filesystem\"},\n\t\t// \t\t},\n\t\t// \t})\n\t}\n}\n\ntype templateJob struct {\n\tTemplatePath  string\n\tWritePath     string\n\tReplaceString []string\n}\n\nfunc mustWriteTemplates(jobs []templateJob) {\n\tlog.Printf(\"Generating %s %s\\n\", cases.Title(language.AmericanEnglish).String(*kind), nameTitle)\n\n\t// Make the folder.\n\tlog.Printf(\"Creating folder %s\\n\", folderPath())\n\terr := makeFolder(folderPath())\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Write the files from templates.\n\tfor _, job := range jobs {\n\t\ttmplBytes, err := os.ReadFile(job.TemplatePath)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\ttmplRaw := string(tmplBytes)\n\n\t\tfor _, rplString := range job.ReplaceString {\n\t\t\trplTitle := cases.Title(language.AmericanEnglish).String(rplString)\n\t\t\ttmplRaw = strings.ReplaceAll(tmplRaw, \"DetectorType_\"+rplTitle, \"DetectorType_<<.Name>>\")\n\t\t\ttmplRaw = strings.ReplaceAll(tmplRaw, strings.ToLower(rplString), \"<<.NameLower>>\")\n\t\t\ttmplRaw = strings.ReplaceAll(tmplRaw, rplTitle, \"<<.NameTitle>>\")\n\t\t\ttmplRaw = strings.ReplaceAll(tmplRaw, strings.ToUpper(rplString), \"<<.NameUpper>>\")\n\t\t}\n\n\t\ttmpl := template.Must(template.New(\"main\").Delims(\"<<\", \">>\").Parse(tmplRaw))\n\n\t\tlog.Printf(\"Writing file %s\\n\", job.WritePath)\n\t\tf, err := os.OpenFile(job.WritePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\terr = tmpl.Execute(f, templateData{\n\t\t\tName:      *name,\n\t\t\tNameTitle: nameTitle,\n\t\t\tNameLower: nameLower,\n\t\t\tNameUpper: nameUpper,\n\t\t})\n\t\tif err != nil {\n\t\t\tlog.Fatal(fmt.Errorf(\"failed to execute template: %w\", err))\n\t\t}\n\t}\n}\n\ntype templateData struct {\n\tName      string\n\tNameTitle string\n\tNameLower string\n\tNameUpper string\n}\n\nfunc folderPath() string {\n\treturn filepath.Join(\"pkg/\", *kind+\"s\", nameLower)\n}\n\nfunc makeFolder(path string) error {\n\t_, err := os.Stat(path)\n\tif os.IsNotExist(err) {\n\t\terr := os.MkdirAll(path, 0755)\n\t\tif err != nil {\n\t\t\treturn errors.New(err)\n\t\t}\n\t\treturn nil\n\t}\n\treturn errors.Errorf(\"%s %s already exists\", *kind, *name)\n}\n"
  },
  {
    "path": "hack/generate/test.sh",
    "content": "#!/usr/bin/env bash\nset -eu\n\nfunction cleanup {\n  rm -rf pkg/detectors/test\n}\ntrap cleanup EXIT\n\nexport CGO_ENABLED=0\n\nexport FORCE_PASS_DIFF=true\n\necho \"████████████ Testing generate Detector\"\ngo run hack/generate/generate.go detector Test\ngo test ./pkg/detectors/test -benchmem -bench .\necho \"\"\n"
  },
  {
    "path": "hack/semgrep-rules/detectors.yaml",
    "content": "rules:\n  - id: no-printing-in-detectors\n    patterns:\n      - pattern-either:\n          - pattern: fmt.Println(...)\n          - pattern: fmt.Printf(...)\n          - pattern: import(\"log\")\n    message: \"Do not print or log inside of detectors.\"\n    languages: [go]\n    severity: ERROR\n"
  },
  {
    "path": "hack/snifftest/README.md",
    "content": "# snifftest\n\nSee the help pages with this command, or look further below to get started quickly.\n\n```\ngo run hack/snifftest/main.go\n```\n\n## Show available secret scanners\n\n```\ngo run hack/snifftest/main.go show-scanners\n```\n\n## Scan\n\nAll scanners\n\n```\ngo run snifftest/main.go scan --db ~/sdb --scanner all --print\n```\n\nParticular scanner\n\n```\ngo run snifftest/main.go scan --db ~/sdb --scanner github --print --print-chunk --fail-threshold 5\n```\n"
  },
  {
    "path": "hack/snifftest/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/alecthomas/kingpin/v2\"\n\t\"github.com/paulbellamy/ratecounter\"\n\t\"golang.org/x/sync/semaphore\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/decoders\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/defaults\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/log\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git\"\n)\n\nvar (\n\t// CLI flags and commands\n\tapp = kingpin.New(\"Snifftest\", \"Test secret detectors against data sets.\")\n\n\tshowDetectorsCmd = app.Command(\"show-detectors\", \"Shows the available detectors.\")\n\n\tscanCmd           = app.Command(\"scan\", \"Scans data.\")\n\tscanCmdDetector   = scanCmd.Flag(\"detector\", \"Detector to scan with. 'all', or a specific name.\").Default(\"all\").String()\n\tscanCmdExclude    = scanCmd.Flag(\"exclude\", \"Detector(s) to exclude\").Strings()\n\tscanCmdRepo       = scanCmd.Flag(\"repo\", \"URI to .git repo.\").Required().String()\n\tscanThreshold     = scanCmd.Flag(\"fail-threshold\", \"Result threshold that causes failure for a single scanner.\").Int()\n\tscanPrintRes      = scanCmd.Flag(\"print\", \"Print results.\").Bool()\n\tscanPrintChunkRes = scanCmd.Flag(\"print-chunk\", \"Print chunks that have results.\").Bool()\n\tscanVerify        = scanCmd.Flag(\"verify\", \"Verify found secrets.\").Bool()\n)\n\nfunc main() {\n\t// setup logger\n\tlogger, flush := log.New(\"trufflehog\", log.WithConsoleSink(os.Stderr))\n\t// make it the default logger for contexts\n\tcontext.SetDefaultLogger(logger)\n\tdefer func() { _ = flush() }()\n\tlogFatal := func(err error, message string, keyAndVals ...any) {\n\t\tlogger.Error(err, message, keyAndVals...)\n\t\tif err != nil {\n\t\t\tos.Exit(1)\n\t\t\treturn\n\t\t}\n\t\tos.Exit(0)\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), time.Hour*2)\n\tvar cancelOnce sync.Once\n\tdefer cancelOnce.Do(cancel)\n\n\tcmd := kingpin.MustParse(app.Parse(os.Args[1:]))\n\n\tswitch cmd {\n\tcase scanCmd.FullCommand():\n\n\t\tchunksChan := make(chan *sources.Chunk, 10000)\n\n\t\tvar wgChunkers sync.WaitGroup\n\n\t\tsem := semaphore.NewWeighted(int64(runtime.NumCPU()))\n\n\t\tselectedScanners := map[string]detectors.Detector{}\n\t\tallScanners := getAllScanners()\n\n\t\tallDecoders := decoders.DefaultDecoders()\n\n\t\tinput := strings.ToLower(*scanCmdDetector)\n\t\tif input == \"all\" {\n\t\t\tselectedScanners = allScanners\n\t\t} else {\n\t\t\t_, ok := allScanners[input]\n\t\t\tif !ok {\n\t\t\t\tlogFatal(fmt.Errorf(\"invalid input\"), \"could not find scanner by that name\")\n\t\t\t}\n\t\t\tselectedScanners[input] = allScanners[input]\n\t\t}\n\t\tif len(selectedScanners) == 0 {\n\t\t\tlogFatal(fmt.Errorf(\"invalid input\"), \"no detectors selected\")\n\t\t}\n\n\t\tfor _, excluded := range *scanCmdExclude {\n\t\t\tdelete(selectedScanners, excluded)\n\t\t}\n\n\t\tlogger.Info(\"loaded secret detectors\", \"count\", len(selectedScanners)+3)\n\n\t\tvar wgScanners sync.WaitGroup\n\n\t\tvar chunkCounter uint64\n\t\tgo func() {\n\t\t\tcounter := ratecounter.NewRateCounter(60 * time.Second)\n\t\t\tvar prev uint64\n\t\t\tfor {\n\t\t\t\ttime.Sleep(60 * time.Second)\n\t\t\t\tcounter.Incr(int64(chunkCounter - prev))\n\t\t\t\tprev = chunkCounter\n\t\t\t\tlogger.Info(\"chunk scan rate per second\", \"rate\", counter.Rate()/60)\n\t\t\t}\n\t\t}()\n\n\t\tresCounter := make(map[string]*uint64)\n\t\tfailed := false\n\n\t\tfor i := 0; i < runtime.NumCPU(); i++ {\n\t\t\twgScanners.Add(1)\n\n\t\t\tgo func() {\n\t\t\t\tdefer wgScanners.Done()\n\n\t\t\t\tfor chunk := range chunksChan {\n\t\t\t\t\tfor name, scanner := range selectedScanners {\n\t\t\t\t\t\tfor _, dec := range allDecoders {\n\t\t\t\t\t\t\tdecoded := dec.FromChunk(&sources.Chunk{Data: chunk.Data})\n\t\t\t\t\t\t\tif decoded != nil {\n\t\t\t\t\t\t\t\tfoundKeyword := false\n\t\t\t\t\t\t\t\tfor _, kw := range scanner.Keywords() {\n\t\t\t\t\t\t\t\t\tif strings.Contains(strings.ToLower(string(decoded.Data)), strings.ToLower(kw)) {\n\t\t\t\t\t\t\t\t\t\tfoundKeyword = true\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif !foundKeyword {\n\t\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tres, err := scanner.FromData(ctx, *scanVerify, decoded.Data)\n\t\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\t\tlogFatal(err, \"error scanning chunk\")\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif len(res) > 0 {\n\t\t\t\t\t\t\t\t\tif resCounter[name] == nil {\n\t\t\t\t\t\t\t\t\t\tzero := uint64(0)\n\t\t\t\t\t\t\t\t\t\tresCounter[name] = &zero\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tatomic.AddUint64(resCounter[name], uint64(len(res)))\n\t\t\t\t\t\t\t\t\tif *scanThreshold != 0 && int(*resCounter[name]) > *scanThreshold {\n\t\t\t\t\t\t\t\t\t\tlogger.Error(\n\t\t\t\t\t\t\t\t\t\t\tfmt.Errorf(\"exceeded result threshold\"), \"snifftest failed\",\n\t\t\t\t\t\t\t\t\t\t\t\"scanner\", name, \"threshold\", *scanThreshold,\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\tfailed = true\n\t\t\t\t\t\t\t\t\t\tos.Exit(1)\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tif *scanPrintRes {\n\t\t\t\t\t\t\t\t\t\tfor _, r := range res {\n\t\t\t\t\t\t\t\t\t\t\tlogger := logger.WithValues(\"secret\", name, \"meta\", chunk.SourceMetadata, \"result\", string(r.Raw))\n\t\t\t\t\t\t\t\t\t\t\tif *scanPrintChunkRes {\n\t\t\t\t\t\t\t\t\t\t\t\tlogger = logger.WithValues(\"chunk\", string(decoded.Data))\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tlogger.Info(\"result\")\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tatomic.AddUint64(&chunkCounter, uint64(1))\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\n\t\tfor _, repo := range strings.Split(*scanCmdRepo, \",\") {\n\t\t\tif err := sem.Acquire(ctx, 1); err != nil {\n\t\t\t\tlogFatal(err, \"timed out waiting for semaphore\")\n\t\t\t}\n\t\t\twgChunkers.Add(1)\n\t\t\tgo func(r string) {\n\t\t\t\tdefer sem.Release(1)\n\t\t\t\tdefer wgChunkers.Done()\n\t\t\t\tlogger.Info(\"cloning repo\", \"repo\", r)\n\t\t\t\tpath, repo, err := git.CloneRepoUsingUnauthenticated(ctx, r, \"\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogFatal(err, \"error cloning repo\", \"repo\", r)\n\t\t\t\t}\n\n\t\t\t\tlogger.Info(\"cloned repo\", \"repo\", r)\n\n\t\t\t\tcfg := &git.Config{\n\t\t\t\t\tSourceName:   \"snifftest\",\n\t\t\t\t\tJobID:        0,\n\t\t\t\t\tSourceID:     0,\n\t\t\t\t\tSourceType:   sourcespb.SourceType_SOURCE_TYPE_GIT,\n\t\t\t\t\tVerify:       false,\n\t\t\t\t\tSkipBinaries: true,\n\t\t\t\t\tSkipArchives: false,\n\t\t\t\t\tConcurrency:  runtime.NumCPU(),\n\t\t\t\t\tSourceMetadataFunc: func(file, email, commit, timestamp, repository, repositoryLocalPath string, line int64) *source_metadatapb.MetaData {\n\t\t\t\t\t\treturn &source_metadatapb.MetaData{\n\t\t\t\t\t\t\tData: &source_metadatapb.MetaData_Git{\n\t\t\t\t\t\t\t\tGit: &source_metadatapb.Git{\n\t\t\t\t\t\t\t\t\tCommit:     commit,\n\t\t\t\t\t\t\t\t\tFile:       file,\n\t\t\t\t\t\t\t\t\tEmail:      email,\n\t\t\t\t\t\t\t\t\tRepository: repository,\n\t\t\t\t\t\t\t\t\tTimestamp:  timestamp,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\ts := git.NewGit(cfg)\n\n\t\t\t\tlogger.Info(\"scanning repo\", \"repo\", r)\n\t\t\t\terr = s.ScanRepo(ctx, repo, path, git.NewScanOptions(), sources.ChanReporter{Ch: chunksChan})\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogFatal(err, \"error scanning repo\")\n\t\t\t\t}\n\t\t\t\tlogger.Info(\"scanned repo\", \"repo\", r)\n\t\t\t\tdefer os.RemoveAll(path)\n\t\t\t}(repo)\n\t\t}\n\n\t\tgo func() {\n\t\t\twgChunkers.Wait()\n\t\t\tclose(chunksChan)\n\t\t}()\n\n\t\twgScanners.Wait()\n\n\t\tlogger.Info(\"completed snifftest\", \"chunks\", chunkCounter)\n\t\tfor scanner, resultsCount := range resCounter {\n\t\t\tlogger.Info(scanner, \"results\", *resultsCount)\n\t\t}\n\n\t\tif failed {\n\t\t\tos.Exit(1)\n\t\t}\n\tcase showDetectorsCmd.FullCommand():\n\t\tfor s := range getAllScanners() {\n\t\t\tfmt.Println(s)\n\t\t}\n\t}\n}\n\nfunc getAllScanners() map[string]detectors.Detector {\n\tallScanners := map[string]detectors.Detector{}\n\tfor _, s := range defaults.DefaultDetectors() {\n\t\tsecretType := reflect.Indirect(reflect.ValueOf(s)).Type().PkgPath()\n\t\tpath := strings.Split(secretType, \"/\")[len(strings.Split(secretType, \"/\"))-1]\n\t\tallScanners[path] = s\n\t}\n\treturn allScanners\n}\n"
  },
  {
    "path": "hack/snifftest/snifftest.sh",
    "content": "#!/usr/bin/env bash\n\nREPO_ARRAY=(\n        \"https://github.com/Netflix/Hystrix.git\"\n        # \"https://github.com/facebook/flow.git\"\n        # \"https://github.com/Netflix/vizceral.git\"\n        # \"https://github.com/Netflix/metaflow.git\"\n        # \"https://github.com/Netflix/dgs-framework.git\"\n        # \"https://github.com/Netflix/vector.git\"\n        # \"https://github.com/expressjs/express.git\"\n        # \"https://github.com/Azure/azure-sdk-for-net\"\n        # \"https://github.com/Azure/azure-cli\"\n)\nREPOS=$(printf \"%s,\" \"${REPO_ARRAY[@]}\" | cut -d \",\" -f 1-${#REPO_ARRAY[@]})\ngo run hack/snifftest/main.go scan --exclude privatekey --exclude uri --exclude github_old --repo \"$REPOS\" --detector all --print  --fail-threshold 99"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t_ \"net/http/pprof\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\n\t\"github.com/alecthomas/kingpin/v2\"\n\t\"github.com/fatih/color\"\n\t\"github.com/felixge/fgprof\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/jpillora/overseer\"\n\t\"github.com/mattn/go-isatty\"\n\t\"go.uber.org/automaxprocs/maxprocs\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cleantemp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/defaults\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/feature\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/handlers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/log\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/output\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/tui\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/updater\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/verificationcache\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/version\"\n)\n\nvar (\n\tcli = kingpin.New(\"TruffleHog\", \"TruffleHog is a tool for finding credentials.\")\n\tcmd string\n\t// https://github.com/trufflesecurity/trufflehog/blob/main/CONTRIBUTING.md#logging-in-trufflehog\n\tlogLevel            = cli.Flag(\"log-level\", `Logging verbosity on a scale of 0 (info) to 5 (trace). Can be disabled with \"-1\".`).Default(\"0\").Int()\n\tdebug               = cli.Flag(\"debug\", \"Run in debug mode.\").Hidden().Bool()\n\ttrace               = cli.Flag(\"trace\", \"Run in trace mode.\").Hidden().Bool()\n\tprofile             = cli.Flag(\"profile\", \"Enables profiling and sets a pprof and fgprof server on :18066.\").Bool()\n\tlocalDev            = cli.Flag(\"local-dev\", \"Hidden feature to disable overseer for local dev.\").Hidden().Bool()\n\tjsonOut             = cli.Flag(\"json\", \"Output in JSON format.\").Short('j').Bool()\n\tjsonLegacy          = cli.Flag(\"json-legacy\", \"Use the pre-v3.0 JSON format. Only works with git, gitlab, and github sources.\").Bool()\n\tgitHubActionsFormat = cli.Flag(\"github-actions\", \"Output in GitHub Actions format.\").Bool()\n\tconcurrency         = cli.Flag(\"concurrency\", \"Number of concurrent workers.\").Default(strconv.Itoa(runtime.NumCPU())).Int()\n\tnoVerification      = cli.Flag(\"no-verification\", \"Don't verify the results.\").Bool()\n\tonlyVerified        = cli.Flag(\"only-verified\", \"Only output verified results.\").Hidden().Bool()\n\tresults             = 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()\n\tnoColor             = cli.Flag(\"no-color\", \"Disable colorized output\").Bool()\n\tnoColour            = cli.Flag(\"no-colour\", \"Alias for --no-color\").Hidden().Bool()\n\n\tallowVerificationOverlap   = cli.Flag(\"allow-verification-overlap\", \"Allow verification of similar credentials across detectors\").Bool()\n\tfilterUnverified           = cli.Flag(\"filter-unverified\", \"Only output first unverified result per chunk per detector if there are more than one results.\").Bool()\n\tfilterEntropy              = cli.Flag(\"filter-entropy\", \"Filter unverified results with Shannon entropy. Start with 3.0.\").Float64()\n\tscanEntireChunk            = cli.Flag(\"scan-entire-chunk\", \"Scan the entire chunk for secrets.\").Hidden().Default(\"false\").Bool()\n\tmaxDecodeDepth             = cli.Flag(\"max-decode-depth\", \"Maximum depth of iterative decoding. Each decoder's output is fed back through all decoders, up to this limit. 1 = single pass, 2+ = chained decoding (e.g., base64 inside utf16).\").Default(\"5\").Int()\n\tcompareDetectionStrategies = cli.Flag(\"compare-detection-strategies\", \"Compare different detection strategies for matching spans\").Hidden().Default(\"false\").Bool()\n\tconfigFilename             = cli.Flag(\"config\", \"Path to configuration file.\").ExistingFile()\n\t// rules = cli.Flag(\"rules\", \"Path to file with custom rules.\").String()\n\tprintAvgDetectorTime = cli.Flag(\"print-avg-detector-time\", \"Print the average time spent on each detector.\").Bool()\n\tnoUpdate             = cli.Flag(\"no-update\", \"Don't check for updates.\").Bool()\n\tfail                 = cli.Flag(\"fail\", \"Exit with code 183 if results are found.\").Bool()\n\tfailOnScanErrors     = cli.Flag(\"fail-on-scan-errors\", \"Exit with non-zero error code if an error occurs during the scan.\").Bool()\n\tverifiers            = cli.Flag(\"verifier\", \"Set custom verification endpoints.\").StringMap()\n\tcustomVerifiersOnly  = cli.Flag(\"custom-verifiers-only\", \"Only use custom verification endpoints.\").Bool()\n\tdetectorTimeout      = cli.Flag(\"detector-timeout\", \"Maximum time to spend scanning chunks per detector (e.g., 30s).\").Duration()\n\tarchiveMaxSize       = cli.Flag(\"archive-max-size\", \"Maximum size of archive to scan. (Byte units eg. 512B, 2KB, 4MB)\").Bytes()\n\tarchiveMaxDepth      = cli.Flag(\"archive-max-depth\", \"Maximum depth of archive to scan.\").Int()\n\tarchiveTimeout       = cli.Flag(\"archive-timeout\", \"Maximum time to spend extracting an archive.\").Duration()\n\tincludeDetectors     = 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()\n\texcludeDetectors     = 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()\n\tjobReportFile        = cli.Flag(\"output-report\", \"Write a scan report to the provided path.\").Hidden().OpenFile(os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)\n\n\tnoVerificationCache = cli.Flag(\"no-verification-cache\", \"Disable verification caching\").Bool()\n\n\t// Add feature flags\n\tforceSkipBinaries  = cli.Flag(\"force-skip-binaries\", \"Force skipping binaries.\").Bool()\n\tforceSkipArchives  = cli.Flag(\"force-skip-archives\", \"Force skipping archives.\").Bool()\n\tgitCloneTimeout    = cli.Flag(\"git-clone-timeout\", \"Maximum time to spend cloning a repository, as a duration.\").Hidden().Duration()\n\tskipAdditionalRefs = cli.Flag(\"skip-additional-refs\", \"Skip additional references.\").Bool()\n\tuserAgentSuffix    = cli.Flag(\"user-agent-suffix\", \"Suffix to add to User-Agent.\").String()\n\n\tgitScan                = cli.Command(\"git\", \"Find credentials in git repositories.\")\n\tgitScanURI             = gitScan.Arg(\"uri\", \"Git repository URL. https://, file://, or ssh:// schema expected.\").Required().String()\n\tgitScanIncludePaths    = gitScan.Flag(\"include-paths\", \"Path to file with newline separated regexes for files to include in scan.\").Short('i').String()\n\tgitScanExcludePaths    = gitScan.Flag(\"exclude-paths\", \"Path to file with newline separated regexes for files to exclude in scan.\").Short('x').String()\n\tgitScanExcludeGlobs    = 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()\n\tgitScanSinceCommit     = gitScan.Flag(\"since-commit\", \"Commit to start scan from.\").String()\n\tgitScanBranch          = gitScan.Flag(\"branch\", \"Branch to scan.\").String()\n\tgitScanMaxDepth        = gitScan.Flag(\"max-depth\", \"Maximum depth of commits to scan.\").Int()\n\tgitScanBare            = gitScan.Flag(\"bare\", \"Scan bare repository (e.g. useful while using in pre-receive hooks)\").Bool()\n\tgitClonePath           = gitScan.Flag(\"clone-path\", \"Custom path where the repository should be cloned (default: temp dir).\").String()\n\tgitNoCleanup           = gitScan.Flag(\"no-cleanup\", \"Do not delete cloned repositories after scanning (can only be used with --clone-path).\").Bool()\n\tgitTrustLocalGitConfig = gitScan.Flag(\"trust-local-git-config\", \"Trust local git config.\").Bool()\n\t_                      = gitScan.Flag(\"allow\", \"No-op flag for backwards compat.\").Bool()\n\t_                      = gitScan.Flag(\"entropy\", \"No-op flag for backwards compat.\").Bool()\n\t_                      = gitScan.Flag(\"regex\", \"No-op flag for backwards compat.\").Bool()\n\n\tgithubScan                  = cli.Command(\"github\", \"Find credentials in GitHub repositories.\")\n\tgithubScanEndpoint          = githubScan.Flag(\"endpoint\", \"GitHub endpoint.\").Default(\"https://api.github.com\").String()\n\tgithubScanRepos             = githubScan.Flag(\"repo\", `GitHub repository to scan. You can repeat this flag. Example: \"https://github.com/dustin-decker/secretsandstuff\"`).Strings()\n\tgithubScanOrgs              = githubScan.Flag(\"org\", `GitHub organization to scan. You can repeat this flag. Example: \"trufflesecurity\"`).Strings()\n\tgithubScanToken             = githubScan.Flag(\"token\", \"GitHub token. Can be provided with environment variable GITHUB_TOKEN.\").Envar(\"GITHUB_TOKEN\").String()\n\tgithubIncludeForks          = githubScan.Flag(\"include-forks\", \"Include forks in scan.\").Bool()\n\tgithubIncludeMembers        = githubScan.Flag(\"include-members\", \"Include organization member repositories in scan.\").Bool()\n\tgithubIncludeRepos          = 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()\n\tgithubIncludeWikis          = githubScan.Flag(\"include-wikis\", \"Include repository wikisin scan.\").Bool()\n\tgithubExcludeRepos          = 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()\n\tgithubScanIncludePaths      = githubScan.Flag(\"include-paths\", \"Path to file with newline separated regexes for files to include in scan.\").Short('i').String()\n\tgithubScanExcludePaths      = githubScan.Flag(\"exclude-paths\", \"Path to file with newline separated regexes for files to exclude in scan.\").Short('x').String()\n\tgithubScanIssueComments     = githubScan.Flag(\"issue-comments\", \"Include issue descriptions and comments in scan.\").Bool()\n\tgithubScanPRComments        = githubScan.Flag(\"pr-comments\", \"Include pull request descriptions and comments in scan.\").Bool()\n\tgithubScanGistComments      = githubScan.Flag(\"gist-comments\", \"Include gist comments in scan.\").Bool()\n\tgithubCommentsTimeframeDays = githubScan.Flag(\"comments-timeframe\", \"Number of days in the past to review when scanning issue, PR, and gist comments.\").Uint32()\n\tgithubAuthInUrl             = githubScan.Flag(\"auth-in-url\", \"Embed authentication credentials in repository URLs instead of using secure HTTP headers\").Bool()\n\tgithubClonePath             = githubScan.Flag(\"clone-path\", \"Custom path where the repository should be cloned (default: temp dir).\").String()\n\tgithubNoCleanup             = githubScan.Flag(\"no-cleanup\", \"Do not delete cloned repositories after scanning (can only be used with --clone-path).\").Bool()\n\tgithubIgnoreGists           = githubScan.Flag(\"ignore-gists\", \"Ignore all gists in scan.\").Bool()\n\n\t// GitHub Cross Fork Object Reference Experimental Feature\n\tgithubExperimentalScan = cli.Command(\"github-experimental\", \"Run an experimental GitHub scan. Must specify at least one experimental sub-module to run: object-discovery.\")\n\t// GitHub Experimental SubModules\n\tgithubExperimentalObjectDiscovery = githubExperimentalScan.Flag(\"object-discovery\", \"Discover hidden data objects in GitHub repositories.\").Bool()\n\t// GitHub Experimental Options\n\tgithubExperimentalToken              = githubExperimentalScan.Flag(\"token\", \"GitHub token. Can be provided with environment variable GITHUB_TOKEN.\").Envar(\"GITHUB_TOKEN\").String()\n\tgithubExperimentalRepo               = githubExperimentalScan.Flag(\"repo\", \"GitHub repository to scan. Example: https://github.com/<user>/<repo>.git\").Required().String()\n\tgithubExperimentalCollisionThreshold = githubExperimentalScan.Flag(\"collision-threshold\", \"Threshold for short-sha collisions in object-discovery submodule. Default is 1.\").Default(\"1\").Int()\n\tgithubExperimentalDeleteCache        = githubExperimentalScan.Flag(\"delete-cached-data\", \"Delete cached data after object-discovery secret scanning.\").Bool()\n\n\tgitlabScan = cli.Command(\"gitlab\", \"Find credentials in GitLab repositories.\")\n\t// TODO: Add more GitLab options\n\tgitlabScanEndpoint     = gitlabScan.Flag(\"endpoint\", \"GitLab endpoint.\").Default(\"https://gitlab.com\").String()\n\tgitlabScanRepos        = 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()\n\tgitlabScanToken        = gitlabScan.Flag(\"token\", \"GitLab token. Can be provided with environment variable GITLAB_TOKEN.\").Envar(\"GITLAB_TOKEN\").Required().String()\n\tgitlabScanGroupIds     = gitlabScan.Flag(\"group-id\", \"GitLab group ID. If provided, it will scan the group and its subgroups. You can repeat this flag.\").Strings()\n\tgitlabScanIncludePaths = gitlabScan.Flag(\"include-paths\", \"Path to file with newline separated regexes for files to include in scan.\").Short('i').String()\n\tgitlabScanExcludePaths = gitlabScan.Flag(\"exclude-paths\", \"Path to file with newline separated regexes for files to exclude in scan.\").Short('x').String()\n\tgitlabScanIncludeRepos = 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()\n\tgitlabScanExcludeRepos = 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()\n\tgitlabAuthInUrl        = gitlabScan.Flag(\"auth-in-url\", \"Embed authentication credentials in repository URLs instead of using secure HTTP headers\").Bool()\n\tgitlabClonePath        = gitlabScan.Flag(\"clone-path\", \"Custom path where the repository should be cloned (default: temp dir)\").String()\n\tgitlabNoCleanup        = gitlabScan.Flag(\"no-cleanup\", \"Do not delete cloned repositories after scanning (can only be used with --clone-path).\").Bool()\n\n\tfilesystemScan  = cli.Command(\"filesystem\", \"Find credentials in a filesystem.\")\n\tfilesystemPaths = filesystemScan.Arg(\"path\", \"Path to file or directory to scan.\").Strings()\n\t// DEPRECATED: --directory is deprecated in favor of arguments.\n\tfilesystemDirectories = filesystemScan.Flag(\"directory\", \"Path to directory to scan. You can repeat this flag.\").Strings()\n\t// TODO: Add more filesystem scan options. Currently only supports scanning a list of directories.\n\t// filesystemScanRecursive = filesystemScan.Flag(\"recursive\", \"Scan recursively.\").Short('r').Bool()\n\tfilesystemScanIncludePaths    = filesystemScan.Flag(\"include-paths\", \"Path to file with newline separated regexes for files to include in scan.\").Short('i').String()\n\tfilesystemScanExcludePaths    = filesystemScan.Flag(\"exclude-paths\", \"Path to file with newline separated regexes for files to exclude in scan.\").Short('x').String()\n\tfilesystemScanMaxSymlinkDepth = filesystemScan.Flag(\"max-symlink-depth\", \"Maximum depth to follow symlinks during filesystem scan.\").Short('s').Int32()\n\n\ts3Scan              = cli.Command(\"s3\", \"Find credentials in S3 buckets.\")\n\ts3ScanKey           = s3Scan.Flag(\"key\", \"S3 key used to authenticate. Can be provided with environment variable AWS_ACCESS_KEY_ID.\").Envar(\"AWS_ACCESS_KEY_ID\").String()\n\ts3ScanRoleArns      = s3Scan.Flag(\"role-arn\", \"Specify the ARN of an IAM role to assume for scanning. You can repeat this flag.\").Strings()\n\ts3ScanSecret        = s3Scan.Flag(\"secret\", \"S3 secret used to authenticate. Can be provided with environment variable AWS_SECRET_ACCESS_KEY.\").Envar(\"AWS_SECRET_ACCESS_KEY\").String()\n\ts3ScanSessionToken  = 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()\n\ts3ScanCloudEnv      = s3Scan.Flag(\"cloud-environment\", \"Use IAM credentials in cloud environment.\").Bool()\n\ts3ScanBuckets       = s3Scan.Flag(\"bucket\", \"Name of S3 bucket to scan. You can repeat this flag. Incompatible with --ignore-bucket.\").Strings()\n\ts3ScanIgnoreBuckets = s3Scan.Flag(\"ignore-bucket\", \"Name of S3 bucket to ignore. You can repeat this flag. Incompatible with --bucket.\").Strings()\n\ts3ScanMaxObjectSize = 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()\n\n\tgcsScan           = cli.Command(\"gcs\", \"Find credentials in GCS buckets.\")\n\tgcsProjectID      = 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()\n\tgcsCloudEnv       = gcsScan.Flag(\"cloud-environment\", \"Use Application Default Credentials, IAM credentials to authenticate.\").Bool()\n\tgcsServiceAccount = gcsScan.Flag(\"service-account\", \"Path to GCS service account JSON file.\").ExistingFile()\n\tgcsWithoutAuth    = gcsScan.Flag(\"without-auth\", \"Scan GCS buckets without authentication. This will only work for public buckets\").Bool()\n\tgcsAPIKey         = gcsScan.Flag(\"api-key\", \"GCS API key used to authenticate. Can be provided with environment variable GOOGLE_API_KEY.\").Envar(\"GOOGLE_API_KEY\").String()\n\tgcsIncludeBuckets = gcsScan.Flag(\"include-buckets\", \"Buckets to scan. Comma separated list of buckets. You can repeat this flag. Globs are supported\").Short('I').Strings()\n\tgcsExcludeBuckets = gcsScan.Flag(\"exclude-buckets\", \"Buckets to exclude from scan. Comma separated list of buckets. Globs are supported\").Short('X').Strings()\n\tgcsIncludeObjects = gcsScan.Flag(\"include-objects\", \"Objects to scan. Comma separated list of objects. you can repeat this flag. Globs are supported\").Short('i').Strings()\n\tgcsExcludeObjects = 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()\n\tgcsMaxObjectSize  = 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()\n\n\tsyslogScan     = cli.Command(\"syslog\", \"Scan syslog\")\n\tsyslogAddress  = syslogScan.Flag(\"address\", \"Address and port to listen on for syslog. Example: 127.0.0.1:514\").String()\n\tsyslogProtocol = syslogScan.Flag(\"protocol\", \"Protocol to listen on. udp or tcp\").String()\n\tsyslogTLSCert  = syslogScan.Flag(\"cert\", \"Path to TLS cert.\").String()\n\tsyslogTLSKey   = syslogScan.Flag(\"key\", \"Path to TLS key.\").String()\n\tsyslogFormat   = syslogScan.Flag(\"format\", \"Log format. Can be rfc3164 or rfc5424\").Required().String()\n\n\tcircleCiScan      = cli.Command(\"circleci\", \"Scan CircleCI\")\n\tcircleCiScanToken = circleCiScan.Flag(\"token\", \"CircleCI token. Can also be provided with environment variable\").Envar(\"CIRCLECI_TOKEN\").Required().String()\n\n\tdockerScan              = cli.Command(\"docker\", \"Scan Docker Image\")\n\tdockerScanImages        = 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()\n\tdockerScanToken         = dockerScan.Flag(\"token\", \"Docker bearer token. Can also be provided with environment variable\").Envar(\"DOCKER_TOKEN\").String()\n\tdockerExcludePaths      = dockerScan.Flag(\"exclude-paths\", \"Comma separated list of paths to exclude from scan\").String()\n\tdockerScanNamespace     = 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()\n\tdockerScanRegistryToken = dockerScan.Flag(\"registry-token\", \"Optional Docker registry access token. Provide this if you want to include private images within the specified namespace.\").String()\n\n\ttravisCiScan      = cli.Command(\"travisci\", \"Scan TravisCI\")\n\ttravisCiScanToken = travisCiScan.Flag(\"token\", \"TravisCI token. Can also be provided with environment variable\").Envar(\"TRAVISCI_TOKEN\").Required().String()\n\n\t// Postman is hidden for now until we get more feedback from the community.\n\tpostmanScan  = cli.Command(\"postman\", \"Scan Postman\")\n\tpostmanToken = postmanScan.Flag(\"token\", \"Postman token. Can also be provided with environment variable\").Envar(\"POSTMAN_TOKEN\").String()\n\n\tpostmanWorkspaces   = postmanScan.Flag(\"workspace\", \"Postman workspace to scan. You can repeat this flag. Deprecated flag.\").Hidden().Strings()\n\tpostmanWorkspaceIDs = postmanScan.Flag(\"workspace-id\", \"Postman workspace ID to scan. You can repeat this flag.\").Strings()\n\n\tpostmanCollections   = postmanScan.Flag(\"collection\", \"Postman collection to scan. You can repeat this flag. Deprecated flag.\").Hidden().Strings()\n\tpostmanCollectionIDs = postmanScan.Flag(\"collection-id\", \"Postman collection ID to scan. You can repeat this flag.\").Strings()\n\n\tpostmanEnvironments = postmanScan.Flag(\"environment\", \"Postman environment to scan. You can repeat this flag.\").Strings()\n\n\tpostmanIncludeCollections   = postmanScan.Flag(\"include-collections\", \"Collections to include in scan. You can repeat this flag. Deprecated flag.\").Hidden().Strings()\n\tpostmanIncludeCollectionIDs = postmanScan.Flag(\"include-collection-id\", \"Collection ID to include in scan. You can repeat this flag.\").Strings()\n\n\tpostmanIncludeEnvironments = postmanScan.Flag(\"include-environments\", \"Environments to include in scan. You can repeat this flag.\").Strings()\n\n\tpostmanExcludeCollections   = postmanScan.Flag(\"exclude-collections\", \"Collections to exclude from scan. You can repeat this flag. Deprecated flag.\").Hidden().Strings()\n\tpostmanExcludeCollectionIDs = postmanScan.Flag(\"exclude-collection-id\", \"Collection ID to exclude from scan. You can repeat this flag.\").Strings()\n\n\tpostmanExcludeEnvironments = postmanScan.Flag(\"exclude-environments\", \"Environments to exclude from scan. You can repeat this flag.\").Strings()\n\tpostmanWorkspacePaths      = postmanScan.Flag(\"workspace-paths\", \"Path to Postman workspaces.\").Strings()\n\tpostmanCollectionPaths     = postmanScan.Flag(\"collection-paths\", \"Path to Postman collections.\").Strings()\n\tpostmanEnvironmentPaths    = postmanScan.Flag(\"environment-paths\", \"Path to Postman environments.\").Strings()\n\n\telasticsearchScan           = cli.Command(\"elasticsearch\", \"Scan Elasticsearch\")\n\telasticsearchNodes          = elasticsearchScan.Flag(\"nodes\", \"Elasticsearch nodes\").Envar(\"ELASTICSEARCH_NODES\").Strings()\n\telasticsearchUsername       = elasticsearchScan.Flag(\"username\", \"Elasticsearch username\").Envar(\"ELASTICSEARCH_USERNAME\").String()\n\telasticsearchPassword       = elasticsearchScan.Flag(\"password\", \"Elasticsearch password\").Envar(\"ELASTICSEARCH_PASSWORD\").String()\n\telasticsearchServiceToken   = elasticsearchScan.Flag(\"service-token\", \"Elasticsearch service token\").Envar(\"ELASTICSEARCH_SERVICE_TOKEN\").String()\n\telasticsearchCloudId        = elasticsearchScan.Flag(\"cloud-id\", \"Elasticsearch cloud ID. Can also be provided with environment variable\").Envar(\"ELASTICSEARCH_CLOUD_ID\").String()\n\telasticsearchAPIKey         = elasticsearchScan.Flag(\"api-key\", \"Elasticsearch API key. Can also be provided with environment variable\").Envar(\"ELASTICSEARCH_API_KEY\").String()\n\telasticsearchIndexPattern   = elasticsearchScan.Flag(\"index-pattern\", \"Filters the indices to search\").Default(\"*\").Envar(\"ELASTICSEARCH_INDEX_PATTERN\").String()\n\telasticsearchQueryJSON      = elasticsearchScan.Flag(\"query-json\", \"Filters the documents to search\").Envar(\"ELASTICSEARCH_QUERY_JSON\").String()\n\telasticsearchSinceTimestamp = 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()\n\telasticsearchBestEffortScan = elasticsearchScan.Flag(\"best-effort-scan\", \"Attempts to continuously scan a cluster\").Envar(\"ELASTICSEARCH_BEST_EFFORT_SCAN\").Bool()\n\n\tjenkinsScan                  = cli.Command(\"jenkins\", \"Scan Jenkins\")\n\tjenkinsURL                   = jenkinsScan.Flag(\"url\", \"Jenkins URL\").Envar(\"JENKINS_URL\").Required().String()\n\tjenkinsUsername              = jenkinsScan.Flag(\"username\", \"Jenkins username\").Envar(\"JENKINS_USERNAME\").String()\n\tjenkinsPassword              = jenkinsScan.Flag(\"password\", \"Jenkins password\").Envar(\"JENKINS_PASSWORD\").String()\n\tjenkinsInsecureSkipVerifyTLS = jenkinsScan.Flag(\"insecure-skip-verify-tls\", \"Skip TLS verification\").Envar(\"JENKINS_INSECURE_SKIP_VERIFY_TLS\").Bool()\n\n\thuggingfaceScan     = cli.Command(\"huggingface\", \"Find credentials in HuggingFace datasets, models and spaces.\")\n\thuggingfaceEndpoint = huggingfaceScan.Flag(\"endpoint\", \"HuggingFace endpoint.\").Default(\"https://huggingface.co\").String()\n\thuggingfaceModels   = huggingfaceScan.Flag(\"model\", \"HuggingFace model to scan. You can repeat this flag. Example: 'username/model'\").Strings()\n\thuggingfaceSpaces   = huggingfaceScan.Flag(\"space\", \"HuggingFace space to scan. You can repeat this flag. Example: 'username/space'\").Strings()\n\thuggingfaceDatasets = huggingfaceScan.Flag(\"dataset\", \"HuggingFace dataset to scan. You can repeat this flag. Example: 'username/dataset'\").Strings()\n\thuggingfaceOrgs     = huggingfaceScan.Flag(\"org\", `HuggingFace organization to scan. You can repeat this flag. Example: \"trufflesecurity\"`).Strings()\n\thuggingfaceUsers    = huggingfaceScan.Flag(\"user\", `HuggingFace user to scan. You can repeat this flag. Example: \"trufflesecurity\"`).Strings()\n\thuggingfaceToken    = huggingfaceScan.Flag(\"token\", \"HuggingFace token. Can be provided with environment variable HUGGINGFACE_TOKEN.\").Envar(\"HUGGINGFACE_TOKEN\").String()\n\n\thuggingfaceIncludeModels      = 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()\n\thuggingfaceIncludeSpaces      = 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()\n\thuggingfaceIncludeDatasets    = 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()\n\thuggingfaceIgnoreModels       = 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()\n\thuggingfaceIgnoreSpaces       = 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()\n\thuggingfaceIgnoreDatasets     = 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()\n\thuggingfaceSkipAllModels      = huggingfaceScan.Flag(\"skip-all-models\", \"Skip all model scans. (Only used with --user or --org)\").Bool()\n\thuggingfaceSkipAllSpaces      = huggingfaceScan.Flag(\"skip-all-spaces\", \"Skip all space scans. (Only used with --user or --org)\").Bool()\n\thuggingfaceSkipAllDatasets    = huggingfaceScan.Flag(\"skip-all-datasets\", \"Skip all dataset scans. (Only used with --user or --org)\").Bool()\n\thuggingfaceIncludeDiscussions = huggingfaceScan.Flag(\"include-discussions\", \"Include discussions in scan.\").Bool()\n\thuggingfaceIncludePrs         = huggingfaceScan.Flag(\"include-prs\", \"Include pull requests in scan.\").Bool()\n\n\tstdinInputScan = cli.Command(\"stdin\", \"Find credentials from stdin.\")\n\tmultiScanScan  = cli.Command(\"multi-scan\", \"Find credentials in multiple sources defined in configuration.\")\n\n\tjsonEnumeratorScan  = cli.Command(\"json-enumerator\", \"Find credentials from a JSON enumerator input.\")\n\tjsonEnumeratorPaths = jsonEnumeratorScan.Arg(\"path\", \"Path to JSON enumerator file to scan.\").Strings()\n\n\tanalyzeCmd = analyzer.Command(cli)\n\tusingTUI   = false\n)\n\nfunc init() {\n\t_, _ = maxprocs.Set()\n\n\tfor i, arg := range os.Args {\n\t\tif strings.HasPrefix(arg, \"--\") {\n\t\t\tsplit := strings.SplitN(arg, \"=\", 2)\n\t\t\tsplit[0] = strings.ReplaceAll(split[0], \"_\", \"-\")\n\t\t\tos.Args[i] = strings.Join(split, \"=\")\n\t\t}\n\t}\n\n\tcli.Version(\"trufflehog \" + version.BuildVersion)\n\n\t// Support -h for help and write it to stdout.\n\tcli.HelpFlag.Short('h')\n\tcli.UsageWriter(os.Stdout)\n\n\t// Check if the TUI environment variable is set.\n\tif ok, err := strconv.ParseBool(os.Getenv(\"TUI_PARENT\")); err == nil {\n\t\tusingTUI = ok\n\t}\n\n\tif isatty.IsTerminal(os.Stdout.Fd()) && (len(os.Args) <= 1 || os.Args[1] == analyzeCmd.FullCommand()) {\n\t\targs := tui.Run(os.Args[1:])\n\t\tif len(args) == 0 {\n\t\t\tos.Exit(0)\n\t\t}\n\n\t\tbinary, err := exec.LookPath(\"sh\")\n\t\tif err == nil {\n\t\t\t// On success, this call will never return. On failure, fallthrough\n\t\t\t// to overwriting os.Args.\n\t\t\tcmd := strings.Join(append(os.Args[:1], args...), \" \")\n\t\t\t_ = syscall.Exec(binary, []string{\"sh\", \"-c\", cmd}, append(os.Environ(), \"TUI_PARENT=true\"))\n\t\t}\n\n\t\t// Overwrite the Args slice so overseer works properly.\n\t\tos.Args = os.Args[:1]\n\t\tos.Args = append(os.Args, args...)\n\n\t\tusingTUI = true\n\t}\n\n\tcmd = kingpin.MustParse(cli.Parse(os.Args[1:]))\n\n\t// Configure logging.\n\tswitch {\n\tcase *trace:\n\t\tlog.SetLevel(5)\n\tcase *debug:\n\t\tlog.SetLevel(2)\n\tdefault:\n\t\tl := int8(*logLevel)\n\t\tif l < -1 || l > 5 {\n\t\t\tfmt.Fprintf(os.Stderr, \"invalid log level: %d\\n\", *logLevel)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\tif l == -1 {\n\t\t\t// Zap uses \"5\" as the value for fatal.\n\t\t\t// We need to pass in \"-5\" because `SetLevel` passes the negation.\n\t\t\tlog.SetLevel(-5)\n\t\t} else {\n\t\t\tlog.SetLevel(l)\n\t\t}\n\t}\n\n\tif *noColor || *noColour {\n\t\tcolor.NoColor = true // disables colorized output\n\t}\n}\n\n// syncLogs flushes logs when the program exits.\nfunc syncLogs(syncFn func() error) {\n\tif syncFn != nil {\n\t\t_ = syncFn()\n\t}\n}\n\nfunc main() {\n\t// setup logger\n\tlogFormat := log.WithConsoleSink\n\tif *jsonOut {\n\t\tlogFormat = log.WithJSONSink\n\t}\n\tlogger, sync := log.New(\"trufflehog\", logFormat(os.Stderr, log.WithGlobalRedaction()))\n\t// make it the default logger for contexts\n\tcontext.SetDefaultLogger(logger)\n\n\tif *localDev {\n\t\trun(overseer.State{}, sync)\n\t\tos.Exit(0)\n\t}\n\n\tlogFatal := logFatalFunc(logger, sync)\n\n\tupdateCfg := overseer.Config{\n\t\tProgram: func(s overseer.State) {\n\t\t\trun(s, sync)\n\t\t},\n\t\tDebug:         *debug,\n\t\tRestartSignal: syscall.SIGTERM,\n\t\t// TODO: Eventually add a PreUpgrade func for signature check w/ x509 PKCS1v15\n\t\t// PreUpgrade: checkUpdateSignature(binaryPath string),\n\t}\n\n\tif !*noUpdate {\n\t\ttopLevelCmd, _, _ := strings.Cut(cmd, \" \")\n\t\tupdateCfg.Fetcher = updater.Fetcher(topLevelCmd, usingTUI)\n\t}\n\tif version.BuildVersion == \"dev\" {\n\t\tupdateCfg.Fetcher = nil\n\t}\n\n\terr := overseer.RunErr(updateCfg)\n\tif err != nil {\n\t\tlogFatal(err, \"error occurred with trufflehog updater 🐷\")\n\t}\n}\n\nfunc run(state overseer.State, logSync func() error) {\n\tctx, cancel := context.WithCancelCause(context.Background())\n\tdefer cancel(nil)\n\tdefer syncLogs(logSync)\n\n\tgo func() {\n\t\tif err := cleantemp.CleanTempArtifacts(ctx); err != nil {\n\t\t\tctx.Logger().Error(err, \"error cleaning temporary artifacts\")\n\t\t}\n\t}()\n\n\tlogger := ctx.Logger()\n\tlogFatal := logFatalFunc(logger, logSync)\n\n\tkillSignal := make(chan os.Signal, 1)\n\tsignal.Notify(killSignal, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)\n\tgo func() {\n\t\t<-killSignal\n\t\tlogger.Info(\"Received signal, shutting down.\")\n\t\tcancel(fmt.Errorf(\"canceling context due to signal\"))\n\n\t\tif err := cleantemp.CleanTempArtifacts(ctx); err != nil {\n\t\t\tlogger.Error(err, \"error cleaning temporary artifacts\")\n\t\t} else {\n\t\t\tlogger.Info(\"cleaned temporary artifacts\")\n\t\t}\n\n\t\tsyncLogs(logSync)\n\t\tos.Exit(0)\n\t}()\n\n\tlogger.V(2).Info(fmt.Sprintf(\"trufflehog %s\", version.BuildVersion))\n\n\tif *githubScanToken != \"\" {\n\t\t// NOTE: this kludge is here to do an authenticated shallow commit\n\t\t// TODO: refactor to better pass credentials\n\t\tos.Setenv(\"GITHUB_TOKEN\", *githubScanToken)\n\t}\n\n\t// When setting a base commit, chunks must be scanned in order.\n\tif *gitScanSinceCommit != \"\" {\n\t\t*concurrency = 1\n\t}\n\n\tif *profile {\n\t\truntime.SetBlockProfileRate(1)\n\t\truntime.SetMutexProfileFraction(-1)\n\t\tgo func() {\n\t\t\trouter := http.NewServeMux()\n\t\t\trouter.Handle(\"/debug/pprof/\", http.DefaultServeMux)\n\t\t\trouter.Handle(\"/debug/fgprof\", fgprof.Handler())\n\t\t\tlogger.Info(\"starting pprof and fgprof server on :18066 /debug/pprof and /debug/fgprof\")\n\t\t\tif err := http.ListenAndServe(\":18066\", router); err != nil {\n\t\t\t\tlogger.Error(err, \"error serving pprof and fgprof\")\n\t\t\t}\n\t\t}()\n\t}\n\n\t// Set feature configurations from CLI flags\n\tif *forceSkipBinaries {\n\t\tfeature.ForceSkipBinaries.Store(true)\n\t}\n\n\tif *forceSkipArchives {\n\t\tfeature.ForceSkipArchives.Store(true)\n\t}\n\n\tif gitCloneTimeout != nil {\n\t\tfeature.GitCloneTimeoutDuration.Store(int64(*gitCloneTimeout))\n\t}\n\n\tif *skipAdditionalRefs {\n\t\tfeature.SkipAdditionalRefs.Store(true)\n\t}\n\n\tif *userAgentSuffix != \"\" {\n\t\tfeature.UserAgentSuffix.Store(*userAgentSuffix)\n\t}\n\n\t// OSS Default APK handling on\n\tfeature.EnableAPKHandler.Store(true)\n\n\t// OSS Default Use Git Mirror on\n\tfeature.UseGitMirror.Store(true)\n\n\t// OSS Default simplified gitlab enumeration\n\tfeature.UseSimplifiedGitlabEnumeration.Store(true)\n\tfeature.GitlabProjectsPerPage.Store(100)\n\n\t// OSS Default using github graphql api for issues, pr's and comments\n\tfeature.UseGithubGraphQLAPI.Store(false)\n\n\tconf := &config.Config{}\n\tif *configFilename != \"\" {\n\t\tvar err error\n\t\tconf, err = config.Read(*configFilename)\n\t\tif err != nil {\n\t\t\tlogFatal(err, \"error parsing the provided configuration file\")\n\t\t}\n\t}\n\n\tif *detectorTimeout != 0 {\n\t\tlogger.Info(\"Setting detector timeout\", \"timeout\", detectorTimeout.String())\n\t\tengine.SetDetectorTimeout(*detectorTimeout)\n\t\tdetectors.OverrideDetectorTimeout(*detectorTimeout)\n\t}\n\tif *archiveMaxSize != 0 {\n\t\thandlers.SetArchiveMaxSize(int(*archiveMaxSize))\n\t}\n\tif *archiveMaxDepth != 0 {\n\t\thandlers.SetArchiveMaxDepth(*archiveMaxDepth)\n\t}\n\tif *archiveTimeout != 0 {\n\t\thandlers.SetArchiveMaxTimeout(*archiveTimeout)\n\t}\n\n\t// Set how the engine will print its results.\n\tvar printer engine.Printer\n\tswitch {\n\tcase *jsonLegacy:\n\t\tprinter = new(output.LegacyJSONPrinter)\n\tcase *jsonOut:\n\t\tprinter = new(output.JSONPrinter)\n\tcase *gitHubActionsFormat:\n\t\tprinter = new(output.GitHubActionsPrinter)\n\tdefault:\n\t\tprinter = new(output.PlainPrinter)\n\t}\n\n\tif !*jsonLegacy && !*jsonOut {\n\t\tfmt.Fprintf(os.Stderr, \"🐷🔑🐷  TruffleHog. Unearth your secrets. 🐷🔑🐷\\n\\n\")\n\t}\n\n\t// Parse --results flag.\n\tif *onlyVerified {\n\t\tr := \"verified\"\n\t\tresults = &r\n\t}\n\tparsedResults, err := parseResults(results)\n\tif err != nil {\n\t\tlogFatal(err, \"failed to configure results flag\")\n\t}\n\n\tverificationCacheMetrics := verificationcache.InMemoryMetrics{}\n\n\tengConf := engine.Config{\n\t\tConcurrency:       *concurrency,\n\t\tConfiguredSources: conf.Sources,\n\t\t// The engine must always be configured with the list of\n\t\t// default detectors, which can be further filtered by the\n\t\t// user. The filters are applied by the engine and are only\n\t\t// subtractive.\n\t\tDetectors:                append(defaults.DefaultDetectors(), conf.Detectors...),\n\t\tVerify:                   !*noVerification,\n\t\tIncludeDetectors:         *includeDetectors,\n\t\tExcludeDetectors:         *excludeDetectors,\n\t\tCustomVerifiersOnly:      *customVerifiersOnly,\n\t\tVerifierEndpoints:        *verifiers,\n\t\tDispatcher:               engine.NewPrinterDispatcher(printer),\n\t\tFilterUnverified:         *filterUnverified,\n\t\tFilterEntropy:            *filterEntropy,\n\t\tVerificationOverlap:      *allowVerificationOverlap,\n\t\tResults:                  parsedResults,\n\t\tPrintAvgDetectorTime:     *printAvgDetectorTime,\n\t\tShouldScanEntireChunk:    *scanEntireChunk,\n\t\tMaxDecodeDepth:           *maxDecodeDepth,\n\t\tVerificationCacheMetrics: &verificationCacheMetrics,\n\t}\n\n\tif !*noVerificationCache {\n\t\tengConf.VerificationResultCache = simple.NewCache[detectors.Result]()\n\t}\n\n\t// Check that there are no sources defined for non-scan subcommands. If\n\t// there are, return an error as it is ambiguous what the user is\n\t// trying to do.\n\tif cmd != multiScanScan.FullCommand() && len(conf.Sources) > 0 {\n\t\tlogFatal(\n\t\t\tfmt.Errorf(\"ambiguous configuration\"),\n\t\t\t\"sources should only be defined in configuration for the 'multi-scan' command\",\n\t\t)\n\t}\n\n\tif *compareDetectionStrategies {\n\t\tif err := compareScans(ctx, cmd, engConf); err != nil {\n\t\t\tlogFatal(err, \"error comparing detection strategies\")\n\t\t}\n\t\treturn\n\t}\n\n\tmetrics, err := runSingleScan(ctx, cmd, engConf)\n\tif err != nil {\n\t\tlogFatal(err, \"error running scan\")\n\t}\n\n\tverificationCacheMetricsSnapshot := struct {\n\t\tHits                    int32\n\t\tMisses                  int32\n\t\tHitsWasted              int32\n\t\tAttemptsSaved           int32\n\t\tVerificationTimeSpentMS int64\n\t}{\n\t\tHits:                    verificationCacheMetrics.ResultCacheHits.Load(),\n\t\tMisses:                  verificationCacheMetrics.ResultCacheMisses.Load(),\n\t\tHitsWasted:              verificationCacheMetrics.ResultCacheHitsWasted.Load(),\n\t\tAttemptsSaved:           verificationCacheMetrics.CredentialVerificationsSaved.Load(),\n\t\tVerificationTimeSpentMS: verificationCacheMetrics.FromDataVerifyTimeSpentMS.Load(),\n\t}\n\n\t// Print results.\n\tlogger.Info(\"finished scanning\",\n\t\t\"chunks\", metrics.ChunksScanned,\n\t\t\"bytes\", metrics.BytesScanned,\n\t\t\"verified_secrets\", metrics.VerifiedSecretsFound,\n\t\t\"unverified_secrets\", metrics.UnverifiedSecretsFound,\n\t\t\"scan_duration\", metrics.ScanDuration.String(),\n\t\t\"trufflehog_version\", version.BuildVersion,\n\t\t\"verification_caching\", verificationCacheMetricsSnapshot,\n\t)\n\n\tif metrics.hasFoundResults && *fail {\n\t\tlogger.V(2).Info(\"exiting with code 183 because results were found\")\n\t\tsyncLogs(logSync)\n\t\tos.Exit(183)\n\t}\n}\n\nfunc compareScans(ctx context.Context, cmd string, cfg engine.Config) error {\n\tvar (\n\t\tentireMetrics    metrics\n\t\tmaxLengthMetrics metrics\n\t\terr              error\n\t)\n\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\n\tgo func() {\n\t\tdefer wg.Done()\n\t\t// Run scan with entire chunk span calculator.\n\t\tcfg.ShouldScanEntireChunk = true\n\t\tentireMetrics, err = runSingleScan(ctx, cmd, cfg)\n\t\tif err != nil {\n\t\t\tctx.Logger().Error(err, \"error running scan with entire chunk span calculator\")\n\t\t}\n\t}()\n\n\t// Run scan with max-length span calculator.\n\tmaxLengthMetrics, err = runSingleScan(ctx, cmd, cfg)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error running scan with custom span calculator: %v\", err)\n\t}\n\n\twg.Wait()\n\n\treturn compareMetrics(maxLengthMetrics.Metrics, entireMetrics.Metrics)\n}\n\nfunc compareMetrics(customMetrics, entireMetrics engine.Metrics) error {\n\tfmt.Printf(\"Comparison of scan results: \\n\")\n\tfmt.Printf(\"Custom span - Chunks: %d, Bytes: %d, Verified Secrets: %d, Unverified Secrets: %d, Duration: %s\\n\",\n\t\tcustomMetrics.ChunksScanned, customMetrics.BytesScanned, customMetrics.VerifiedSecretsFound, customMetrics.UnverifiedSecretsFound, customMetrics.ScanDuration.String())\n\tfmt.Printf(\"Entire chunk - Chunks: %d, Bytes: %d, Verified Secrets: %d, Unverified Secrets: %d, Duration: %s\\n\",\n\t\tentireMetrics.ChunksScanned, entireMetrics.BytesScanned, entireMetrics.VerifiedSecretsFound, entireMetrics.UnverifiedSecretsFound, entireMetrics.ScanDuration.String())\n\n\t// Check for differences in scan metrics.\n\tif customMetrics.ChunksScanned != entireMetrics.ChunksScanned ||\n\t\tcustomMetrics.BytesScanned != entireMetrics.BytesScanned ||\n\t\tcustomMetrics.VerifiedSecretsFound != entireMetrics.VerifiedSecretsFound {\n\t\treturn fmt.Errorf(\"scan metrics do not match\")\n\t}\n\n\treturn nil\n}\n\ntype metrics struct {\n\tengine.Metrics\n\thasFoundResults bool\n}\n\nfunc runSingleScan(ctx context.Context, cmd string, cfg engine.Config) (metrics, error) {\n\tvar scanMetrics metrics\n\n\t// Setup job report writer if provided\n\tvar jobReportWriter io.WriteCloser\n\tif *jobReportFile != nil {\n\t\tjobReportWriter = *jobReportFile\n\t}\n\n\thandleFinishedMetrics := func(ctx context.Context, finishedMetrics <-chan sources.UnitMetrics, jobReportWriter io.WriteCloser) {\n\t\tgo func() {\n\t\t\tdefer func() {\n\t\t\t\tjobReportWriter.Close()\n\t\t\t\tif namer, ok := jobReportWriter.(interface{ Name() string }); ok {\n\t\t\t\t\tctx.Logger().Info(\"report written\", \"path\", namer.Name())\n\t\t\t\t} else {\n\t\t\t\t\tctx.Logger().Info(\"report written\")\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tfor metrics := range finishedMetrics {\n\t\t\t\tmetrics.Errors = common.ExportErrors(metrics.Errors...)\n\t\t\t\tdetails, err := json.Marshal(map[string]any{\n\t\t\t\t\t\"version\": 1,\n\t\t\t\t\t\"data\":    metrics,\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tctx.Logger().Error(err, \"error marshalling job details\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, err := jobReportWriter.Write(append(details, '\\n')); err != nil {\n\t\t\t\t\tctx.Logger().Error(err, \"error writing to file\")\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\n\tconst defaultOutputBufferSize = 64\n\topts := []func(*sources.SourceManager){\n\t\tsources.WithConcurrentSources(cfg.Concurrency),\n\t\tsources.WithConcurrentUnits(cfg.Concurrency),\n\t\tsources.WithSourceUnits(),\n\t\tsources.WithBufferedOutput(defaultOutputBufferSize),\n\t}\n\n\tif jobReportWriter != nil {\n\t\tunitHook, finishedMetrics := sources.NewUnitHook(ctx)\n\t\topts = append(opts, sources.WithReportHook(unitHook))\n\t\thandleFinishedMetrics(ctx, finishedMetrics, jobReportWriter)\n\t}\n\n\tcfg.SourceManager = sources.NewManager(opts...)\n\n\teng, err := engine.NewEngine(ctx, &cfg)\n\tif err != nil {\n\t\treturn scanMetrics, fmt.Errorf(\"error initializing engine: %v\", err)\n\t}\n\teng.Start(ctx)\n\n\tpersistGitRepo := *gitNoCleanup || *githubNoCleanup || *gitlabNoCleanup\n\tgitCloneTempPath := \"\"\n\n\tdefer func() {\n\t\t// Clean up temporary artifacts.\n\t\tif err := cleantemp.CleanTempArtifacts(ctx); err != nil {\n\t\t\tctx.Logger().Error(err, \"error cleaning temp artifacts\")\n\t\t}\n\n\t\tif *jsonLegacy {\n\t\t\t// If JSON legacy is enabled, that means the cloned repos are not deleted yet\n\t\t\t// because they were needed for outputting legacy JSON.\n\t\t\t// We only clean them up here if the user did not request to persist them.\n\t\t\tif !persistGitRepo {\n\t\t\t\tif err := cleantemp.CleanTempDirsForLegacyJSON(gitCloneTempPath); err != nil {\n\t\t\t\t\tctx.Logger().Error(err, \"error cleaning temp artifacts for legacy JSON\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\n\tvar refs []sources.JobProgressRef\n\tswitch cmd {\n\tcase gitScan.FullCommand():\n\n\t\tif err := validateClonePath(*gitClonePath, *gitNoCleanup); err != nil {\n\t\t\treturn scanMetrics, err\n\t\t}\n\n\t\tgitCfg := sources.GitConfig{\n\t\t\tURI:                 *gitScanURI,\n\t\t\tIncludePathsFile:    *gitScanIncludePaths,\n\t\t\tExcludePathsFile:    *gitScanExcludePaths,\n\t\t\tHeadRef:             *gitScanBranch,\n\t\t\tBaseRef:             *gitScanSinceCommit,\n\t\t\tMaxDepth:            *gitScanMaxDepth,\n\t\t\tBare:                *gitScanBare,\n\t\t\tExcludeGlobs:        *gitScanExcludeGlobs,\n\t\t\tClonePath:           *gitClonePath,\n\t\t\tNoCleanup:           *gitNoCleanup,\n\t\t\tPrintLegacyJSON:     *jsonLegacy,\n\t\t\tTrustLocalGitConfig: *gitTrustLocalGitConfig,\n\t\t}\n\n\t\t// detect if trufflehog is running git source as a pre-commit hook\n\t\tif isPreCommitHook() {\n\t\t\tctx.Logger().Info(\"Running as a pre-commit hook, overriding default flags for hook context\")\n\n\t\t\t// Override git configuration for pre-commit hook context\n\t\t\tgitCfg.TrustLocalGitConfig = true\n\t\t\tgitCfg.BaseRef = \"HEAD\" // Only scan staged changes\n\n\t\t\t// Override result filters for pre-commit hook context\n\t\t\t// In hook mode, we only want to show verified secrets and unknown findings\n\t\t\t*results = \"verified,unknown\"\n\n\t\t\t// Override failure behavior for pre-commit hook context\n\t\t\t// In hook mode, we want to fail the commit if any secrets are found\n\t\t\t*fail = true\n\t\t}\n\n\t\tif ref, err := eng.ScanGit(ctx, gitCfg); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan Git: %v\", err)\n\t\t} else {\n\t\t\trefs = []sources.JobProgressRef{ref}\n\t\t}\n\tcase githubScan.FullCommand():\n\t\tgitCloneTempPath = *githubClonePath\n\t\tfilter, err := common.FilterFromFiles(*githubScanIncludePaths, *githubScanExcludePaths)\n\t\tif err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"could not create filter: %v\", err)\n\t\t}\n\t\tif len(*githubScanOrgs) == 0 && len(*githubScanRepos) == 0 {\n\t\t\treturn scanMetrics, fmt.Errorf(\"invalid config: you must specify at least one organization or repository\")\n\t\t}\n\t\tif len(*githubScanOrgs) > 0 && len(*githubScanRepos) > 0 {\n\t\t\treturn scanMetrics, fmt.Errorf(\"invalid config: you cannot specify both organizations and repositories at the same time\")\n\t\t}\n\n\t\tif err := validateClonePath(*githubClonePath, *githubNoCleanup); err != nil {\n\t\t\treturn scanMetrics, err\n\t\t}\n\n\t\tcfg := sources.GithubConfig{\n\t\t\tEndpoint:                   *githubScanEndpoint,\n\t\t\tToken:                      *githubScanToken,\n\t\t\tIncludeForks:               *githubIncludeForks,\n\t\t\tIncludeMembers:             *githubIncludeMembers,\n\t\t\tIncludeWikis:               *githubIncludeWikis,\n\t\t\tConcurrency:                *concurrency,\n\t\t\tExcludeRepos:               *githubExcludeRepos,\n\t\t\tIncludeRepos:               *githubIncludeRepos,\n\t\t\tRepos:                      *githubScanRepos,\n\t\t\tOrgs:                       *githubScanOrgs,\n\t\t\tIncludeIssueComments:       *githubScanIssueComments,\n\t\t\tIncludePullRequestComments: *githubScanPRComments,\n\t\t\tIncludeGistComments:        *githubScanGistComments,\n\t\t\tCommentsTimeframeDays:      *githubCommentsTimeframeDays,\n\t\t\tFilter:                     filter,\n\t\t\tAuthInUrl:                  *githubAuthInUrl,\n\t\t\tClonePath:                  *githubClonePath,\n\t\t\tNoCleanup:                  *githubNoCleanup,\n\t\t\tIgnoreGists:                *githubIgnoreGists,\n\t\t\tPrintLegacyJSON:            *jsonLegacy,\n\t\t}\n\n\t\tif ref, err := eng.ScanGitHub(ctx, cfg); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan Github: %v\", err)\n\t\t} else {\n\t\t\trefs = []sources.JobProgressRef{ref}\n\t\t}\n\tcase githubExperimentalScan.FullCommand():\n\t\tcfg := sources.GitHubExperimentalConfig{\n\t\t\tToken:              *githubExperimentalToken,\n\t\t\tRepository:         *githubExperimentalRepo,\n\t\t\tObjectDiscovery:    *githubExperimentalObjectDiscovery,\n\t\t\tCollisionThreshold: *githubExperimentalCollisionThreshold,\n\t\t\tDeleteCachedData:   *githubExperimentalDeleteCache,\n\t\t}\n\t\tif ref, err := eng.ScanGitHubExperimental(ctx, cfg); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan using Github Experimental: %v\", err)\n\t\t} else {\n\t\t\trefs = []sources.JobProgressRef{ref}\n\t\t}\n\tcase gitlabScan.FullCommand():\n\t\tgitCloneTempPath = *gitlabClonePath\n\t\tfilter, err := common.FilterFromFiles(*gitlabScanIncludePaths, *gitlabScanExcludePaths)\n\t\tif err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"could not create filter: %v\", err)\n\t\t}\n\n\t\tif len(*gitlabScanRepos) > 0 && len(*gitlabScanGroupIds) > 0 {\n\t\t\treturn scanMetrics, fmt.Errorf(\"invalid config: you cannot specify both repositories and groups at the same time\")\n\t\t}\n\n\t\tif err := validateClonePath(*gitlabClonePath, *gitlabNoCleanup); err != nil {\n\t\t\treturn scanMetrics, err\n\t\t}\n\n\t\tcfg := sources.GitlabConfig{\n\t\t\tEndpoint:        *gitlabScanEndpoint,\n\t\t\tToken:           *gitlabScanToken,\n\t\t\tRepos:           *gitlabScanRepos,\n\t\t\tGroupIds:        *gitlabScanGroupIds,\n\t\t\tIncludeRepos:    *gitlabScanIncludeRepos,\n\t\t\tExcludeRepos:    *gitlabScanExcludeRepos,\n\t\t\tFilter:          filter,\n\t\t\tAuthInUrl:       *gitlabAuthInUrl,\n\t\t\tClonePath:       *gitlabClonePath,\n\t\t\tNoCleanup:       *gitlabNoCleanup,\n\t\t\tPrintLegacyJSON: *jsonLegacy,\n\t\t}\n\n\t\tif ref, err := eng.ScanGitLab(ctx, cfg); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan GitLab: %v\", err)\n\t\t} else {\n\t\t\trefs = []sources.JobProgressRef{ref}\n\t\t}\n\tcase filesystemScan.FullCommand():\n\t\tif len(*filesystemDirectories) > 0 {\n\t\t\tctx.Logger().Info(\"--directory flag is deprecated, please pass directories as arguments\")\n\t\t}\n\t\tpaths := make([]string, 0, len(*filesystemPaths)+len(*filesystemDirectories))\n\t\tpaths = append(paths, *filesystemPaths...)\n\t\tpaths = append(paths, *filesystemDirectories...)\n\t\tcfg := sources.FilesystemConfig{\n\t\t\tPaths:            paths,\n\t\t\tIncludePathsFile: *filesystemScanIncludePaths,\n\t\t\tExcludePathsFile: *filesystemScanExcludePaths,\n\t\t\tMaxSymlinkDepth:  *filesystemScanMaxSymlinkDepth,\n\t\t}\n\t\tif ref, err := eng.ScanFileSystem(ctx, cfg); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan filesystem: %v\", err)\n\t\t} else {\n\t\t\trefs = []sources.JobProgressRef{ref}\n\t\t}\n\tcase s3Scan.FullCommand():\n\t\tcfg := sources.S3Config{\n\t\t\tKey:           *s3ScanKey,\n\t\t\tSecret:        *s3ScanSecret,\n\t\t\tSessionToken:  *s3ScanSessionToken,\n\t\t\tBuckets:       *s3ScanBuckets,\n\t\t\tIgnoreBuckets: *s3ScanIgnoreBuckets,\n\t\t\tRoles:         *s3ScanRoleArns,\n\t\t\tCloudCred:     *s3ScanCloudEnv,\n\t\t\tMaxObjectSize: int64(*s3ScanMaxObjectSize),\n\t\t}\n\t\tif ref, err := eng.ScanS3(ctx, cfg); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan S3: %v\", err)\n\t\t} else {\n\t\t\trefs = []sources.JobProgressRef{ref}\n\t\t}\n\tcase syslogScan.FullCommand():\n\t\tcfg := sources.SyslogConfig{\n\t\t\tAddress:     *syslogAddress,\n\t\t\tFormat:      *syslogFormat,\n\t\t\tProtocol:    *syslogProtocol,\n\t\t\tCertPath:    *syslogTLSCert,\n\t\t\tKeyPath:     *syslogTLSKey,\n\t\t\tConcurrency: *concurrency,\n\t\t}\n\t\tif ref, err := eng.ScanSyslog(ctx, cfg); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan syslog: %v\", err)\n\t\t} else {\n\t\t\trefs = []sources.JobProgressRef{ref}\n\t\t}\n\tcase circleCiScan.FullCommand():\n\t\tif ref, err := eng.ScanCircleCI(ctx, *circleCiScanToken); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan CircleCI: %v\", err)\n\t\t} else {\n\t\t\trefs = []sources.JobProgressRef{ref}\n\t\t}\n\tcase travisCiScan.FullCommand():\n\t\tif ref, err := eng.ScanTravisCI(ctx, *travisCiScanToken); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan TravisCI: %v\", err)\n\t\t} else {\n\t\t\trefs = []sources.JobProgressRef{ref}\n\t\t}\n\tcase gcsScan.FullCommand():\n\t\tcfg := sources.GCSConfig{\n\t\t\tProjectID:      *gcsProjectID,\n\t\t\tCloudCred:      *gcsCloudEnv,\n\t\t\tServiceAccount: *gcsServiceAccount,\n\t\t\tWithoutAuth:    *gcsWithoutAuth,\n\t\t\tApiKey:         *gcsAPIKey,\n\t\t\tIncludeBuckets: commaSeparatedToSlice(*gcsIncludeBuckets),\n\t\t\tExcludeBuckets: commaSeparatedToSlice(*gcsExcludeBuckets),\n\t\t\tIncludeObjects: commaSeparatedToSlice(*gcsIncludeObjects),\n\t\t\tExcludeObjects: commaSeparatedToSlice(*gcsExcludeObjects),\n\t\t\tConcurrency:    *concurrency,\n\t\t\tMaxObjectSize:  int64(*gcsMaxObjectSize),\n\t\t}\n\t\tif ref, err := eng.ScanGCS(ctx, cfg); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan GCS: %v\", err)\n\t\t} else {\n\t\t\trefs = []sources.JobProgressRef{ref}\n\t\t}\n\tcase dockerScan.FullCommand():\n\t\tif *dockerScanImages != nil && *dockerScanNamespace != \"\" {\n\t\t\treturn scanMetrics, fmt.Errorf(\"invalid config: you cannot specify both images and namespace at the same time\")\n\t\t}\n\n\t\tif *dockerScanImages == nil && *dockerScanNamespace == \"\" {\n\t\t\treturn scanMetrics, fmt.Errorf(\"invalid config: both images and namespace cannot be empty; one is required\")\n\t\t}\n\n\t\tif *dockerScanRegistryToken != \"\" && *dockerScanNamespace == \"\" {\n\t\t\treturn scanMetrics, fmt.Errorf(\"invalid config: registry token can only be used with registry namespace\")\n\t\t}\n\n\t\tcfg := sources.DockerConfig{\n\t\t\tBearerToken:       *dockerScanToken,\n\t\t\tImages:            *dockerScanImages,\n\t\t\tUseDockerKeychain: *dockerScanToken == \"\",\n\t\t\tExcludePaths:      strings.Split(*dockerExcludePaths, \",\"),\n\t\t\tNamespace:         *dockerScanNamespace,\n\t\t\tRegistryToken:     *dockerScanRegistryToken,\n\t\t}\n\t\tif ref, err := eng.ScanDocker(ctx, cfg); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan Docker: %v\", err)\n\t\t} else {\n\t\t\trefs = []sources.JobProgressRef{ref}\n\t\t}\n\tcase postmanScan.FullCommand():\n\t\t// handle deprecated flag\n\t\tworkspaceIDs := make([]string, 0, len(*postmanWorkspaceIDs)+len(*postmanWorkspaces))\n\t\tworkspaceIDs = append(workspaceIDs, *postmanWorkspaceIDs...)\n\t\tworkspaceIDs = append(workspaceIDs, *postmanWorkspaces...)\n\n\t\t// handle deprecated flag\n\t\tcollectionIDs := make([]string, 0, len(*postmanCollectionIDs)+len(*postmanCollections))\n\t\tcollectionIDs = append(collectionIDs, *postmanCollectionIDs...)\n\t\tcollectionIDs = append(collectionIDs, *postmanCollections...)\n\n\t\t// handle deprecated flag\n\t\tincludeCollectionIDs := make([]string, 0, len(*postmanIncludeCollectionIDs)+len(*postmanIncludeCollections))\n\t\tincludeCollectionIDs = append(includeCollectionIDs, *postmanIncludeCollectionIDs...)\n\t\tincludeCollectionIDs = append(includeCollectionIDs, *postmanIncludeCollections...)\n\n\t\t// handle deprecated flag\n\t\texcludeCollectionIDs := make([]string, 0, len(*postmanExcludeCollectionIDs)+len(*postmanExcludeCollections))\n\t\texcludeCollectionIDs = append(excludeCollectionIDs, *postmanExcludeCollectionIDs...)\n\t\texcludeCollectionIDs = append(excludeCollectionIDs, *postmanExcludeCollections...)\n\n\t\tcfg := sources.PostmanConfig{\n\t\t\tToken:               *postmanToken,\n\t\t\tWorkspaces:          workspaceIDs,\n\t\t\tCollections:         collectionIDs,\n\t\t\tEnvironments:        *postmanEnvironments,\n\t\t\tIncludeCollections:  includeCollectionIDs,\n\t\t\tIncludeEnvironments: *postmanIncludeEnvironments,\n\t\t\tExcludeCollections:  excludeCollectionIDs,\n\t\t\tExcludeEnvironments: *postmanExcludeEnvironments,\n\t\t\tCollectionPaths:     *postmanCollectionPaths,\n\t\t\tWorkspacePaths:      *postmanWorkspacePaths,\n\t\t\tEnvironmentPaths:    *postmanEnvironmentPaths,\n\t\t}\n\t\tif ref, err := eng.ScanPostman(ctx, cfg); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan Postman: %v\", err)\n\t\t} else {\n\t\t\trefs = []sources.JobProgressRef{ref}\n\t\t}\n\tcase elasticsearchScan.FullCommand():\n\t\tcfg := sources.ElasticsearchConfig{\n\t\t\tNodes:          *elasticsearchNodes,\n\t\t\tUsername:       *elasticsearchUsername,\n\t\t\tPassword:       *elasticsearchPassword,\n\t\t\tCloudID:        *elasticsearchCloudId,\n\t\t\tAPIKey:         *elasticsearchAPIKey,\n\t\t\tServiceToken:   *elasticsearchServiceToken,\n\t\t\tIndexPattern:   *elasticsearchIndexPattern,\n\t\t\tQueryJSON:      *elasticsearchQueryJSON,\n\t\t\tSinceTimestamp: *elasticsearchSinceTimestamp,\n\t\t\tBestEffortScan: *elasticsearchBestEffortScan,\n\t\t}\n\t\tif ref, err := eng.ScanElasticsearch(ctx, cfg); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan Elasticsearch: %v\", err)\n\t\t} else {\n\t\t\trefs = []sources.JobProgressRef{ref}\n\t\t}\n\tcase jenkinsScan.FullCommand():\n\t\tcfg := engine.JenkinsConfig{\n\t\t\tEndpoint:              *jenkinsURL,\n\t\t\tInsecureSkipVerifyTLS: *jenkinsInsecureSkipVerifyTLS,\n\t\t\tUsername:              *jenkinsUsername,\n\t\t\tPassword:              *jenkinsPassword,\n\t\t}\n\t\tif ref, err := eng.ScanJenkins(ctx, cfg); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan Jenkins: %v\", err)\n\t\t} else {\n\t\t\trefs = []sources.JobProgressRef{ref}\n\t\t}\n\tcase huggingfaceScan.FullCommand():\n\t\tif *huggingfaceEndpoint != \"\" {\n\t\t\t*huggingfaceEndpoint = strings.TrimRight(*huggingfaceEndpoint, \"/\")\n\t\t}\n\n\t\tif len(*huggingfaceModels) == 0 && len(*huggingfaceSpaces) == 0 && len(*huggingfaceDatasets) == 0 && len(*huggingfaceOrgs) == 0 && len(*huggingfaceUsers) == 0 {\n\t\t\treturn scanMetrics, fmt.Errorf(\"invalid config: you must specify at least one organization, user, model, space or dataset\")\n\t\t}\n\n\t\tcfg := engine.HuggingfaceConfig{\n\t\t\tEndpoint:           *huggingfaceEndpoint,\n\t\t\tModels:             *huggingfaceModels,\n\t\t\tSpaces:             *huggingfaceSpaces,\n\t\t\tDatasets:           *huggingfaceDatasets,\n\t\t\tOrganizations:      *huggingfaceOrgs,\n\t\t\tUsers:              *huggingfaceUsers,\n\t\t\tToken:              *huggingfaceToken,\n\t\t\tIncludeModels:      *huggingfaceIncludeModels,\n\t\t\tIncludeSpaces:      *huggingfaceIncludeSpaces,\n\t\t\tIncludeDatasets:    *huggingfaceIncludeDatasets,\n\t\t\tIgnoreModels:       *huggingfaceIgnoreModels,\n\t\t\tIgnoreSpaces:       *huggingfaceIgnoreSpaces,\n\t\t\tIgnoreDatasets:     *huggingfaceIgnoreDatasets,\n\t\t\tSkipAllModels:      *huggingfaceSkipAllModels,\n\t\t\tSkipAllSpaces:      *huggingfaceSkipAllSpaces,\n\t\t\tSkipAllDatasets:    *huggingfaceSkipAllDatasets,\n\t\t\tIncludeDiscussions: *huggingfaceIncludeDiscussions,\n\t\t\tIncludePrs:         *huggingfaceIncludePrs,\n\t\t\tConcurrency:        *concurrency,\n\t\t}\n\t\tif ref, err := eng.ScanHuggingface(ctx, cfg); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan HuggingFace: %v\", err)\n\t\t} else {\n\t\t\trefs = []sources.JobProgressRef{ref}\n\t\t}\n\tcase multiScanScan.FullCommand():\n\t\tif *configFilename == \"\" {\n\t\t\treturn scanMetrics, fmt.Errorf(\"missing required flag: --config\")\n\t\t}\n\t\tif rs, err := eng.ScanConfig(ctx, cfg.ConfiguredSources...); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan via config: %w\", err)\n\t\t} else {\n\t\t\trefs = rs\n\t\t}\n\tcase stdinInputScan.FullCommand():\n\t\tcfg := sources.StdinConfig{}\n\t\tif ref, err := eng.ScanStdinInput(ctx, cfg); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan stdin input: %v\", err)\n\t\t} else {\n\t\t\trefs = []sources.JobProgressRef{ref}\n\t\t}\n\tcase jsonEnumeratorScan.FullCommand():\n\t\tcfg := sources.JSONEnumeratorConfig{Paths: *jsonEnumeratorPaths}\n\t\tif ref, err := eng.ScanJSONEnumeratorInput(ctx, cfg); err != nil {\n\t\t\treturn scanMetrics, fmt.Errorf(\"failed to scan JSON enumerator input: %v\", err)\n\t\t} else {\n\t\t\trefs = []sources.JobProgressRef{ref}\n\t\t}\n\tdefault:\n\t\treturn scanMetrics, fmt.Errorf(\"invalid command: %s\", cmd)\n\t}\n\n\t// Wait for all workers to finish.\n\tif err = eng.Finish(ctx); err != nil {\n\t\treturn scanMetrics, fmt.Errorf(\"engine failed to finish execution: %v\", err)\n\t}\n\n\t// Print any non-fatal errors reported during the scan.\n\tvar retErr error\n\tfor _, ref := range refs {\n\t\tif errs := ref.Snapshot().Errors; len(errs) > 0 {\n\t\t\tif *failOnScanErrors {\n\t\t\t\tretErr = fmt.Errorf(\"encountered errors during scan\")\n\t\t\t}\n\t\t\terrMsgs := make([]string, len(errs))\n\t\t\tfor i := 0; i < len(errs); i++ {\n\t\t\t\terrMsgs[i] = errs[i].Error()\n\t\t\t}\n\t\t\tctx.Logger().Error(nil, \"encountered errors during scan\",\n\t\t\t\t\"job\", ref.JobID,\n\t\t\t\t\"source_name\", ref.SourceName,\n\t\t\t\t\"errors\", errMsgs,\n\t\t\t)\n\t\t}\n\t}\n\n\tif *printAvgDetectorTime {\n\t\tprintAverageDetectorTime(eng)\n\t}\n\n\treturn metrics{Metrics: eng.GetMetrics(), hasFoundResults: eng.HasFoundResults()}, retErr\n}\n\n// parseResults ensures that users provide valid CSV input to `--results`.\n//\n// This is a work-around to kingpin not supporting CSVs.\n// See: https://github.com/trufflesecurity/trufflehog/pull/2372#issuecomment-1983868917\nfunc parseResults(input *string) (map[string]struct{}, error) {\n\tif *input == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tvar (\n\t\tvalues  = strings.Split(strings.ToLower(*input), \",\")\n\t\tresults = make(map[string]struct{}, 3)\n\t)\n\tfor _, value := range values {\n\t\tswitch value {\n\t\tcase \"verified\", \"unknown\", \"unverified\", \"filtered_unverified\":\n\t\t\tresults[value] = struct{}{}\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"invalid value '%s', valid values are 'verified,unknown,unverified,filtered_unverified'\", value)\n\t\t}\n\t}\n\treturn results, nil\n}\n\n// logFatalFunc returns a log.Fatal style function. Calling the returned\n// function will terminate the program without cleanup.\nfunc logFatalFunc(logger logr.Logger, logSync func() error) func(error, string, ...any) {\n\treturn func(err error, message string, keyAndVals ...any) {\n\t\tlogger.Error(err, message, keyAndVals...)\n\t\tsyncLogs(logSync)\n\t\tif err != nil {\n\t\t\tos.Exit(1)\n\t\t\treturn\n\t\t}\n\t\tos.Exit(0)\n\t}\n}\n\nfunc commaSeparatedToSlice(s []string) []string {\n\tvar result []string\n\tfor _, items := range s {\n\t\tfor _, item := range strings.Split(items, \",\") {\n\t\t\titem = strings.TrimSpace(item)\n\t\t\tif item == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tresult = append(result, item)\n\t\t}\n\t}\n\treturn result\n}\n\nfunc printAverageDetectorTime(e *engine.Engine) {\n\tfmt.Fprintln(\n\t\tos.Stderr,\n\t\t\"Average detector time is the measurement of average time spent on each detector when results are returned.\",\n\t)\n\tfor detectorName, duration := range e.GetDetectorsMetrics() {\n\t\tfmt.Fprintf(os.Stderr, \"%s: %s\\n\", detectorName, duration)\n\t}\n}\n\n// validateClonePath ensures that --clone-path, if provided, exists and is a directory.\n// It also verifies that --no-cleanup is only allowed when --clone-path is set.\n// Note: without a custom clone path, repositories are cloned into temporary directories, which should never be retained.\nfunc validateClonePath(clonePath string, noCleanup bool) error {\n\tif noCleanup && clonePath == \"\" {\n\t\treturn fmt.Errorf(\"invalid configuration: --no-cleanup can only be used together with --clone-path\")\n\t}\n\n\tif clonePath == \"\" {\n\t\treturn nil\n\t}\n\n\tinfo, err := os.Stat(clonePath)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn fmt.Errorf(\"path provided to --clone-path: %q does not exist\", clonePath)\n\t\t}\n\n\t\treturn fmt.Errorf(\"failed to access --clone-path %q: %w\", clonePath, err)\n\t}\n\n\tif !info.IsDir() {\n\t\treturn fmt.Errorf(\"path provided to --clone-path: %q is not a directory\", clonePath)\n\t}\n\n\treturn nil\n}\n\n// isPreCommitHook detects if trufflehog is running as a pre-commit hook\nfunc isPreCommitHook() bool {\n\t// Pre-commit.com framework detection\n\t// Docs: https://pre-commit.com/#pre-commit\n\t// Sets PRE_COMMIT=1 environment variable when running hooks\n\tif os.Getenv(\"PRE_COMMIT\") == \"1\" {\n\t\treturn true\n\t}\n\n\t// Husky framework detection (modern versions)\n\t// Docs: https://typicode.github.io/husky/get-started.html#disabling-hooks\n\t// Sets HUSKY=1 environment variable for all hooks\n\tif os.Getenv(\"HUSKY\") == \"1\" {\n\t\treturn true\n\t}\n\n\t// Husky legacy detection (versions < 4.0)\n\t// Sets HUSKY_GIT_PARAMS for git hooks, containing commit parameters\n\t// Reference: https://github.com/typicode/husky/tree/v0.14.3\n\tif os.Getenv(\"HUSKY_GIT_PARAMS\") != \"\" {\n\t\treturn true\n\t}\n\n\t// Local Git hook detection (non-framework)\n\t// Native Git hooks don't set specific environment variables by default.\n\t// To detect local hooks without frameworks, we must explicitly set\n\t// an environment variable in the hook script:\n\t// Example in .git/hooks/pre-commit:\n\t//   export TRUFFLEHOG_PRE_COMMIT=1\n\t// Than we can detect it\n\tif os.Getenv(\"TRUFFLEHOG_PRE_COMMIT\") == \"1\" {\n\t\treturn true\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "pkg/analyzer/README.md",
    "content": "# Implementing Analyzers\n\n## Defining the Permissions\n\nPermissions can be defined in:\n- lower snake case as `permission_name:access_level`\n- kebab case as `permission-name:read`\n- dot notation as `permission.name:read`\n\nThe Permissions are initially defined as a [yaml file](analyzers/twilio/permissions.yaml).\n\nAt the top of the [analyzer implementation](analyzers/twilio/twilio.go) you specify the go generate command.\n\nYou can install the generator with `go install github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/generate_permissions`.\n\nThen you can run `go generate ./...` to generate the Permission types for the analyzer.\n\nThe generated Permission types are to be used in the `AnalyzerResult` struct when defining the `Permissions` and in your code.\n"
  },
  {
    "path": "pkg/analyzer/analyzers/airbrake/airbrake.go",
    "content": "package airbrake\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeAirbrake }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tinfo, err := AnalyzePermissions(a.Cfg, credInfo[\"key\"])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\tresult := analyzers.AnalyzerResult{\n\t\tMetadata: map[string]any{\n\t\t\t\"key_type\":  info.KeyType,\n\t\t\t\"reference\": info.Reference,\n\t\t},\n\t}\n\t// Copy the rest of the metadata over.\n\tfor k, v := range info.Misc {\n\t\tresult.Metadata[k] = v\n\t}\n\n\t// Build a list of Bindings by referencing the same permissions list\n\t// for each resource.\n\tpermissions := allPermissions()\n\tfor _, proj := range info.Projects {\n\t\tresource := analyzers.Resource{\n\t\t\tName:               proj.Name,\n\t\t\tFullyQualifiedName: strconv.Itoa(proj.ID),\n\t\t\tType:               \"project\",\n\t\t}\n\t\tfor _, perm := range permissions {\n\t\t\tbinding := analyzers.Binding{\n\t\t\t\tResource:   resource,\n\t\t\t\tPermission: perm,\n\t\t\t}\n\t\t\tresult.Bindings = append(result.Bindings, binding)\n\t\t}\n\t}\n\n\treturn &result\n}\n\ntype SecretInfo struct {\n\tKeyType   string\n\tProjects  []Project\n\tReference string\n\tScopes    []analyzers.Permission\n\tMisc      map[string]string\n}\n\ntype Project struct {\n\tName string `json:\"name\"`\n\tID   int    `json:\"id\"`\n}\n\n// validateKey checks if the key is valid and returns the projects associated with the key\nfunc validateKey(cfg *config.Config, key string) (bool, []Project, error) {\n\ttype ProjectsJSON struct {\n\t\tProjects []Project `json:\"projects\"`\n\t}\n\t// create struct to hold response\n\tvar projects ProjectsJSON\n\n\t// create http client\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\t// create request\n\treq, err := http.NewRequest(\"GET\", \"https://api.airbrake.io/api/v4/projects\", nil)\n\tif err != nil {\n\t\treturn false, projects.Projects, err\n\t}\n\n\t// add key as url param\n\tq := req.URL.Query()\n\tq.Add(\"key\", key)\n\treq.URL.RawQuery = q.Encode()\n\n\t// send request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, projects.Projects, err\n\t}\n\n\t// read response\n\tdefer resp.Body.Close()\n\n\t// if status code is 200, decode response\n\tif resp.StatusCode == 200 {\n\t\terr := json.NewDecoder(resp.Body).Decode(&projects)\n\t\treturn true, projects.Projects, err\n\t}\n\n\t// if status code is not 200, return false\n\treturn false, projects.Projects, nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[x] %s\", err.Error())\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid Airbrake User API Key\\n\\n\")\n\tcolor.Green(\"[i] Key Type: \" + info.KeyType)\n\tif v, ok := info.Misc[\"expiration\"]; ok {\n\t\tcolor.Green(\"[i] Expiration: %s\", v)\n\t}\n\tif v, ok := info.Misc[\"duration\"]; ok {\n\t\tcolor.Green(\"[i] Duration: %s\", v)\n\t}\n\n\tcolor.Green(\"\\n[i] Projects:\")\n\tprintProjects(info.Projects...)\n\n\tcolor.Green(\"\\n[i] Permissions:\")\n\tprintPermissions(info.Scopes)\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\tvalid, projects, err := validateKey(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !valid {\n\t\treturn nil, fmt.Errorf(\"Invalid Airbrake User API Key\")\n\t}\n\n\tinfo := &SecretInfo{\n\t\tProjects:  projects,\n\t\tReference: \"https://docs.airbrake.io/docs/devops-tools/api/\",\n\t\t// If the token exists, it has all permissions.\n\t\tScopes: allPermissions(),\n\t\tMisc:   make(map[string]string),\n\t}\n\tif len(key) == 40 {\n\t\tinfo.KeyType = \"User Key\"\n\t\tinfo.Misc[\"expiration\"] = \"Never\"\n\t} else {\n\t\tinfo.KeyType = \"User Token\"\n\t\tinfo.Misc[\"duration\"] = \"Short Lived\"\n\t}\n\treturn info, nil\n}\n\nfunc allPermissions() []analyzers.Permission {\n\tpermissions := make([]analyzers.Permission, len(scope_order))\n\tfor i, perm := range scope_order {\n\t\tpermissions[i] = analyzers.Permission{Value: perm}\n\t}\n\treturn permissions\n}\n\nfunc printProjects(projects ...Project) {\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Project ID\", \"Project Name\"})\n\tfor _, project := range projects {\n\t\tt.AppendRow([]any{color.GreenString(\"%d\", project.ID), color.GreenString(\"%s\", project.Name)})\n\t}\n\tt.Render()\n}\n\nfunc printPermissions(scopes []analyzers.Permission) {\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Scope\", \"Permissions\"})\n\tfor _, scope := range scopes {\n\t\tscope := scope.Value\n\t\tfor i, permission := range scope_mapping[scope] {\n\t\t\tif i == 0 {\n\t\t\t\tt.AppendRow([]any{color.GreenString(\"%s\", scope), color.GreenString(\"%s\", permission)})\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n\tt.Render()\n\tfmt.Println(\"| Ref: https://docs.airbrake.io/docs/devops-tools/api/     |\")\n\tfmt.Println(\"+------------------------+---------------------------------+\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/airbrake/scopes.go",
    "content": "package airbrake\n\nvar scope_order = []string{\n\t\"Authentication\",\n\t\"Performance Monitoring\",\n\t\"Error Notification\",\n\t\"Projects\",\n\t\"Deploys\",\n\t\"Groups\",\n\t\"Notices\",\n\t\"Project Activities\",\n\t\"Source Maps\",\n\t\"iOS Crash Reports\",\n}\n\nvar scope_mapping = map[string][]string{\n\t\"Authentication\":         {\"Create user token\"},\n\t\"Performance Monitoring\": {\"Route performance endpoint\", \"Routes breakdown endpoint\", \"Database query stats\", \"Queue stats\"},\n\t\"Error Notification\":     {\"Create notice\"},\n\t\"Projects\":               {\"List projects\", \"Show projects\"},\n\t\"Deploys\":                {\"Create deploy\", \"List deploys\", \"Show deploy\"},\n\t\"Groups\":                 {\"List groups\", \"Show group\", \"Mute group\", \"Unmute group\", \"Delete group\", \"List groups across all projects\", \"Show group statistics\"},\n\t\"Notices\":                {\"List notices\", \"Show notice status\"},\n\t\"Project Activities\":     {\"List project activities\", \"Show project statistics\"},\n\t\"Source Maps\":            {\"Create source map\", \"List source maps\", \"Show source map\", \"Delete source map\"},\n\t\"iOS Crash Reports\":      {\"Create iOS crash report\"},\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/airtable/airtableoauth/airtable.go",
    "content": "package airtableoauth\n\nimport (\n\t\"errors\"\n\n\t\"github.com/fatih/color\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airtable/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeAirtableOAuth }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\ttoken, ok := credInfo[\"token\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"token not found in credInfo\")\n\t}\n\n\tuserInfo, err := common.FetchAirtableUserInfo(token)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar basesInfo *common.AirtableBases\n\tbaseScope := common.PermissionStrings[common.SchemaBasesRead]\n\tif hasScope(userInfo.Scopes, baseScope) {\n\t\tbasesInfo, _ = common.FetchAirtableBases(token)\n\t}\n\n\treturn common.MapToAnalyzerResult(userInfo, basesInfo), nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, token string) {\n\tuserInfo, err := common.FetchAirtableUserInfo(token)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid Airtable OAuth2 Access Token\\n\\n\")\n\tprintUserAndPermissions(userInfo)\n\n\tbaseScope := common.PermissionStrings[common.SchemaBasesRead]\n\tif hasScope(userInfo.Scopes, baseScope) {\n\t\tvar basesInfo *common.AirtableBases\n\t\tbasesInfo, _ = common.FetchAirtableBases(token)\n\t\tcommon.PrintBases(basesInfo)\n\t}\n}\n\nfunc hasScope(scopes []string, target string) bool {\n\tfor _, scope := range scopes {\n\t\tif scope == target {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc printUserAndPermissions(info *common.AirtableUserInfo) {\n\tscopeStatusMap := make(map[string]bool)\n\tfor _, scope := range common.PermissionStrings {\n\t\tscopeStatusMap[scope] = false\n\t}\n\tfor _, scope := range info.Scopes {\n\t\tscopeStatusMap[scope] = true\n\t}\n\n\tcommon.PrintUserAndPermissions(info, scopeStatusMap)\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/airtable/airtableoauth/airtable_test.go",
    "content": "package airtableoauth\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\ttoken   string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\ttoken:   testSecrets.MustGetField(\"AIRTABLEOAUTH_TOKEN\"),\n\t\t\tname:    \"valid Airtable OAuth Token\",\n\t\t\twant:    string(expectedOutput),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"token\": tt.token})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/airtable/airtableoauth/expected_output.json",
    "content": "{\r\n    \"AnalyzerType\": 28,\r\n    \"Bindings\": [\r\n        {\r\n            \"Resource\": {\r\n                \"Name\": \"usraS0CjAASH3XMpU\",\r\n                \"FullyQualifiedName\": \"usraS0CjAASH3XMpU\",\r\n                \"Type\": \"user\",\r\n                \"Metadata\": {},\r\n                \"Parent\": null\r\n            },\r\n            \"Permission\": {\r\n                \"Value\": \"data.records:read\",\r\n                \"Parent\": null\r\n            }\r\n        },\r\n        {\r\n            \"Resource\": {\r\n                \"Name\": \"usraS0CjAASH3XMpU\",\r\n                \"FullyQualifiedName\": \"usraS0CjAASH3XMpU\",\r\n                \"Type\": \"user\",\r\n                \"Metadata\": {},\r\n                \"Parent\": null\r\n            },\r\n            \"Permission\": {\r\n                \"Value\": \"schema.bases:read\",\r\n                \"Parent\": null\r\n            }\r\n        }\r\n    ],\r\n    \"UnboundedResources\": [\r\n        {\r\n            \"Name\": \"Client Leads and Sales Management\",\r\n            \"FullyQualifiedName\": \"appzRyj5Q9R9kK6cF\",\r\n            \"Type\": \"base\",\r\n            \"Parent\": null\r\n        }\r\n    ]\r\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/airtable/airtablepat/airtable.go",
    "content": "package airtablepat\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airtable/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeAirtablePat }\n\nvar scopeStatusMap = make(map[string]bool)\n\nfunc getEndpoint(endpointName common.EndpointName) (common.Endpoint, bool) {\n\treturn common.GetEndpoint(endpointName)\n}\n\nfunc getScopeEndpoint(scope string) (common.Endpoint, bool) {\n\treturn common.GetScopeEndpoint(scope)\n}\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\ttoken, ok := credInfo[\"token\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"token not found in credInfo\")\n\t}\n\n\tuserInfo, err := common.FetchAirtableUserInfo(token)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tscopeStatusMap[common.PermissionStrings[common.UserEmailRead]] = userInfo.Email != nil\n\n\tvar basesInfo *common.AirtableBases\n\tgranted, err := determineScope(token, common.SchemaBasesRead, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif granted {\n\t\tbasesInfo, err = common.FetchAirtableBases(token)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// If bases are fetched, determine the token scopes\n\t\terr := determineScopes(token, basesInfo)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn mapToAnalyzerResult(userInfo, basesInfo), nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, token string) {\n\tuserInfo, err := common.FetchAirtableUserInfo(token)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t\treturn\n\t}\n\n\tscopeStatusMap[common.PermissionStrings[common.UserEmailRead]] = userInfo.Email != nil\n\n\tvar basesInfo *common.AirtableBases\n\tbasesReadPermission := common.SchemaBasesRead\n\tif granted, err := determineScope(token, basesReadPermission, nil); granted {\n\t\tif err != nil {\n\t\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t\t\treturn\n\t\t}\n\t\tbasesInfo, _ = common.FetchAirtableBases(token)\n\t\terr := determineScopes(token, basesInfo)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t\t\treturn\n\t\t}\n\t}\n\n\tcolor.Green(\"[!] Valid Airtable Personal Access Token\\n\\n\")\n\n\tcommon.PrintUserAndPermissions(userInfo, scopeStatusMap)\n\tif scopeStatusMap[common.PermissionStrings[basesReadPermission]] {\n\t\tcommon.PrintBases(basesInfo)\n\t}\n}\n\n// determineScope checks whether the given token has the specified permission by making an API call.\n//\n// The function performs the following actions:\n//   - Determines the appropriate API Endpoint based on the input scope/permission.\n//   - Constructs an HTTP request using the endpoint's URL, method, and required IDs.\n//     If the URL contains path parameters (e.g., \"{baseID}\"), they must be replaced using `requiredIDs`.\n//   - Sends the request and analyzes the response to determine if the token has the requested permission.\n//\n// Returns `true` if the token has the permission, `false` otherwise.\n// If an error occurs, it returns false along with the encountered error.\nfunc determineScope(token string, perm common.Permission, requiredIDs map[string]string) (bool, error) {\n\tscopeString := common.PermissionStrings[perm]\n\tendpoint, exists := getScopeEndpoint(scopeString)\n\tif !exists {\n\t\treturn false, nil\n\t}\n\n\turl := endpoint.URL\n\tif requiredIDs != nil {\n\t\tfor _, key := range endpoint.RequiredIDs {\n\t\t\tif value, ok := requiredIDs[key]; ok {\n\t\t\t\turl = strings.Replace(url, fmt.Sprintf(\"{%s}\", key), value, -1)\n\t\t\t}\n\t\t}\n\t}\n\n\tresp, err := common.CallAirtableAPI(token, endpoint.Method, url)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode == endpoint.ExpectedSuccessStatus {\n\t\tscopeStatusMap[scopeString] = true\n\t\treturn true, nil\n\t}\n\n\t// If the response status is not 200 OK, we need to verify if the error is as expected\n\tif endpoint.ExpectedErrorResponse != nil {\n\t\tvar result map[string]any\n\t\tif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\terrorInfo, ok := result[\"error\"].(map[string]any)\n\t\tif !ok {\n\t\t\t// If no error is found in the response, the scope is unverified\n\t\t\treturn false, nil\n\t\t}\n\t\terrorType, ok := errorInfo[\"type\"].(string)\n\t\tif !ok || errorType != endpoint.ExpectedErrorResponse.Type {\n\t\t\t// If \"type\" is missing from the error body, or mismatches the expected type, the scope is unverified\n\t\t\treturn false, nil\n\t\t}\n\n\t\t// The token lacks the scope/permission to fulfill the request\n\t\tscopeStatusMap[scopeString] = false\n\t\treturn false, nil\n\t}\n\n\t// Can not determine scope as the expected error is unknown\n\treturn false, nil\n}\n\nfunc determineScopes(token string, basesInfo *common.AirtableBases) error {\n\tif basesInfo == nil || len(basesInfo.Bases) == 0 {\n\t\treturn nil\n\t}\n\n\tfor _, base := range basesInfo.Bases {\n\t\trequiredIDs := map[string]string{\"baseID\": base.ID}\n\t\ttableScopesDetermined := false\n\n\t\t// Verify token \"webhooks:manage\" permission\n\t\t_, err := determineScope(token, common.WebhookManage, requiredIDs)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// Verify token \"block:manage\" permission\n\t\t_, err = determineScope(token, common.BlockManage, requiredIDs)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif base.Schema == nil || len(base.Schema.Tables) == 0 {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Verifying scopes that require an existing table\n\t\tfor _, table := range base.Schema.Tables {\n\t\t\trequiredIDs[\"tableID\"] = table.ID\n\n\t\t\tif !tableScopesDetermined {\n\t\t\t\t_, err = determineScope(token, common.SchemaBasesWrite, requiredIDs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\t_, err = determineScope(token, common.DataRecordsWrite, requiredIDs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\ttableScopesDetermined = true\n\t\t\t}\n\n\t\t\tgranted, err := determineScope(token, common.DataRecordsRead, requiredIDs)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif !granted {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Verifying scopes that require an existing \"record\" and the \"data records read\" permission\n\t\t\trecords, err := fetchAirtableRecords(token, base.ID, table.ID)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfor _, record := range records {\n\t\t\t\trequiredIDs[\"recordID\"] = record.ID\n\t\t\t\t_, err = determineScope(token, common.DataRecordcommentsRead, requiredIDs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif len(records) != 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc mapToAnalyzerResult(userInfo *common.AirtableUserInfo, basesInfo *common.AirtableBases) *analyzers.AnalyzerResult {\n\tfor scope, status := range scopeStatusMap {\n\t\tif status {\n\t\t\tuserInfo.Scopes = append(userInfo.Scopes, scope)\n\t\t}\n\t}\n\treturn common.MapToAnalyzerResult(userInfo, basesInfo)\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/airtable/airtablepat/airtable_test.go",
    "content": "package airtablepat\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\ttoken   string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\ttoken:   testSecrets.MustGetField(\"AIRTABLEOAUTH_TOKEN\"),\n\t\t\tname:    \"valid Airtable Personal Access Token\",\n\t\t\twant:    string(expectedOutput),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"token\": tt.token})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/airtable/airtablepat/expected_output.json",
    "content": "{\r\n    \"AnalyzerType\": 29,\r\n    \"Bindings\": [\r\n        {\r\n            \"Resource\": {\r\n                \"Name\": \"usraS0CjAASH3XMpU\",\r\n                \"FullyQualifiedName\": \"usraS0CjAASH3XMpU\",\r\n                \"Type\": \"user\",\r\n                \"Metadata\": {},\r\n                \"Parent\": null\r\n            },\r\n            \"Permission\": {\r\n                \"Value\": \"data.records:read\",\r\n                \"Parent\": null\r\n            }\r\n        },\r\n        {\r\n            \"Resource\": {\r\n                \"Name\": \"usraS0CjAASH3XMpU\",\r\n                \"FullyQualifiedName\": \"usraS0CjAASH3XMpU\",\r\n                \"Type\": \"user\",\r\n                \"Metadata\": {},\r\n                \"Parent\": null\r\n            },\r\n            \"Permission\": {\r\n                \"Value\": \"schema.bases:read\",\r\n                \"Parent\": null\r\n            }\r\n        }\r\n    ],\r\n    \"UnboundedResources\": [\r\n        {\r\n            \"Name\": \"Client Leads and Sales Management\",\r\n            \"FullyQualifiedName\": \"appzRyj5Q9R9kK6cF\",\r\n            \"Type\": \"base\",\r\n            \"Parent\": null\r\n        }\r\n    ]\r\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/airtable/airtablepat/requests.go",
    "content": "package airtablepat\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airtable/common\"\n)\n\ntype AirtableRecordsResponse struct {\n\tRecords []common.AirtableEntity `json:\"records\"`\n}\n\nfunc fetchAirtableRecords(token string, baseID string, tableID string) ([]common.AirtableEntity, error) {\n\tendpoint, exists := getEndpoint(common.ListRecordsEndpoint)\n\tif !exists {\n\t\treturn nil, fmt.Errorf(\"endpoint for ListRecordsEndpoint does not exist\")\n\t}\n\turl := strings.Replace(strings.Replace(endpoint.URL, \"{baseID}\", baseID, -1), \"{tableID}\", tableID, -1)\n\tresp, err := common.CallAirtableAPI(token, \"GET\", url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"failed to fetch Airtable records, status: %d\", resp.StatusCode)\n\t}\n\n\tvar recordsResponse AirtableRecordsResponse\n\tif err := json.NewDecoder(resp.Body).Decode(&recordsResponse); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn recordsResponse.Records, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/airtable/common/common.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go common\npackage common\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n)\n\nfunc CallAirtableAPI(token string, method string, url string) (*http.Response, error) {\n\treq, err := http.NewRequest(method, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\n\tresp, err := detectors.DetectorHttpClientWithNoLocalAddresses.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn resp, nil\n}\n\nfunc FetchAirtableUserInfo(token string) (*AirtableUserInfo, error) {\n\tendpoint, exists := GetEndpoint(GetUserInfoEndpoint)\n\tif !exists {\n\t\treturn nil, fmt.Errorf(\"endpoint for GetUserInfoEndpoint does not exist\")\n\t}\n\tresp, err := CallAirtableAPI(token, endpoint.Method, endpoint.URL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"failed to fetch Airtable user info, status: %d\", resp.StatusCode)\n\t}\n\n\tvar userInfo AirtableUserInfo\n\tif err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &userInfo, nil\n}\n\nfunc FetchAirtableBases(token string) (*AirtableBases, error) {\n\tendpoint, exists := GetEndpoint(ListBasesEndpoint)\n\tif !exists {\n\t\treturn nil, fmt.Errorf(\"endpoint for ListBasesEndpoint does not exist\")\n\t}\n\tresp, err := CallAirtableAPI(token, endpoint.Method, endpoint.URL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"failed to fetch Airtable bases, status: %d\", resp.StatusCode)\n\t}\n\n\tvar basesInfo AirtableBases\n\tif err := json.NewDecoder(resp.Body).Decode(&basesInfo); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Fetch schema for each base\n\tfor i, base := range basesInfo.Bases {\n\t\tschema, err := fetchBaseSchema(token, base.ID)\n\t\tif err != nil {\n\t\t\tbasesInfo.Bases[i].Schema = nil\n\t\t} else {\n\t\t\tbasesInfo.Bases[i].Schema = schema\n\t\t}\n\t}\n\n\treturn &basesInfo, nil\n}\n\nfunc fetchBaseSchema(token string, baseID string) (*Schema, error) {\n\tendpoint, exists := GetEndpoint(GetBaseSchemaEndpoint)\n\tif !exists {\n\t\treturn nil, fmt.Errorf(\"endpoint for GetBaseSchemaEndpoint does not exist\")\n\t}\n\turl := strings.ReplaceAll(endpoint.URL, \"{baseID}\", baseID)\n\tresp, err := CallAirtableAPI(token, endpoint.Method, url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"failed to fetch schema for base %s, status: %d\", baseID, resp.StatusCode)\n\t}\n\n\tvar schema Schema\n\tif err := json.NewDecoder(resp.Body).Decode(&schema); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &schema, nil\n}\n\nfunc MapToAnalyzerResult(userInfo *AirtableUserInfo, basesInfo *AirtableBases) *analyzers.AnalyzerResult {\n\tif userInfo == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeAirtableOAuth,\n\t}\n\tvar permissions []analyzers.Permission\n\tfor _, scope := range userInfo.Scopes {\n\t\tpermissions = append(permissions, analyzers.Permission{Value: scope})\n\t}\n\tuserResource := analyzers.Resource{\n\t\tName:               userInfo.ID,\n\t\tFullyQualifiedName: userInfo.ID,\n\t\tType:               \"user\",\n\t\tMetadata:           map[string]any{},\n\t}\n\n\tif userInfo.Email != nil {\n\t\tuserResource.Metadata[\"email\"] = *userInfo.Email\n\t}\n\n\tresult.Bindings = analyzers.BindAllPermissions(userResource, permissions...)\n\n\tif basesInfo != nil {\n\t\tfor _, base := range basesInfo.Bases {\n\t\t\tresource := analyzers.Resource{\n\t\t\t\tName:               base.Name,\n\t\t\t\tFullyQualifiedName: base.ID,\n\t\t\t\tType:               \"base\",\n\t\t\t}\n\t\t\tresult.UnboundedResources = append(result.UnboundedResources, resource)\n\t\t}\n\t}\n\n\treturn &result\n}\n\nfunc PrintUserAndPermissions(info *AirtableUserInfo, scopeStatusMap map[string]bool) {\n\tcolor.Yellow(\"[i] User:\")\n\tt1 := table.NewWriter()\n\temail := \"N/A\"\n\tif info.Email != nil {\n\t\temail = *info.Email\n\t}\n\tt1.SetOutputMirror(os.Stdout)\n\tt1.AppendHeader(table.Row{\"ID\", \"Email\"})\n\tt1.AppendRow(table.Row{color.GreenString(info.ID), color.GreenString(email)})\n\tt1.SetOutputMirror(os.Stdout)\n\tt1.Render()\n\n\tcolor.Yellow(\"\\n[i] Scopes:\")\n\tt2 := table.NewWriter()\n\tt2.SetOutputMirror(os.Stdout)\n\tt2.AppendHeader(table.Row{\"Scope\", \"Permission\", \"Status\"})\n\tfor _, scope := range PermissionStrings {\n\t\tscopeStatus := \"Could not verify\"\n\t\tif status, ok := scopeStatusMap[scope]; ok {\n\t\t\tif status {\n\t\t\t\tscopeStatus = \"Granted\"\n\t\t\t} else {\n\t\t\t\tscopeStatus = \"Denied\"\n\t\t\t}\n\t\t}\n\t\tpermissions, ok := GetScopePermissions(scope)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tfor i, permission := range permissions {\n\t\t\tscopeString := \"\"\n\t\t\tif i == 0 {\n\t\t\t\tscopeString = scope\n\t\t\t}\n\t\t\tt2.AppendRow(table.Row{color.GreenString(scopeString), color.GreenString(permission), color.GreenString(scopeStatus)})\n\t\t\tscopeStatus = \"\"\n\t\t}\n\t\tt2.AppendSeparator()\n\t}\n\tt2.Render()\n\tfmt.Printf(\"%s: https://airtable.com/developers/web/api/scopes\\n\", color.GreenString(\"Ref\"))\n}\n\nfunc PrintBases(bases *AirtableBases) {\n\tcolor.Yellow(\"\\n[i] Bases:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tif len(bases.Bases) > 0 {\n\t\tt.AppendHeader(table.Row{\"ID\", \"Name\"})\n\t\tfor _, base := range bases.Bases {\n\t\t\tt.AppendRow(table.Row{color.GreenString(base.ID), color.GreenString(base.Name)})\n\t\t}\n\t} else {\n\t\tfmt.Printf(\"%s\\n\", color.GreenString(\"No bases associated with token\"))\n\t}\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/airtable/common/endpoints.go",
    "content": "package common\n\nimport \"net/http\"\n\ntype ErrorResponse struct {\n\tType string\n}\n\ntype Endpoint struct {\n\tURL                   string\n\tMethod                string\n\tRequiredIDs           []string\n\tRequiredPermission    *string\n\tExpectedSuccessStatus int\n\tExpectedErrorResponse *ErrorResponse\n}\n\ntype EndpointName int\n\nconst (\n\tGetUserInfoEndpoint            EndpointName = iota\n\tListBasesEndpoint              EndpointName = iota\n\tUpdateBaseEndpoint             EndpointName = iota\n\tGetBaseSchemaEndpoint          EndpointName = iota\n\tListRecordsEndpoint            EndpointName = iota\n\tCreateRecordEndpoint           EndpointName = iota\n\tListRecordCommentsEndpoint     EndpointName = iota\n\tListWebhooksEndpoint           EndpointName = iota\n\tListBlockInstallationsEndpoint EndpointName = iota\n)\n\nvar endpoints map[EndpointName]Endpoint\n\nfunc init() {\n\tendpoints = map[EndpointName]Endpoint{\n\t\tGetUserInfoEndpoint: {\n\t\t\tURL:    \"https://api.airtable.com/v0/meta/whoami\",\n\t\t\tMethod: \"GET\",\n\t\t},\n\t\tListBasesEndpoint: {\n\t\t\tURL:                   \"https://api.airtable.com/v0/meta/bases\",\n\t\t\tMethod:                \"GET\",\n\t\t\tRequiredPermission:    GetRequiredPermission(SchemaBasesRead),\n\t\t\tExpectedSuccessStatus: http.StatusOK,\n\t\t\tExpectedErrorResponse: &ErrorResponse{\n\t\t\t\tType: \"INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND\",\n\t\t\t},\n\t\t},\n\t\tUpdateBaseEndpoint: {\n\t\t\tURL:                   \"https://api.airtable.com/v0/meta/bases/{baseID}/tables/{tableID}\",\n\t\t\tMethod:                \"PATCH\",\n\t\t\tRequiredIDs:           []string{\"baseID\", \"tableID\"},\n\t\t\tRequiredPermission:    GetRequiredPermission(SchemaBasesWrite),\n\t\t\tExpectedSuccessStatus: http.StatusUnprocessableEntity,\n\t\t\tExpectedErrorResponse: &ErrorResponse{\n\t\t\t\tType: \"INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND\",\n\t\t\t},\n\t\t},\n\t\tGetBaseSchemaEndpoint: {\n\t\t\tURL:                   \"https://api.airtable.com/v0/meta/bases/{baseID}/tables\",\n\t\t\tMethod:                \"GET\",\n\t\t\tRequiredIDs:           []string{\"baseID\"},\n\t\t\tRequiredPermission:    GetRequiredPermission(SchemaBasesRead),\n\t\t\tExpectedSuccessStatus: http.StatusOK,\n\t\t\tExpectedErrorResponse: &ErrorResponse{\n\t\t\t\tType: \"INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND\",\n\t\t\t},\n\t\t},\n\t\tListRecordsEndpoint: {\n\t\t\tURL:                   \"https://api.airtable.com/v0/{baseID}/{tableID}\",\n\t\t\tMethod:                \"GET\",\n\t\t\tRequiredIDs:           []string{\"baseID\", \"tableID\"},\n\t\t\tRequiredPermission:    GetRequiredPermission(DataRecordsRead),\n\t\t\tExpectedSuccessStatus: http.StatusOK,\n\t\t\tExpectedErrorResponse: &ErrorResponse{\n\t\t\t\tType: \"INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND\",\n\t\t\t},\n\t\t},\n\t\tCreateRecordEndpoint: {\n\t\t\tURL:                   \"https://api.airtable.com/v0/{baseID}/{tableID}\",\n\t\t\tMethod:                \"POST\",\n\t\t\tRequiredIDs:           []string{\"baseID\", \"tableID\"},\n\t\t\tRequiredPermission:    GetRequiredPermission(DataRecordsWrite),\n\t\t\tExpectedSuccessStatus: http.StatusUnprocessableEntity,\n\t\t\tExpectedErrorResponse: &ErrorResponse{\n\t\t\t\tType: \"INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND\",\n\t\t\t},\n\t\t},\n\t\tListRecordCommentsEndpoint: {\n\t\t\tURL:                   \"https://api.airtable.com/v0/{baseID}/{tableID}/{recordID}/comments\",\n\t\t\tMethod:                \"GET\",\n\t\t\tRequiredIDs:           []string{\"baseID\", \"tableID\", \"recordID\"},\n\t\t\tRequiredPermission:    GetRequiredPermission(DataRecordcommentsRead),\n\t\t\tExpectedSuccessStatus: http.StatusOK,\n\t\t\tExpectedErrorResponse: &ErrorResponse{\n\t\t\t\tType: \"INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND\",\n\t\t\t},\n\t\t},\n\t\tListWebhooksEndpoint: {\n\t\t\tURL:                   \"https://api.airtable.com/v0/bases/{baseID}/webhooks\",\n\t\t\tMethod:                \"GET\",\n\t\t\tRequiredIDs:           []string{\"baseID\"},\n\t\t\tRequiredPermission:    GetRequiredPermission(WebhookManage),\n\t\t\tExpectedSuccessStatus: http.StatusOK,\n\t\t\tExpectedErrorResponse: &ErrorResponse{\n\t\t\t\tType: \"INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND\",\n\t\t\t},\n\t\t},\n\t\tListBlockInstallationsEndpoint: {\n\t\t\tURL:                   \"https://api.airtable.com/v0/meta/bases/{baseID}/blockInstallations\",\n\t\t\tMethod:                \"GET\",\n\t\t\tRequiredIDs:           []string{\"baseID\"},\n\t\t\tRequiredPermission:    GetRequiredPermission(BlockManage),\n\t\t\tExpectedSuccessStatus: http.StatusOK,\n\t\t\tExpectedErrorResponse: &ErrorResponse{\n\t\t\t\tType: \"INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND\",\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc GetRequiredPermission(permission Permission) *string {\n\tif val, exists := PermissionStrings[permission]; exists {\n\t\treturn &val\n\t}\n\treturn nil\n}\n\n// GetEndpoint returns the endpoint object for the provided name and whether it exists\nfunc GetEndpoint(name EndpointName) (Endpoint, bool) {\n\tendpoint, exists := endpoints[name]\n\treturn endpoint, exists\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/airtable/common/models.go",
    "content": "package common\n\ntype AirtableUserInfo struct {\n\tID     string   `json:\"id\"`\n\tEmail  *string  `json:\"email,omitempty\"`\n\tScopes []string `json:\"scopes\"`\n}\n\ntype AirtableBases struct {\n\tBases []struct {\n\t\tID     string  `json:\"id\"`\n\t\tName   string  `json:\"name\"`\n\t\tSchema *Schema `json:\"schema,omitempty\"`\n\t} `json:\"bases\"`\n}\n\ntype Schema struct {\n\tTables []AirtableEntity `json:\"tables\"`\n}\n\ntype AirtableEntity struct {\n\tID string `json:\"id\"`\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/airtable/common/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage common\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    DataRecordsRead Permission = iota\n    DataRecordsWrite Permission = iota\n    DataRecordcommentsRead Permission = iota\n    DataRecordcommentsWrite Permission = iota\n    SchemaBasesRead Permission = iota\n    SchemaBasesWrite Permission = iota\n    WebhookManage Permission = iota\n    BlockManage Permission = iota\n    UserEmailRead Permission = iota\n    EnterpriseGroupsRead Permission = iota\n    WorkspacesandbasesRead Permission = iota\n    WorkspacesandbasesWrite Permission = iota\n    WorkspacesandbasesSharesManage Permission = iota\n    EnterpriseScimUsersandgroupsManage Permission = iota\n    EnterpriseAuditlogsRead Permission = iota\n    EnterpriseChangeeventsRead Permission = iota\n    EnterpriseExportsManage Permission = iota\n    EnterpriseAccountRead Permission = iota\n    EnterpriseAccountWrite Permission = iota\n    EnterpriseUserRead Permission = iota\n    EnterpriseUserWrite Permission = iota\n    EnterpriseGroupsManage Permission = iota\n    WorkspacesandbasesManage Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        DataRecordsRead: \"data.records:read\",\n        DataRecordsWrite: \"data.records:write\",\n        DataRecordcommentsRead: \"data.recordComments:read\",\n        DataRecordcommentsWrite: \"data.recordComments:write\",\n        SchemaBasesRead: \"schema.bases:read\",\n        SchemaBasesWrite: \"schema.bases:write\",\n        WebhookManage: \"webhook:manage\",\n        BlockManage: \"block:manage\",\n        UserEmailRead: \"user.email:read\",\n        EnterpriseGroupsRead: \"enterprise.groups:read\",\n        WorkspacesandbasesRead: \"workspacesAndBases:read\",\n        WorkspacesandbasesWrite: \"workspacesAndBases:write\",\n        WorkspacesandbasesSharesManage: \"workspacesAndBases.shares:manage\",\n        EnterpriseScimUsersandgroupsManage: \"enterprise.scim.usersAndGroups:manage\",\n        EnterpriseAuditlogsRead: \"enterprise.auditLogs:read\",\n        EnterpriseChangeeventsRead: \"enterprise.changeEvents:read\",\n        EnterpriseExportsManage: \"enterprise.exports:manage\",\n        EnterpriseAccountRead: \"enterprise.account:read\",\n        EnterpriseAccountWrite: \"enterprise.account:write\",\n        EnterpriseUserRead: \"enterprise.user:read\",\n        EnterpriseUserWrite: \"enterprise.user:write\",\n        EnterpriseGroupsManage: \"enterprise.groups:manage\",\n        WorkspacesandbasesManage: \"workspacesAndBases:manage\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"data.records:read\": DataRecordsRead,\n        \"data.records:write\": DataRecordsWrite,\n        \"data.recordComments:read\": DataRecordcommentsRead,\n        \"data.recordComments:write\": DataRecordcommentsWrite,\n        \"schema.bases:read\": SchemaBasesRead,\n        \"schema.bases:write\": SchemaBasesWrite,\n        \"webhook:manage\": WebhookManage,\n        \"block:manage\": BlockManage,\n        \"user.email:read\": UserEmailRead,\n        \"enterprise.groups:read\": EnterpriseGroupsRead,\n        \"workspacesAndBases:read\": WorkspacesandbasesRead,\n        \"workspacesAndBases:write\": WorkspacesandbasesWrite,\n        \"workspacesAndBases.shares:manage\": WorkspacesandbasesSharesManage,\n        \"enterprise.scim.usersAndGroups:manage\": EnterpriseScimUsersandgroupsManage,\n        \"enterprise.auditLogs:read\": EnterpriseAuditlogsRead,\n        \"enterprise.changeEvents:read\": EnterpriseChangeeventsRead,\n        \"enterprise.exports:manage\": EnterpriseExportsManage,\n        \"enterprise.account:read\": EnterpriseAccountRead,\n        \"enterprise.account:write\": EnterpriseAccountWrite,\n        \"enterprise.user:read\": EnterpriseUserRead,\n        \"enterprise.user:write\": EnterpriseUserWrite,\n        \"enterprise.groups:manage\": EnterpriseGroupsManage,\n        \"workspacesAndBases:manage\": WorkspacesandbasesManage,\n    }\n\n    PermissionIDs = map[Permission]int{\n        DataRecordsRead: 1,\n        DataRecordsWrite: 2,\n        DataRecordcommentsRead: 3,\n        DataRecordcommentsWrite: 4,\n        SchemaBasesRead: 5,\n        SchemaBasesWrite: 6,\n        WebhookManage: 7,\n        BlockManage: 8,\n        UserEmailRead: 9,\n        EnterpriseGroupsRead: 10,\n        WorkspacesandbasesRead: 11,\n        WorkspacesandbasesWrite: 12,\n        WorkspacesandbasesSharesManage: 13,\n        EnterpriseScimUsersandgroupsManage: 14,\n        EnterpriseAuditlogsRead: 15,\n        EnterpriseChangeeventsRead: 16,\n        EnterpriseExportsManage: 17,\n        EnterpriseAccountRead: 18,\n        EnterpriseAccountWrite: 19,\n        EnterpriseUserRead: 20,\n        EnterpriseUserWrite: 21,\n        EnterpriseGroupsManage: 22,\n        WorkspacesandbasesManage: 23,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: DataRecordsRead,\n        2: DataRecordsWrite,\n        3: DataRecordcommentsRead,\n        4: DataRecordcommentsWrite,\n        5: SchemaBasesRead,\n        6: SchemaBasesWrite,\n        7: WebhookManage,\n        8: BlockManage,\n        9: UserEmailRead,\n        10: EnterpriseGroupsRead,\n        11: WorkspacesandbasesRead,\n        12: WorkspacesandbasesWrite,\n        13: WorkspacesandbasesSharesManage,\n        14: EnterpriseScimUsersandgroupsManage,\n        15: EnterpriseAuditlogsRead,\n        16: EnterpriseChangeeventsRead,\n        17: EnterpriseExportsManage,\n        18: EnterpriseAccountRead,\n        19: EnterpriseAccountWrite,\n        20: EnterpriseUserRead,\n        21: EnterpriseUserWrite,\n        22: EnterpriseGroupsManage,\n        23: WorkspacesandbasesManage,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/airtable/common/permissions.yaml",
    "content": "permissions:\n  - data.records:read\n  - data.records:write\n  - data.recordComments:read\n  - data.recordComments:write\n  - schema.bases:read\n  - schema.bases:write\n  - webhook:manage\n  - block:manage\n  - user.email:read\n  - enterprise.groups:read\n  - workspacesAndBases:read\n  - workspacesAndBases:write\n  - workspacesAndBases.shares:manage\n  - enterprise.scim.usersAndGroups:manage\n  - enterprise.auditLogs:read\n  - enterprise.changeEvents:read\n  - enterprise.exports:manage\n  - enterprise.account:read\n  - enterprise.account:write\n  - enterprise.user:read\n  - enterprise.user:write\n  - enterprise.groups:manage\n  - workspacesAndBases:manage\n"
  },
  {
    "path": "pkg/analyzer/analyzers/airtable/common/scopes.go",
    "content": "package common\n\nvar scopeToPermissions = map[string][]string{\n\t// Basic Scopes\n\t\"data.records:read\": {\n\t\t\"List records\",\n\t\t\"Get record\",\n\t},\n\t\"data.records:write\": {\n\t\t\"Create records\",\n\t\t\"Update record\",\n\t\t\"Update multiple records\",\n\t\t\"Delete record\",\n\t\t\"Delete multiple records\",\n\t\t\"Sync CSV data\",\n\t},\n\t\"data.recordComments:read\": {\n\t\t\"List comments\",\n\t},\n\t\"data.recordComments:write\": {\n\t\t\"Create comment\",\n\t\t\"Delete comment\",\n\t\t\"Update comment\",\n\t},\n\t\"schema.bases:read\": {\n\t\t\"List bases\",\n\t\t\"Get base schema\",\n\t},\n\t\"schema.bases:write\": {\n\t\t\"Create base\",\n\t\t\"Create table\",\n\t\t\"Update table\",\n\t\t\"Create field\",\n\t\t\"Update field\",\n\t\t\"Sync CSV data\",\n\t},\n\t\"webhook:manage\": {\n\t\t\"List webhooks\",\n\t\t\"Create a webhook\",\n\t\t\"Delete a webhook\",\n\t\t\"Enable/disable webhook notifications\",\n\t\t\"Refresh a webhook\",\n\t},\n\t\"block:manage\": {\n\t\t\"Create new releases and submissions for custom extensions\",\n\t},\n\t\"user.email:read\": {\n\t\t\"See the user's email address\",\n\t},\n\n\t// Enterprise scopes\n\t\"enterprise.groups:read\": {\n\t\t\"Get user group\",\n\t},\n\t\"workspacesAndBases:read\": {\n\t\t\"Get base collaborators\",\n\t\t\"List block installations\",\n\t\t\"Get interface\",\n\t\t\"List views\",\n\t\t\"Get view metadata\",\n\t\t\"Get workspace collaborators\",\n\t},\n\t\"workspacesAndBases:write\": {\n\t\t\"Delete block installation\",\n\t\t\"Manage block installation\",\n\t\t\"Add base collaborator\",\n\t\t\"Delete base collaborator\",\n\t\t\"Update collaborator base permission\",\n\t\t\"Add interface collaborator\",\n\t\t\"Delete interface collaborator\",\n\t\t\"Update interface collaborator\",\n\t\t\"Delete interface invite\",\n\t\t\"Delete base invite\",\n\t\t\"Delete view\",\n\t\t\"Add workspace collaborator\",\n\t\t\"Delete workspace collaborator\",\n\t\t\"Update workspace collaborator\",\n\t\t\"Delete workspace invite\",\n\t\t\"Update workspace restrictions\",\n\t},\n\t\"workspacesAndBases.shares:manage\": {\n\t\t\"List shares\",\n\t\t\"Delete share\",\n\t\t\"Manage share\",\n\t},\n\t\"enterprise.scim.usersAndGroups:manage\": {\n\t\t\"List groups\",\n\t\t\"Create group\",\n\t\t\"Delete group\",\n\t\t\"Get group\",\n\t\t\"Patch group\",\n\t\t\"Put group\",\n\t\t\"List users\",\n\t\t\"Create user\",\n\t\t\"Delete user\",\n\t\t\"Get user\",\n\t\t\"Patch user\",\n\t\t\"Put user\",\n\t},\n\t\"enterprise.auditLogs:read\": {\n\t\t\"Audit log events\",\n\t\t\"List audit log requests\",\n\t\t\"Create audit log request\",\n\t\t\"Get audit log request\",\n\t},\n\t\"enterprise.changeEvents:read\": {\n\t\t\"Change events\",\n\t},\n\t\"enterprise.exports:manage\": {\n\t\t\"List eDiscovery exports\",\n\t\t\"Create eDiscovery export\",\n\t\t\"Get eDiscovery export\",\n\t},\n\t\"enterprise.account:read\": {\n\t\t\"Get enterprise\",\n\t},\n\t\"enterprise.account:write\": {\n\t\t\"Create descendant enterprise\",\n\t},\n\t\"enterprise.user:read\": {\n\t\t\"Get users by id or email\",\n\t\t\"Get user by id\",\n\t},\n\t\"enterprise.user:write\": {\n\t\t\"Delete users by email\",\n\t\t\"Manage user batched\",\n\t\t\"Manage user membership\",\n\t\t\"Grant admin access\",\n\t\t\"Revoke admin access\",\n\t\t\"Delete user by id\",\n\t\t\"Manage user\",\n\t\t\"Logout user\",\n\t\t\"Remove user from enterprise\",\n\t},\n\t\"enterprise.groups:manage\": {\n\t\t\"Move user groups\",\n\t},\n\t\"workspacesAndBases:manage\": {\n\t\t\"Delete base\",\n\t\t\"Move workspaces\",\n\t\t\"Delete workspace\",\n\t\t\"Move base\",\n\t},\n}\n\nvar scopeToEndpointName = map[string]EndpointName{\n\t\"schema.bases:read\":        ListBasesEndpoint,\n\t\"schema.bases:write\":       UpdateBaseEndpoint,\n\t\"webhook:manage\":           ListWebhooksEndpoint,\n\t\"block:manage\":             ListBlockInstallationsEndpoint,\n\t\"data.records:read\":        ListRecordsEndpoint,\n\t\"data.records:write\":       CreateRecordEndpoint,\n\t\"data.recordComments:read\": ListRecordCommentsEndpoint,\n}\n\nvar scopeToEndpoint map[string]Endpoint\n\nfunc init() {\n\tscopeToEndpoint = make(map[string]Endpoint)\n\tfor scope, endpointName := range scopeToEndpointName {\n\t\tif endpoint, exists := GetEndpoint(endpointName); exists {\n\t\t\tscopeToEndpoint[scope] = endpoint\n\t\t}\n\t}\n}\n\nfunc GetScopePermissions(scope string) ([]string, bool) {\n\tpermission, exists := scopeToPermissions[scope]\n\treturn permission, exists\n}\n\nfunc GetScopeEndpoint(scope string) (Endpoint, bool) {\n\tendpoint, exists := scopeToEndpoint[scope]\n\treturn endpoint, exists\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/analyzers.go",
    "content": "package analyzers\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"sort\"\n\n\t\"github.com/fatih/color\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\ntype (\n\tAnalyzer interface {\n\t\tType() AnalyzerType\n\t\tAnalyze(ctx context.Context, credentialInfo map[string]string) (*AnalyzerResult, error)\n\t}\n\n\tAnalyzerType int\n\n\t// AnalyzerResult is the output of analysis.\n\tAnalyzerResult struct {\n\t\tAnalyzerType       AnalyzerType\n\t\tBindings           []Binding\n\t\tUnboundedResources []Resource\n\t\tMetadata           map[string]any\n\t}\n\n\tResource struct {\n\t\tName               string\n\t\tFullyQualifiedName string\n\t\tType               string\n\t\tMetadata           map[string]any\n\t\tParent             *Resource\n\t}\n\n\tPermission struct {\n\t\tValue  string\n\t\tParent *Permission\n\t}\n\n\tBinding struct {\n\t\tResource   Resource\n\t\tPermission Permission\n\t\tCondition  string\n\t}\n)\n\ntype PermissionType string\n\nconst (\n\tREAD       PermissionType = \"Read\"\n\tWRITE      PermissionType = \"Write\"\n\tREAD_WRITE PermissionType = \"Read & Write\"\n\tNONE       PermissionType = \"None\"\n\tERROR      PermissionType = \"Error\"\n\n\tFullAccess string = \"full_access\"\n)\n\nconst (\n\tAnalyzerTypeInvalid AnalyzerType = iota\n\tAnalyzerTypeAirbrake\n\tAnalyzerAnthropic\n\tAnalyzerTypeAsana\n\tAnalyzerTypeBitbucket\n\tAnalyzerTypeDockerHub\n\tAnalyzerTypeElevenLabs\n\tAnalyzerTypeGitHub\n\tAnalyzerTypeGitLab\n\tAnalyzerTypeHuggingFace\n\tAnalyzerTypeMailchimp\n\tAnalyzerTypeMailgun\n\tAnalyzerTypeMySQL\n\tAnalyzerTypeOpenAI\n\tAnalyzerTypeOpsgenie\n\tAnalyzerTypePostgres\n\tAnalyzerTypePostman\n\tAnalyzerTypeSendgrid\n\tAnalyzerTypeShopify\n\tAnalyzerTypeSlack\n\tAnalyzerTypeSourcegraph\n\tAnalyzerTypeSquare\n\tAnalyzerTypeStripe\n\tAnalyzerTypeTwilio\n\tAnalyzerTypePrivateKey\n\tAnalyzerTypeNotion\n\tAnalyzerTypeDigitalOcean\n\tAnalyzerTypePlanetScale\n\tAnalyzerTypeAirtableOAuth\n\tAnalyzerTypeAirtablePat\n\tAnalyzerTypeGroq\n\tAnalyzerTypeLaunchDarkly\n\tAnalyzerTypeFigma\n\tAnalyzerTypePlaid\n\tAnalyzerTypeNetlify\n\tAnalyzerTypeFastly\n\tAnalyzerTypeMonday\n\tAnalyzerTypeDatadog\n\tAnalyzerTypeNgrok\n\tAnalyzerTypeMux\n\tAnalyzerTypePosthog\n\tAnalyzerTypeDropbox\n\tAnalyzerTypeDataBricks\n\tAnalyzerTypeJira\n\t// Add new items here with AnalyzerType prefix\n)\n\n// analyzerTypeStrings maps the enum to its string representation.\nvar analyzerTypeStrings = map[AnalyzerType]string{\n\tAnalyzerTypeInvalid:       \"Invalid\",\n\tAnalyzerTypeAirbrake:      \"Airbrake\",\n\tAnalyzerAnthropic:         \"Anthropic\",\n\tAnalyzerTypeAsana:         \"Asana\",\n\tAnalyzerTypeBitbucket:     \"Bitbucket\",\n\tAnalyzerTypeDigitalOcean:  \"DigitalOcean\",\n\tAnalyzerTypeDockerHub:     \"DockerHub\",\n\tAnalyzerTypeElevenLabs:    \"ElevenLabs\",\n\tAnalyzerTypeGitHub:        \"GitHub\",\n\tAnalyzerTypeGitLab:        \"GitLab\",\n\tAnalyzerTypeHuggingFace:   \"HuggingFace\",\n\tAnalyzerTypeMailchimp:     \"Mailchimp\",\n\tAnalyzerTypeMailgun:       \"Mailgun\",\n\tAnalyzerTypeMySQL:         \"MySQL\",\n\tAnalyzerTypeOpenAI:        \"OpenAI\",\n\tAnalyzerTypeOpsgenie:      \"Opsgenie\",\n\tAnalyzerTypePostgres:      \"Postgres\",\n\tAnalyzerTypePostman:       \"Postman\",\n\tAnalyzerTypeSendgrid:      \"Sendgrid\",\n\tAnalyzerTypeShopify:       \"Shopify\",\n\tAnalyzerTypeSlack:         \"Slack\",\n\tAnalyzerTypeSourcegraph:   \"Sourcegraph\",\n\tAnalyzerTypeSquare:        \"Square\",\n\tAnalyzerTypeStripe:        \"Stripe\",\n\tAnalyzerTypeTwilio:        \"Twilio\",\n\tAnalyzerTypePrivateKey:    \"PrivateKey\",\n\tAnalyzerTypeNotion:        \"Notion\",\n\tAnalyzerTypePlanetScale:   \"PlanetScale\",\n\tAnalyzerTypeAirtableOAuth: \"AirtableOAuth\",\n\tAnalyzerTypeAirtablePat:   \"AirtablePat\",\n\tAnalyzerTypeGroq:          \"Groq\",\n\tAnalyzerTypeLaunchDarkly:  \"LaunchDarkly\",\n\tAnalyzerTypeFigma:         \"Figma\",\n\tAnalyzerTypePlaid:         \"Plaid\",\n\tAnalyzerTypeNetlify:       \"Netlify\",\n\tAnalyzerTypeFastly:        \"Fastly\",\n\tAnalyzerTypeMonday:        \"Monday\",\n\tAnalyzerTypeDatadog:       \"Datadog\",\n\tAnalyzerTypeNgrok:         \"Ngrok\",\n\tAnalyzerTypeMux:           \"Mux\",\n\tAnalyzerTypePosthog:       \"Posthog\",\n\tAnalyzerTypeDropbox:       \"Dropbox\",\n\tAnalyzerTypeDataBricks:    \"DataBricks\",\n\tAnalyzerTypeJira:          \"Jira\",\n\t// Add new mappings here\n}\n\n// String method to get the string representation of an AnalyzerType.\nfunc (a AnalyzerType) String() string {\n\tif str, ok := analyzerTypeStrings[a]; ok {\n\t\treturn str\n\t}\n\treturn \"Unknown\"\n}\n\n// AvailableAnalyzers returns a sorted slice of AnalyzerType strings, skipping \"Invalid\".\nfunc AvailableAnalyzers() []string {\n\tvar analyzerStrings []string\n\n\t// Iterate through the map to collect all string values except \"Invalid\".\n\tfor typ, str := range analyzerTypeStrings {\n\t\tif typ != AnalyzerTypeInvalid {\n\t\t\tanalyzerStrings = append(analyzerStrings, str)\n\t\t}\n\t}\n\n\t// Sort the slice alphabetically.\n\tsort.Strings(analyzerStrings)\n\n\treturn analyzerStrings\n}\n\ntype PermissionStatus struct {\n\tValue   bool\n\tIsError bool\n}\n\ntype HttpStatusTest struct {\n\tURL     string\n\tMethod  string\n\tPayload map[string]interface{}\n\tParams  map[string]string\n\tValid   []int\n\tInvalid []int\n\tType    PermissionType\n\tStatus  PermissionStatus\n\tRisk    string\n}\n\nfunc (h *HttpStatusTest) RunTest(headers map[string]string) error {\n\t// If body data, marshal to JSON\n\tvar data io.Reader\n\tif h.Payload != nil {\n\t\tjsonData, err := json.Marshal(h.Payload)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdata = bytes.NewBuffer(jsonData)\n\t}\n\n\t// Create new HTTP request\n\tclient := &http.Client{}\n\treq, err := http.NewRequest(h.Method, h.URL, data)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Add custom headers if provided\n\tfor key, value := range headers {\n\t\treq.Header.Set(key, value)\n\t}\n\n\t// Execute HTTP Request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\n\t// Check response status code\n\tswitch {\n\tcase StatusContains(resp.StatusCode, h.Valid):\n\t\th.Status.Value = true\n\tcase StatusContains(resp.StatusCode, h.Invalid):\n\t\th.Status.Value = false\n\tdefault:\n\t\th.Status.IsError = true\n\t}\n\treturn nil\n}\n\ntype Scope struct {\n\tName  string\n\tTests []interface{}\n}\n\nfunc StatusContains(status int, vals []int) bool {\n\tfor _, v := range vals {\n\t\tif status == v {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc GetWriterFromStatus(status PermissionType) func(a ...interface{}) string {\n\tswitch status {\n\tcase READ:\n\t\treturn color.New(color.FgYellow).SprintFunc()\n\tcase WRITE:\n\t\treturn color.New(color.FgGreen).SprintFunc()\n\tcase READ_WRITE:\n\t\treturn color.New(color.FgGreen).SprintFunc()\n\tcase NONE:\n\t\treturn color.New().SprintFunc()\n\tcase ERROR:\n\t\treturn color.New(color.FgRed).SprintFunc()\n\tdefault:\n\t\treturn color.New().SprintFunc()\n\t}\n}\n\nvar GreenWriter = color.New(color.FgGreen).SprintFunc()\nvar YellowWriter = color.New(color.FgYellow).SprintFunc()\nvar RedWriter = color.New(color.FgRed).SprintFunc()\nvar DefaultWriter = color.New().SprintFunc()\n\n// BindAllPermissions creates a Binding for each permission to the given\n// resource.\nfunc BindAllPermissions(r Resource, perms ...Permission) []Binding {\n\tbindings := make([]Binding, len(perms))\n\tfor i, perm := range perms {\n\t\tbindings[i] = Binding{\n\t\t\tResource:   r,\n\t\t\tPermission: perm,\n\t\t}\n\t}\n\treturn bindings\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/anthropic/anthropic.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go anthropic\npackage anthropic\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\nconst (\n\t// Key Types\n\tAPIKey   = \"API-Key\"\n\tAdminKey = \"Admin-Key\"\n)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\n// SecretInfo hold the information about the anthropic key\ntype SecretInfo struct {\n\tValid              bool\n\tType               string // key type - TODO: Handle Anthropic Admin Keys\n\tAnthropicResources []AnthropicResource\n\tPermissions        string // always full_access\n\tMisc               map[string]string\n}\n\n// AnthropicResource is any resource that can be accessed with anthropic key\ntype AnthropicResource struct {\n\tID       string\n\tName     string\n\tType     string\n\tParent   *AnthropicResource\n\tMetadata map[string]string\n}\n\nfunc (a Analyzer) Type() analyzers.AnalyzerType {\n\treturn analyzers.AnalyzerAnthropic\n}\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, exist := credInfo[\"key\"]\n\tif !exist {\n\t\treturn nil, errors.New(\"key not found in credentials info\")\n\t}\n\n\tsecretInfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfoToAnalyzerResult(secretInfo), nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\t// just print the error in cli and continue as a partial success\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t}\n\n\tif info == nil {\n\t\tcolor.Red(\"[x] Error : %s\", \"No information found\")\n\t\treturn\n\t}\n\n\tif info.Valid {\n\t\tcolor.Green(\"[!] Valid Anthropic %s\\n\\n\", info.Type)\n\t\t// no user information\n\t\t// print full access permission\n\t\tprintPermission(info.Permissions)\n\t\t// print resources\n\t\tprintAnthropicResources(info.AnthropicResources)\n\n\t\tcolor.Yellow(\"\\n[i] Expires: Never\")\n\t}\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\t// create a HTTP client\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\tkeyType := getKeyType(key)\n\n\tvar secretInfo = &SecretInfo{\n\t\tType: keyType,\n\t}\n\n\tswitch keyType {\n\tcase APIKey:\n\t\tif err := captureAPIKeyResources(client, key, secretInfo); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tcase AdminKey:\n\t\tif err := captureAdminKeyResources(client, key, secretInfo); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\tdefault:\n\t\treturn nil, errors.New(\"unsupported key type\")\n\t}\n\n\t// anthropic key has full access only\n\tsecretInfo.Permissions = PermissionStrings[FullAccess]\n\tsecretInfo.Valid = true\n\n\treturn secretInfo, nil\n}\n\n// secretInfoToAnalyzerResult translate secret info to Analyzer Result\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerAnthropic,\n\t\tMetadata:     map[string]any{\"Valid_Key\": info.Valid},\n\t\tBindings:     make([]analyzers.Binding, 0, len(info.AnthropicResources)), // pre-allocate with zero length\n\t}\n\n\t// extract information to create bindings and append to result bindings\n\tfor _, Anthropicresource := range info.AnthropicResources {\n\t\tbinding := analyzers.Binding{\n\t\t\tResource: analyzers.Resource{\n\t\t\t\tName:               Anthropicresource.Name,\n\t\t\t\tFullyQualifiedName: Anthropicresource.ID,\n\t\t\t\tType:               Anthropicresource.Type,\n\t\t\t\tMetadata:           map[string]any{},\n\t\t\t},\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: info.Permissions,\n\t\t\t},\n\t\t}\n\n\t\tif Anthropicresource.Parent != nil {\n\t\t\tbinding.Resource.Parent = &analyzers.Resource{\n\t\t\t\tName:               Anthropicresource.Parent.Name,\n\t\t\t\tFullyQualifiedName: Anthropicresource.Parent.ID,\n\t\t\t\tType:               Anthropicresource.Parent.Type,\n\t\t\t}\n\t\t}\n\n\t\tfor key, value := range Anthropicresource.Metadata {\n\t\t\tbinding.Resource.Metadata[key] = value\n\t\t}\n\n\t\tresult.Bindings = append(result.Bindings, binding)\n\t}\n\n\treturn &result\n}\n\nfunc printPermission(permission string) {\n\tcolor.Yellow(\"[i] Permissions:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Permission\"})\n\tt.AppendRow(table.Row{color.GreenString(permission)})\n\tt.Render()\n}\n\nfunc printAnthropicResources(resources []AnthropicResource) {\n\tcolor.Green(\"\\n[i] Resources:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Resource Type\", \"Resource ID\", \"Resource Name\"})\n\tfor _, resource := range resources {\n\t\tt.AppendRow(table.Row{color.GreenString(resource.Type), color.GreenString(resource.ID), color.GreenString(resource.Name)})\n\t}\n\tt.Render()\n}\n\n// getKeyType return the type of key\nfunc getKeyType(key string) string {\n\tif strings.Contains(key, \"sk-ant-admin01\") {\n\t\treturn AdminKey\n\t} else if strings.Contains(key, \"sk-ant-api03\") {\n\t\treturn APIKey\n\t}\n\n\treturn \"\"\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/anthropic/anthropic_test.go",
    "content": "package anthropic\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed result_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tsecret := testSecrets.MustGetField(\"ANTHROPIC\")\n\n\ttests := []struct {\n\t\tname    string\n\t\tsecret  string\n\t\twant    []byte // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid anthropic key\",\n\t\t\tsecret:  secret,\n\t\t\twant:    expectedOutput,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.secret})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/anthropic/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage anthropic\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    FullAccess Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        FullAccess: \"full_access\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"full_access\": FullAccess,\n    }\n\n    PermissionIDs = map[Permission]int{\n        FullAccess: 1,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: FullAccess,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/anthropic/permissions.yaml",
    "content": "permissions:\n  - full_access\n"
  },
  {
    "path": "pkg/analyzer/analyzers/anthropic/requests.go",
    "content": "package anthropic\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n)\n\nvar endpoints = map[string]string{\n\t// api key endpoints\n\t\"models\":         \"https://api.anthropic.com/v1/models\",\n\t\"messageBatches\": \"https://api.anthropic.com/v1/messages/batches\",\n\n\t// admin key endpoints\n\t\"orgUsers\":         \"https://api.anthropic.com/v1/organizations/users\",\n\t\"workspaces\":       \"https://api.anthropic.com/v1/organizations/workspaces\",\n\t\"workspaceMembers\": \"https://api.anthropic.com/v1/organizations/workspaces/%s/members\", // require workspace id\n\t\"apiKeys\":          \"https://api.anthropic.com/v1/organizations/api_keys\",\n}\n\ntype ModelsResponse struct {\n\tData []struct {\n\t\tID          string `json:\"id\"`\n\t\tDisplayName string `json:\"display_name\"`\n\t\tType        string `json:\"type\"`\n\t} `json:\"data\"`\n}\n\ntype MessageResponse struct {\n\tData []struct {\n\t\tID               string `json:\"id\"`\n\t\tType             string `json:\"type\"`\n\t\tProcessingStatus string `json:\"processing_status\"`\n\t\tExpiresAt        string `json:\"expires_at\"`\n\t\tResultsURL       string `json:\"results_url\"`\n\t} `json:\"data\"`\n}\n\ntype OrgUsersResponse struct {\n\tData []struct {\n\t\tID    string `json:\"id\"`\n\t\tType  string `json:\"type\"`\n\t\tEmail string `json:\"email\"`\n\t\tName  string `json:\"name\"`\n\t\tRole  string `json:\"role\"`\n\t} `json:\"data\"`\n}\n\ntype WorkspacesResponse struct {\n\tData []struct {\n\t\tID   string `json:\"id\"`\n\t\tType string `json:\"type\"`\n\t\tName string `json:\"name\"`\n\t} `json:\"data\"`\n}\n\ntype WorkspaceMembersResponse struct {\n\tData []struct {\n\t\tWorkspaceID   string `json:\"workspace_id\"`\n\t\tUserID        string `json:\"user_id\"`\n\t\tType          string `json:\"type\"`\n\t\tWorkspaceRole string `json:\"workspace_role\"`\n\t} `json:\"data\"`\n}\n\ntype APIKeysResponse struct {\n\tData []struct {\n\t\tID          string `json:\"id\"`\n\t\tType        string `json:\"type\"`\n\t\tName        string `json:\"name\"`\n\t\tWorkspaceID string `json:\"workspace_id\"`\n\t\tCreatedBy   struct {\n\t\t\tID string `json:\"id\"`\n\t\t} `json:\"created_by\"`\n\t\tPartialKeyHint string `json:\"partial_key_hint\"`\n\t\tStatus         string `json:\"status\"`\n\t} `json:\"data\"`\n}\n\n// makeAnthropicRequest send the API request to passed url with passed key as API Key and return response body and status code\nfunc makeAnthropicRequest(client *http.Client, url, key string) ([]byte, int, error) {\n\t// create request\n\treq, err := http.NewRequest(http.MethodGet, url, http.NoBody)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\t// add required keys in the header\n\treq.Header.Set(\"x-api-key\", key)\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"anthropic-version\", \"2023-06-01\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tresponseBodyByte, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn responseBodyByte, resp.StatusCode, nil\n}\n\n// captureAPIKeyResources capture resources associated with api key\nfunc captureAPIKeyResources(client *http.Client, apiKey string, secretInfo *SecretInfo) error {\n\tif err := captureModels(client, apiKey, secretInfo); err != nil {\n\t\treturn err\n\t}\n\n\tif err := captureMessageBatches(client, apiKey, secretInfo); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// captureAdminKeyResources capture resources associated with admin key\nfunc captureAdminKeyResources(client *http.Client, adminKey string, secretInfo *SecretInfo) error {\n\tif err := captureOrgUsers(client, adminKey, secretInfo); err != nil {\n\t\treturn err\n\t}\n\n\tif err := captureWorkspaces(client, adminKey, secretInfo); err != nil {\n\t\treturn err\n\t}\n\n\tif err := captureAPIKeys(client, adminKey, secretInfo); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc captureModels(client *http.Client, apiKey string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeAnthropicRequest(client, endpoints[\"models\"], apiKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar models ModelsResponse\n\n\t\tif err := json.Unmarshal(response, &models); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, model := range models.Data {\n\t\t\tsecretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{\n\t\t\t\tID:   model.ID,\n\t\t\t\tName: model.DisplayName,\n\t\t\t\tType: model.Type,\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusNotFound, http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/revoked api-key\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while fetching models\", statusCode)\n\t}\n}\n\nfunc captureMessageBatches(client *http.Client, apiKey string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeAnthropicRequest(client, endpoints[\"messageBatches\"], apiKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar messageBatches MessageResponse\n\n\t\tif err := json.Unmarshal(response, &messageBatches); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, messageBatch := range messageBatches.Data {\n\t\t\tsecretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{\n\t\t\t\tID:   messageBatch.ID,\n\t\t\t\tName: \"\", // no name\n\t\t\t\tType: messageBatch.Type,\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"expires_at\":  messageBatch.ExpiresAt,\n\t\t\t\t\t\"results_url\": messageBatch.ResultsURL,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusNotFound, http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/revoked api-key\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while fetching models\", statusCode)\n\t}\n}\n\nfunc captureOrgUsers(client *http.Client, adminKey string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeAnthropicRequest(client, endpoints[\"orgUsers\"], adminKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar users OrgUsersResponse\n\n\t\tif err := json.Unmarshal(response, &users); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, user := range users.Data {\n\t\t\tsecretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{\n\t\t\t\tID:   user.ID,\n\t\t\t\tName: user.Name,\n\t\t\t\tType: user.Type,\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"Role\":  user.Role,\n\t\t\t\t\t\"Email\": user.Email,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusNotFound, http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/revoked api-key\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while fetching models\", statusCode)\n\t}\n}\n\nfunc captureWorkspaces(client *http.Client, adminKey string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeAnthropicRequest(client, endpoints[\"workspaces\"], adminKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar workspaces WorkspacesResponse\n\n\t\tif err := json.Unmarshal(response, &workspaces); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, workspace := range workspaces.Data {\n\t\t\tresource := AnthropicResource{\n\t\t\t\tID:   workspace.ID,\n\t\t\t\tName: workspace.Name,\n\t\t\t\tType: workspace.Type,\n\t\t\t}\n\n\t\t\tsecretInfo.AnthropicResources = append(secretInfo.AnthropicResources, resource)\n\t\t\t// capture each workspace members\n\t\t\tif err := captureWorkspaceMembers(client, adminKey, resource, secretInfo); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusNotFound, http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/revoked api-key\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while fetching models\", statusCode)\n\t}\n}\n\nfunc captureWorkspaceMembers(client *http.Client, key string, parentWorkspace AnthropicResource, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeAnthropicRequest(client, fmt.Sprintf(endpoints[\"workspaceMembers\"], parentWorkspace.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar members WorkspaceMembersResponse\n\n\t\tif err := json.Unmarshal(response, &members); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, member := range members.Data {\n\t\t\tsecretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{\n\t\t\t\tID:     fmt.Sprintf(\"anthropic/workspace/%s/member/%s\", member.WorkspaceID, member.UserID),\n\t\t\t\tName:   member.UserID,\n\t\t\t\tType:   member.Type,\n\t\t\t\tParent: &parentWorkspace,\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusNotFound, http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/revoked api-key\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while fetching models\", statusCode)\n\t}\n}\n\nfunc captureAPIKeys(client *http.Client, adminKey string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeAnthropicRequest(client, endpoints[\"apiKeys\"], adminKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar apiKeys APIKeysResponse\n\n\t\tif err := json.Unmarshal(response, &apiKeys); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, apiKey := range apiKeys.Data {\n\t\t\tsecretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{\n\t\t\t\tID:   apiKey.ID,\n\t\t\t\tName: apiKey.Name,\n\t\t\t\tType: apiKey.Type,\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"WorkspaceID\":    apiKey.WorkspaceID,\n\t\t\t\t\t\"CreatedBy\":      apiKey.CreatedBy.ID,\n\t\t\t\t\t\"PartialKeyHint\": apiKey.PartialKeyHint,\n\t\t\t\t\t\"Status\":         apiKey.Status,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusNotFound, http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/revoked api-key\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while fetching models\", statusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/anthropic/result_output.json",
    "content": "{\n  \"AnalyzerType\": 2,\n  \"Bindings\": [\n    {\n      \"Resource\": {\n        \"Name\": \"Claude Sonnet 4.6\",\n        \"FullyQualifiedName\": \"claude-sonnet-4-6\",\n        \"Type\": \"model\",\n        \"Metadata\": {},\n        \"Parent\": null\n      },\n      \"Permission\": { \"Value\": \"full_access\", \"Parent\": null },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Claude Opus 4.6\",\n        \"FullyQualifiedName\": \"claude-opus-4-6\",\n        \"Type\": \"model\",\n        \"Metadata\": {},\n        \"Parent\": null\n      },\n      \"Permission\": { \"Value\": \"full_access\", \"Parent\": null },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Claude Opus 4.5\",\n        \"FullyQualifiedName\": \"claude-opus-4-5-20251101\",\n        \"Type\": \"model\",\n        \"Metadata\": {},\n        \"Parent\": null\n      },\n      \"Permission\": { \"Value\": \"full_access\", \"Parent\": null },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Claude Haiku 4.5\",\n        \"FullyQualifiedName\": \"claude-haiku-4-5-20251001\",\n        \"Type\": \"model\",\n        \"Metadata\": {},\n        \"Parent\": null\n      },\n      \"Permission\": { \"Value\": \"full_access\", \"Parent\": null },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Claude Sonnet 4.5\",\n        \"FullyQualifiedName\": \"claude-sonnet-4-5-20250929\",\n        \"Type\": \"model\",\n        \"Metadata\": {},\n        \"Parent\": null\n      },\n      \"Permission\": { \"Value\": \"full_access\", \"Parent\": null },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Claude Opus 4.1\",\n        \"FullyQualifiedName\": \"claude-opus-4-1-20250805\",\n        \"Type\": \"model\",\n        \"Metadata\": {},\n        \"Parent\": null\n      },\n      \"Permission\": { \"Value\": \"full_access\", \"Parent\": null },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Claude Opus 4\",\n        \"FullyQualifiedName\": \"claude-opus-4-20250514\",\n        \"Type\": \"model\",\n        \"Metadata\": {},\n        \"Parent\": null\n      },\n      \"Permission\": { \"Value\": \"full_access\", \"Parent\": null },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Claude Sonnet 4\",\n        \"FullyQualifiedName\": \"claude-sonnet-4-20250514\",\n        \"Type\": \"model\",\n        \"Metadata\": {},\n        \"Parent\": null\n      },\n      \"Permission\": { \"Value\": \"full_access\", \"Parent\": null },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Claude Sonnet 3.7\",\n        \"FullyQualifiedName\": \"claude-3-7-sonnet-20250219\",\n        \"Type\": \"model\",\n        \"Metadata\": {},\n        \"Parent\": null\n      },\n      \"Permission\": { \"Value\": \"full_access\", \"Parent\": null },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Claude Haiku 3.5\",\n        \"FullyQualifiedName\": \"claude-3-5-haiku-20241022\",\n        \"Type\": \"model\",\n        \"Metadata\": {},\n        \"Parent\": null\n      },\n      \"Permission\": { \"Value\": \"full_access\", \"Parent\": null },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Claude Haiku 3\",\n        \"FullyQualifiedName\": \"claude-3-haiku-20240307\",\n        \"Type\": \"model\",\n        \"Metadata\": {},\n        \"Parent\": null\n      },\n      \"Permission\": { \"Value\": \"full_access\", \"Parent\": null },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"\",\n        \"FullyQualifiedName\": \"msgbatch_015FDqbx29LDeVvbwwyCe314\",\n        \"Type\": \"message_batch\",\n        \"Metadata\": {\n          \"expires_at\": \"2025-02-05T07:36:34.761695+00:00\",\n          \"results_url\": \"\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": { \"Value\": \"full_access\", \"Parent\": null },\n      \"Condition\": \"\"\n    }\n  ],\n  \"UnboundedResources\": null,\n  \"Metadata\": { \"Valid_Key\": true }\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/asana/asana.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go asana\npackage asana\n\n// ToDo: Add OAuth token support.\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeAsana }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credInfo[\"key\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"key not found in credInfo\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{}\n\n\t// resources/permission setup\n\tpermissions := allPermissions()\n\tuserResource := analyzers.Resource{\n\t\tName:               info.Data.Name,\n\t\tFullyQualifiedName: info.Data.ID,\n\t\tType:               \"user\",\n\t\tMetadata: map[string]any{\n\t\t\t\"email\": info.Data.Email,\n\t\t\t\"type\":  info.Data.Type,\n\t\t},\n\t}\n\n\t// bindings to all permissions to resources\n\tbindings := analyzers.BindAllPermissions(userResource, permissions...)\n\tresult.Bindings = append(result.Bindings, bindings...)\n\n\t// unbounded resources\n\tresult.UnboundedResources = make([]analyzers.Resource, 0, len(info.Data.Workspaces))\n\tfor _, workspace := range info.Data.Workspaces {\n\t\tresource := analyzers.Resource{\n\t\t\tName:               workspace.Name,\n\t\t\tFullyQualifiedName: workspace.ID,\n\t\t\tType:               \"workspace\",\n\t\t}\n\t\tresult.UnboundedResources = append(result.UnboundedResources, resource)\n\t}\n\n\treturn &result\n}\n\ntype SecretInfo struct {\n\tData struct {\n\t\tID         string `json:\"gid\"`\n\t\tEmail      string `json:\"email\"`\n\t\tName       string `json:\"name\"`\n\t\tType       string `json:\"resource_type\"`\n\t\tWorkspaces []struct {\n\t\t\tID   string `json:\"gid\"`\n\t\t\tName string `json:\"name\"`\n\t\t} `json:\"workspaces\"`\n\t} `json:\"data\"`\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tme, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[x] %s\", err.Error())\n\t\treturn\n\t}\n\tprintMetadata(me)\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\tvar me SecretInfo\n\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(\"GET\", \"https://app.asana.com/api/1.0/users/me\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq.Header.Set(\"Authorization\", \"Bearer \"+key)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif resp.StatusCode != 200 {\n\t\treturn nil, fmt.Errorf(\"Invalid Asana API Key\")\n\t}\n\n\tdefer resp.Body.Close()\n\n\terr = json.NewDecoder(resp.Body).Decode(&me)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif me.Data.Email == \"\" {\n\t\treturn nil, fmt.Errorf(\"Invalid Asana API Key\")\n\t}\n\treturn &me, nil\n}\n\nfunc printMetadata(me *SecretInfo) {\n\tcolor.Green(\"[!] Valid Asana API Key\\n\\n\")\n\tcolor.Yellow(\"[i] User Information\")\n\tcolor.Yellow(\"    Name: %s\", me.Data.Name)\n\tcolor.Yellow(\"    Email: %s\", me.Data.Email)\n\tcolor.Yellow(\"    Type: %s\\n\\n\", me.Data.Type)\n\n\tcolor.Green(\"[i] Permissions: Full Access\\n\\n\")\n\n\tcolor.Yellow(\"[i] Accessible Workspaces\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Workspace Name\"})\n\tfor _, workspace := range me.Data.Workspaces {\n\t\tt.AppendRow(table.Row{color.GreenString(workspace.Name)})\n\t}\n\tt.Render()\n}\n\nfunc allPermissions() []analyzers.Permission {\n\tpermissions := make([]analyzers.Permission, 0, len(PermissionStrings))\n\tfor _, permission := range PermissionStrings {\n\t\tpermissions = append(permissions, analyzers.Permission{\n\t\t\tValue: permission,\n\t\t})\n\t}\n\treturn permissions\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/asana/asana_test.go",
    "content": "package asana\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid Asana OAUTH Token\",\n\t\t\tkey:     testSecrets.MustGetField(\"ASANAOAUTH_TOKEN\"),\n\t\t\twant:    string(expectedOutput),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/asana/expected_output.json",
    "content": "{\"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}"
  },
  {
    "path": "pkg/analyzer/analyzers/asana/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage asana\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    AllocationsRead Permission = iota\n    AllocationsWrite Permission = iota\n    AttachmentsRead Permission = iota\n    AttachmentsWrite Permission = iota\n    AutditLogsRead Permission = iota\n    AutditLogsWrite Permission = iota\n    CustomFieldsRead Permission = iota\n    CustomFieldsWrite Permission = iota\n    CustomFieldSettingsRead Permission = iota\n    CustomFieldSettingsWrite Permission = iota\n    BatchApiRead Permission = iota\n    BatchApiWrite Permission = iota\n    EventsRead Permission = iota\n    EventsWrite Permission = iota\n    GoalsRead Permission = iota\n    GoalsWrite Permission = iota\n    JobsRead Permission = iota\n    JobsWrite Permission = iota\n    PortfoliosRead Permission = iota\n    PortfoliosWrite Permission = iota\n    ProjectsRead Permission = iota\n    ProjectsWrite Permission = iota\n    ProjectMembershipsRead Permission = iota\n    ProjectMembershipsWrite Permission = iota\n    SectionsRead Permission = iota\n    SectionsWrite Permission = iota\n    TagsRead Permission = iota\n    TagsWrite Permission = iota\n    TasksRead Permission = iota\n    TasksWrite Permission = iota\n    TeamsRead Permission = iota\n    TeamsWrite Permission = iota\n    UsersRead Permission = iota\n    UsersWrite Permission = iota\n    UserTaskListsRead Permission = iota\n    UserTaskListsWrite Permission = iota\n    MembershipsRead Permission = iota\n    MembershipsWrite Permission = iota\n    RulesRead Permission = iota\n    RulesWrite Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        AllocationsRead: \"allocations:read\",\n        AllocationsWrite: \"allocations:write\",\n        AttachmentsRead: \"attachments:read\",\n        AttachmentsWrite: \"attachments:write\",\n        AutditLogsRead: \"autdit_logs:read\",\n        AutditLogsWrite: \"autdit_logs:write\",\n        CustomFieldsRead: \"custom_fields:read\",\n        CustomFieldsWrite: \"custom_fields:write\",\n        CustomFieldSettingsRead: \"custom_field_settings:read\",\n        CustomFieldSettingsWrite: \"custom_field_settings:write\",\n        BatchApiRead: \"batch_api:read\",\n        BatchApiWrite: \"batch_api:write\",\n        EventsRead: \"events:read\",\n        EventsWrite: \"events:write\",\n        GoalsRead: \"goals:read\",\n        GoalsWrite: \"goals:write\",\n        JobsRead: \"jobs:read\",\n        JobsWrite: \"jobs:write\",\n        PortfoliosRead: \"portfolios:read\",\n        PortfoliosWrite: \"portfolios:write\",\n        ProjectsRead: \"projects:read\",\n        ProjectsWrite: \"projects:write\",\n        ProjectMembershipsRead: \"project_memberships:read\",\n        ProjectMembershipsWrite: \"project_memberships:write\",\n        SectionsRead: \"sections:read\",\n        SectionsWrite: \"sections:write\",\n        TagsRead: \"tags:read\",\n        TagsWrite: \"tags:write\",\n        TasksRead: \"tasks:read\",\n        TasksWrite: \"tasks:write\",\n        TeamsRead: \"teams:read\",\n        TeamsWrite: \"teams:write\",\n        UsersRead: \"users:read\",\n        UsersWrite: \"users:write\",\n        UserTaskListsRead: \"user_task_lists:read\",\n        UserTaskListsWrite: \"user_task_lists:write\",\n        MembershipsRead: \"memberships:read\",\n        MembershipsWrite: \"memberships:write\",\n        RulesRead: \"rules:read\",\n        RulesWrite: \"rules:write\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"allocations:read\": AllocationsRead,\n        \"allocations:write\": AllocationsWrite,\n        \"attachments:read\": AttachmentsRead,\n        \"attachments:write\": AttachmentsWrite,\n        \"autdit_logs:read\": AutditLogsRead,\n        \"autdit_logs:write\": AutditLogsWrite,\n        \"custom_fields:read\": CustomFieldsRead,\n        \"custom_fields:write\": CustomFieldsWrite,\n        \"custom_field_settings:read\": CustomFieldSettingsRead,\n        \"custom_field_settings:write\": CustomFieldSettingsWrite,\n        \"batch_api:read\": BatchApiRead,\n        \"batch_api:write\": BatchApiWrite,\n        \"events:read\": EventsRead,\n        \"events:write\": EventsWrite,\n        \"goals:read\": GoalsRead,\n        \"goals:write\": GoalsWrite,\n        \"jobs:read\": JobsRead,\n        \"jobs:write\": JobsWrite,\n        \"portfolios:read\": PortfoliosRead,\n        \"portfolios:write\": PortfoliosWrite,\n        \"projects:read\": ProjectsRead,\n        \"projects:write\": ProjectsWrite,\n        \"project_memberships:read\": ProjectMembershipsRead,\n        \"project_memberships:write\": ProjectMembershipsWrite,\n        \"sections:read\": SectionsRead,\n        \"sections:write\": SectionsWrite,\n        \"tags:read\": TagsRead,\n        \"tags:write\": TagsWrite,\n        \"tasks:read\": TasksRead,\n        \"tasks:write\": TasksWrite,\n        \"teams:read\": TeamsRead,\n        \"teams:write\": TeamsWrite,\n        \"users:read\": UsersRead,\n        \"users:write\": UsersWrite,\n        \"user_task_lists:read\": UserTaskListsRead,\n        \"user_task_lists:write\": UserTaskListsWrite,\n        \"memberships:read\": MembershipsRead,\n        \"memberships:write\": MembershipsWrite,\n        \"rules:read\": RulesRead,\n        \"rules:write\": RulesWrite,\n    }\n\n    PermissionIDs = map[Permission]int{\n        AllocationsRead: 1,\n        AllocationsWrite: 2,\n        AttachmentsRead: 3,\n        AttachmentsWrite: 4,\n        AutditLogsRead: 5,\n        AutditLogsWrite: 6,\n        CustomFieldsRead: 7,\n        CustomFieldsWrite: 8,\n        CustomFieldSettingsRead: 9,\n        CustomFieldSettingsWrite: 10,\n        BatchApiRead: 11,\n        BatchApiWrite: 12,\n        EventsRead: 13,\n        EventsWrite: 14,\n        GoalsRead: 15,\n        GoalsWrite: 16,\n        JobsRead: 17,\n        JobsWrite: 18,\n        PortfoliosRead: 19,\n        PortfoliosWrite: 20,\n        ProjectsRead: 21,\n        ProjectsWrite: 22,\n        ProjectMembershipsRead: 23,\n        ProjectMembershipsWrite: 24,\n        SectionsRead: 25,\n        SectionsWrite: 26,\n        TagsRead: 27,\n        TagsWrite: 28,\n        TasksRead: 29,\n        TasksWrite: 30,\n        TeamsRead: 31,\n        TeamsWrite: 32,\n        UsersRead: 33,\n        UsersWrite: 34,\n        UserTaskListsRead: 35,\n        UserTaskListsWrite: 36,\n        MembershipsRead: 37,\n        MembershipsWrite: 38,\n        RulesRead: 39,\n        RulesWrite: 40,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: AllocationsRead,\n        2: AllocationsWrite,\n        3: AttachmentsRead,\n        4: AttachmentsWrite,\n        5: AutditLogsRead,\n        6: AutditLogsWrite,\n        7: CustomFieldsRead,\n        8: CustomFieldsWrite,\n        9: CustomFieldSettingsRead,\n        10: CustomFieldSettingsWrite,\n        11: BatchApiRead,\n        12: BatchApiWrite,\n        13: EventsRead,\n        14: EventsWrite,\n        15: GoalsRead,\n        16: GoalsWrite,\n        17: JobsRead,\n        18: JobsWrite,\n        19: PortfoliosRead,\n        20: PortfoliosWrite,\n        21: ProjectsRead,\n        22: ProjectsWrite,\n        23: ProjectMembershipsRead,\n        24: ProjectMembershipsWrite,\n        25: SectionsRead,\n        26: SectionsWrite,\n        27: TagsRead,\n        28: TagsWrite,\n        29: TasksRead,\n        30: TasksWrite,\n        31: TeamsRead,\n        32: TeamsWrite,\n        33: UsersRead,\n        34: UsersWrite,\n        35: UserTaskListsRead,\n        36: UserTaskListsWrite,\n        37: MembershipsRead,\n        38: MembershipsWrite,\n        39: RulesRead,\n        40: RulesWrite,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/asana/permissions.yaml",
    "content": "permissions:\n  - allocations:read\n  - allocations:write\n  - attachments:read\n  - attachments:write\n  - autdit_logs:read\n  - autdit_logs:write\n  - custom_fields:read\n  - custom_fields:write\n  - custom_field_settings:read\n  - custom_field_settings:write\n  - batch_api:read\n  - batch_api:write\n  - events:read\n  - events:write\n  - goals:read\n  - goals:write\n  - jobs:read\n  - jobs:write\n  - portfolios:read\n  - portfolios:write\n  - projects:read\n  - projects:write\n  - project_memberships:read\n  - project_memberships:write\n  - sections:read\n  - sections:write\n  - tags:read\n  - tags:write\n  - tasks:read\n  - tasks:write\n  - teams:read\n  - teams:write\n  - users:read\n  - users:write\n  - user_task_lists:read\n  - user_task_lists:write\n  - memberships:read\n  - memberships:write\n  - rules:read\n  - rules:write\n"
  },
  {
    "path": "pkg/analyzer/analyzers/bitbucket/bitbucket.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go bitbucket\npackage bitbucket\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"net/http\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\nvar resource_name_map = map[string]string{\n\t\"repo_access_token\":      \"Repository\",\n\t\"project_access_token\":   \"Project\",\n\t\"workspace_access_token\": \"Workspace\",\n}\n\ntype SecretInfo struct {\n\tType        string\n\tOauthScopes []string\n\tRepos       []Repo\n}\n\ntype Repo struct {\n\tID       string `json:\"uuid\"`\n\tFullName string `json:\"full_name\"`\n\tRepoName string `json:\"name\"`\n\tProject  struct {\n\t\tID   string `json:\"uuid\"`\n\t\tName string `json:\"name\"`\n\t} `json:\"project\"`\n\tWorkspace struct {\n\t\tID   string `json:\"uuid\"`\n\t\tName string `json:\"name\"`\n\t} `json:\"workspace\"`\n\tIsPrivate bool `json:\"is_private\"`\n\tOwner     struct {\n\t\tID       string `json:\"uuid\"`\n\t\tUsername string `json:\"username\"`\n\t} `json:\"owner\"`\n\tRole string\n}\n\ntype RepoJSON struct {\n\tValues []Repo `json:\"values\"`\n}\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeBitbucket }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credInfo[\"key\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"key not found in credentialInfo\")\n\t}\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeBitbucket,\n\t}\n\n\t// add unbounded resources\n\tresult.UnboundedResources = make([]analyzers.Resource, len(info.Repos))\n\tfor i, repo := range info.Repos {\n\t\tresult.UnboundedResources[i] = analyzers.Resource{\n\t\t\tType:               \"repository\",\n\t\t\tName:               repo.FullName,\n\t\t\tFullyQualifiedName: \"bitbucket.com/repository/\" + repo.ID,\n\t\t\tParent: &analyzers.Resource{\n\t\t\t\tType:               \"project\",\n\t\t\t\tName:               repo.Project.Name,\n\t\t\t\tFullyQualifiedName: \"bitbucket.com/project/\" + repo.Project.ID,\n\t\t\t\tParent: &analyzers.Resource{\n\t\t\t\t\tType:               \"workspace\",\n\t\t\t\t\tName:               repo.Workspace.Name,\n\t\t\t\t\tFullyQualifiedName: \"bitbucket.com/workspace/\" + repo.Workspace.ID,\n\t\t\t\t},\n\t\t\t},\n\t\t\tMetadata: map[string]any{\n\t\t\t\t\"owner_id\":   repo.Owner.ID,\n\t\t\t\t\"owner\":      repo.Owner.Username,\n\t\t\t\t\"is_private\": repo.IsPrivate,\n\t\t\t\t\"role\":       repo.Role,\n\t\t\t},\n\t\t}\n\t}\n\n\tcredentialResource := &analyzers.Resource{\n\t\tType:               info.Type,\n\t\tName:               resource_name_map[info.Type],\n\t\tFullyQualifiedName: \"bitbucket.com/credential/\" + info.Type,\n\t\tMetadata: map[string]any{\n\t\t\t\"type\": credential_type_map[info.Type],\n\t\t},\n\t}\n\n\tfor _, scope := range info.OauthScopes {\n\t\tresult.Bindings = append(result.Bindings, analyzers.Binding{\n\t\t\tResource: *credentialResource,\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: scope,\n\t\t\t},\n\t\t})\n\t}\n\n\treturn &result\n}\n\nfunc getScopesAndType(cfg *config.Config, key string) (string, []string, error) {\n\t// client\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\t// request\n\treq, err := http.NewRequest(\"GET\", \"https://api.bitbucket.org/2.0/repositories\", nil)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\t// headers\n\treq.Header.Set(\"Authorization\", \"Bearer \"+key)\n\n\t// response\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\t// parse response headers\n\tcredentialType := resp.Header.Get(\"x-credential-type\")\n\toauthScopes := resp.Header.Get(\"x-oauth-scopes\")\n\n\tscopes := strings.Split(oauthScopes, \", \")\n\treturn credentialType, scopes, nil\n}\n\nfunc scopesToBitbucketScopes(scopes ...analyzers.Permission) []BitbucketScope {\n\tscopesSlice := []BitbucketScope{}\n\tfor _, scope := range scopes {\n\t\tscope := scope.Value\n\t\tmapping := oauth_scope_map[scope]\n\t\tfor _, impliedScope := range mapping.ImpliedScopes {\n\t\t\tscopesSlice = append(scopesSlice, oauth_scope_map[impliedScope])\n\t\t}\n\t\tscopesSlice = append(scopesSlice, oauth_scope_map[scope])\n\t}\n\n\t// sort scopes by category\n\tsort.Sort(ByCategoryAndName(scopesSlice))\n\treturn scopesSlice\n}\n\nfunc getRepositories(cfg *config.Config, key string, role string) (RepoJSON, error) {\n\tvar repos RepoJSON\n\n\t// client\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\t// request\n\treq, err := http.NewRequest(\"GET\", \"https://api.bitbucket.org/2.0/repositories\", nil)\n\tif err != nil {\n\t\treturn repos, err\n\t}\n\n\t// headers\n\treq.Header.Set(\"Authorization\", \"Bearer \"+key)\n\n\t// add query params\n\tq := req.URL.Query()\n\tq.Add(\"role\", role)\n\tq.Add(\"pagelen\", \"100\")\n\treq.URL.RawQuery = q.Encode()\n\n\t// response\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn repos, err\n\t}\n\tdefer resp.Body.Close()\n\n\t// parse response body\n\terr = json.NewDecoder(resp.Body).Decode(&repos)\n\tif err != nil {\n\t\treturn repos, err\n\t}\n\n\treturn repos, nil\n}\n\nfunc getAllRepos(cfg *config.Config, key string) ([]Repo, error) {\n\troles := []string{\"member\", \"contributor\", \"admin\", \"owner\"}\n\n\tvar allRepos = make(map[string]Repo, 0)\n\tfor _, role := range roles {\n\t\trepos, err := getRepositories(cfg, key, role)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// purposefully overwriting, so that get the most permissive role\n\t\tfor _, repo := range repos.Values {\n\t\t\trepo.Role = role\n\t\t\tallRepos[repo.FullName] = repo\n\t\t}\n\t}\n\trepoSlice := make([]Repo, 0, len(allRepos))\n\tfor _, repo := range allRepos {\n\t\trepoSlice = append(repoSlice, repo)\n\t}\n\treturn repoSlice, nil\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\tcredentialType, oauthScopes, err := getScopesAndType(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// get all repos available to user\n\t// ToDo: pagination\n\trepos, err := getAllRepos(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &SecretInfo{\n\t\tType:        credentialType,\n\t\tOauthScopes: oauthScopes,\n\t\tRepos:       repos,\n\t}, nil\n}\n\nfunc convertScopeToAnalyzerPermissions(scopes []string) []analyzers.Permission {\n\tpermissions := make([]analyzers.Permission, 0, len(scopes))\n\tfor _, scope := range scopes {\n\t\tpermissions = append(permissions, analyzers.Permission{Value: scope})\n\t}\n\treturn permissions\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error: %s\", err.Error())\n\t\treturn\n\t}\n\tprintScopes(info.Type, convertScopeToAnalyzerPermissions(info.OauthScopes))\n\tprintAccessibleRepositories(info.Repos)\n}\n\nfunc printScopes(credentialType string, scopes []analyzers.Permission) {\n\tif credentialType == \"\" {\n\t\tcolor.Red(\"[x] Invalid Bitbucket access token.\")\n\t\treturn\n\t}\n\tcolor.Green(\"[!] Valid Bitbucket access token.\\n\\n\")\n\tcolor.Green(\"[i] Credential Type: %s\\n\\n\", credential_type_map[credentialType])\n\n\tcolor.Yellow(\"[i] Access Token Scopes:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Category\", \"Permission\"})\n\n\tcurrentCategory := \"\"\n\tfor _, scope := range scopesToBitbucketScopes(scopes...) {\n\t\tif currentCategory != scope.Category {\n\t\t\tcurrentCategory = scope.Category\n\t\t\tt.AppendRow([]any{scope.Category, \"\"})\n\t\t}\n\t\tt.AppendRow([]any{\"\", color.GreenString(scope.Name)})\n\t}\n\n\tt.Render()\n\n}\n\nfunc printAccessibleRepositories(repos []Repo) {\n\tcolor.Yellow(\"\\n[i] Accessible Repositories:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Repository\", \"Project\", \"Workspace\", \"Owner\", \"Is Private\", \"This User's Role\"})\n\n\tfor _, repo := range repos {\n\t\tprivate := \"\"\n\t\tif repo.IsPrivate {\n\t\t\tprivate = color.GreenString(\"Yes\")\n\t\t} else {\n\t\t\tprivate = color.RedString(\"No\")\n\t\t}\n\t\tt.AppendRow([]any{\n\t\t\tcolor.GreenString(repo.RepoName),\n\t\t\tcolor.GreenString(repo.Project.Name),\n\t\t\tcolor.GreenString(repo.Workspace.Name),\n\t\t\tcolor.GreenString(repo.Owner.Username),\n\t\t\tprivate,\n\t\t\tcolor.GreenString(repo.Role),\n\t\t})\n\t}\n\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/bitbucket/bitbucket_test.go",
    "content": "package bitbucket\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tsid     string\n\t\tkey     string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid Bitbucket key\",\n\t\t\tkey:     testSecrets.MustGetField(\"BITBUCKET_ANALYZE_TOKEN\"),\n\t\t\twant:    string(expectedOutput),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key, \"sid\": tt.sid})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = \\n%s\", gotIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/bitbucket/expected_output.json",
    "content": "{\n  \"AnalyzerType\": 3,\n  \"Bindings\": [\n   {\n    \"Resource\": {\n     \"Name\": \"Repository\",\n     \"FullyQualifiedName\": \"bitbucket.com/credential/repo_access_token\",\n     \"Type\": \"repo_access_token\",\n     \"Metadata\": {\n      \"type\": \"Repository Access Token (Can access 1 repository)\"\n     },\n     \"Parent\": null\n    },\n    \"Permission\": {\n     \"Value\": \"pipeline\",\n     \"Parent\": null\n    }\n   },\n   {\n    \"Resource\": {\n     \"Name\": \"Repository\",\n     \"FullyQualifiedName\": \"bitbucket.com/credential/repo_access_token\",\n     \"Type\": \"repo_access_token\",\n     \"Metadata\": {\n      \"type\": \"Repository Access Token (Can access 1 repository)\"\n     },\n     \"Parent\": null\n    },\n    \"Permission\": {\n     \"Value\": \"pullrequest\",\n     \"Parent\": null\n    }\n   },\n   {\n    \"Resource\": {\n     \"Name\": \"Repository\",\n     \"FullyQualifiedName\": \"bitbucket.com/credential/repo_access_token\",\n     \"Type\": \"repo_access_token\",\n     \"Metadata\": {\n      \"type\": \"Repository Access Token (Can access 1 repository)\"\n     },\n     \"Parent\": null\n    },\n    \"Permission\": {\n     \"Value\": \"runner\",\n     \"Parent\": null\n    }\n   },\n   {\n    \"Resource\": {\n     \"Name\": \"Repository\",\n     \"FullyQualifiedName\": \"bitbucket.com/credential/repo_access_token\",\n     \"Type\": \"repo_access_token\",\n     \"Metadata\": {\n      \"type\": \"Repository Access Token (Can access 1 repository)\"\n     },\n     \"Parent\": null\n    },\n    \"Permission\": {\n     \"Value\": \"webhook\",\n     \"Parent\": null\n    }\n   }\n  ],\n  \"UnboundedResources\": [\n   {\n    \"Name\": \"basit-trufflesec/repo1\",\n    \"FullyQualifiedName\": \"bitbucket.com/repository/{8961ef70-000c-47ca-9348-5f9ecee875d6}\",\n    \"Type\": \"repository\",\n    \"Metadata\": {\n     \"is_private\": true,\n     \"owner\": \"basit-trufflesec\",\n     \"owner_id\": \"{521b49b6-7709-484a-8aa8-ecc3a6da08eb}\",\n     \"role\": \"admin\"\n    },\n    \"Parent\": {\n     \"Name\": \"repo-analyzer\",\n     \"FullyQualifiedName\": \"bitbucket.com/project/{8a693e10-087f-41fc-ba67-2d1414ab1c86}\",\n     \"Type\": \"project\",\n     \"Metadata\": null,\n     \"Parent\": {\n      \"Name\": \"basit-trufflesec\",\n      \"FullyQualifiedName\": \"bitbucket.com/workspace/{521b49b6-7709-484a-8aa8-ecc3a6da08eb}\",\n      \"Type\": \"workspace\",\n      \"Metadata\": null,\n      \"Parent\": null\n     }\n    }\n   }\n  ],\n  \"Metadata\": null\n }"
  },
  {
    "path": "pkg/analyzer/analyzers/bitbucket/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage bitbucket\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    Project Permission = iota\n    ProjectAdmin Permission = iota\n    Repository Permission = iota\n    RepositoryWrite Permission = iota\n    RepositoryAdmin Permission = iota\n    RepositoryDelete Permission = iota\n    Pullrequest Permission = iota\n    PullrequestWrite Permission = iota\n    Webhook Permission = iota\n    Account Permission = iota\n    Pipeline Permission = iota\n    PipelineWrite Permission = iota\n    PipelineVariable Permission = iota\n    Runner Permission = iota\n    RunnerWrite Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        Project: \"project\",\n        ProjectAdmin: \"project:admin\",\n        Repository: \"repository\",\n        RepositoryWrite: \"repository:write\",\n        RepositoryAdmin: \"repository:admin\",\n        RepositoryDelete: \"repository:delete\",\n        Pullrequest: \"pullrequest\",\n        PullrequestWrite: \"pullrequest:write\",\n        Webhook: \"webhook\",\n        Account: \"account\",\n        Pipeline: \"pipeline\",\n        PipelineWrite: \"pipeline:write\",\n        PipelineVariable: \"pipeline:variable\",\n        Runner: \"runner\",\n        RunnerWrite: \"runner:write\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"project\": Project,\n        \"project:admin\": ProjectAdmin,\n        \"repository\": Repository,\n        \"repository:write\": RepositoryWrite,\n        \"repository:admin\": RepositoryAdmin,\n        \"repository:delete\": RepositoryDelete,\n        \"pullrequest\": Pullrequest,\n        \"pullrequest:write\": PullrequestWrite,\n        \"webhook\": Webhook,\n        \"account\": Account,\n        \"pipeline\": Pipeline,\n        \"pipeline:write\": PipelineWrite,\n        \"pipeline:variable\": PipelineVariable,\n        \"runner\": Runner,\n        \"runner:write\": RunnerWrite,\n    }\n\n    PermissionIDs = map[Permission]int{\n        Project: 1,\n        ProjectAdmin: 2,\n        Repository: 3,\n        RepositoryWrite: 4,\n        RepositoryAdmin: 5,\n        RepositoryDelete: 6,\n        Pullrequest: 7,\n        PullrequestWrite: 8,\n        Webhook: 9,\n        Account: 10,\n        Pipeline: 11,\n        PipelineWrite: 12,\n        PipelineVariable: 13,\n        Runner: 14,\n        RunnerWrite: 15,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: Project,\n        2: ProjectAdmin,\n        3: Repository,\n        4: RepositoryWrite,\n        5: RepositoryAdmin,\n        6: RepositoryDelete,\n        7: Pullrequest,\n        8: PullrequestWrite,\n        9: Webhook,\n        10: Account,\n        11: Pipeline,\n        12: PipelineWrite,\n        13: PipelineVariable,\n        14: Runner,\n        15: RunnerWrite,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/bitbucket/permissions.yaml",
    "content": "permissions:\n- project\n- project:admin\n- repository\n- repository:write\n- repository:admin\n- repository:delete\n- pullrequest\n- pullrequest:write\n- webhook\n- account\n- pipeline\n- pipeline:write\n- pipeline:variable\n- runner\n- runner:write\n"
  },
  {
    "path": "pkg/analyzer/analyzers/bitbucket/scopes.go",
    "content": "package bitbucket\n\nvar credential_type_map = map[string]string{\n\t\"repo_access_token\":      \"Repository Access Token (Can access 1 repository)\",\n\t\"project_access_token\":   \"Project Access Token (Can access all repos in 1 project)\",\n\t\"workspace_access_token\": \"Workspace Access Token (Can access all projects and repos in 1 workspace)\",\n}\n\ntype BitbucketScope struct {\n\tName          string   `json:\"name\"`\n\tCategory      string   `json:\"category\"`\n\tImpliedScopes []string `json:\"implied_scopes\"`\n}\n\ntype ByCategoryAndName []BitbucketScope\n\nfunc (a ByCategoryAndName) Len() int      { return len(a) }\nfunc (a ByCategoryAndName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }\nfunc (a ByCategoryAndName) Less(i, j int) bool {\n\tcategoryOrder := map[string]int{\n\t\t\"Account\":       0,\n\t\t\"Projects\":      1,\n\t\t\"Repositories\":  2,\n\t\t\"Pull Requests\": 3,\n\t\t\"Webhooks\":      4,\n\t\t\"Pipelines\":     5,\n\t\t\"Runners\":       6,\n\t}\n\tnameOrder := map[string]int{\n\t\t\"Read\":           0,\n\t\t\"Write\":          1,\n\t\t\"Admin\":          2,\n\t\t\"Delete\":         3,\n\t\t\"Edit variables\": 4,\n\t\t\"Read and write\": 5,\n\t}\n\n\tif categoryOrder[a[i].Category] != categoryOrder[a[j].Category] {\n\t\treturn categoryOrder[a[i].Category] < categoryOrder[a[j].Category]\n\t}\n\treturn nameOrder[a[i].Name] < nameOrder[a[j].Name]\n}\n\nvar oauth_scope_map = map[string]BitbucketScope{\n\t\"repository\": {\n\t\tName:     \"Read\",\n\t\tCategory: \"Repositories\",\n\t},\n\t\"repository:write\": {\n\t\tName:          \"Write\",\n\t\tCategory:      \"Repositories\",\n\t\tImpliedScopes: []string{\"repository\"},\n\t},\n\t\"repository:admin\": {\n\t\tName:     \"Admin\",\n\t\tCategory: \"Repositories\",\n\t},\n\t\"repository:delete\": {\n\t\tName:     \"Delete\",\n\t\tCategory: \"Repositories\",\n\t},\n\t\"pullrequest\": {\n\t\tName:          \"Read\",\n\t\tCategory:      \"Pull Requests\",\n\t\tImpliedScopes: []string{\"repository\"},\n\t},\n\t\"pullrequest:write\": {\n\t\tName:          \"Write\",\n\t\tCategory:      \"Pull Requests\",\n\t\tImpliedScopes: []string{\"pullrequest\", \"repository\", \"repository:write\"},\n\t},\n\t\"webhook\": {\n\t\tName:     \"Read and write\",\n\t\tCategory: \"Webhooks\",\n\t},\n\t\"pipeline\": {\n\t\tName:     \"Read\",\n\t\tCategory: \"Pipelines\",\n\t},\n\t\"pipeline:write\": {\n\t\tName:          \"Write\",\n\t\tCategory:      \"Pipelines\",\n\t\tImpliedScopes: []string{\"pipeline\"},\n\t},\n\t\"pipeline:variable\": {\n\t\tName:          \"Edit variables\",\n\t\tCategory:      \"Pipelines\",\n\t\tImpliedScopes: []string{\"pipeline\", \"pipeline:write\"},\n\t},\n\t\"runner\": {\n\t\tName:     \"Read\",\n\t\tCategory: \"Runners\",\n\t},\n\t\"runner:write\": {\n\t\tName:          \"Write\",\n\t\tCategory:      \"Runners\",\n\t\tImpliedScopes: []string{\"runner\"},\n\t},\n\t\"project\": {\n\t\tName:          \"Read\",\n\t\tCategory:      \"Projects\",\n\t\tImpliedScopes: []string{\"repository\"},\n\t},\n\t\"project:admin\": {\n\t\tName:     \"Admin\",\n\t\tCategory: \"Projects\",\n\t},\n\t\"account\": {\n\t\tName:     \"Read\",\n\t\tCategory: \"Account\",\n\t},\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/client.go",
    "content": "package analyzers\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"golang.org/x/time/rate\"\n)\n\ntype AnalyzeClient struct {\n\thttp.Client\n\tLoggingEnabled bool\n\tLogFile        string\n}\n\nfunc CreateLogFileName(baseName string) string {\n\t// Get the current time\n\tcurrentTime := time.Now()\n\n\t// Format the time as \"2024_06_30_07_15_30\"\n\ttimeString := currentTime.Format(\"2006_01_02_15_04_05\")\n\n\t// Create the log file name\n\tlogFileName := fmt.Sprintf(\"%s_%s.log\", timeString, baseName)\n\treturn logFileName\n}\n\ntype ClientOption func(*http.Client)\n\n// This returns a client that is restricted and filters out unsafe requests returning a success status code.\nfunc NewAnalyzeClient(cfg *config.Config, opts ...func(*http.Client)) *http.Client {\n\tclient := &http.Client{\n\t\tTransport: AnalyzerRoundTripper{parent: http.DefaultTransport},\n\t}\n\tif cfg != nil && cfg.LoggingEnabled {\n\t\tclient = &http.Client{\n\t\t\tTransport: LoggingRoundTripper{\n\t\t\t\tparent:  client.Transport,\n\t\t\t\tlogFile: cfg.LogFile,\n\t\t\t},\n\t\t}\n\t}\n\tfor _, opt := range opts {\n\t\topt(client)\n\t}\n\treturn client\n}\n\n// This returns a client that is unrestricted and does not filter out unsafe requests returning a success status code.\nfunc NewAnalyzeClientUnrestricted(cfg *config.Config, opts ...ClientOption) *http.Client {\n\tclient := &http.Client{\n\t\tTransport: http.DefaultTransport,\n\t}\n\tif cfg != nil && cfg.LoggingEnabled {\n\t\tclient = &http.Client{\n\t\t\tTransport: LoggingRoundTripper{\n\t\t\t\tparent:  client.Transport,\n\t\t\t\tlogFile: cfg.LogFile,\n\t\t\t},\n\t\t}\n\t}\n\tfor _, opt := range opts {\n\t\topt(client)\n\t}\n\treturn client\n}\n\ntype LoggingRoundTripper struct {\n\tparent http.RoundTripper\n\t// TODO: io.Writer\n\tlogFile string\n}\n\nfunc (r LoggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {\n\tstartTime := time.Now()\n\n\tresp, parentErr := r.parent.RoundTrip(req)\n\tif resp == nil {\n\t\treturn resp, parentErr\n\t}\n\n\t// TODO: JSON\n\tvar logEntry string\n\tif parentErr != nil {\n\t\tlogEntry = fmt.Sprintf(\"Date: %s, Method: %s, Path: %s, Status: %d, Error: %s\\n\",\n\t\t\tstartTime.Format(time.RFC3339),\n\t\t\treq.Method,\n\t\t\treq.URL.Path,\n\t\t\tresp.StatusCode,\n\t\t\tparentErr.Error(),\n\t\t)\n\t} else {\n\t\tlogEntry = fmt.Sprintf(\"Date: %s, Method: %s, Path: %s, Status: %d\\n\",\n\t\t\tstartTime.Format(time.RFC3339),\n\t\t\treq.Method,\n\t\t\treq.URL.Path,\n\t\t\tresp.StatusCode,\n\t\t)\n\t}\n\n\t// Open log file in append mode.\n\tfile, err := os.OpenFile(r.logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)\n\tif err != nil {\n\t\treturn resp, fmt.Errorf(\"failed to open log file: %w\", err)\n\t}\n\tdefer file.Close()\n\n\t// Write log entry to file.\n\tif _, err := file.WriteString(logEntry); err != nil {\n\t\treturn resp, fmt.Errorf(\"failed to write log entry to file: %w\", err)\n\t}\n\n\treturn resp, parentErr\n}\n\ntype AnalyzerRoundTripper struct {\n\tparent http.RoundTripper\n}\n\nfunc (r AnalyzerRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {\n\tresp, err := r.parent.RoundTrip(req)\n\tif err != nil || IsMethodSafe(req.Method) {\n\t\treturn resp, err\n\t}\n\t// Check that unsafe methods did NOT return a valid status code.\n\tif resp.StatusCode >= 200 && resp.StatusCode < 300 {\n\t\treturn resp, fmt.Errorf(\"non-safe request returned success\")\n\t}\n\treturn resp, nil\n}\n\n// IsMethodSafe is a helper method to check whether the HTTP method is safe according to MDN Web Docs.\n// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods#safe_idempotent_and_cacheable_request_methods\nfunc IsMethodSafe(method string) bool {\n\tswitch strings.ToUpper(method) {\n\tcase http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\ntype RateLimitRoundTripper struct {\n\tparent  http.RoundTripper\n\tlimiter *rate.Limiter\n}\n\nfunc (rt RateLimitRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {\n\tif rt.parent == nil {\n\t\trt.parent = http.DefaultTransport\n\t}\n\tif rt.limiter != nil {\n\t\tif err := rt.limiter.Wait(req.Context()); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn rt.parent.RoundTrip(req)\n}\n\nfunc WithRateLimiter(l *rate.Limiter) ClientOption {\n\treturn func(c *http.Client) {\n\t\tc.Transport = RateLimitRoundTripper{\n\t\t\tparent:  c.Transport,\n\t\t\tlimiter: l,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/client_test.go",
    "content": "package analyzers\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\nfunc TestAnalyzerClientUnsafeSuccess(t *testing.T) {\n\ttestCases := []struct {\n\t\tname           string\n\t\tmethod         string\n\t\texpectedStatus int\n\t\texpectedError  bool\n\t}{\n\t\t{\n\t\t\tname:           \"Safe method (GET)\",\n\t\t\tmethod:         http.MethodGet,\n\t\t\texpectedStatus: http.StatusOK,\n\t\t\texpectedError:  false,\n\t\t},\n\t\t{\n\t\t\tname:           \"Safe method (HEAD)\",\n\t\t\tmethod:         http.MethodHead,\n\t\t\texpectedStatus: http.StatusOK,\n\t\t\texpectedError:  false,\n\t\t},\n\t\t{\n\t\t\tname:           \"Safe method (OPTIONS)\",\n\t\t\tmethod:         http.MethodOptions,\n\t\t\texpectedStatus: http.StatusOK,\n\t\t\texpectedError:  false,\n\t\t},\n\t\t{\n\t\t\tname:           \"Safe method (TRACE)\",\n\t\t\tmethod:         http.MethodTrace,\n\t\t\texpectedStatus: http.StatusOK,\n\t\t\texpectedError:  false,\n\t\t},\n\t\t{\n\t\t\tname:           \"Unsafe method (POST) with success status\",\n\t\t\tmethod:         http.MethodPost,\n\t\t\texpectedStatus: http.StatusOK,\n\t\t\texpectedError:  true,\n\t\t},\n\t\t{\n\t\t\tname:           \"Unsafe method (PUT) with success status\",\n\t\t\tmethod:         http.MethodPut,\n\t\t\texpectedStatus: http.StatusOK,\n\t\t\texpectedError:  true,\n\t\t},\n\t\t{\n\t\t\tname:           \"Unsafe method (DELETE) with success status\",\n\t\t\tmethod:         http.MethodDelete,\n\t\t\texpectedStatus: http.StatusOK,\n\t\t\texpectedError:  true,\n\t\t},\n\t\t{\n\t\t\tname:           \"Unsafe method (POST) with error status\",\n\t\t\tmethod:         http.MethodPost,\n\t\t\texpectedStatus: http.StatusInternalServerError,\n\t\t\texpectedError:  false,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t// Create a test server that returns the expected status code\n\t\t\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tw.WriteHeader(tc.expectedStatus)\n\t\t\t}))\n\t\t\tdefer server.Close()\n\n\t\t\t// Create a test request\n\t\t\treq, err := http.NewRequest(tc.method, server.URL, nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to create test request: %v\", err)\n\t\t\t}\n\n\t\t\t// Create the AnalyzerRoundTripper with a test client\n\t\t\tclient := NewAnalyzeClient(nil)\n\n\t\t\t// Perform the request\n\t\t\tresp, err := client.Do(req)\n\t\t\tif resp != nil {\n\t\t\t\t_ = resp.Body.Close()\n\t\t\t}\n\n\t\t\t// Check the error\n\t\t\tif err != nil && !tc.expectedError {\n\t\t\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t\t\t} else if err == nil && tc.expectedError {\n\t\t\t\tt.Errorf(\"Expected error, but got nil\")\n\t\t\t}\n\n\t\t\t// Check the response status code\n\t\t\tif resp != nil && resp.StatusCode != tc.expectedStatus {\n\t\t\t\tt.Errorf(\"Expected status code: %d, but got: %d\", tc.expectedStatus, resp.StatusCode)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/databricks/databricks.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go databricks\npackage databricks\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (a Analyzer) Type() analyzers.AnalyzerType {\n\treturn analyzers.AnalyzerTypeDataBricks\n}\n\nfunc (a Analyzer) Analyze(ctx context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\ttoken, exist := credInfo[\"token\"]\n\tif !exist {\n\t\treturn nil, fmt.Errorf(\"key not found in credential info\")\n\t}\n\n\tdomain, exist := credInfo[\"domain\"]\n\tif !exist {\n\t\treturn nil, fmt.Errorf(\"domain not found in credential info\")\n\t}\n\n\tinfo, err := AnalyzePermissions(ctx, a.Cfg, domain, token)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, domain, token string) {\n\tctx := context.Background()\n\n\tinfo, err := AnalyzePermissions(ctx, cfg, domain, token)\n\tif err != nil {\n\t\t// just print the error in cli and continue as a partial success\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t}\n\n\tif info == nil {\n\t\tcolor.Red(\"[x] Error : %s\", \"No information found\")\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid DataBricks Access Token\\n\\n\")\n\n\tprintUserInfo(info.UserInfo)\n\tprintTokenInfo(info.Tokens)\n\tprintPermissions(info.TokenPermissionLevels)\n\n\tif len(info.Resources) > 0 {\n\t\tprintResources(info.Resources)\n\t}\n\n\tcolor.Yellow(\"\\n[i] Expires: %s\", \"N/A (Refer to Token Information Table)\")\n}\n\nfunc AnalyzePermissions(ctx context.Context, cfg *config.Config, domain, token string) (*SecretInfo, error) {\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\tvar secretInfo = &SecretInfo{}\n\n\tif err := captureUserInfo(ctx, client, domain, token, secretInfo); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := captureTokensInfo(ctx, client, domain, token, secretInfo); err != nil {\n\t\treturn secretInfo, err\n\t}\n\n\tif err := captureTokenPermissions(ctx, client, domain, token, secretInfo); err != nil {\n\t\treturn secretInfo, err\n\t}\n\n\t// capture resources\n\tif err := captureDataBricksResources(ctx, client, domain, token, secretInfo); err != nil {\n\t\treturn secretInfo, err\n\t}\n\n\treturn secretInfo, nil\n}\n\n// secretInfoToAnalyzerResult translate secret info to Analyzer Result\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeDataBricks,\n\t\tMetadata:     map[string]any{},\n\t\tBindings:     make([]analyzers.Binding, 0),\n\t}\n\n\t// extract information from resource to create bindings and append to result bindings\n\tfor _, resource := range info.Resources {\n\t\tbinding := analyzers.Binding{\n\t\t\tResource: analyzers.Resource{\n\t\t\t\tName:               resource.Name,\n\t\t\t\tFullyQualifiedName: fmt.Sprintf(\"databricks/%s/%s\", resource.Type, resource.ID), // e.g: netlify/site/123\n\t\t\t\tType:               resource.Type,\n\t\t\t\tMetadata:           map[string]any{}, // to avoid panic\n\t\t\t},\n\t\t}\n\n\t\tfor key, value := range resource.Metadata {\n\t\t\tbinding.Resource.Metadata[key] = value\n\t\t}\n\n\t\t// for each permission add a binding to resource\n\t\tfor _, perm := range info.TokenPermissionLevels {\n\t\t\tbinding.Permission = analyzers.Permission{\n\t\t\t\tValue: perm,\n\t\t\t}\n\n\t\t\tresult.Bindings = append(result.Bindings, binding)\n\t\t}\n\t}\n\n\treturn &result\n}\n\n// cli print functions\nfunc printUserInfo(user User) {\n\tcolor.Yellow(\"[i] User Information:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"ID\", \"UserName\", \"Primary Email\"})\n\tt.AppendRow(table.Row{color.GreenString(user.ID), color.GreenString(user.UserName), color.GreenString(user.PrimaryEmail)})\n\n\tt.Render()\n}\n\nfunc printTokenInfo(tokens []Token) {\n\tcolor.Yellow(\"[i] Tokens Information:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Name\", \"Expiry Time\", \"Created By\", \"Last Used At\"})\n\tfor _, token := range tokens {\n\t\tt.AppendRow(table.Row{color.GreenString(token.Name),\n\t\t\tcolor.GreenString(token.ExpiryTime), color.GreenString(token.CreatedBy), color.GreenString(token.LastUsedDay)})\n\t}\n\tt.Render()\n}\n\nfunc printPermissions(permissions []string) {\n\tcolor.Yellow(\"[i] Token Permission Levels:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Permission Level\"})\n\tfor _, permission := range permissions {\n\t\tt.AppendRow(table.Row{color.GreenString(permission)})\n\t}\n\tt.Render()\n}\n\nfunc printResources(resources []DataBricksResource) {\n\tcolor.Yellow(\"[i] Resources:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Name\", \"Type\"})\n\tfor _, resource := range resources {\n\t\tt.AppendRow(table.Row{color.GreenString(resource.Name), color.GreenString(resource.Type)})\n\t}\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/databricks/databricks_test.go",
    "content": "package databricks\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed result_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"analyzers1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttoken := testSecrets.MustGetField(\"DATABRICKS_TOKEN\")\n\tdomain := testSecrets.MustGetField(\"DATABRICKS_DOMAIN\")\n\n\ttests := []struct {\n\t\tname    string\n\t\tdomain  string\n\t\ttoken   string\n\t\twant    []byte // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid databricks credentials\",\n\t\t\tdomain:  domain,\n\t\t\ttoken:   token,\n\t\t\twant:    expectedOutput,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"token\": tt.token, \"domain\": tt.domain})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/databricks/models.go",
    "content": "package databricks\n\ntype ResourceType string\n\nfunc (r ResourceType) String() string {\n\treturn string(r)\n}\n\nconst (\n\tCurrentUser      ResourceType = \"User\"\n\tTokensInfo       ResourceType = \"Token\"\n\tTokenPermissions ResourceType = \"Token Permission\"\n\tRepositories     ResourceType = \"Repository\"\n\tGitCredentials   ResourceType = \"Git Credential\"\n\tJobs             ResourceType = \"Job\"\n\tClusters         ResourceType = \"Cluster\"\n\tGroups           ResourceType = \"Group\"\n\tUsers            ResourceType = \"Member\"\n)\n\ntype SecretInfo struct {\n\tUserInfo              User\n\tTokenPermissionLevels []string\n\tTokens                []Token\n\tResources             []DataBricksResource\n}\n\ntype User struct {\n\tID           string\n\tUserName     string\n\tPrimaryEmail string\n}\n\ntype Token struct {\n\tID          string\n\tName        string\n\tExpiryTime  string\n\tCreatedBy   string\n\tLastUsedDay string\n}\n\ntype DataBricksResource struct {\n\tID       string\n\tName     string\n\tType     string\n\tMetadata map[string]string\n}\n\n// API response models\n\ntype CurrentUserInfo struct {\n\tID       string `json:\"id\"`\n\tUserName string `json:\"userName\"`\n\tEmails   []struct {\n\t\tDisplay string `json:\"display\"`\n\t\tValue   string `json:\"value\"`\n\t\tPrimary bool   `json:\"primary\"`\n\t} `json:\"emails\"`\n}\n\ntype Tokens struct {\n\tTokensInfo []struct {\n\t\tID          string `json:\"token_id\"`\n\t\tName        string `json:\"comment\"`\n\t\tExpiryTime  int    `json:\"expiry_time\"`\n\t\tLastUsedDay int    `json:\"last_used_day\"`\n\t\tCreatedBy   string `json:\"created_by_username\"`\n\t} `json:\"token_infos\"`\n}\n\ntype Permissions struct {\n\tPermissionLevels []struct {\n\t\tDescription     string `json:\"description\"`\n\t\tPermissionLevel string `json:\"permission_level\"`\n\t} `json:\"permission_levels\"`\n}\n\ntype ReposResponse struct {\n\tRepositories []struct {\n\t\tID       string `json:\"id\"`\n\t\tPath     string `json:\"path\"`\n\t\tProvider string `json:\"provider\"`\n\t\tURL      string `json:\"url\"`\n\t} `json:\"repos\"`\n}\n\ntype GitCreds struct {\n\tCredentials []struct {\n\t\tID       string `json:\"credentials_id\"`\n\t\tUserName string `json:\"git_username\"`\n\t\tProvider string `json:\"git_provider\"`\n\t} `json:\"credentials\"`\n}\n\ntype JobsResponse struct {\n\tJobs []struct {\n\t\tID          string `json:\"job_id\"`\n\t\tName        string `json:\"name\"`\n\t\tDescription string `json:\"description\"`\n\t} `json:\"jobs\"`\n}\n\ntype ClustersResponse struct {\n\tClusters []struct {\n\t\tID        string `json:\"cluster_id\"`\n\t\tName      string `json:\"cluster_name\"`\n\t\tCreatedBy string `json:\"creator_user_name\"`\n\t} `json:\"clusters\"`\n}\n\ntype GroupsResponse struct {\n\tResources []struct {\n\t\tID   string `json:\"id\"`\n\t\tName string `json:\"displayName\"`\n\t\t// TODO: capture members if needed\n\t} `json:\"Resources\"`\n}\n\ntype UsersResponse struct {\n\tResources []struct {\n\t\tID       string `json:\"id\"`\n\t\tUserName string `json:\"userName\"`\n\t\tActive   bool   `json:\"active\"`\n\t} `json:\"Resources\"`\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/databricks/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage databricks\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    CanManage Permission = iota\n    CanUse Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        CanManage: \"CAN_MANAGE\",\n        CanUse: \"CAN_USE\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"CAN_MANAGE\": CanManage,\n        \"CAN_USE\": CanUse,\n    }\n\n    PermissionIDs = map[Permission]int{\n        CanManage: 1,\n        CanUse: 2,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: CanManage,\n        2: CanUse,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/databricks/permissions.yaml",
    "content": "permissions:\n  - CAN_MANAGE\n  - CAN_USE\n"
  },
  {
    "path": "pkg/analyzer/analyzers/databricks/requests.go",
    "content": "package databricks\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar (\n\t// ErrUnauthorized is returned when the Databricks API answers with HTTP-401.\n\terrUnAuthorized = errors.New(\"invalid/expired personal access token\")\n\n\tapiEndpoints = map[ResourceType]string{\n\t\tCurrentUser:      \"/api/2.0/preview/scim/v2/Me\",\n\t\tTokensInfo:       \"/api/2.0/token-management/tokens\",\n\t\tTokenPermissions: \"/api/2.0/permissions/authorization/tokens/permissionLevels\",\n\t\tRepositories:     \"/api/2.0/repos\",\n\t\tGitCredentials:   \"/api/2.0/git-credentials\",\n\t\tJobs:             \"/api/2.2/jobs/list\",\n\t\tClusters:         \"/api/2.1/clusters/list\",\n\t\tGroups:           \"/api/2.0/preview/scim/v2/Groups\",\n\t\tUsers:            \"/api/2.0/preview/scim/v2/Users\",\n\t\t/*\n\t\t\tTODO:\n\t\t\t\t- https://docs.databricks.com/api/gcp/workspace/workspace/list (list content inside path)\n\t\t\t\t- http://docs.databricks.com/api/gcp/workspace/libraries/allclusterlibrarystatuses (list cluster statuses)\n\t\t*/\n\t}\n)\n\n// doAndDecode performs an authenticated GET request against the constructed\n// Databricks URL and JSON-decodes the response into the supplied result.\n//\n// The generic type parameter T allows the caller to decide which concrete\n// struct the response should be unmarshalled into:\nfunc doAndDecode[T any](ctx context.Context, client *http.Client, domain string, rt ResourceType, token string, out *T) error {\n\tu := url.URL{\n\t\tScheme: \"https\",\n\t\tHost:   domain,\n\t\tPath:   apiEndpoints[rt],\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), http.NoBody)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"building request: %w\", err)\n\t}\n\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\t// Execute request and read / decode body. We stream directly into the\n\t// decoder instead of loading the whole response into memory first.\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"performing request: %w\", err)\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tif err := json.NewDecoder(resp.Body).Decode(out); err != nil {\n\t\t\treturn fmt.Errorf(\"decoding response: %w\", err)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn errUnAuthorized\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code %d for API %s\", resp.StatusCode, apiEndpoints[rt])\n\t}\n}\n\nfunc captureDataBricksResources(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {\n\tif err := captureRepos(ctx, client, domain, token, secretInfo); err != nil {\n\t\treturn err\n\t}\n\n\tif err := captureGitCreds(ctx, client, domain, token, secretInfo); err != nil {\n\t\treturn err\n\t}\n\n\tif err := captureJobs(ctx, client, domain, token, secretInfo); err != nil {\n\t\treturn err\n\t}\n\n\tif err := captureClusters(ctx, client, domain, token, secretInfo); err != nil {\n\t\treturn err\n\t}\n\n\tif err := captureGroups(ctx, client, domain, token, secretInfo); err != nil {\n\t\treturn err\n\t}\n\n\tif err := captureUsers(ctx, client, domain, token, secretInfo); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc captureUserInfo(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {\n\tvar user CurrentUserInfo\n\n\tif err := doAndDecode(ctx, client, domain, CurrentUser, token, &user); err != nil {\n\t\treturn err\n\t}\n\n\tsecretInfo.UserInfo = User{\n\t\tID:       user.ID,\n\t\tUserName: user.UserName,\n\t}\n\n\tfor _, email := range user.Emails {\n\t\tif email.Primary {\n\t\t\tsecretInfo.UserInfo.PrimaryEmail = email.Value\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc captureTokensInfo(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {\n\tvar tokens Tokens\n\n\tif err := doAndDecode(ctx, client, domain, TokensInfo, token, &tokens); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, t := range tokens.TokensInfo {\n\t\tsecretInfo.Tokens = append(secretInfo.Tokens, Token{\n\t\t\tID:          t.ID,\n\t\t\tName:        t.Name,\n\t\t\tExpiryTime:  readableTime(t.ExpiryTime),\n\t\t\tLastUsedDay: readableTime(t.LastUsedDay),\n\t\t\tCreatedBy:   t.CreatedBy,\n\t\t})\n\t}\n\n\treturn nil\n}\n\nfunc captureTokenPermissions(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {\n\tvar permissions Permissions\n\n\tif err := doAndDecode(ctx, client, domain, TokenPermissions, token, &permissions); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, item := range permissions.PermissionLevels {\n\t\tsecretInfo.TokenPermissionLevels = append(secretInfo.TokenPermissionLevels, item.PermissionLevel)\n\t}\n\n\treturn nil\n}\n\nfunc captureRepos(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {\n\tvar repos ReposResponse\n\n\tif err := doAndDecode(ctx, client, domain, Repositories, token, &repos); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, repo := range repos.Repositories {\n\t\tif repo.ID == \"\" {\n\t\t\trepo.ID = repo.URL\n\t\t}\n\n\t\tsecretInfo.Resources = append(secretInfo.Resources, DataBricksResource{\n\t\t\tID:   repo.ID,\n\t\t\tName: repo.Path,\n\t\t\tType: Repositories.String(),\n\t\t\tMetadata: map[string]string{\n\t\t\t\t\"provider\": repo.Provider,\n\t\t\t\t\"url\":      repo.URL,\n\t\t\t},\n\t\t})\n\t}\n\n\treturn nil\n}\n\nfunc captureGitCreds(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {\n\tvar creds GitCreds\n\n\tif err := doAndDecode(ctx, client, domain, GitCredentials, token, &creds); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, credential := range creds.Credentials {\n\t\tsecretInfo.Resources = append(secretInfo.Resources, DataBricksResource{\n\t\t\tID:   credential.ID,\n\t\t\tName: credential.UserName,\n\t\t\tType: GitCredentials.String(),\n\t\t\tMetadata: map[string]string{\n\t\t\t\t\"provider\": credential.Provider,\n\t\t\t},\n\t\t})\n\t}\n\n\treturn nil\n}\n\nfunc captureJobs(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {\n\tvar jobs JobsResponse\n\n\tif err := doAndDecode(ctx, client, domain, Jobs, token, &jobs); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, job := range jobs.Jobs {\n\t\tsecretInfo.Resources = append(secretInfo.Resources, DataBricksResource{\n\t\t\tID:   job.ID,\n\t\t\tName: job.Name,\n\t\t\tType: Jobs.String(),\n\t\t\tMetadata: map[string]string{\n\t\t\t\t\"description\": job.Description,\n\t\t\t},\n\t\t})\n\t}\n\n\treturn nil\n}\n\nfunc captureClusters(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {\n\tvar clusters ClustersResponse\n\n\tif err := doAndDecode(ctx, client, domain, Clusters, token, &clusters); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, cluster := range clusters.Clusters {\n\t\tsecretInfo.Resources = append(secretInfo.Resources, DataBricksResource{\n\t\t\tID:   cluster.ID,\n\t\t\tName: cluster.Name,\n\t\t\tType: Clusters.String(),\n\t\t\tMetadata: map[string]string{\n\t\t\t\t\"created by\": cluster.CreatedBy,\n\t\t\t},\n\t\t})\n\t}\n\n\treturn nil\n}\n\nfunc captureGroups(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {\n\tvar groups GroupsResponse\n\n\tif err := doAndDecode(ctx, client, domain, Groups, token, &groups); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, group := range groups.Resources {\n\t\tsecretInfo.Resources = append(secretInfo.Resources, DataBricksResource{\n\t\t\tID:   group.ID,\n\t\t\tName: group.Name,\n\t\t\tType: Groups.String(),\n\t\t})\n\t}\n\n\treturn nil\n}\n\nfunc captureUsers(ctx context.Context, client *http.Client, domain, token string, secretInfo *SecretInfo) error {\n\tvar users UsersResponse\n\n\tif err := doAndDecode(ctx, client, domain, Users, token, &users); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, user := range users.Resources {\n\t\tsecretInfo.Resources = append(secretInfo.Resources, DataBricksResource{\n\t\t\tID:   user.ID,\n\t\t\tName: user.UserName,\n\t\t\tType: Users.String(),\n\t\t\tMetadata: map[string]string{\n\t\t\t\t\"active\": fmt.Sprintf(\"%t\", user.Active),\n\t\t\t},\n\t\t})\n\t}\n\n\treturn nil\n}\n\nfunc readableTime(timestamp int) string {\n\ttimestampMillis := int64(timestamp)\n\tt := time.Unix(timestampMillis/1000, (timestampMillis%1000)*int64(time.Millisecond))\n\n\treturn t.Format(\"2006-01-02 15:04:05\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/databricks/result_output.json",
    "content": "{\n    \"AnalyzerType\": 41,\n    \"Bindings\": [\n        {\n            \"Resource\": {\n                \"Name\": \"admins\",\n                \"FullyQualifiedName\": \"databricks/Group/601448505198850\",\n                \"Type\": \"Group\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"CAN_MANAGE\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"admins\",\n                \"FullyQualifiedName\": \"databricks/Group/601448505198850\",\n                \"Type\": \"Group\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"CAN_USE\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"kashif.khan@trufflesec.com\",\n                \"FullyQualifiedName\": \"databricks/Member/8639341364955455\",\n                \"Type\": \"Member\",\n                \"Metadata\": {\n                    \"active\": \"true\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"CAN_MANAGE\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"kashif.khan@trufflesec.com\",\n                \"FullyQualifiedName\": \"databricks/Member/8639341364955455\",\n                \"Type\": \"Member\",\n                \"Metadata\": {\n                    \"active\": \"true\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"CAN_USE\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"kashifkhan\",\n                \"FullyQualifiedName\": \"databricks/Git Credential/\",\n                \"Type\": \"Git Credential\",\n                \"Metadata\": {\n                    \"provider\": \"gitHub\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"CAN_MANAGE\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"kashifkhan\",\n                \"FullyQualifiedName\": \"databricks/Git Credential/\",\n                \"Type\": \"Git Credential\",\n                \"Metadata\": {\n                    \"provider\": \"gitHub\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"CAN_USE\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"users\",\n                \"FullyQualifiedName\": \"databricks/Group/1000729253926373\",\n                \"Type\": \"Group\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"CAN_MANAGE\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"users\",\n                \"FullyQualifiedName\": \"databricks/Group/1000729253926373\",\n                \"Type\": \"Group\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"CAN_USE\",\n                \"Parent\": null\n            }\n        }\n    ],\n    \"UnboundedResources\": null,\n    \"Metadata\": {}\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/datadog/datadog.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go datadog\npackage datadog\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (a Analyzer) Type() analyzers.AnalyzerType {\n\treturn analyzers.AnalyzerTypeDatadog\n}\n\n// Analyze performs the analysis of the Datadog API key and returns the analyzer result.\nfunc (a Analyzer) Analyze(ctx context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tapiKey := credInfo[\"api_key\"]\n\tappKey := credInfo[\"app_key\"]\n\tendpoint := credInfo[\"endpoint\"]\n\n\tinfo, err := AnalyzePermissions(a.Cfg, apiKey, appKey, endpoint)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, apiKey, appKey, endpoint string) {\n\tinfo, err := AnalyzePermissions(cfg, apiKey, appKey, endpoint)\n\tif err != nil {\n\t\t// just print the error in cli and continue as a partial success\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t}\n\n\tif info == nil {\n\t\tcolor.Red(\"[x] No information retrieved\")\n\t\treturn\n\t}\n\n\tcolor.Green(\"[i] Valid Datadog API Key\\n\")\n\tprintUser(info.User)\n\tprintResources(info.Resources)\n\tprintPermissions(info.Permissions)\n}\n\n// AnalyzePermissions will collect all the scopes assigned to token along with resource it can access\nfunc AnalyzePermissions(cfg *config.Config, apiKey, appKey, endpoint string) (*SecretInfo, error) {\n\tif apiKey == \"\" {\n\t\treturn nil, errors.New(\"api key not found in credentials info\")\n\t}\n\n\t// create the http client\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\tvar secretInfo = &SecretInfo{}\n\n\tvar baseURL string\n\tvar err error\n\n\t// If endpoint is provided, use it directly; otherwise detect domain\n\tif endpoint != \"\" {\n\t\tbaseURL, err = url.JoinPath(endpoint, \"api\")\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to join path: %w\", err)\n\t\t}\n\t} else {\n\t\tbaseURL, err = DetectDomain(client, apiKey, appKey)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"[x] %v\", err)\n\t\t}\n\t}\n\n\tif appKey == \"\" {\n\t\t// If no application key is provided, we can only validate the API key\n\t\tif endpoint != \"\" {\n\t\t\t// If endpoint is empty we don't need to validate again because DetectDomain would have already validated the API key against detected domain\n\t\t\t// But if endpoint is provided, we should validate the API key against the provided endpoint to ensure it's valid before proceeding\n\t\t\tisValidApiKey, err := ValidateApiKey(client, baseURL, apiKey)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to validate api key: %v\", err)\n\t\t\t}\n\t\t\tif !isValidApiKey {\n\t\t\t\treturn nil, errors.New(\"invalid api key provided\")\n\t\t\t}\n\t\t}\n\t\tif err := CaptureApiKeyPermissions(secretInfo); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to fetch permissions: %v\", err)\n\t\t}\n\t\treturn secretInfo, nil\n\t}\n\n\t// capture user information in secretInfo\n\t// If the application key is scoped, user information cannot be retrieved even if all the permissions are granted\n\t// This is a non-documented Endpoint and can lead to unexpected behavior in future updates\n\t// If user information is not retrieved, we will move ahead with the rest of the analysis and print the error\n\t_ = CaptureUserInformation(client, baseURL, apiKey, appKey, secretInfo)\n\n\t// capture resources in secretInfo\n\tif err := CaptureResources(client, baseURL, apiKey, appKey, secretInfo); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to fetch resources: %v\", err)\n\t}\n\n\t// capture permissions in secretInfo\n\tif err := CapturePermissions(client, baseURL, apiKey, appKey, secretInfo); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to fetch permissions: %v\", err)\n\t}\n\n\t// Capture API key permissions\n\tif err := CaptureApiKeyPermissions(secretInfo); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to fetch permissions: %v\", err)\n\t}\n\treturn secretInfo, nil\n}\n\n// secretInfoToAnalyzerResult translate secret info to Analyzer Result\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeDatadog,\n\t\tMetadata:     map[string]any{},\n\t\tBindings:     make([]analyzers.Binding, 0),\n\t}\n\n\t// Create user resource to use as parent\n\tvar userResource *analyzers.Resource\n\tif info.User.Id != \"\" {\n\t\tuserResource = &analyzers.Resource{\n\t\t\tFullyQualifiedName: info.User.Id,\n\t\t\tName:               info.User.Name,\n\t\t\tType:               \"User\",\n\t\t\tMetadata: map[string]any{\n\t\t\t\t\"email\": info.User.Email,\n\t\t\t},\n\t\t}\n\t}\n\n\tpermissionBindings := secretInfoPermissionsToAnalyzerPermission(info.Permissions)\n\tif userResource != nil && len(*permissionBindings) > 0 {\n\t\tresult.Bindings = analyzers.BindAllPermissions(*userResource, *permissionBindings...)\n\t}\n\tif userResource == nil && len(*permissionBindings) > 0 {\n\t\tresult.Bindings = analyzers.BindAllPermissions(analyzers.Resource{\n\t\t\tFullyQualifiedName: \"Unknown User\",\n\t\t\tName:               \"Unknown User\",\n\t\t\tType:               \"User\",\n\t\t\tMetadata:           map[string]any{},\n\t\t}, *permissionBindings...)\n\t}\n\n\t// Extract information from resources to create bindings\n\tfor _, resource := range info.Resources {\n\t\tresource := secretInfoResourceToAnalyzerResource(resource)\n\n\t\t// Set the user resource as parent if available\n\t\tif userResource != nil {\n\t\t\tresource.Parent = userResource\n\t\t}\n\n\t\tbinding := analyzers.Binding{\n\t\t\tResource: *resource,\n\t\t}\n\n\t\tresult.Bindings = append(result.Bindings, binding)\n\t}\n\n\treturn &result\n}\n\n// secretInfoPermissionsToAnalyzerPermission translate secret info Permission to analyzer resource for binding\nfunc secretInfoPermissionsToAnalyzerPermission(perms []Permission) *[]analyzers.Permission {\n\tpermissions := make([]analyzers.Permission, 0, len(perms))\n\tfor _, perm := range perms {\n\t\tpermissions = append(permissions, analyzers.Permission{\n\t\t\tValue: perm.Title,\n\t\t})\n\t}\n\treturn &permissions\n}\n\n// secretInfoResourceToAnalyzerResource translate secret info Resource to analyzer resource for binding\nfunc secretInfoResourceToAnalyzerResource(resource Resource) *analyzers.Resource {\n\tanalyzerRes := analyzers.Resource{\n\t\tFullyQualifiedName: resource.ID,\n\t\tName:               resource.Name,\n\t\tType:               resource.Type,\n\t\tMetadata:           map[string]any{},\n\t}\n\n\tfor key, value := range resource.MetaData {\n\t\tanalyzerRes.Metadata[key] = value\n\t}\n\n\treturn &analyzerRes\n}\n\nfunc printUser(user User) {\n\tif user.Id == \"\" {\n\t\tcolor.Red(\"\\n[x] User information not available\")\n\t\treturn\n\t}\n\n\tcolor.Green(\"\\n[i] User Information:\")\n\tuserTable := table.NewWriter()\n\tuserTable.SetOutputMirror(os.Stdout)\n\tuserTable.AppendHeader(table.Row{\"User Id\", \"Name\", \"Email\"})\n\tuserTable.AppendRow(table.Row{color.GreenString(user.Id), color.GreenString(user.Name), color.GreenString(user.Email)})\n\tuserTable.Render()\n}\n\nfunc printResources(resources []Resource) {\n\tif len(resources) == 0 {\n\t\tcolor.Red(\"[x] No resources found\")\n\t\treturn\n\t}\n\n\tcolor.Green(\"\\n[i] Resources:\")\n\tresourceTable := table.NewWriter()\n\tresourceTable.SetOutputMirror(os.Stdout)\n\tresourceTable.AppendHeader(table.Row{\"Name\", \"Type\"})\n\tfor _, resource := range resources {\n\t\tresourceTable.AppendRow(table.Row{\n\t\t\tcolor.GreenString(resource.Name),\n\t\t\tcolor.GreenString(resource.Type),\n\t\t})\n\t}\n\tresourceTable.Render()\n}\n\nfunc printPermissions(permissions []Permission) {\n\tif len(permissions) == 0 {\n\t\tcolor.Red(\"[x] No permissions found\")\n\t\treturn\n\t}\n\n\tcolor.Green(\"\\n[i] Permissions:\")\n\tpermissionTable := table.NewWriter()\n\tpermissionTable.SetOutputMirror(os.Stdout)\n\tpermissionTable.AppendHeader(table.Row{\"Title\", \"Name\", \"Description\"})\n\n\t// Set wrapping for long descriptions\n\tpermissionTable.SetColumnConfigs([]table.ColumnConfig{\n\t\t{Number: 3, WidthMax: 50},\n\t})\n\n\tfor _, permission := range permissions {\n\t\tpermissionTable.AppendRow(table.Row{\n\t\t\tcolor.GreenString(permission.Title),\n\t\t\tcolor.GreenString(permission.Name),\n\t\t\tcolor.GreenString(permission.Description),\n\t\t})\n\t}\n\tpermissionTable.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/datadog/datadog_test.go",
    "content": "package datadog\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\n//go:embed expected_output_apikey.json\nvar expectedOutputAPIKey []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*2)\n\tdefer cancel()\n\n\t// Get API keys from GCP\n\tvar apiKey, appKey string\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"analyzers1\")\n\tif err != nil {\n\t\tt.Fatalf(\"Could not get test secrets from GCP: %s\", err)\n\t}\n\n\t// Get the required credentials\n\tapiKey = testSecrets.MustGetField(\"DATADOG_API_KEY\")\n\tappKey = testSecrets.MustGetField(\"DATADOG_APP_KEY\")\n\n\t// Fail if credentials are not available\n\tif apiKey == \"\" || appKey == \"\" {\n\t\tt.Fatalf(\"Datadog credentials are required for this test\")\n\t}\n\n\ttests := []struct {\n\t\tname     string\n\t\tapiKey   string\n\t\tappKey   string\n\t\tendpoint string\n\t\twant     []byte // JSON string\n\t\twantErr  bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid datadog credentials\",\n\t\t\tapiKey:  apiKey,\n\t\t\tappKey:  appKey,\n\t\t\twant:    expectedOutput,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:     \"valid datadog credentials with endpoint\",\n\t\t\tapiKey:   apiKey,\n\t\t\tappKey:   appKey,\n\t\t\tendpoint: \"https://api.us5.datadoghq.com\",\n\t\t\twant:     expectedOutput,\n\t\t\twantErr:  false,\n\t\t},\n\t\t{\n\t\t\tname:     \"valid datadog credentials with invalid endpoint\",\n\t\t\tapiKey:   apiKey,\n\t\t\tappKey:   appKey,\n\t\t\tendpoint: \"https://api.eu.datadoghq.com\",\n\t\t\twant: []byte(fmt.Sprintf(`{\n\t\t\t\t\"AnalyzerType\": %d,\n\t\t\t\t\"Bindings\": [],\n\t\t\t\t\"UnboundedResources\": null,\n\t\t\t\t\"Metadata\": {}\n\t\t\t}`, analyzers.AnalyzerTypeDatadog)),\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid credentials\",\n\t\t\tapiKey:  \"invalid_api_key\",\n\t\t\tappKey:  \"invalid_app_key\",\n\t\t\twant:    nil,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"api_key\": tt.apiKey, \"app_key\": tt.appKey, \"endpoint\": tt.endpoint})\n\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Skip verification for error cases\n\t\t\tif tt.wantErr {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// For valid cases, verify we got a result\n\t\t\tif got == nil {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = nil, want non-nil\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Verify type is correct\n\t\t\tif got.AnalyzerType != analyzers.AnalyzerTypeDatadog {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() returned wrong analyzer type, got %d want %d\",\n\t\t\t\t\tgot.AnalyzerType, analyzers.AnalyzerTypeDatadog)\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal(tt.want, &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAnalyzer_Analyze_ApiKeyOnly(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*2)\n\tdefer cancel()\n\n\t// Get API keys from GCP\n\tvar apiKey string\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"analyzers1\")\n\tif err != nil {\n\t\tt.Fatalf(\"Could not get test secrets from GCP: %s\", err)\n\t}\n\n\t// Get the required credentials\n\tapiKey = testSecrets.MustGetField(\"DATADOG_API_KEY\")\n\n\t// Fail if credentials are not available\n\tif apiKey == \"\" {\n\t\tt.Fatalf(\"Datadog credentials are required for this test\")\n\t}\n\twant := expectedOutputAPIKey\n\n\ta := Analyzer{Cfg: &config.Config{}}\n\tgot, err := a.Analyze(ctx, map[string]string{\"api_key\": apiKey, \"endpoint\": \"https://api.us5.datadoghq.com\"})\n\tif err != nil {\n\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, false)\n\t\treturn\n\t}\n\n\t// For valid cases, verify we got a result\n\tif got == nil {\n\t\tt.Errorf(\"Analyzer.Analyze() = nil, want non-nil\")\n\t\treturn\n\t}\n\n\t// Verify type is correct\n\tif got.AnalyzerType != analyzers.AnalyzerTypeDatadog {\n\t\tt.Errorf(\"Analyzer.Analyze() returned wrong analyzer type, got %d want %d\",\n\t\t\tgot.AnalyzerType, analyzers.AnalyzerTypeDatadog)\n\t}\n\n\t// Bindings need to be in the same order to be comparable\n\tsortBindings(got.Bindings)\n\n\t// Marshal the actual result to JSON\n\tgotJSON, err := json.Marshal(got)\n\tif err != nil {\n\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t}\n\n\t// Parse the expected JSON string\n\tvar wantObj analyzers.AnalyzerResult\n\tif err := json.Unmarshal(want, &wantObj); err != nil {\n\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t}\n\n\t// Bindings need to be in the same order to be comparable\n\tsortBindings(wantObj.Bindings)\n\n\t// Marshal the expected result to JSON (to normalize)\n\twantJSON, err := json.Marshal(wantObj)\n\tif err != nil {\n\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t}\n\n\t// Compare the JSON strings\n\tif string(gotJSON) != string(wantJSON) {\n\t\t// Pretty-print both JSON strings for easier comparison\n\t\tvar gotIndented, wantIndented []byte\n\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t}\n\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t}\n\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/datadog/expected_output.json",
    "content": "{\n  \"AnalyzerType\": 37,\n  \"Bindings\": [\n    {\n      \"Resource\": {\n        \"Name\": \"My Monitor\",\n        \"FullyQualifiedName\": \"4429851\",\n        \"Type\": \"Monitor\",\n        \"Metadata\": {},\n        \"Parent\": {\n          \"Name\": \"Truffle Sec\",\n          \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n          \"Type\": \"User\",\n          \"Metadata\": {\n            \"email\": \"detectors@trufflesec.com\"\n          },\n          \"Parent\": null\n        }\n      },\n      \"Permission\": {\n        \"Value\": \"\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"API Keys Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"API Keys Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"APM Generate Metrics\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"APM Pipelines Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"APM Pipelines Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"APM Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"APM Retention Filters Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"APM Retention Filters Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"AWS Configurations Manage\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Audit Trail Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Azure Configurations Manage\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Connections Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Connections Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Dashboards Public Share\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Dashboards Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Dashboards Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Data Scanner Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Data Scanner Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"GCP Configurations Manage\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Incident Settings Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Incidents Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Integrations Manage\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Logs Generate Metrics\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Logs Modify Indexes\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Logs Read Archives\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Logs Read Data\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Logs Write Archives\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Logs Write Pipelines\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Manage Downtimes\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Metric Tags Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Monitor Configuration Policy Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Monitors Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Monitors Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Notebooks Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Notebooks Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Observability Pipelines Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Org App Keys Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Org App Keys Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Org Management\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"RUM Apps Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"RUM Apps Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"SLOs Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"SLOs Status Corrections\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"SLOs Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Security Filters Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Security Filters Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Security Rules Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Security Signals Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Security Signals Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Service Account Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Submit Events\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Submit Logs\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Submit Metrics\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Submit Service Checks\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Synthetics Default Settings Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Synthetics Global Variable Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Synthetics Global Variable Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Synthetics Private Locations Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Synthetics Private Locations Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Synthetics Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Synthetics Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Usage Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"User Access Invite\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"User Access Manage\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"User App Keys\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Workflows Read\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle Sec\",\n        \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n        \"Type\": \"User\",\n        \"Metadata\": {\n          \"email\": \"detectors@trufflesec.com\"\n        },\n        \"Parent\": null\n      },\n      \"Permission\": {\n        \"Value\": \"Workflows Write\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    },\n    {\n      \"Resource\": {\n        \"Name\": \"Truffle's Dashboard\",\n        \"FullyQualifiedName\": \"fvx-idw-ani\",\n        \"Type\": \"Dashboard\",\n        \"Metadata\": {\n          \"Author Handle\": \"detectors@trufflesec.com\",\n          \"Layout Type\": \"ordered\",\n          \"URL\": \"/dashboard/fvx-idw-ani/truffles-dashboard\"\n        },\n        \"Parent\": {\n          \"Name\": \"Truffle Sec\",\n          \"FullyQualifiedName\": \"a4b3b545-24ec-11f0-9f57-22795724d251\",\n          \"Type\": \"User\",\n          \"Metadata\": {\n            \"email\": \"detectors@trufflesec.com\"\n          },\n          \"Parent\": null\n        }\n      },\n      \"Permission\": {\n        \"Value\": \"\",\n        \"Parent\": null\n      },\n      \"Condition\": \"\"\n    }\n  ],\n  \"UnboundedResources\": null,\n  \"Metadata\": {}\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/datadog/expected_output_apikey.json",
    "content": "{\n    \"AnalyzerType\": 37,\n    \"Bindings\": [\n        {\n            \"Resource\": {\n                \"Name\": \"Unknown User\",\n                \"FullyQualifiedName\": \"Unknown User\",\n                \"Type\": \"User\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"Submit Events\",\n                \"Parent\": null\n            },\n            \"Condition\": \"\"\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Unknown User\",\n                \"FullyQualifiedName\": \"Unknown User\",\n                \"Type\": \"User\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"Submit Logs\",\n                \"Parent\": null\n            },\n            \"Condition\": \"\"\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Unknown User\",\n                \"FullyQualifiedName\": \"Unknown User\",\n                \"Type\": \"User\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"Submit Metrics\",\n                \"Parent\": null\n            },\n            \"Condition\": \"\"\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Unknown User\",\n                \"FullyQualifiedName\": \"Unknown User\",\n                \"Type\": \"User\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"Submit Service Checks\",\n                \"Parent\": null\n            },\n            \"Condition\": \"\"\n        }\n    ],\n    \"UnboundedResources\": null,\n    \"Metadata\": {}\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/datadog/models.go",
    "content": "package datadog\n\nimport \"sync\"\n\n// Resource type constants for consistent usage\nconst (\n\tResourceTypeValidate    = \"Validate\"\n\tResourceTypeCurrentUser = \"Current User\"\n\tResourceTypeDashboard   = \"Dashboard\"\n\tResourceTypeMonitor     = \"Monitor\"\n)\n\n// Permission represents a permission granted to an API key\ntype Permission struct {\n\tName        string\n\tTitle       string\n\tDescription string\n\tMetaData    map[string]string\n}\n\n// SecretInfo holds all information gathered about a Datadog API key\ntype SecretInfo struct {\n\tUser        User\n\tPermissions []Permission\n\n\tmu        sync.RWMutex\n\tResources []Resource\n}\n\n// User is the information about the user to whom the token belongs\ntype User struct {\n\tId    string\n\tName  string\n\tEmail string\n}\n\n// Resource represents a Datadog resource\ntype Resource struct {\n\tID       string\n\tName     string\n\tType     string\n\tMetaData map[string]string\n}\n\n// API response structures\ntype currentUserResponse struct {\n\tData struct {\n\t\tId         string `json:\"id\"`\n\t\tAttributes struct {\n\t\t\tName  string `json:\"name\"`\n\t\t\tEmail string `json:\"email\"`\n\t\t} `json:\"attributes\"`\n\t} `json:\"data\"`\n}\n\ntype dashboardResponse struct {\n\tDashboards []DashboardItem `json:\"dashboards\"`\n}\n\ntype DashboardItem struct {\n\tID           string  `json:\"id\"`\n\tTitle        string  `json:\"title\"`\n\tURL          string  `json:\"url\"`\n\tIsReadOnly   bool    `json:\"is_read_only\"`\n\tCreatedAt    string  `json:\"created_at\"`\n\tModifiedAt   string  `json:\"modified_at\"`\n\tAuthorHandle string  `json:\"author_handle\"`\n\tDescription  *string `json:\"description\"`\n\tLayoutType   string  `json:\"layout_type\"`\n\tDeletedAt    *string `json:\"deleted_at\"`\n}\n\ntype monitorResponse []struct {\n\tID   int    `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\n// appendResource adds a resource to secret info resources list\nfunc (s *SecretInfo) appendResource(resource Resource) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\ts.Resources = append(s.Resources, resource)\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/datadog/permissions.yaml",
    "content": "permissions:\n  - dashboards_read\n  - dashboards_write\n  - dashboards_public_share\n  - monitors_read\n  - monitors_write\n  - logs_modify_indexes\n  - logs_write_pipelines\n  - logs_write_archives\n  - logs_generate_metrics\n  - monitors_downtime\n  - logs_read_data\n  - logs_read_archives\n  - security_monitoring_rules_read\n  - security_monitoring_rules_write\n  - security_monitoring_signals_read\n  - security_monitoring_signals_write\n  - user_access_invite\n  - user_app_keys\n  - org_app_keys_read\n  - org_app_keys_write\n  - user_access_manage\n  - synthetics_private_location_read\n  - synthetics_private_location_write\n  - usage_read\n  - metric_tags_write\n  - audit_logs_read\n  - api_keys_read\n  - api_keys_write\n  - synthetics_global_variable_read\n  - synthetics_global_variable_write\n  - synthetics_read\n  - synthetics_write\n  - synthetics_default_settings_read\n  - service_account_write\n  - apm_read\n  - apm_retention_filter_read\n  - apm_retention_filter_write\n  - rum_apps_write\n  - data_scanner_read\n  - data_scanner_write\n  - org_management\n  - security_monitoring_filters_read\n  - security_monitoring_filters_write\n  - incident_read\n  - incident_write\n  - incident_settings_write\n  - rum_apps_read\n  - security_monitoring_notification_profiles_read\n  - security_monitoring_notification_profiles_write\n  - apm_generate_metrics\n  - apm_pipelines_write\n  - apm_pipelines_read\n  - observability_pipelines_read\n  - workflows_read\n  - workflows_write\n  - workflows_run\n  - connections_read\n  - connections_write\n  - notebooks_read\n  - notebooks_write\n  - aws_configurations_manage\n  - azure_configurations_manage\n  - gcp_configurations_manage\n  - manage_integrations\n  - slos_read\n  - slos_write\n  - slos_corrections\n  - monitor_config_policy_write"
  },
  {
    "path": "pkg/analyzer/analyzers/datadog/requests.go",
    "content": "package datadog\n\nimport (\n\t\"context\"\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"slices\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n)\n\n// Constants and configuration\nconst (\n\tdefaultTimeout = 12 * time.Second\n\tapiKeyHeader   = \"DD-API-KEY\"\n\tappKeyHeader   = \"DD-APPLICATION-KEY\"\n)\n\n// List of all DataDog domains to try\nvar datadogDomains = []string{\n\t\"https://api.us5.datadoghq.com/api\", // Default domain\n\t\"https://api.app.datadoghq.com/api\",\n\t\"https://api.us3.datadoghq.com/api\",\n\t\"https://api.app.datadoghq.eu/api\",\n\t\"https://api.app.ddog-gov.com/api\",\n\t\"https://api.ap1.datadoghq.com/api\",\n}\n\n// Endpoints map for API paths\nvar endpoints = map[string]string{\n\tResourceTypeCurrentUser: \"/v2/current_user\",\n\tResourceTypeDashboard:   \"/v1/dashboard\",\n\tResourceTypeMonitor:     \"/v1/monitor\",\n\tResourceTypeValidate:    \"/v1/validate\",\n}\n\n//go:embed scopes.json\nvar scopesConfig []byte\n\n// --------------------------------\n// Data models\n// --------------------------------\n\n// HttpStatusTest defines a test for checking HTTP endpoint permissions\ntype HttpStatusTest struct {\n\tMethod          string `json:\"method\"`\n\tEndpoint        string `json:\"endpoint\"`\n\tValidStatuses   []int  `json:\"valid_statuses\"`\n\tInvalidStatuses []int  `json:\"invalid_statuses\"`\n}\n\n// Scope represents a permission scope with a test\ntype Scope struct {\n\tName        string         `json:\"name\"`\n\tTitle       string         `json:\"title\"`\n\tDescription string         `json:\"description\"`\n\tResource    string         `json:\"resource\"`\n\tHttpTest    HttpStatusTest `json:\"test\"`\n}\n\n// --------------------------------\n// Domain detection\n// --------------------------------\n\n// DetectDomain tries each DataDog domain to find a working one\nfunc DetectDomain(client *http.Client, apiKey string, appKey string) (string, error) {\n\tfor _, domain := range datadogDomains {\n\t\t// Use a simple endpoint to test if the domain works\n\t\tendpoint := domain + endpoints[ResourceTypeValidate]\n\n\t\tctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)\n\t\tdefer cancel()\n\n\t\t// Create request\n\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", endpoint, http.NoBody)\n\t\tif err != nil {\n\t\t\tcontinue // Skip to next domain if request creation fails\n\t\t}\n\n\t\t// Add required keys in the header\n\t\treq.Header.Set(apiKeyHeader, apiKey)\n\n\t\tif appKey != \"\" {\n\t\t\treq.Header.Set(appKeyHeader, appKey)\n\t\t}\n\n\t\tresp, err := client.Do(req)\n\n\t\tif err != nil {\n\t\t\tcontinue // Skip to next domain if request fails\n\t\t}\n\n\t\tdefer func() {\n\t\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t\t_ = resp.Body.Close()\n\t\t}()\n\n\t\t// If we get a response that's not a connection error, this domain works\n\t\tif resp.StatusCode == http.StatusOK {\n\t\t\treturn domain, nil\n\t\t}\n\t}\n\n\treturn \"\", errors.New(\"unable to validate any DataDog domain with the provided API key\")\n}\n\n// --------------------------------\n// HTTP request utilities\n// --------------------------------\n\n// makeDataDogRequest sends an HTTP GET API request to the specified endpoint with auth tokens\nfunc makeDataDogRequest(client *http.Client, baseURL, endpoint, method, apiKey string, appKey string) ([]byte, int, error) {\n\tctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)\n\tdefer cancel()\n\n\t// create request\n\turl, err := url.JoinPath(baseURL, endpoint)\n\tif err != nil {\n\t\treturn nil, 0, fmt.Errorf(\"failed to build URL: %w\", err)\n\t}\n\treq, err := http.NewRequestWithContext(ctx, method, url, http.NoBody)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\t// add required keys in the header\n\treq.Header.Set(apiKeyHeader, apiKey)\n\n\tif appKey != \"\" {\n\t\treq.Header.Set(appKeyHeader, appKey)\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tresponseBodyByte, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn responseBodyByte, resp.StatusCode, nil\n}\n\n// RunTest executes an HTTP test against an API endpoint with provided headers\nfunc (h *HttpStatusTest) RunTest(client *http.Client, baseURL string, headers map[string]string) (bool, error) {\n\tapiKey := headers[apiKeyHeader]\n\tappKey := headers[appKeyHeader]\n\n\t_, statusCode, err := makeDataDogRequest(client, baseURL, h.Endpoint, h.Method, apiKey, appKey)\n\n\tif err != nil {\n\t\tfmt.Printf(\"Error making request: %v\\n\", err)\n\t\treturn false, err\n\t}\n\n\t// Check response status code\n\tswitch {\n\tcase slices.Contains(h.ValidStatuses, statusCode):\n\t\treturn true, nil\n\tcase slices.Contains(h.InvalidStatuses, statusCode):\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// --------------------------------\n// Validate ApiKey\n// --------------------------------\nfunc ValidateApiKey(client *http.Client, baseURL, apiKey string) (bool, error) {\n\t// Use a simple endpoint to test if the domain works\n\tendpoint, err := url.JoinPath(baseURL, endpoints[ResourceTypeValidate])\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to build endpoint: %w\", err)\n\t}\n\tctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)\n\tdefer cancel()\n\n\t// Create request\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", endpoint, http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Add required keys in the header\n\treq.Header.Set(apiKeyHeader, apiKey)\n\n\tresp, err := client.Do(req)\n\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\t// If we get a response that's not a connection error, this domain works\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"Unable to validate api key with status code: %d\", resp.StatusCode)\n\t}\n}\n\n// --------------------------------\n// Data capture functions\n// --------------------------------\n\n// CaptureUserInformation retrieves and stores user information\nfunc CaptureUserInformation(client *http.Client, baseURL, apiKey, appKey string, secretInfo *SecretInfo) error {\n\tcaller, err := getCurrentUserInfo(client, baseURL, apiKey, appKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\taddUserToSecretInfo(caller, secretInfo)\n\n\treturn nil\n}\n\n// CaptureResources retrieves and stores dashboard and monitor resources\nfunc CaptureResources(client *http.Client, baseURL, apiKey, appKey string, secretInfo *SecretInfo) error {\n\tvar wg sync.WaitGroup\n\terrChan := make(chan error, 2) // Buffer size matches the number of tasks\n\n\t// helper to launch tasks concurrently\n\tlaunchTask := func(task func() error) {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tif err := task(); err != nil {\n\t\t\t\terrChan <- err\n\t\t\t}\n\t\t}()\n\t}\n\n\tlaunchTask(func() error { return captureDashboard(client, baseURL, apiKey, appKey, secretInfo) })\n\tlaunchTask(func() error { return captureMonitor(client, baseURL, apiKey, appKey, secretInfo) })\n\n\t// Wait for all tasks to complete\n\twg.Wait()\n\tclose(errChan)\n\n\t// Collect any errors\n\tvar errs []error\n\tfor err := range errChan {\n\t\terrs = append(errs, err)\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn errors.Join(errs...)\n\t}\n\n\treturn nil\n}\n\n// CapturePermissions tests and records available permissions\nfunc CapturePermissions(client *http.Client, baseURL, apiKey, appKey string, secretInfo *SecretInfo) error {\n\tscopes, err := readInScopes()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"reading in scopes: %w\", err)\n\t}\n\n\tpermissions := make([]Permission, 0)\n\theaders := map[string]string{\n\t\tapiKeyHeader: apiKey,\n\t\tappKeyHeader: appKey,\n\t}\n\n\tfor _, scope := range scopes {\n\t\tif scope.HttpTest.Endpoint != \"\" {\n\t\t\tstatus, err := scope.HttpTest.RunTest(client, baseURL, headers)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"running test for scope %s: %w\", scope.Name, err)\n\t\t\t}\n\n\t\t\tmetadata := map[string]string{\n\t\t\t\t\"Resource\": scope.Resource,\n\t\t\t}\n\n\t\t\tif status {\n\t\t\t\tpermission := Permission{\n\t\t\t\t\tName:        scope.Name,\n\t\t\t\t\tTitle:       scope.Title,\n\t\t\t\t\tDescription: scope.Description,\n\t\t\t\t\tMetaData:    metadata,\n\t\t\t\t}\n\t\t\t\tpermissions = append(permissions, permission)\n\t\t\t}\n\t\t}\n\t}\n\n\tsecretInfo.Permissions = permissions\n\treturn nil\n}\n\n// API key is not finely grained, so we assign some default permissions\nfunc CaptureApiKeyPermissions(secretInfo *SecretInfo) error {\n\tscopes, err := readInScopes()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"reading in scopes: %w\", err)\n\t}\n\tpermissions := make([]Permission, 0)\n\tfor _, scope := range scopes {\n\t\tmetadata := map[string]string{\n\t\t\t\"Resource\": scope.Resource,\n\t\t}\n\t\tif scope.HttpTest.Endpoint == \"\" {\n\t\t\tpermission := Permission{\n\t\t\t\tName:        scope.Name,\n\t\t\t\tTitle:       scope.Title,\n\t\t\t\tDescription: scope.Description,\n\t\t\t\tMetaData:    metadata,\n\t\t\t}\n\t\t\tpermissions = append(permissions, permission)\n\t\t}\n\t}\n\tsecretInfo.Permissions = append(secretInfo.Permissions, permissions...)\n\treturn nil\n}\n\n// --------------------------------\n// Resource capture helper functions\n// --------------------------------\n\n// getCurrentUserInfo retrieves information about the current user\nfunc getCurrentUserInfo(client *http.Client, baseURL, apiKey, appKey string) (*currentUserResponse, error) {\n\tresponse, statusCode, err := makeDataDogRequest(client, baseURL, endpoints[ResourceTypeCurrentUser], http.MethodGet, apiKey, appKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar caller = &currentUserResponse{}\n\t\tif err := json.Unmarshal(response, caller); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unmarshalling user response: %w\", err)\n\t\t}\n\t\treturn caller, nil\n\tcase http.StatusUnauthorized:\n\t\treturn nil, errors.New(\"invalid API key or application key\")\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// addUserToSecretInfo adds user information to the secret info object\nfunc addUserToSecretInfo(caller *currentUserResponse, secretInfo *SecretInfo) {\n\tuser := User{\n\t\tId:    caller.Data.Id,\n\t\tName:  caller.Data.Attributes.Name,\n\t\tEmail: caller.Data.Attributes.Email,\n\t}\n\n\tsecretInfo.User = user\n}\n\n// captureDashboard retrieves dashboard information\nfunc captureDashboard(client *http.Client, baseURL, apiKey, appKey string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeDataDogRequest(client, baseURL, endpoints[ResourceTypeDashboard], http.MethodGet, apiKey, appKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar dashboardResponse = &dashboardResponse{}\n\t\tif err := json.Unmarshal(response, dashboardResponse); err != nil {\n\t\t\treturn fmt.Errorf(\"unmarshalling dashboard response: %w\", err)\n\t\t}\n\n\t\tfor _, dashboard := range dashboardResponse.Dashboards {\n\t\t\tmetadata := map[string]string{\n\t\t\t\t\"Layout Type\":   dashboard.LayoutType,\n\t\t\t\t\"URL\":           dashboard.URL,\n\t\t\t\t\"Author Handle\": dashboard.AuthorHandle,\n\t\t\t}\n\n\t\t\tresource := Resource{\n\t\t\t\tID:       dashboard.ID,\n\t\t\t\tName:     dashboard.Title,\n\t\t\t\tType:     ResourceTypeDashboard,\n\t\t\t\tMetaData: metadata,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\t\treturn nil\n\tcase http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code for dashboard API: %d\", statusCode)\n\t}\n}\n\n// captureMonitor retrieves monitor information\nfunc captureMonitor(client *http.Client, baseURL, apiKey, appKey string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeDataDogRequest(client, baseURL, endpoints[ResourceTypeMonitor], http.MethodGet, apiKey, appKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar monitorResponse = &monitorResponse{}\n\t\tif err := json.Unmarshal(response, monitorResponse); err != nil {\n\t\t\treturn fmt.Errorf(\"unmarshalling monitor response: %w\", err)\n\t\t}\n\n\t\tfor _, monitor := range *monitorResponse {\n\t\t\tresource := Resource{\n\t\t\t\tID:   strconv.Itoa(monitor.ID),\n\t\t\t\tName: monitor.Name,\n\t\t\t\tType: ResourceTypeMonitor,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\t\treturn nil\n\tcase http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code for monitor API: %d\", statusCode)\n\t}\n}\n\n// --------------------------------\n// Utility functions\n// --------------------------------\n\n// readInScopes loads permission scopes from the embedded configuration\nfunc readInScopes() ([]Scope, error) {\n\tvar scopes []Scope\n\tif err := json.Unmarshal(scopesConfig, &scopes); err != nil {\n\t\treturn nil, fmt.Errorf(\"unmarshalling scopes config: %w\", err)\n\t}\n\treturn scopes, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/datadog/scopes.json",
    "content": "[\n  {\n    \"name\": \"dashboards_read\",\n    \"title\": \"Dashboards Read\",\n    \"description\": \"View dashboards.\",\n    \"resource\": \"Dashboards\",\n    \"test\": {\n      \"endpoint\": \"/v1/dashboard\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"dashboards_write\",\n    \"title\": \"Dashboards Write\",\n    \"description\": \"Create and change dashboards.\",\n    \"resource\": \"Dashboards\",\n    \"test\": {\n      \"endpoint\": \"/v1/dashboard\",\n      \"method\": \"POST\",\n      \"valid_statuses\": [200, 400, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"dashboards_public_share\",\n    \"title\": \"Dashboards Public Share\",\n    \"description\": \"Create, modify and delete shared dashboards with share type 'Public'. These dashboards can be accessed by anyone on the internet.\",\n    \"resource\": \"Dashboards\",\n    \"test\": {\n      \"endpoint\": \"/v1/dashboard/public\",\n      \"method\": \"POST\",\n      \"valid_statuses\": [200, 400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"monitors_read\",\n    \"title\": \"Monitors Read\",\n    \"description\": \"View monitors.\",\n    \"resource\": \"Monitors\",\n    \"test\": {\n      \"endpoint\": \"/v1/monitor\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 400, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"monitors_write\",\n    \"title\": \"Monitors Write\",\n    \"description\": \"Edit and delete individual monitors.\",\n    \"resource\": \"Monitors\",\n    \"test\": {\n      \"endpoint\": \"/v1/monitor\",\n      \"method\": \"POST\",\n      \"valid_statuses\": [200, 400, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"logs_modify_indexes\",\n    \"title\": \"Logs Modify Indexes\",\n    \"description\": \"Read and modify all indexes in your account.\",\n    \"resource\": \"Logs\",\n    \"test\": {\n      \"endpoint\": \"/v1/logs/config/indexes/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [200, 400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"logs_write_pipelines\",\n    \"title\": \"Logs Write Pipelines\",\n    \"description\": \"Add and change log pipeline configurations.\",\n    \"resource\": \"Logs\",\n    \"test\": {\n      \"endpoint\": \"/v1/logs/config/pipelines/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [200, 400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"logs_write_archives\",\n    \"title\": \"Logs Write Archives\",\n    \"description\": \"Add and edit Log Archives.\",\n    \"resource\": \"Logs\",\n    \"test\": {\n      \"endpoint\": \"/v2/logs/config/archives/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [200, 400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"logs_generate_metrics\",\n    \"title\": \"Logs Generate Metrics\",\n    \"description\": \"Create custom metrics from logs.\",\n    \"resource\": \"Logs\",\n    \"test\": {\n      \"endpoint\": \"/v2/logs/config/metrics\",\n      \"method\": \"POST\",\n      \"valid_statuses\": [200, 400, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"monitors_downtime\",\n    \"title\": \"Manage Downtimes\",\n    \"description\": \"Set downtimes to suppress alerts from any monitor in an organization.\",\n    \"resource\": \"Monitors\",\n    \"test\": {\n      \"endpoint\": \"/v1/downtime\",\n      \"method\": \"POST\",\n      \"valid_statuses\": [200, 400, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"logs_read_data\",\n    \"title\": \"Logs Read Data\",\n    \"description\": \"Read log data. In order to read log data, a user must have both this permission and Logs Read Index Data.\",\n    \"resource\": \"Logs\",\n    \"test\": {\n      \"endpoint\": \"/v2/logs/events\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"logs_read_archives\",\n    \"title\": \"Logs Read Archives\",\n    \"description\": \"Read Log Archives location and use it for rehydration.\",\n    \"resource\": \"Logs\",\n    \"test\": {\n      \"endpoint\": \"/v2/logs/config/archives\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 400, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"security_monitoring_rules_read\",\n    \"title\": \"Security Rules Read\",\n    \"description\": \"Read Detection Rules.\",\n    \"resource\": \"Security Monitoring\",\n    \"test\": {\n      \"endpoint\": \"/v2/cloud_security_management/custom_frameworks/must/not-exist\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 400, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"security_monitoring_rules_write\",\n    \"title\": \"Security Rules Write\",\n    \"description\": \"Create and edit Detection Rules.\",\n    \"resource\": \"Security Monitoring\",\n    \"test\": {\n      \"endpoint\": \"/v2/security_monitoring/rules\",\n      \"method\": \"POST\",\n      \"valid_statuses\": [200, 400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"security_monitoring_signals_read\",\n    \"title\": \"Security Signals Read\",\n    \"description\": \"View Security Signals.\",\n    \"resource\": \"Security Monitoring\",\n    \"test\": {\n      \"endpoint\": \"/v2/security_monitoring/signals\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"security_monitoring_signals_write\",\n    \"title\": \"Security Signals Write\",\n    \"description\": \"Modify Security Signals.\",\n    \"resource\": \"Security Monitoring\",\n    \"test\": {\n      \"endpoint\": \"/v1/security_analytics/signals/must-not-exist/add_to_incident\",\n      \"method\": \"PATCH\",\n      \"valid_statuses\": [200, 400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"user_access_invite\",\n    \"title\": \"User Access Invite\",\n    \"description\": \"Invite other users to your organization.\",\n    \"resource\": \"Users\",\n    \"test\": {\n      \"endpoint\": \"/v2/user_invitations/does-not-exist\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"title\": \"User App Keys\",\n    \"name\": \"user_app_keys\",\n    \"description\": \"View and manage Application Keys owned by the user.\",\n    \"resource\": \"Key Management\",\n    \"test\": {\n      \"endpoint\": \"/v2/current_user/application_keys/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [200, 400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"org_app_keys_read\",\n    \"title\": \"Org App Keys Read\",\n    \"description\": \"View Application Keys owned by all users in the organization.\",\n    \"resource\": \"Key Management\",\n    \"test\": {\n      \"endpoint\": \"/v2/application_keys\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"org_app_keys_write\",\n    \"title\": \"Org App Keys Write\",\n    \"description\": \"Manage Application Keys owned by all users in the organization.\",\n    \"resource\": \"Key Management\",\n    \"test\": {\n      \"endpoint\": \"/v2/application_keys/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [200, 400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"user_access_manage\",\n    \"title\": \"User Access Manage\",\n    \"description\": \"Disable users, manage user roles, manage SAML-to-role mappings, and configure logs restriction queries.\",\n    \"resource\": \"Users\",\n    \"test\": {\n      \"endpoint\": \"/v2/users/does-not-exist\",\n      \"method\": \"PATCH\",\n      \"valid_statuses\": [200, 400, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"synthetics_private_location_read\",\n    \"title\": \"Synthetics Private Locations Read\",\n    \"description\": \"View, search, and use Synthetics private locations.\",\n    \"resource\": \"Synthetics\",\n    \"test\": {\n      \"endpoint\": \"/v1/synthetics/private-locations/does-not-exit\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"synthetics_private_location_write\",\n    \"title\": \"Synthetics Private Locations Write\",\n    \"description\": \"Create and delete private locations in addition to having access to the associated installation guidelines.\",\n    \"resource\": \"Synthetics\",\n    \"test\": {\n      \"endpoint\": \"/v1/synthetics/private-locations/does-not-exit\",\n      \"method\": \"PUT\",\n      \"valid_statuses\": [200, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"usage_read\",\n    \"title\": \"Usage Read\",\n    \"description\": \"View your organization's usage and usage attribution.\",\n    \"resource\": \"Usage Metering\",\n    \"test\": {\n      \"endpoint\": \"/v2/usage/hourly_usage\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 400, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"metric_tags_write\",\n    \"title\": \"Metric Tags Write\",\n    \"description\": \"Edit and save tag configurations for custom metrics.\",\n    \"resource\": \"Metrics\",\n    \"test\": {\n      \"endpoint\": \"/v2/metrics/does-not-exit/tags\",\n      \"method\": \"POST\",\n      \"valid_statuses\": [200, 400, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"audit_logs_read\",\n    \"title\": \"Audit Trail Read\",\n    \"description\": \"View Audit Trail in your organization.\",\n    \"resource\": \"Audit\",\n    \"test\": {\n      \"endpoint\": \"/v2/audit/events\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"api_keys_read\",\n    \"title\": \"API Keys Read\",\n    \"description\": \"List and retrieve the key values of all API Keys in your organization.\",\n    \"resource\": \"Key Management\",\n    \"test\": {\n      \"endpoint\": \"/v2/api_keys\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"api_keys_write\",\n    \"title\": \"API Keys Write\",\n    \"description\": \"Create and rename API Keys for your organization.\",\n    \"resource\": \"Key Management\",\n    \"test\": {\n      \"endpoint\": \"/v2/api_keys/does-not-exist\",\n      \"method\": \"PATCH\",\n      \"valid_statuses\": [200, 400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"synthetics_global_variable_read\",\n    \"title\": \"Synthetics Global Variable Read\",\n    \"description\": \"View, search, and use Synthetics global variables.\",\n    \"resource\": \"Synthetics\",\n    \"test\": {\n      \"endpoint\": \"/v1/synthetics/variables\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"synthetics_global_variable_write\",\n    \"title\": \"Synthetics Global Variable Write\",\n    \"description\": \"Create, edit, and delete global variables for Synthetics.\",\n    \"resource\": \"Synthetics\",\n    \"test\": {\n      \"endpoint\": \"/v1/synthetics/variables\",\n      \"method\": \"POST\",\n      \"valid_statuses\": [200, 400, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"synthetics_read\",\n    \"title\": \"Synthetics Read\",\n    \"description\": \"List and view configured Synthetic tests and test results.\",\n    \"resource\": \"Synthetics\",\n    \"test\": {\n      \"endpoint\": \"/v1/synthetics/tests\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"synthetics_write\",\n    \"title\": \"Synthetics Write\",\n    \"description\": \"Create, edit, and delete Synthetic tests.\",\n    \"resource\": \"Synthetics\",\n    \"test\": {\n      \"endpoint\": \"/v1/synthetics/tests/mobile/does-not-exit\",\n      \"method\": \"PUT\",\n      \"valid_statuses\": [200, 400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"synthetics_default_settings_read\",\n    \"title\": \"Synthetics Default Settings Read\",\n    \"description\": \"View the default settings for Synthetic Monitoring.\",\n    \"resource\": \"Synthetics\",\n    \"test\": {\n      \"endpoint\": \"/v1/synthetics/settings/default_locations\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"service_account_write\",\n    \"title\": \"Service Account Write\",\n    \"description\": \"Create, disable, and use Service Accounts in your organization.\",\n    \"resource\": \"Service Accounts\",\n    \"test\": {\n      \"endpoint\": \"/v2/service_accounts/does-not-exist/application_keys\",\n      \"method\": \"POST\",\n      \"valid_statuses\": [200, 400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"apm_read\",\n    \"title\": \"APM Read\",\n    \"description\": \"Read and query APM and Trace Analytics.\",\n    \"resource\": \"APM\",\n    \"test\": {\n      \"endpoint\": \"/v2/apm/config/metrics\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"apm_retention_filter_read\",\n    \"title\": \"APM Retention Filters Read\",\n    \"description\": \"Read trace retention filters. A user with this permission can view the retention filters page, list of filters, their statistics, and creation info.\",\n    \"resource\": \"APM\",\n    \"test\": {\n      \"endpoint\": \"/v2/apm/config/retention-filters/should-not-exist\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"apm_retention_filter_write\",\n    \"title\": \"APM Retention Filters Write\",\n    \"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.\",\n    \"resource\": \"APM\",\n    \"test\": {\n      \"endpoint\": \"/v2/apm/config/retention-filters/should-not-exit\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"rum_apps_write\",\n    \"title\": \"RUM Apps Write\",\n    \"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.\",\n    \"resource\": \"RUM\",\n    \"test\": {\n      \"endpoint\": \"/v2/rum/applications/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"data_scanner_read\",\n    \"title\": \"Data Scanner Read\",\n    \"description\": \"View Sensitive Data Scanner configurations and scanning results.\",\n    \"resource\": \"Sensitive Data Scanner\",\n    \"test\": {\n      \"endpoint\": \"/v2/sensitive-data-scanner/config/standard-patterns\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"data_scanner_write\",\n    \"title\": \"Data Scanner Write\",\n    \"description\": \"Edit Sensitive Data Scanner configurations.\",\n    \"resource\": \"Sensitive Data Scanner\",\n    \"test\": {\n      \"endpoint\": \"/v2/sensitive-data-scanner/config/groups/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"org_management\",\n    \"title\": \"Org Management\",\n    \"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.\",\n    \"resource\": \"Organizations\",\n    \"test\": {\n      \"endpoint\": \"/v1/org\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"security_monitoring_filters_read\",\n    \"title\": \"Security Filters Read\",\n    \"description\": \"Read Security Filters.\",\n    \"resource\": \"Security Monitoring\",\n    \"test\": {\n      \"endpoint\": \"/v2/security_monitoring/configuration/security_filters\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"security_monitoring_filters_write\",\n    \"title\": \"Security Filters Write\",\n    \"description\": \"Create, edit, and delete Security Filters.\",\n    \"resource\": \"Security Monitoring\",\n    \"test\": {\n      \"endpoint\": \"/v2/security_monitoring/configuration/security_filters/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"incident_read\",\n    \"title\": \"Incidents Read\",\n    \"description\": \"View incidents in Datadog.\",\n    \"resource\": \"Incidents\",\n    \"test\": {\n      \"endpoint\": \"/v2/incidents\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"incident_write\",\n    \"title\": \"Incidents Write\",\n    \"description\": \"Create, view, and manage incidents in Datadog.\",\n    \"resource\": \"Incidents\",\n    \"test\": {\n      \"endpoint\": \"/v2/incidents/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"incident_settings_write\",\n    \"title\": \"Incident Settings Write\",\n    \"description\": \"Configure Incident Settings.\",\n    \"resource\": \"Incidents\",\n    \"test\": {\n      \"endpoint\": \"/v2/incidents/config/types/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"rum_apps_read\",\n    \"title\": \"RUM Apps Read\",\n    \"description\": \"View RUM Applications data.\",\n    \"resource\": \"RUM\",\n    \"test\": {\n      \"endpoint\": \"/v2/rum/applications\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"security_monitoring_notification_profiles_read\",\n    \"title\": \"Security Notification Rules Read\",\n    \"description\": \"Read Notification Rules.\",\n    \"resource\": \"Security Monitoring\",\n    \"test\": {\n      \"endpoint\": \"/v2/security/signals/notification_rules\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"security_monitoring_notification_profiles_write\",\n    \"title\": \"Security Notification Rules Write\",\n    \"description\": \"Create, edit, and delete Notification Rules.\",\n    \"resource\": \"Security Monitoring\",\n    \"test\": {\n      \"endpoint\": \"/v2/security/signals/notification_rules/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"apm_generate_metrics\",\n    \"title\": \"APM Generate Metrics\",\n    \"description\": \"Create custom metrics from spans.\",\n    \"resource\": \"APM\",\n    \"test\": {\n      \"endpoint\": \"/v2/apm/config/metrics/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"apm_pipelines_write\",\n    \"title\": \"APM Pipelines Write\",\n    \"description\": \"Add and change APM pipeline configurations.\",\n    \"resource\": \"APM\",\n    \"test\": {\n      \"endpoint\": \"/v2/apm/config/retention-filters/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"apm_pipelines_read\",\n    \"title\": \"APM Pipelines Read\",\n    \"description\": \"View APM pipeline configurations.\",\n    \"resource\": \"APM\",\n    \"test\": {\n      \"endpoint\": \"/v2/apm/config/retention-filters\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"observability_pipelines_read\",\n    \"title\": \"Observability Pipelines Read\",\n    \"description\": \"View pipelines in your organization.\",\n    \"resource\": \"Observability Pipelines\",\n    \"test\": {\n      \"endpoint\": \"/v2/remote_config/products/obs_pipelines/pipelines\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"workflows_read\",\n    \"title\": \"Workflows Read\",\n    \"description\": \"View workflows.\",\n    \"resource\": \"Workflows\",\n    \"test\": {\n      \"endpoint\": \"/v2/workflows/does-not-exist\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 400, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"workflows_write\",\n    \"title\": \"Workflows Write\",\n    \"description\": \"Create, edit, and delete workflows.\",\n    \"resource\": \"Workflows\",\n    \"test\": {\n      \"endpoint\": \"/v2/workflows/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"workflows_run\",\n    \"title\": \"Workflows Run\",\n    \"description\": \"Run workflows.\",\n    \"resource\": \"Workflows\",\n    \"test\": {\n      \"endpoint\": \"/v2/workflows/should-not-exist/instances\",\n      \"method\": \"POST\",\n      \"valid_statuses\": [400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"connections_read\",\n    \"title\": \"Connections Read\",\n    \"description\": \"List and view available connections. Connections contain secrets that cannot be revealed.\",\n    \"resource\": \"Connections\",\n    \"test\": {\n      \"endpoint\": \"/v2/actions/connections/does-not-exist\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 400, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"connections_write\",\n    \"title\": \"Connections Write\",\n    \"description\": \"Create and delete connections.\",\n    \"resource\": \"Connections\",\n    \"test\": {\n      \"endpoint\": \"/v2/actions/connections/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"notebooks_read\",\n    \"title\": \"Notebooks Read\",\n    \"description\": \"View notebooks.\",\n    \"resource\": \"Notebooks\",\n    \"test\": {\n      \"endpoint\": \"/v1/notebooks\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"notebooks_write\",\n    \"title\": \"Notebooks Write\",\n    \"description\": \"Create and change notebooks.\",\n    \"resource\": \"Notebooks\",\n    \"test\": {\n      \"endpoint\": \"/v1/notebooks/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"aws_configurations_manage\",\n    \"title\": \"AWS Configurations Manage\",\n    \"description\": \"Add or remove but not edit AWS integration configurations.\",\n    \"resource\": \"Integrations\",\n    \"test\": {\n      \"endpoint\": \"/v2/integration/aws/accounts/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"azure_configurations_manage\",\n    \"title\": \"Azure Configurations Manage\",\n    \"description\": \"Add or remove but not edit Azure integration configurations.\",\n    \"resource\": \"Integrations\",\n    \"test\": {\n      \"endpoint\": \"/v1/integration/azure\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"gcp_configurations_manage\",\n    \"title\": \"GCP Configurations Manage\",\n    \"description\": \"Add or remove but not edit GCP integration configurations.\",\n    \"resource\": \"Integrations\",\n    \"test\": {\n      \"endpoint\": \"/v2/integration/gcp/accounts/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"manage_integrations\",\n    \"title\": \"Integrations Manage\",\n    \"description\": \"Install, uninstall, and configure integrations.\",\n    \"resource\": \"Integrations\",\n    \"test\": {\n      \"endpoint\": \"/v2/integrations/cloudflare/accounts/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"slos_read\",\n    \"title\": \"SLOs Read\",\n    \"description\": \"View SLOs and status corrections.\",\n    \"resource\": \"SLOs\",\n    \"test\": {\n      \"endpoint\": \"/v1/slo\",\n      \"method\": \"GET\",\n      \"valid_statuses\": [200, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"slos_write\",\n    \"title\": \"SLOs Write\",\n    \"description\": \"Create, edit, and delete SLOs.\",\n    \"resource\": \"SLOs\",\n    \"test\": {\n      \"endpoint\": \"/v1/slo/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"slos_corrections\",\n    \"title\": \"SLOs Status Corrections\",\n    \"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.\",\n    \"resource\": \"SLOs\",\n    \"test\": {\n      \"endpoint\": \"/v1/slo/correction\",\n      \"method\": \"POST\",\n      \"valid_statuses\": [400, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"monitor_config_policy_write\",\n    \"title\": \"Monitor Configuration Policy Write\",\n    \"description\": \"Create, update, and delete monitor configuration policies.\",\n    \"resource\": \"Monitors\",\n    \"test\": {\n      \"endpoint\": \"/v2/monitor/policy/does-not-exist\",\n      \"method\": \"DELETE\",\n      \"valid_statuses\": [400, 404, 429],\n      \"invalid_statuses\": [403]\n    }\n  },\n  {\n    \"name\": \"metrics_write\",\n    \"title\": \"Submit Metrics\",\n    \"description\": \"Submit custom metrics to Datadog.\",\n    \"resource\": \"Metrics\"\n  },\n  {\n    \"name\": \"logs_write\",\n    \"title\": \"Submit Logs\",\n    \"description\": \"Send logs to Datadog for indexing and processing.\",\n    \"resource\": \"Logs\"\n  },\n  {\n    \"name\": \"events_write\",\n    \"title\": \"Submit Events\",\n    \"description\": \"Post events to the Datadog event stream.\",\n    \"resource\": \"Events\"\n  },\n  {\n    \"name\": \"service_checks_write\",\n    \"title\": \"Submit Service Checks\",\n    \"description\": \"Send service check statuses to Datadog.\",\n    \"resource\": \"Service Checks\"\n  }\n]\n"
  },
  {
    "path": "pkg/analyzer/analyzers/digitalocean/digitalocean.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go digitalocean\n\npackage digitalocean\n\nimport (\n\t\"bytes\"\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\n// to avoid rate limiting\nconst MAX_CONCURRENT_TESTS = 10\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeDigitalOcean }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credInfo[\"key\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"missing key in credInfo\")\n\t}\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeDigitalOcean,\n\t\tMetadata:     nil,\n\t\tBindings:     make([]analyzers.Binding, len(info.Permissions)),\n\t}\n\n\tresource := analyzers.Resource{\n\t\tName:               info.User.Name,\n\t\tFullyQualifiedName: info.User.UUID,\n\t\tType:               \"User\",\n\t\tMetadata: map[string]any{\n\t\t\t\"email\":  info.User.Email,\n\t\t\t\"status\": info.User.Status,\n\t\t},\n\t}\n\n\tfor idx, permission := range info.Permissions {\n\t\tresult.Bindings[idx] = analyzers.Binding{\n\t\t\tResource: resource,\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: permission,\n\t\t\t},\n\t\t}\n\t}\n\n\treturn &result\n}\n\n//go:embed scopes.json\nvar scopesConfig []byte\n\ntype HttpStatusTest struct {\n\tEndpoint        string      `json:\"endpoint\"`\n\tMethod          string      `json:\"method\"`\n\tPayload         interface{} `json:\"payload\"`\n\tValidStatuses   []int       `json:\"valid_status_code\"`\n\tInvalidStatuses []int       `json:\"invalid_status_code\"`\n}\n\nfunc StatusContains(status int, vals []int) bool {\n\tfor _, v := range vals {\n\t\tif status == v {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (h *HttpStatusTest) RunTest(cfg *config.Config, headers map[string]string) (bool, error) {\n\t// If body data, marshal to JSON\n\tvar data io.Reader\n\tif h.Payload != nil {\n\t\tjsonData, err := json.Marshal(h.Payload)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tdata = bytes.NewBuffer(jsonData)\n\t}\n\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\treq, err := http.NewRequest(h.Method, h.Endpoint, data)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Add custom headers if provided\n\tfor key, value := range headers {\n\t\treq.Header.Set(key, value)\n\t}\n\n\t// Execute HTTP Request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer resp.Body.Close()\n\n\t// Check response status code\n\tswitch {\n\tcase StatusContains(resp.StatusCode, h.ValidStatuses):\n\t\treturn true, nil\n\tcase StatusContains(resp.StatusCode, h.InvalidStatuses):\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, errors.New(\"error checking response status code\")\n\t}\n}\n\ntype Scope struct {\n\tName     string         `json:\"name\"`\n\tHttpTest HttpStatusTest `json:\"test\"`\n}\n\nfunc readInScopes() ([]Scope, error) {\n\tvar scopes []Scope\n\tif err := json.Unmarshal(scopesConfig, &scopes); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn scopes, nil\n}\n\nfunc checkPermissions(cfg *config.Config, key string) ([]string, error) {\n\tscopes, err := readInScopes()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"reading in scopes: %w\", err)\n\t}\n\n\tvar (\n\t\tpermissions = make([]string, 0, len(scopes))\n\t\tmu          sync.Mutex\n\t\twg          sync.WaitGroup\n\t\tslots       = make(chan struct{}, MAX_CONCURRENT_TESTS)\n\t\terrCh       = make(chan error, 1)\n\t)\n\n\tfor _, scope := range scopes {\n\t\twg.Add(1)\n\t\tgo func(scope Scope) {\n\t\t\tdefer wg.Done()\n\n\t\t\t// acquire a slot\n\t\t\tslots <- struct{}{}\n\t\t\tdefer func() { <-slots }()\n\n\t\t\tstatus, err := scope.HttpTest.RunTest(cfg, map[string]string{\"Authorization\": \"Bearer \" + key})\n\t\t\tif err != nil {\n\t\t\t\t// send first error and ignore the rest\n\t\t\t\tselect {\n\t\t\t\tcase errCh <- fmt.Errorf(\"Scope %s: %w\", scope.Name, err):\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif status {\n\t\t\t\tmu.Lock()\n\t\t\t\tpermissions = append(permissions, scope.Name)\n\t\t\t\tmu.Unlock()\n\t\t\t}\n\t\t}(scope)\n\t}\n\n\t// wait for all goroutines to finish or an error to occur\n\tgo func() {\n\t\twg.Wait()\n\t\tclose(errCh)\n\t}()\n\n\tif err := <-errCh; err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn permissions, nil\n}\n\ntype user struct {\n\tEmail  string `json:\"email\"`\n\tName   string `json:\"name\"`\n\tUUID   string `json:\"uuid\"`\n\tStatus string `json:\"status\"`\n}\n\ntype userJSON struct {\n\tAccount user `json:\"account\"`\n}\n\nfunc getUser(cfg *config.Config, token string) (*user, error) {\n\t// Create new HTTP request\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(\"GET\", \"https://api.digitalocean.com/v2/account\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Add custom headers if provided\n\treq.Header.Set(\"Authorization\", \"Bearer \"+token)\n\n\t// Execute HTTP Request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\t// Decode response body\n\t\tvar response userJSON\n\t\terr = json.NewDecoder(resp.Body).Decode(&response)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn &response.Account, nil\n\tcase http.StatusUnauthorized:\n\t\treturn nil, errors.New(\"invalid token\")\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\ntype SecretInfo struct {\n\tUser        user\n\tPermissions []string\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid DigitalOcean API key\\n\\n\")\n\n\tcolor.Yellow(\"[i] User: %s (%s)\\n\\n\", info.User.Name, info.User.Email)\n\n\tprintPermissions(info.Permissions)\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\tvar info = &SecretInfo{}\n\n\tuser, err := getUser(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinfo.User = *user\n\n\tpermissions, err := checkPermissions(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(permissions) == 0 {\n\t\treturn nil, fmt.Errorf(\"invalid DigitalOcean API key\")\n\t}\n\n\tinfo.Permissions = permissions\n\n\treturn info, nil\n}\n\nfunc printPermissions(permissions []string) {\n\tcolor.Yellow(\"[i] Permissions:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Permission\"})\n\tfor _, permission := range permissions {\n\t\tt.AppendRow(table.Row{color.GreenString(permission)})\n\t}\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/digitalocean/digitalocean_test.go",
    "content": "package digitalocean\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*15)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid digitalocean key\",\n\t\t\tkey:     testSecrets.MustGetField(\"DIGITALOCEAN_PERSONAL_ACCESS_TOKEN\"),\n\t\t\twant:    string(expectedOutput),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/digitalocean/expected_output.json",
    "content": "{\"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}"
  },
  {
    "path": "pkg/analyzer/analyzers/digitalocean/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage digitalocean\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    OneClickRead Permission = iota\n    OneClickCreate Permission = iota\n    ActionRead Permission = iota\n    AppRead Permission = iota\n    AppCreate Permission = iota\n    AppUpdate Permission = iota\n    AppDelete Permission = iota\n    BillingRead Permission = iota\n    BlockStorageRead Permission = iota\n    BlockStorageCreate Permission = iota\n    BlockStorageDelete Permission = iota\n    CdnEndpointRead Permission = iota\n    CdnEndpointCreate Permission = iota\n    CdnEndpointUpdate Permission = iota\n    CdnEndpointDelete Permission = iota\n    CertificateRead Permission = iota\n    CertificateCreate Permission = iota\n    CertificateDelete Permission = iota\n    ContainerRegistryRead Permission = iota\n    ContainerRegistryCreate Permission = iota\n    DatabaseRead Permission = iota\n    DatabaseCreate Permission = iota\n    DatabaseUpdate Permission = iota\n    DatabaseDelete Permission = iota\n    DomainRecordRead Permission = iota\n    DomainRecordCreate Permission = iota\n    DomainRecordUpdate Permission = iota\n    DomainRecordDelete Permission = iota\n    DomainRead Permission = iota\n    DomainCreate Permission = iota\n    DomainDelete Permission = iota\n    DropletRead Permission = iota\n    DropletCreate Permission = iota\n    DropletDelete Permission = iota\n    DropletAutoscalePoolRead Permission = iota\n    DropletAutoscalePoolCreate Permission = iota\n    DropletAutoscalePoolUpdate Permission = iota\n    DropletAutoscalePoolDelete Permission = iota\n    FirewallRead Permission = iota\n    FirewallCreate Permission = iota\n    FirewallUpdate Permission = iota\n    FirewallDelete Permission = iota\n    FloatingIpRead Permission = iota\n    FloatingIpCreate Permission = iota\n    FloatingIpDelete Permission = iota\n    NamespaceRead Permission = iota\n    NamespaceCreate Permission = iota\n    NamespaceDelete Permission = iota\n    GenaiAgentRead Permission = iota\n    GenaiAgentCreate Permission = iota\n    GenaiAgentUpdate Permission = iota\n    GenaiAgentDelete Permission = iota\n    ImageRead Permission = iota\n    ImageCreate Permission = iota\n    ImageUpdate Permission = iota\n    ImageDelete Permission = iota\n    KubernetesRead Permission = iota\n    KubernetesCreate Permission = iota\n    KubernetesUpdate Permission = iota\n    KubernetesDelete Permission = iota\n    LoadBalancerRead Permission = iota\n    LoadBalancerCreate Permission = iota\n    LoadBalancerUpdate Permission = iota\n    LoadBalancerDelete Permission = iota\n    MonitoringRead Permission = iota\n    MonitoringCreate Permission = iota\n    MonitoringUpdate Permission = iota\n    MonitoringDelete Permission = iota\n    ProjectRead Permission = iota\n    ProjectCreate Permission = iota\n    ProjectUpdate Permission = iota\n    ProjectDelete Permission = iota\n    RegionRead Permission = iota\n    ReservedIpRead Permission = iota\n    ReservedIpCreate Permission = iota\n    ReservedIpDelete Permission = iota\n    SizeRead Permission = iota\n    SnapshotRead Permission = iota\n    SnapshotDelete Permission = iota\n    SshKeyRead Permission = iota\n    SshKeyCreate Permission = iota\n    SshKeyUpdate Permission = iota\n    SshKeyDelete Permission = iota\n    TagRead Permission = iota\n    TagCreate Permission = iota\n    TagDelete Permission = iota\n    UptimeRead Permission = iota\n    UptimeCreate Permission = iota\n    UptimeUpdate Permission = iota\n    UptimeDelete Permission = iota\n    VpcPeeringRead Permission = iota\n    VpcPeeringCreate Permission = iota\n    VpcPeeringUpdate Permission = iota\n    VpcPeeringDelete Permission = iota\n    VpcRead Permission = iota\n    VpcCreate Permission = iota\n    VpcUpdate Permission = iota\n    VpcDelete Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        OneClickRead: \"one_click:read\",\n        OneClickCreate: \"one_click:create\",\n        ActionRead: \"action:read\",\n        AppRead: \"app:read\",\n        AppCreate: \"app:create\",\n        AppUpdate: \"app:update\",\n        AppDelete: \"app:delete\",\n        BillingRead: \"billing:read\",\n        BlockStorageRead: \"block_storage:read\",\n        BlockStorageCreate: \"block_storage:create\",\n        BlockStorageDelete: \"block_storage:delete\",\n        CdnEndpointRead: \"cdn_endpoint:read\",\n        CdnEndpointCreate: \"cdn_endpoint:create\",\n        CdnEndpointUpdate: \"cdn_endpoint:update\",\n        CdnEndpointDelete: \"cdn_endpoint:delete\",\n        CertificateRead: \"certificate:read\",\n        CertificateCreate: \"certificate:create\",\n        CertificateDelete: \"certificate:delete\",\n        ContainerRegistryRead: \"container_registry:read\",\n        ContainerRegistryCreate: \"container_registry:create\",\n        DatabaseRead: \"database:read\",\n        DatabaseCreate: \"database:create\",\n        DatabaseUpdate: \"database:update\",\n        DatabaseDelete: \"database:delete\",\n        DomainRecordRead: \"domain_record:read\",\n        DomainRecordCreate: \"domain_record:create\",\n        DomainRecordUpdate: \"domain_record:update\",\n        DomainRecordDelete: \"domain_record:delete\",\n        DomainRead: \"domain:read\",\n        DomainCreate: \"domain:create\",\n        DomainDelete: \"domain:delete\",\n        DropletRead: \"droplet:read\",\n        DropletCreate: \"droplet:create\",\n        DropletDelete: \"droplet:delete\",\n        DropletAutoscalePoolRead: \"droplet_autoscale_pool:read\",\n        DropletAutoscalePoolCreate: \"droplet_autoscale_pool:create\",\n        DropletAutoscalePoolUpdate: \"droplet_autoscale_pool:update\",\n        DropletAutoscalePoolDelete: \"droplet_autoscale_pool:delete\",\n        FirewallRead: \"firewall:read\",\n        FirewallCreate: \"firewall:create\",\n        FirewallUpdate: \"firewall:update\",\n        FirewallDelete: \"firewall:delete\",\n        FloatingIpRead: \"floating_ip:read\",\n        FloatingIpCreate: \"floating_ip:create\",\n        FloatingIpDelete: \"floating_ip:delete\",\n        NamespaceRead: \"namespace:read\",\n        NamespaceCreate: \"namespace:create\",\n        NamespaceDelete: \"namespace:delete\",\n        GenaiAgentRead: \"genai_agent:read\",\n        GenaiAgentCreate: \"genai_agent:create\",\n        GenaiAgentUpdate: \"genai_agent:update\",\n        GenaiAgentDelete: \"genai_agent:delete\",\n        ImageRead: \"image:read\",\n        ImageCreate: \"image:create\",\n        ImageUpdate: \"image:update\",\n        ImageDelete: \"image:delete\",\n        KubernetesRead: \"kubernetes:read\",\n        KubernetesCreate: \"kubernetes:create\",\n        KubernetesUpdate: \"kubernetes:update\",\n        KubernetesDelete: \"kubernetes:delete\",\n        LoadBalancerRead: \"load_balancer:read\",\n        LoadBalancerCreate: \"load_balancer:create\",\n        LoadBalancerUpdate: \"load_balancer:update\",\n        LoadBalancerDelete: \"load_balancer:delete\",\n        MonitoringRead: \"monitoring:read\",\n        MonitoringCreate: \"monitoring:create\",\n        MonitoringUpdate: \"monitoring:update\",\n        MonitoringDelete: \"monitoring:delete\",\n        ProjectRead: \"project:read\",\n        ProjectCreate: \"project:create\",\n        ProjectUpdate: \"project:update\",\n        ProjectDelete: \"project:delete\",\n        RegionRead: \"region:read\",\n        ReservedIpRead: \"reserved_ip:read\",\n        ReservedIpCreate: \"reserved_ip:create\",\n        ReservedIpDelete: \"reserved_ip:delete\",\n        SizeRead: \"size:read\",\n        SnapshotRead: \"snapshot:read\",\n        SnapshotDelete: \"snapshot:delete\",\n        SshKeyRead: \"ssh_key:read\",\n        SshKeyCreate: \"ssh_key:create\",\n        SshKeyUpdate: \"ssh_key:update\",\n        SshKeyDelete: \"ssh_key:delete\",\n        TagRead: \"tag:read\",\n        TagCreate: \"tag:create\",\n        TagDelete: \"tag:delete\",\n        UptimeRead: \"uptime:read\",\n        UptimeCreate: \"uptime:create\",\n        UptimeUpdate: \"uptime:update\",\n        UptimeDelete: \"uptime:delete\",\n        VpcPeeringRead: \"vpc_peering:read\",\n        VpcPeeringCreate: \"vpc_peering:create\",\n        VpcPeeringUpdate: \"vpc_peering:update\",\n        VpcPeeringDelete: \"vpc_peering:delete\",\n        VpcRead: \"vpc:read\",\n        VpcCreate: \"vpc:create\",\n        VpcUpdate: \"vpc:update\",\n        VpcDelete: \"vpc:delete\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"one_click:read\": OneClickRead,\n        \"one_click:create\": OneClickCreate,\n        \"action:read\": ActionRead,\n        \"app:read\": AppRead,\n        \"app:create\": AppCreate,\n        \"app:update\": AppUpdate,\n        \"app:delete\": AppDelete,\n        \"billing:read\": BillingRead,\n        \"block_storage:read\": BlockStorageRead,\n        \"block_storage:create\": BlockStorageCreate,\n        \"block_storage:delete\": BlockStorageDelete,\n        \"cdn_endpoint:read\": CdnEndpointRead,\n        \"cdn_endpoint:create\": CdnEndpointCreate,\n        \"cdn_endpoint:update\": CdnEndpointUpdate,\n        \"cdn_endpoint:delete\": CdnEndpointDelete,\n        \"certificate:read\": CertificateRead,\n        \"certificate:create\": CertificateCreate,\n        \"certificate:delete\": CertificateDelete,\n        \"container_registry:read\": ContainerRegistryRead,\n        \"container_registry:create\": ContainerRegistryCreate,\n        \"database:read\": DatabaseRead,\n        \"database:create\": DatabaseCreate,\n        \"database:update\": DatabaseUpdate,\n        \"database:delete\": DatabaseDelete,\n        \"domain_record:read\": DomainRecordRead,\n        \"domain_record:create\": DomainRecordCreate,\n        \"domain_record:update\": DomainRecordUpdate,\n        \"domain_record:delete\": DomainRecordDelete,\n        \"domain:read\": DomainRead,\n        \"domain:create\": DomainCreate,\n        \"domain:delete\": DomainDelete,\n        \"droplet:read\": DropletRead,\n        \"droplet:create\": DropletCreate,\n        \"droplet:delete\": DropletDelete,\n        \"droplet_autoscale_pool:read\": DropletAutoscalePoolRead,\n        \"droplet_autoscale_pool:create\": DropletAutoscalePoolCreate,\n        \"droplet_autoscale_pool:update\": DropletAutoscalePoolUpdate,\n        \"droplet_autoscale_pool:delete\": DropletAutoscalePoolDelete,\n        \"firewall:read\": FirewallRead,\n        \"firewall:create\": FirewallCreate,\n        \"firewall:update\": FirewallUpdate,\n        \"firewall:delete\": FirewallDelete,\n        \"floating_ip:read\": FloatingIpRead,\n        \"floating_ip:create\": FloatingIpCreate,\n        \"floating_ip:delete\": FloatingIpDelete,\n        \"namespace:read\": NamespaceRead,\n        \"namespace:create\": NamespaceCreate,\n        \"namespace:delete\": NamespaceDelete,\n        \"genai_agent:read\": GenaiAgentRead,\n        \"genai_agent:create\": GenaiAgentCreate,\n        \"genai_agent:update\": GenaiAgentUpdate,\n        \"genai_agent:delete\": GenaiAgentDelete,\n        \"image:read\": ImageRead,\n        \"image:create\": ImageCreate,\n        \"image:update\": ImageUpdate,\n        \"image:delete\": ImageDelete,\n        \"kubernetes:read\": KubernetesRead,\n        \"kubernetes:create\": KubernetesCreate,\n        \"kubernetes:update\": KubernetesUpdate,\n        \"kubernetes:delete\": KubernetesDelete,\n        \"load_balancer:read\": LoadBalancerRead,\n        \"load_balancer:create\": LoadBalancerCreate,\n        \"load_balancer:update\": LoadBalancerUpdate,\n        \"load_balancer:delete\": LoadBalancerDelete,\n        \"monitoring:read\": MonitoringRead,\n        \"monitoring:create\": MonitoringCreate,\n        \"monitoring:update\": MonitoringUpdate,\n        \"monitoring:delete\": MonitoringDelete,\n        \"project:read\": ProjectRead,\n        \"project:create\": ProjectCreate,\n        \"project:update\": ProjectUpdate,\n        \"project:delete\": ProjectDelete,\n        \"region:read\": RegionRead,\n        \"reserved_ip:read\": ReservedIpRead,\n        \"reserved_ip:create\": ReservedIpCreate,\n        \"reserved_ip:delete\": ReservedIpDelete,\n        \"size:read\": SizeRead,\n        \"snapshot:read\": SnapshotRead,\n        \"snapshot:delete\": SnapshotDelete,\n        \"ssh_key:read\": SshKeyRead,\n        \"ssh_key:create\": SshKeyCreate,\n        \"ssh_key:update\": SshKeyUpdate,\n        \"ssh_key:delete\": SshKeyDelete,\n        \"tag:read\": TagRead,\n        \"tag:create\": TagCreate,\n        \"tag:delete\": TagDelete,\n        \"uptime:read\": UptimeRead,\n        \"uptime:create\": UptimeCreate,\n        \"uptime:update\": UptimeUpdate,\n        \"uptime:delete\": UptimeDelete,\n        \"vpc_peering:read\": VpcPeeringRead,\n        \"vpc_peering:create\": VpcPeeringCreate,\n        \"vpc_peering:update\": VpcPeeringUpdate,\n        \"vpc_peering:delete\": VpcPeeringDelete,\n        \"vpc:read\": VpcRead,\n        \"vpc:create\": VpcCreate,\n        \"vpc:update\": VpcUpdate,\n        \"vpc:delete\": VpcDelete,\n    }\n\n    PermissionIDs = map[Permission]int{\n        OneClickRead: 1,\n        OneClickCreate: 2,\n        ActionRead: 3,\n        AppRead: 4,\n        AppCreate: 5,\n        AppUpdate: 6,\n        AppDelete: 7,\n        BillingRead: 8,\n        BlockStorageRead: 9,\n        BlockStorageCreate: 10,\n        BlockStorageDelete: 11,\n        CdnEndpointRead: 12,\n        CdnEndpointCreate: 13,\n        CdnEndpointUpdate: 14,\n        CdnEndpointDelete: 15,\n        CertificateRead: 16,\n        CertificateCreate: 17,\n        CertificateDelete: 18,\n        ContainerRegistryRead: 19,\n        ContainerRegistryCreate: 20,\n        DatabaseRead: 21,\n        DatabaseCreate: 22,\n        DatabaseUpdate: 23,\n        DatabaseDelete: 24,\n        DomainRecordRead: 25,\n        DomainRecordCreate: 26,\n        DomainRecordUpdate: 27,\n        DomainRecordDelete: 28,\n        DomainRead: 29,\n        DomainCreate: 30,\n        DomainDelete: 31,\n        DropletRead: 32,\n        DropletCreate: 33,\n        DropletDelete: 34,\n        DropletAutoscalePoolRead: 35,\n        DropletAutoscalePoolCreate: 36,\n        DropletAutoscalePoolUpdate: 37,\n        DropletAutoscalePoolDelete: 38,\n        FirewallRead: 39,\n        FirewallCreate: 40,\n        FirewallUpdate: 41,\n        FirewallDelete: 42,\n        FloatingIpRead: 43,\n        FloatingIpCreate: 44,\n        FloatingIpDelete: 45,\n        NamespaceRead: 46,\n        NamespaceCreate: 47,\n        NamespaceDelete: 48,\n        GenaiAgentRead: 49,\n        GenaiAgentCreate: 50,\n        GenaiAgentUpdate: 51,\n        GenaiAgentDelete: 52,\n        ImageRead: 53,\n        ImageCreate: 54,\n        ImageUpdate: 55,\n        ImageDelete: 56,\n        KubernetesRead: 57,\n        KubernetesCreate: 58,\n        KubernetesUpdate: 59,\n        KubernetesDelete: 60,\n        LoadBalancerRead: 61,\n        LoadBalancerCreate: 62,\n        LoadBalancerUpdate: 63,\n        LoadBalancerDelete: 64,\n        MonitoringRead: 65,\n        MonitoringCreate: 66,\n        MonitoringUpdate: 67,\n        MonitoringDelete: 68,\n        ProjectRead: 69,\n        ProjectCreate: 70,\n        ProjectUpdate: 71,\n        ProjectDelete: 72,\n        RegionRead: 73,\n        ReservedIpRead: 74,\n        ReservedIpCreate: 75,\n        ReservedIpDelete: 76,\n        SizeRead: 77,\n        SnapshotRead: 78,\n        SnapshotDelete: 79,\n        SshKeyRead: 80,\n        SshKeyCreate: 81,\n        SshKeyUpdate: 82,\n        SshKeyDelete: 83,\n        TagRead: 84,\n        TagCreate: 85,\n        TagDelete: 86,\n        UptimeRead: 87,\n        UptimeCreate: 88,\n        UptimeUpdate: 89,\n        UptimeDelete: 90,\n        VpcPeeringRead: 91,\n        VpcPeeringCreate: 92,\n        VpcPeeringUpdate: 93,\n        VpcPeeringDelete: 94,\n        VpcRead: 95,\n        VpcCreate: 96,\n        VpcUpdate: 97,\n        VpcDelete: 98,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: OneClickRead,\n        2: OneClickCreate,\n        3: ActionRead,\n        4: AppRead,\n        5: AppCreate,\n        6: AppUpdate,\n        7: AppDelete,\n        8: BillingRead,\n        9: BlockStorageRead,\n        10: BlockStorageCreate,\n        11: BlockStorageDelete,\n        12: CdnEndpointRead,\n        13: CdnEndpointCreate,\n        14: CdnEndpointUpdate,\n        15: CdnEndpointDelete,\n        16: CertificateRead,\n        17: CertificateCreate,\n        18: CertificateDelete,\n        19: ContainerRegistryRead,\n        20: ContainerRegistryCreate,\n        21: DatabaseRead,\n        22: DatabaseCreate,\n        23: DatabaseUpdate,\n        24: DatabaseDelete,\n        25: DomainRecordRead,\n        26: DomainRecordCreate,\n        27: DomainRecordUpdate,\n        28: DomainRecordDelete,\n        29: DomainRead,\n        30: DomainCreate,\n        31: DomainDelete,\n        32: DropletRead,\n        33: DropletCreate,\n        34: DropletDelete,\n        35: DropletAutoscalePoolRead,\n        36: DropletAutoscalePoolCreate,\n        37: DropletAutoscalePoolUpdate,\n        38: DropletAutoscalePoolDelete,\n        39: FirewallRead,\n        40: FirewallCreate,\n        41: FirewallUpdate,\n        42: FirewallDelete,\n        43: FloatingIpRead,\n        44: FloatingIpCreate,\n        45: FloatingIpDelete,\n        46: NamespaceRead,\n        47: NamespaceCreate,\n        48: NamespaceDelete,\n        49: GenaiAgentRead,\n        50: GenaiAgentCreate,\n        51: GenaiAgentUpdate,\n        52: GenaiAgentDelete,\n        53: ImageRead,\n        54: ImageCreate,\n        55: ImageUpdate,\n        56: ImageDelete,\n        57: KubernetesRead,\n        58: KubernetesCreate,\n        59: KubernetesUpdate,\n        60: KubernetesDelete,\n        61: LoadBalancerRead,\n        62: LoadBalancerCreate,\n        63: LoadBalancerUpdate,\n        64: LoadBalancerDelete,\n        65: MonitoringRead,\n        66: MonitoringCreate,\n        67: MonitoringUpdate,\n        68: MonitoringDelete,\n        69: ProjectRead,\n        70: ProjectCreate,\n        71: ProjectUpdate,\n        72: ProjectDelete,\n        73: RegionRead,\n        74: ReservedIpRead,\n        75: ReservedIpCreate,\n        76: ReservedIpDelete,\n        77: SizeRead,\n        78: SnapshotRead,\n        79: SnapshotDelete,\n        80: SshKeyRead,\n        81: SshKeyCreate,\n        82: SshKeyUpdate,\n        83: SshKeyDelete,\n        84: TagRead,\n        85: TagCreate,\n        86: TagDelete,\n        87: UptimeRead,\n        88: UptimeCreate,\n        89: UptimeUpdate,\n        90: UptimeDelete,\n        91: VpcPeeringRead,\n        92: VpcPeeringCreate,\n        93: VpcPeeringUpdate,\n        94: VpcPeeringDelete,\n        95: VpcRead,\n        96: VpcCreate,\n        97: VpcUpdate,\n        98: VpcDelete,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/digitalocean/permissions.yaml",
    "content": "permissions:\n  - one_click:read\n  - one_click:create\n  - action:read\n  - app:read\n  - app:create\n  - app:update\n  - app:delete\n  - billing:read\n  - block_storage:read\n  - block_storage:create\n  - block_storage:delete\n  - cdn_endpoint:read\n  - cdn_endpoint:create\n  - cdn_endpoint:update\n  - cdn_endpoint:delete\n  - certificate:read\n  - certificate:create\n  - certificate:delete\n  - container_registry:read\n  - container_registry:create\n  - database:read\n  - database:create\n  - database:update\n  - database:delete\n  - domain_record:read\n  - domain_record:create\n  - domain_record:update\n  - domain_record:delete\n  - domain:read\n  - domain:create\n  - domain:delete\n  - droplet:read\n  - droplet:create\n  - droplet:delete\n  - droplet_autoscale_pool:read\n  - droplet_autoscale_pool:create\n  - droplet_autoscale_pool:update\n  - droplet_autoscale_pool:delete\n  - firewall:read\n  - firewall:create\n  - firewall:update\n  - firewall:delete\n  - floating_ip:read\n  - floating_ip:create\n  - floating_ip:delete\n  - namespace:read\n  - namespace:create\n  - namespace:delete\n  - genai_agent:read\n  - genai_agent:create\n  - genai_agent:update\n  - genai_agent:delete\n  - image:read\n  - image:create\n  - image:update\n  - image:delete\n  - kubernetes:read\n  - kubernetes:create\n  - kubernetes:update\n  - kubernetes:delete\n  - load_balancer:read\n  - load_balancer:create\n  - load_balancer:update\n  - load_balancer:delete\n  - monitoring:read\n  - monitoring:create\n  - monitoring:update\n  - monitoring:delete\n  - project:read\n  - project:create\n  - project:update\n  - project:delete\n  - region:read\n  - reserved_ip:read\n  - reserved_ip:create\n  - reserved_ip:delete\n  - size:read\n  - snapshot:read\n  - snapshot:delete\n  - ssh_key:read\n  - ssh_key:create\n  - ssh_key:update\n  - ssh_key:delete\n  - tag:read\n  - tag:create\n  - tag:delete\n  - uptime:read\n  - uptime:create\n  - uptime:update\n  - uptime:delete\n  - vpc_peering:read\n  - vpc_peering:create\n  - vpc_peering:update\n  - vpc_peering:delete\n  - vpc:read\n  - vpc:create\n  - vpc:update\n  - vpc:delete\n"
  },
  {
    "path": "pkg/analyzer/analyzers/digitalocean/scopes.json",
    "content": "[\n    {\n        \"name\": \"one_click:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/1-clicks\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"one_click:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/1-clicks/kubernetes\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"action:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/actions\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"app:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/apps\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"app:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/apps\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"app:update\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/apps/`nowaythisidcanexist\",\n            \"method\": \"PUT\",\n            \"valid_status_code\": [400],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"app:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/apps/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [400],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"billing:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/customers/my/balance\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"block_storage:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/volumes\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"block_storage:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/volumes\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"block_storage:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/volumes/0000\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"cdn_endpoint:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/cdn/endpoints\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"cdn_endpoint:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/cdn/endpoints\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"cdn_endpoint:update\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/cdn/endpoints/`nowaythisidcanexist\",\n            \"method\": \"PUT\",\n            \"valid_status_code\": [400],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"cdn_endpoint:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/cdn/endpoints/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [400],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"certificate:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/certificates\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"certificate:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/certificates\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"certificate:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/certificates/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"container_registry:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/registry\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200, 404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"container_registry:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/registry\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"database:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/databases\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"database:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/databases\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"database:update\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/databases/`nowaythisidcanexist/config\",\n            \"method\": \"PATCH\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"database:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/databases/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"domain_record:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/domains/`nowaythisdomaincanexist.com/records\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"domain_record:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/domains/`nowaythisdomaincanexist.com/records\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"domain_record:update\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/domains/`nowaythisdomaincanexist.com/records/`nowaythisidcanexist\",\n            \"method\": \"PUT\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"domain_record:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/domains/`nowaythisdomaincanexist.com/records/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"domain:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/domains\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"domain:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/domains\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"domain:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/domains/`nowaythisdomaincanexist.com\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"droplet:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/droplets\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"droplet:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/droplets\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"droplet:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/droplets/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"droplet_autoscale_pool:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/droplets/autoscale\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"droplet_autoscale_pool:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/droplets/autoscale\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"droplet_autoscale_pool:update\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/droplets/autoscale/0d3db13e-a604-4944-9827-7ec2642d32ac\",\n            \"method\": \"PUT\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"droplet_autoscale_pool:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/droplets/autoscale/0d3db13e-a604-4944-9827-7ec2642d32ac\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"firewall:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/firewalls\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"firewall:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/firewalls\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"firewall:update\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/firewalls/`nowaythisidcanexist\",\n            \"method\": \"PUT\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"firewall:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/firewalls/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"floating_ip:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/floating_ips\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"floating_ip:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/floating_ips\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"floating_ip:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/floating_ips/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"namespace:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/functions/namespaces\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"namespace:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/functions/namespaces\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"namespace:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/functions/namespaces/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"genai_agent:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/gen-ai/agents\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"genai_agent:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/gen-ai/agents\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"genai_agent:update\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/gen-ai/agents/`nowaythisidcanexist\",\n            \"method\": \"PUT\",\n            \"valid_status_code\": [400],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"genai_agent:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/gen-ai/agents/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [400],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"image:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/images\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"image:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/images\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"image:update\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/images/`nowaythisidcanexist\",\n            \"method\": \"PUT\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"image:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/images/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"kubernetes:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/kubernetes/clusters\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"kubernetes:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/kubernetes/clusters\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"kubernetes:update\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/kubernetes/clusters/`nowaythisidcanexist\",\n            \"method\": \"PUT\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"kubernetes:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/kubernetes/clusters/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"load_balancer:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/load_balancers\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"load_balancer:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/load_balancers\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"load_balancer:update\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/load_balancers/`nowaythisidcanexist\",\n            \"method\": \"PUT\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"load_balancer:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/load_balancers/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"monitoring:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/monitoring/alerts\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"monitoring:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/monitoring/alerts\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"monitoring:update\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/monitoring/alerts/`nowaythisidcanexist\",\n            \"method\": \"PUT\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"monitoring:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/monitoring/alerts/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"project:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/projects\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"project:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/projects\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"project:update\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/projects/`nowaythisidcanexist\",\n            \"method\": \"PUT\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"project:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/projects/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"region:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/regions\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"reserved_ip:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/reserved_ips\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"reserved_ip:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/reserved_ips\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"reserved_ip:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/reserved_ips/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"size:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/sizes\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"snapshot:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/snapshots\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"snapshot:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/snapshots/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"ssh_key:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/account/keys\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"ssh_key:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/account/keys\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"ssh_key:update\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/account/keys/`nowaythisidcanexist\",\n            \"method\": \"PUT\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"ssh_key:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/account/keys/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"tag:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/tags\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"tag:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/tags\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"tag:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/tags/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"uptime:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/uptime/checks\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"uptime:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/uptime/checks\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"uptime:update\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/uptime/checks/`nowaythisidcanexist\",\n            \"method\": \"PUT\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"uptime:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/uptime/checks/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"vpc_peering:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/vpc_peerings\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"vpc_peering:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/vpc_peerings\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"vpc_peering:update\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/vpc_peerings/5a4981aa-9653-4bd1-bef5-d6bff52042e4\",\n            \"method\": \"PATCH\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"vpc_peering:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/vpc_peerings/5a4981aa-9653-4bd1-bef5-d6bff52042e4\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"vpc:read\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/vpcs\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"vpc:create\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/vpcs\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [422],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"vpc:update\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/vpcs/`nowaythisidcanexist\",\n            \"method\": \"PUT\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"vpc:delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.digitalocean.com/v2/vpcs/`nowaythisidcanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [404],\n            \"invalid_status_code\": [403]\n        }\n    }\n]"
  },
  {
    "path": "pkg/analyzer/analyzers/dockerhub/dockerhub.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go dockerhub\npackage dockerhub\n\nimport (\n\t\"errors\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\n// SecretInfo hold the information about the token generated from username and pat\ntype SecretInfo struct {\n\tUser         User\n\tValid        bool\n\tReference    string\n\tPermissions  []string\n\tRepositories []Repository\n\tExpiresIn    string\n\tMisc         map[string]string\n}\n\n// User hold the information about user to whom the personal access token belongs\ntype User struct {\n\tID       string\n\tUsername string\n\tEmail    string\n}\n\n// Repository hold information about each repository the user can access\ntype Repository struct {\n\tID        string\n\tName      string\n\tType      string\n\tIsPrivate bool\n\tStarCount int\n\tPullCount int\n}\n\nfunc (a Analyzer) Type() analyzers.AnalyzerType {\n\treturn analyzers.AnalyzerTypeDockerHub\n}\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tusername, exist := credInfo[\"username\"]\n\tif !exist {\n\t\treturn nil, errors.New(\"username not found in the credentials info\")\n\t}\n\n\tpat, exist := credInfo[\"pat\"]\n\tif !exist {\n\t\treturn nil, errors.New(\"personal access token(PAT) not found in the credentials info\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, username, pat)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\n// AnalyzePermissions will collect all the scopes assigned to token along with resource it can access\nfunc AnalyzePermissions(cfg *config.Config, username, pat string) (*SecretInfo, error) {\n\t// create the http client\n\tclient := analyzers.NewAnalyzeClientUnrestricted(cfg) // `/user/login` is a non-safe request\n\n\tvar secretInfo = &SecretInfo{}\n\n\t// try to login and get jwt token\n\ttoken, err := login(client, username, pat)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := decodeTokenToSecretInfo(token, secretInfo); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// fetch repositories using the jwt token and translate them to secret info\n\tif err := fetchRepositories(client, username, token, secretInfo); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// return secret info\n\treturn secretInfo, nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, username, pat string) {\n\tinfo, err := AnalyzePermissions(cfg, username, pat)\n\tif err != nil {\n\t\t// just print the error in cli and continue as a partial success\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t}\n\n\tif info == nil {\n\t\tcolor.Red(\"[x] Error : %s\", \"No information found\")\n\t\treturn\n\t}\n\n\tif info.Valid {\n\t\tcolor.Green(\"[!] Valid DockerHub Credentials\\n\\n\")\n\t\t// print user information\n\t\tprintUser(info.User)\n\t\t// print permissions\n\t\tprintPermissions(info.Permissions)\n\t\t// print repositories\n\t\tprintRepositories(info.Repositories)\n\n\t\tcolor.Yellow(\"\\n[i] Expires: %s\", info.ExpiresIn)\n\t}\n}\n\n// secretInfoToAnalyzerResult translate secret info to Analyzer Result\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeDockerHub,\n\t\tMetadata:     map[string]any{\"Valid_Key\": info.Valid},\n\t\tBindings:     make([]analyzers.Binding, len(info.Repositories)),\n\t}\n\n\t// extract information to create bindings and append to result bindings\n\tfor _, repo := range info.Repositories {\n\t\tbinding := analyzers.Binding{\n\t\t\tResource: analyzers.Resource{\n\t\t\t\tName:               repo.Name,\n\t\t\t\tFullyQualifiedName: repo.ID,\n\t\t\t\tType:               repo.Type,\n\t\t\t\tMetadata: map[string]any{\n\t\t\t\t\t\"is_private\": repo.IsPrivate,\n\t\t\t\t\t\"pull_count\": repo.PullCount,\n\t\t\t\t\t\"star_count\": repo.StarCount,\n\t\t\t\t},\n\t\t\t},\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\t// as all permissions are against repo, we assign the highest available permission\n\t\t\t\tValue: assignHighestPermission(info.Permissions),\n\t\t\t},\n\t\t}\n\n\t\tresult.Bindings = append(result.Bindings, binding)\n\t}\n\n\treturn &result\n}\n\n// cli print functions\nfunc printUser(user User) {\n\tcolor.Green(\"\\n[i] User:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"ID\", \"Username\", \"Email\"})\n\tt.AppendRow(table.Row{color.GreenString(user.ID), color.GreenString(user.Username), color.GreenString(user.Email)})\n\tt.Render()\n}\n\nfunc printPermissions(permissions []string) {\n\tcolor.Yellow(\"[i] Permissions:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Permission\"})\n\tfor _, permission := range permissions {\n\t\tt.AppendRow(table.Row{color.GreenString(permission)})\n\t}\n\tt.Render()\n}\n\nfunc printRepositories(repos []Repository) {\n\tcolor.Green(\"\\n[i] Repositories:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Type\", \"ID(username/repo/repo_type/repo_name)\", \"Name\", \"Is Private\", \"Pull Count\", \"Star Count\"})\n\tfor _, repo := range repos {\n\t\tt.AppendRow(table.Row{color.GreenString(repo.Type), color.GreenString(repo.ID), color.GreenString(repo.Name),\n\t\t\tcolor.GreenString(\"%t\", repo.IsPrivate), color.GreenString(\"%d\", repo.PullCount), color.GreenString(\"%d\", repo.StarCount)})\n\t}\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/dockerhub/dockerhub_test.go",
    "content": "package dockerhub\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed result_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tusername := testSecrets.MustGetField(\"DOCKERHUB_USERNAME\")\n\tpat := testSecrets.MustGetField(\"DOCKERHUB_PAT\")\n\n\ttests := []struct {\n\t\tname     string\n\t\tusername string\n\t\tpat      string\n\t\twant     []byte // JSON string\n\t\twantErr  bool\n\t}{\n\t\t{\n\t\t\tname:     \"valid dockerhub credentials\",\n\t\t\tusername: username,\n\t\t\tpat:      pat,\n\t\t\twant:     expectedOutput,\n\t\t\twantErr:  false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"username\": tt.username, \"pat\": tt.pat})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/dockerhub/helper.go",
    "content": "package dockerhub\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"sort\"\n\t\"time\"\n\n\t\"github.com/golang-jwt/jwt/v5\"\n)\n\n// permission hierarchy - always keep from highest permission to lowest\nvar permissionHierarchy = []string{\"repo:admin\", \"repo:write\", \"repo:read\", \"repo:public_read\"}\n\n// precompute a ranking map for the ranking approach.\n// lower index means higher permission.\nvar permissionRank = func() map[string]int {\n\trank := make(map[string]int, len(permissionHierarchy))\n\t// loop over permissions hierarchy to assign index to each permission\n\t// as hierarchy start from highest to lowest, the 0 index will be assigned to highest possible permission and n will be lowest possible permission\n\tfor i, perm := range permissionHierarchy {\n\t\trank[perm] = i\n\t}\n\n\t// return the rank map with indexed permissions\n\treturn rank\n}()\n\n// decodeTokenToSecretInfo decode the jwt token and add the information to secret info\nfunc decodeTokenToSecretInfo(jwtToken string, secretInfo *SecretInfo) error {\n\ttype userClaims struct {\n\t\tID       string `json:\"uuid\"`\n\t\tUsername string `json:\"username\"`\n\t\tEmail    string `json:\"email\"`\n\t}\n\n\ttype hubJwtClaims struct {\n\t\tScope     string     `json:\"scope\"`\n\t\tHubClaims userClaims `json:\"https://hub.docker.com\"`\n\t\tExpiresIn int        `json:\"exp\"`\n\t\tjwt.RegisteredClaims\n\t}\n\n\tparser := jwt.NewParser()\n\ttoken, _, err := parser.ParseUnverified(jwtToken, &hubJwtClaims{})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif claims, ok := token.Claims.(*hubJwtClaims); ok {\n\t\tsecretInfo.User = User{\n\t\t\tID:       claims.HubClaims.ID,\n\t\t\tUsername: claims.HubClaims.Username,\n\t\t\tEmail:    claims.HubClaims.Email,\n\t\t}\n\n\t\tsecretInfo.ExpiresIn = humandReadableTime(claims.ExpiresIn)\n\n\t\tsecretInfo.Permissions = append(secretInfo.Permissions, claims.Scope)\n\t\tsecretInfo.Valid = true\n\n\t\treturn nil\n\t}\n\n\treturn errors.New(\"failed to parse claims\")\n}\n\n// repositoriesToSecretInfo translate repositories to secretInfo after sorting them\nfunc repositoriesToSecretInfo(username string, repos *RepositoriesResponse, secretInfo *SecretInfo) {\n\t// sort the repositories first\n\tsortRepositories(repos)\n\n\tfor _, repo := range repos.Result {\n\t\tsecretInfo.Repositories = append(secretInfo.Repositories, Repository{\n\t\t\t// as repositories does not have a unique key, we make one by combining multiple fields\n\t\t\tID:        fmt.Sprintf(\"%s/repo/%s/%s\", username, repo.Type, repo.Name), // e.g: user123/repo/image/repo1\n\t\t\tName:      repo.Name,\n\t\t\tType:      repo.Type,\n\t\t\tIsPrivate: repo.IsPrivate,\n\t\t\tStarCount: repo.StarCount,\n\t\t\tPullCount: repo.PullCount,\n\t\t})\n\t}\n}\n\n/*\nsortRepositories sort the repositories as following\n\nprivate:\n  - pullcount(descending)\n  - starcount(descending)\n\npublic:\n  - pullcount(descending)\n  - starcount(descending)\n*/\nfunc sortRepositories(repos *RepositoriesResponse) {\n\tsort.SliceStable(repos.Result, func(i, j int) bool {\n\t\ta, b := repos.Result[i], repos.Result[j]\n\n\t\t// prioritize private repositories over public\n\t\tif a.IsPrivate != b.IsPrivate {\n\t\t\treturn a.IsPrivate\n\t\t}\n\n\t\t// sort by Pull Count (descending)\n\t\tif a.PullCount != b.PullCount {\n\t\t\treturn a.PullCount > b.PullCount\n\t\t}\n\n\t\t// sort by Star Count (descending)\n\t\treturn a.StarCount > b.StarCount\n\t})\n}\n\n// assignHighestPermission selects the highest available permission\nfunc assignHighestPermission(permissions []string) string {\n\tbestRank := len(permissionHierarchy)\n\tbestPerm := \"\"\n\tfor _, perm := range permissions {\n\t\t// check in indexes permissions\n\t\tif rank, ok := permissionRank[perm]; ok {\n\t\t\t// early exit if highest permission is found.\n\t\t\tif rank == 0 {\n\t\t\t\treturn perm\n\t\t\t}\n\n\t\t\tif rank < bestRank {\n\t\t\t\tbestRank = rank\n\t\t\t\tbestPerm = perm\n\t\t\t}\n\t\t}\n\t}\n\n\treturn bestPerm\n\n}\n\n// humandReadableTime converts seconds to days, hours, minutes, or seconds based on the value\nfunc humandReadableTime(seconds int) string {\n\t// Convert Unix timestamp to time.Time object\n\tt := time.Unix(int64(seconds), 0)\n\n\t// Format the time as \"March 2\" (Month Day format)\n\treturn t.Format(\"January 2, 2006\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/dockerhub/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage dockerhub\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    RepoRead Permission = iota\n    RepoWrite Permission = iota\n    RepoAdmin Permission = iota\n    RepoPublicRead Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        RepoRead: \"repo:read\",\n        RepoWrite: \"repo:write\",\n        RepoAdmin: \"repo:admin\",\n        RepoPublicRead: \"repo:public_read\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"repo:read\": RepoRead,\n        \"repo:write\": RepoWrite,\n        \"repo:admin\": RepoAdmin,\n        \"repo:public_read\": RepoPublicRead,\n    }\n\n    PermissionIDs = map[Permission]int{\n        RepoRead: 1,\n        RepoWrite: 2,\n        RepoAdmin: 3,\n        RepoPublicRead: 4,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: RepoRead,\n        2: RepoWrite,\n        3: RepoAdmin,\n        4: RepoPublicRead,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/dockerhub/permissions.yaml",
    "content": "permissions:\n- repo:read\n- repo:write\n- repo:admin\n- repo:public_read\n"
  },
  {
    "path": "pkg/analyzer/analyzers/dockerhub/requests.go",
    "content": "package dockerhub\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// LoginResponse is the successful response from the /login API\ntype LoginResponse struct {\n\tToken string `json:\"token\"`\n}\n\n// ErrorLoginResponse is the error response from the /login API\ntype ErrorLoginResponse struct {\n\tDetail        string `json:\"detail\"`\n\tLogin2FAToken string `json:\"login_2fa_token\"` // if login require 2FA authentication\n}\n\n// RepositoriesResponse is the /repositories/<namespace> response\ntype RepositoriesResponse struct {\n\tResult []struct {\n\t\tName      string `json:\"name\"`\n\t\tType      string `json:\"repository_type\"`\n\t\tIsPrivate bool   `json:\"is_private\"`\n\t\tStarCount int    `json:\"star_count\"`\n\t\tPullCount int    `json:\"pull_count\"`\n\t} `json:\"results\"`\n}\n\n// login call the /login api with username and jwt token and if successful retrieve the token string and return\nfunc login(client *http.Client, username, pat string) (string, error) {\n\tpayload := strings.NewReader(fmt.Sprintf(`{\"username\": \"%s\", \"password\": \"%s\"}`, username, pat))\n\n\treq, err := http.NewRequest(http.MethodPost, \"https://hub.docker.com/v2/users/login\", payload)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tvar token LoginResponse\n\t\tif err := json.NewDecoder(resp.Body).Decode(&token); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\treturn token.Token, nil\n\tcase http.StatusUnauthorized:\n\t\tvar errorLogin ErrorLoginResponse\n\t\tif err := json.NewDecoder(resp.Body).Decode(&errorLogin); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tif errorLogin.Login2FAToken != \"\" {\n\t\t\t// TODO: handle it more appropriately\n\t\t\treturn \"\", errors.New(\"valid credentials; account require 2fa authentication\")\n\t\t}\n\n\t\treturn \"\", errors.New(errorLogin.Detail)\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\n// fetchRepositories call /repositories/<user_name> API\nfunc fetchRepositories(client *http.Client, username, token string, secretInfo *SecretInfo) error {\n\treq, err := http.NewRequest(http.MethodGet, fmt.Sprintf(\"https://hub.docker.com/v2/repositories/%s\", username), http.NoBody)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Authorization\", \"Bearer \"+token)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tvar repositories RepositoriesResponse\n\n\t\tif err := json.NewDecoder(resp.Body).Decode(&repositories); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// translate repositories response to secretInfo\n\t\trepositoriesToSecretInfo(username, &repositories, secretInfo)\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\t// the token is valid and this shall never happen because the least scope a token can have is repo:public_read.\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d; while fetching repositories information\", resp.StatusCode)\n\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/dockerhub/result_output.json",
    "content": "{\n    \"AnalyzerType\": 4,\n    \"Bindings\": [\n        {\n            \"Resource\": {\n                \"Name\": \"test-private\",\n                \"FullyQualifiedName\": \"truffledockerman/repo/image/test-private\",\n                \"Type\": \"image\",\n                \"Metadata\": {\n                    \"is_private\": true,\n                    \"pull_count\": 0,\n                    \"star_count\": 0\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"repo:admin\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"test\",\n                \"FullyQualifiedName\": \"truffledockerman/repo/image/test\",\n                \"Type\": \"image\",\n                \"Metadata\": {\n                    \"is_private\": false,\n                    \"pull_count\": 0,\n                    \"star_count\": 0\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"repo:admin\",\n                \"Parent\": null\n            }\n        }\n    ],\n    \"UnboundedResources\": null,\n    \"Metadata\": {\n        \"Valid_Key\": true\n    }\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/dropbox/dropbox.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go dropbox\npackage dropbox\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\n\t_ \"embed\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\n//go:embed scopes.json\nvar scopeConfigJson []byte\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\ntype PermissionStatus string\n\nconst (\n\tStatusGranted    PermissionStatus = \"Granted\"\n\tStatusDenied     PermissionStatus = \"Denied\"\n\tStatusUnverified PermissionStatus = \"Unverified\"\n)\n\nfunc (a Analyzer) Type() analyzers.AnalyzerType {\n\treturn analyzers.AnalyzerTypeDropbox\n}\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\ttoken, exist := credInfo[\"token\"]\n\tif !exist {\n\t\treturn nil, errors.New(\"token not found in credentials info\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, token)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, token string) {\n\tinfo, err := AnalyzePermissions(cfg, token)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Invalid Dropbox Token\\n\")\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t\treturn\n\t}\n\n\tif info == nil {\n\t\tcolor.Red(\"[x] Error : %s\", \"No information found\")\n\t\treturn\n\t}\n\n\tcolor.Green(\"[i] Valid Dropbox OAuth2 Credentials\\n\")\n\tprintAccountAndPermissions(info)\n}\n\nfunc AnalyzePermissions(cfg *config.Config, token string) (*secretInfo, error) {\n\t// Dropbox API uses POST requests for all requests, so we need to use an unrestricted client\n\tclient := analyzers.NewAnalyzeClientUnrestricted(cfg)\n\tscopeConfigMap, err := getScopeConfigMap()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsecretInfo := &secretInfo{}\n\n\taccountInfoPermission := PermissionStrings[AccountInfoRead]\n\tfor _, perm := range PermissionStrings {\n\t\tscopeDetails := scopeConfigMap.Scopes[perm]\n\t\tstatus := StatusUnverified\n\t\tif perm == accountInfoPermission {\n\t\t\t// Account Info Read permission is always enabled\n\t\t\tstatus = StatusGranted\n\t\t}\n\t\tsecretInfo.Permissions = append(secretInfo.Permissions, accountPermission{\n\t\t\tName:    perm,\n\t\t\tStatus:  status,\n\t\t\tActions: scopeDetails.Actions,\n\t\t})\n\t}\n\n\tif err := populateAccountInfo(client, secretInfo, token); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := testAllPermissions(client, secretInfo, scopeConfigMap, token); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfo, nil\n}\n\nfunc populateAccountInfo(client *http.Client, info *secretInfo, token string) error {\n\tendpoint := \"/2/users/get_current_account\"\n\tbody, statusCode, err := callDropboxAPIEndpoint(client, endpoint, token)\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tif err := json.Unmarshal([]byte(body), &info.Account); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to unmarshal account info: %w\", err)\n\t\t}\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"failed to validate scope. Status %d: %s\", statusCode, body)\n\t}\n}\n\nfunc testAllPermissions(client *http.Client, info *secretInfo, scopeConfigMap *scopeConfig, token string) error {\n\tpermissionStatuses := make(map[string]PermissionStatus)\n\n\tfor _, perm := range PermissionStrings {\n\t\tscopeDetails := scopeConfigMap.Scopes[perm]\n\n\t\tif _, ok := permissionStatuses[perm]; ok || scopeDetails.TestEndpoint == \"\" {\n\t\t\t// Skip if the scope has already been determined or has no test endpoint\n\t\t\tcontinue\n\t\t}\n\n\t\tif perm == PermissionStrings[Openid] {\n\t\t\t// The OpenID permission can be validated using the \"/2/users/get_current_account\" endpoint\n\t\t\t// If the response contains the \"email\" key, that implies that the \"email\" permission is also granted\n\t\t\t// Similar case for the \"given_name\" key and the \"profile\" permission\n\t\t\tbody, statusCode, err := callDropboxAPIEndpoint(client, scopeDetails.TestEndpoint, token)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tswitch statusCode {\n\t\t\tcase http.StatusOK, http.StatusConflict:\n\t\t\t\t// The endpoint responds with 409 Conflict if the openid scope\n\t\t\t\t// is granted but the email and profile scopes are not granted\n\t\t\t\tpermissionStatuses[perm] = StatusGranted\n\n\t\t\t\t// Check for the \"email\" key in the response body\n\t\t\t\tif strings.Contains(body, \"\\\"email\\\":\") {\n\t\t\t\t\tpermissionStatuses[PermissionStrings[Email]] = StatusGranted\n\t\t\t\t} else {\n\t\t\t\t\tpermissionStatuses[PermissionStrings[Email]] = StatusDenied\n\t\t\t\t}\n\n\t\t\t\t// Check for the \"given_name\" key in the response body\n\t\t\t\tif strings.Contains(body, \"\\\"given_name\\\":\") {\n\t\t\t\t\tpermissionStatuses[PermissionStrings[Profile]] = StatusGranted\n\t\t\t\t} else {\n\t\t\t\t\tpermissionStatuses[PermissionStrings[Profile]] = StatusDenied\n\t\t\t\t}\n\t\t\tcase http.StatusUnauthorized:\n\t\t\t\tpermissionStatuses[perm] = StatusDenied\n\t\t\t\tpermissionStatuses[PermissionStrings[Email]] = StatusDenied\n\t\t\t\tpermissionStatuses[PermissionStrings[Profile]] = StatusDenied\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tisGranted, err := testPermission(client, scopeDetails.TestEndpoint, token)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif !isGranted {\n\t\t\tpermissionStatuses[perm] = StatusDenied\n\t\t\tcontinue\n\t\t}\n\n\t\tpermissionStatuses[perm] = StatusGranted\n\t\tfor _, impliedScope := range scopeDetails.ImpliedScopes {\n\t\t\tpermissionStatuses[impliedScope] = StatusGranted\n\t\t}\n\t}\n\n\tfor idx, permission := range info.Permissions {\n\t\tpermission.Status = permissionStatuses[permission.Name]\n\t\tinfo.Permissions[idx] = permission\n\t}\n\n\treturn nil\n}\n\nfunc testPermission(client *http.Client, testEndpoint string, token string) (bool, error) {\n\tbody, statusCode, err := callDropboxAPIEndpoint(client, testEndpoint, token)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tcase http.StatusBadRequest:\n\t\tif strings.Contains(body, \"does not have the required scope\") {\n\t\t\treturn false, nil\n\t\t}\n\t\tif strings.Contains(body, \"your request body is empty\") {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\treturn false, fmt.Errorf(\"failed to validate scope. Status %d: %s\", statusCode, body)\n}\n\nfunc callDropboxAPIEndpoint(client *http.Client, endpoint string, token string) (string, int, error) {\n\tbaseURL := \"https://api.dropboxapi.com\"\n\treq, err := http.NewRequest(http.MethodPost, baseURL+endpoint, nil)\n\tif err != nil {\n\t\treturn \"\", 0, err\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+token)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn \"\", 0, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tbodyBytes, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn \"\", 0, fmt.Errorf(\"failed to read response body: %w\", err)\n\t}\n\n\treturn string(bodyBytes), res.StatusCode, nil\n}\n\nfunc getScopeConfigMap() (*scopeConfig, error) {\n\tvar scopeConfigMap scopeConfig\n\tif err := json.Unmarshal(scopeConfigJson, &scopeConfigMap); err != nil {\n\t\treturn nil, errors.New(\"failed to unmarshal scopes.json: \" + err.Error())\n\t}\n\treturn &scopeConfigMap, nil\n}\n\nfunc secretInfoToAnalyzerResult(info *secretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\taccount := info.Account\n\taccountID := account.AccountID\n\tallPermissions := getValidatedPermissions(info)\n\n\tresource := analyzers.Resource{\n\t\tName:               fmt.Sprintf(\"%s %s\", account.Name.GivenName, account.Name.Surname),\n\t\tFullyQualifiedName: accountID,\n\t\tType:               \"account\",\n\t\tMetadata: map[string]any{\n\t\t\t\"email\":         account.Email,\n\t\t\t\"emailVerified\": account.EmailVerified,\n\t\t\t\"disabled\":      account.Disabled,\n\t\t\t\"country\":       account.Country,\n\t\t\t\"accountType\":   account.AccountType.Tag,\n\t\t},\n\t}\n\tanalyzers.BindAllPermissions(resource, allPermissions...)\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeDropbox,\n\t\tMetadata:     nil,\n\t\tBindings:     analyzers.BindAllPermissions(resource, allPermissions...),\n\t}\n\treturn &result\n}\n\nfunc getValidatedPermissions(info *secretInfo) []analyzers.Permission {\n\tpermissions := []analyzers.Permission{}\n\n\tfor _, permission := range info.Permissions {\n\t\tif permission.Status != StatusGranted {\n\t\t\tcontinue\n\t\t}\n\t\tpermissions = append(permissions, analyzers.Permission{\n\t\t\tValue: permission.Name,\n\t\t})\n\t}\n\n\treturn permissions\n}\n\nfunc printAccountAndPermissions(info *secretInfo) {\n\tcolor.Yellow(\"\\n[i] Accounts Info:\")\n\tt1 := table.NewWriter()\n\tt1.SetOutputMirror(os.Stdout)\n\tt1.AppendHeader(table.Row{\"ID\", \"Name\", \"Email\", \"Email Verified\", \"Disabled\", \"Country\", \"Account Type\"})\n\temailVerified := \"No\"\n\tdisabled := \"No\"\n\tif info.Account.EmailVerified {\n\t\temailVerified = \"Yes\"\n\t}\n\tif info.Account.Disabled {\n\t\tdisabled = \"Yes\"\n\t}\n\tt1.AppendRow(table.Row{\n\t\tcolor.GreenString(info.Account.AccountID),\n\t\tcolor.GreenString(info.Account.Name.GivenName + \" \" + info.Account.Name.Surname),\n\t\tcolor.GreenString(info.Account.Email),\n\t\tcolor.GreenString(emailVerified),\n\t\tcolor.GreenString(disabled),\n\t\tcolor.GreenString(info.Account.Country),\n\t\tcolor.GreenString(info.Account.AccountType.Tag),\n\t})\n\tt1.SetOutputMirror(os.Stdout)\n\tt1.Render()\n\n\tcolor.Yellow(\"\\n[i] Permissions:\")\n\tt2 := table.NewWriter()\n\tt2.AppendHeader(table.Row{\"Permission\", \"Access\", \"Actions\"})\n\n\tpermissions := info.Permissions\n\tfor _, permission := range permissions {\n\t\taccess := \"Denied\"\n\t\tpermissionStatus := permission.Status\n\t\tif permissionStatus == StatusGranted {\n\t\t\taccess = \"Granted\"\n\t\t}\n\t\tif permissionStatus == StatusUnverified {\n\t\t\taccess = \"Unverified\"\n\t\t}\n\t\tfor idx, action := range permission.Actions {\n\t\t\tpermissionCell := \"\"\n\t\t\taccessCell := \"\"\n\t\t\tif idx == 0 {\n\t\t\t\tpermissionCell = color.GreenString(permission.Name)\n\t\t\t\taccessCell = color.GreenString(access)\n\t\t\t}\n\n\t\t\tt2.AppendRow(table.Row{\n\t\t\t\tpermissionCell,\n\t\t\t\taccessCell,\n\t\t\t\taction,\n\t\t\t})\n\t\t}\n\t\tt2.AppendSeparator()\n\t}\n\n\tt2.SetOutputMirror(os.Stdout)\n\tt2.Render()\n\tfmt.Printf(\"%s: https://www.dropbox.com/developers/documentation\\n\\n\", color.GreenString(\"Ref\"))\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/dropbox/dropbox_test.go",
    "content": "package dropbox\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttoken := testSecrets.MustGetField(\"DROPBOX\")\n\n\ttests := []struct {\n\t\tname    string\n\t\tsecret  string\n\t\twant    string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid dropbox credentials\",\n\t\t\tsecret:  token,\n\t\t\twant:    string(expectedOutput),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\n\t\t\t\t\"token\": tt.secret,\n\t\t\t})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\tfmt.Println(string(gotJSON))\n\n\t\t\t// compare the JSON strings\n\t\t\tif string(gotJSON) != string(tt.want) {\n\t\t\t\t// pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(tt.want, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/dropbox/expected_output.json",
    "content": "{\"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}"
  },
  {
    "path": "pkg/analyzer/analyzers/dropbox/models.go",
    "content": "package dropbox\n\ntype scopeConfig struct {\n\tScopes map[string]scope `json:\"scopes\"`\n}\n\ntype scope struct {\n\tTestEndpoint  string   `json:\"test_endpoint\"`\n\tImpliedScopes []string `json:\"implied_scopes\"`\n\tActions       []string `json:\"actions\"`\n}\n\ntype account struct {\n\tAccountID     string      `json:\"account_id\"`\n\tName          name        `json:\"name\"`\n\tEmail         string      `json:\"email\"`\n\tEmailVerified bool        `json:\"email_verified\"`\n\tDisabled      bool        `json:\"disabled\"`\n\tCountry       string      `json:\"country\"`\n\tAccountType   accountType `json:\"account_type\"`\n}\n\ntype accountType struct {\n\tTag string `json:\".tag\"`\n}\n\ntype name struct {\n\tGivenName string `json:\"given_name\"`\n\tSurname   string `json:\"surname\"`\n}\n\ntype accountPermission struct {\n\tName    string\n\tStatus  PermissionStatus\n\tActions []string\n}\n\ntype secretInfo struct {\n\tAccount     account\n\tPermissions []accountPermission\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/dropbox/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage dropbox\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    AccountInfoWrite Permission = iota\n    AccountInfoRead Permission = iota\n    FilesMetadataWrite Permission = iota\n    FilesMetadataRead Permission = iota\n    FilesContentWrite Permission = iota\n    FilesContentRead Permission = iota\n    SharingWrite Permission = iota\n    SharingRead Permission = iota\n    FileRequestsWrite Permission = iota\n    FileRequestsRead Permission = iota\n    ContactsWrite Permission = iota\n    ContactsRead Permission = iota\n    Openid Permission = iota\n    Profile Permission = iota\n    Email Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        AccountInfoWrite: \"account_info.write\",\n        AccountInfoRead: \"account_info.read\",\n        FilesMetadataWrite: \"files.metadata.write\",\n        FilesMetadataRead: \"files.metadata.read\",\n        FilesContentWrite: \"files.content.write\",\n        FilesContentRead: \"files.content.read\",\n        SharingWrite: \"sharing.write\",\n        SharingRead: \"sharing.read\",\n        FileRequestsWrite: \"file_requests.write\",\n        FileRequestsRead: \"file_requests.read\",\n        ContactsWrite: \"contacts.write\",\n        ContactsRead: \"contacts.read\",\n        Openid: \"openid\",\n        Profile: \"profile\",\n        Email: \"email\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"account_info.write\": AccountInfoWrite,\n        \"account_info.read\": AccountInfoRead,\n        \"files.metadata.write\": FilesMetadataWrite,\n        \"files.metadata.read\": FilesMetadataRead,\n        \"files.content.write\": FilesContentWrite,\n        \"files.content.read\": FilesContentRead,\n        \"sharing.write\": SharingWrite,\n        \"sharing.read\": SharingRead,\n        \"file_requests.write\": FileRequestsWrite,\n        \"file_requests.read\": FileRequestsRead,\n        \"contacts.write\": ContactsWrite,\n        \"contacts.read\": ContactsRead,\n        \"openid\": Openid,\n        \"profile\": Profile,\n        \"email\": Email,\n    }\n\n    PermissionIDs = map[Permission]int{\n        AccountInfoWrite: 1,\n        AccountInfoRead: 2,\n        FilesMetadataWrite: 3,\n        FilesMetadataRead: 4,\n        FilesContentWrite: 5,\n        FilesContentRead: 6,\n        SharingWrite: 7,\n        SharingRead: 8,\n        FileRequestsWrite: 9,\n        FileRequestsRead: 10,\n        ContactsWrite: 11,\n        ContactsRead: 12,\n        Openid: 13,\n        Profile: 14,\n        Email: 15,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: AccountInfoWrite,\n        2: AccountInfoRead,\n        3: FilesMetadataWrite,\n        4: FilesMetadataRead,\n        5: FilesContentWrite,\n        6: FilesContentRead,\n        7: SharingWrite,\n        8: SharingRead,\n        9: FileRequestsWrite,\n        10: FileRequestsRead,\n        11: ContactsWrite,\n        12: ContactsRead,\n        13: Openid,\n        14: Profile,\n        15: Email,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/dropbox/permissions.yaml",
    "content": "permissions:\r\n  - account_info.write\r\n  - account_info.read\r\n  - files.metadata.write\r\n  - files.metadata.read\r\n  - files.content.write\r\n  - files.content.read\r\n  - sharing.write\r\n  - sharing.read\r\n  - file_requests.write\r\n  - file_requests.read\r\n  - contacts.write\r\n  - contacts.read\r\n  - openid\r\n  - profile\r\n  - email\r\n"
  },
  {
    "path": "pkg/analyzer/analyzers/dropbox/scopes.json",
    "content": "{\r\n    \"scopes\": {\r\n        \"account_info.write\": {\r\n            \"test_endpoint\": \"/2/account/set_profile_photo\",\r\n            \"actions\": [\r\n                \"Set a user's profile photo\"\r\n            ]\r\n        },\r\n        \"account_info.read\": {\r\n            \"test_endpoint\": \"/2/account/set_profile_photo\",\r\n            \"actions\": [\r\n                \"Validate user access token\",\r\n                \"Get a list of feature values for the current account\",\r\n                \"Get information about the current user's account\",\r\n                \"Get the space usage information for the current user's account\"\r\n            ]\r\n        },\r\n        \"files.metadata.write\": {\r\n            \"test_endpoint\": \"/2/file_properties/properties/add\",\r\n            \"implied_scopes\": [\r\n                \"files.metadata.read\"\r\n            ],\r\n            \"actions\": [\r\n                \"Add, update or remove property groups associated with files\",\r\n                \"Add, update or remove properties associated with files and templates\",\r\n                \"Add, update or remove templates associated with a user\",\r\n                \"Add or remove tags from items\"\r\n            ]\r\n        },\r\n        \"files.metadata.read\": {\r\n            \"test_endpoint\": \"/2/file_properties/properties/search\",\r\n            \"actions\": [\r\n                \"Search across property templates for particular property field values\",\r\n                \"Get the schema for a specified template\",\r\n                \"Get the template identifiers for a team\",\r\n                \"Get the metadata for a file or folder\",\r\n                \"Get files, revisions, and folder contents\",\r\n                \"Monitor for file changes\",\r\n                \"Get tags from items\",\r\n                \"Get file metadata\",\r\n                \"Get user templates\",\r\n                \"Get user Paper docs\"\r\n            ]\r\n        },\r\n        \"files.content.write\": {\r\n            \"test_endpoint\": \"/2/files/copy_v2\",\r\n            \"implied_scopes\": [\r\n                \"files.metadata.read\"\r\n            ],\r\n            \"actions\": [\r\n                \"Add, update, move, or remove files\",\r\n                \"Add, update, move, or remove folders\",\r\n                \"Upload file content\",\r\n                \"Lock/unlock files for writing\",\r\n                \"Restore files to previous versions\",\r\n                \"Add, update, or archive Paper docs\",\r\n                \"Save URLs to Dropbox\"\r\n            ]\r\n        },\r\n        \"files.content.read\": {\r\n            \"test_endpoint\": \"/2/files/get_file_lock_batch\",\r\n            \"actions\": [\r\n                \"Export or download files\",\r\n                \"Get lock information for files and folders\",\r\n                \"Get file previews\",\r\n                \"Stream file content\",\r\n                \"Get image file thumbnails\",\r\n                \"Export or download Paper docs\"\r\n            ]\r\n        },\r\n        \"sharing.write\": {\r\n            \"test_endpoint\": \"/2/sharing/add_file_member\",\r\n            \"implied_scopes\": [\r\n                \"sharing.read\"\r\n            ],\r\n            \"actions\": [\r\n                \"Add, update, or remove file members\",\r\n                \"Add, update, or remove folder members\",\r\n                \"Get status of all asynchronous jobs\",\r\n                \"Add, update, or remove shared links\",\r\n                \"Share or unshare folders\",\r\n                \"Add, update, or remove shared folder access policies\",\r\n                \"Mount or unmount folders\",\r\n                \"Add or remove users from Paper docs\"\r\n            ]\r\n        },\r\n        \"sharing.read\": {\r\n            \"test_endpoint\": \"/2/sharing/get_file_metadata\",\r\n            \"actions\": [\r\n                \"Get file metadata\",\r\n                \"Get folder metadata\",\r\n                \"Get shared link metadata\",\r\n                \"Get file members\",\r\n                \"Get folder members\",\r\n                \"Get shared files\",\r\n                \"Get shared folders\",\r\n                \"Get mountable shared folders\",\r\n                \"Get shared links\",\r\n                \"Get information about the user's account\",\r\n                \"Get file and folder information for Paper doc\",\r\n                \"Get all users with Paper doc access\"\r\n            ]\r\n        },\r\n        \"file_requests.write\": {\r\n            \"test_endpoint\": \"/2/file_requests/update\",\r\n            \"implied_scopes\": [\r\n                \"file_requests.read\"\r\n            ],\r\n            \"actions\": [\r\n                \"Add, update, or remove file requests\"\r\n            ]\r\n        },\r\n        \"file_requests.read\": {\r\n            \"test_endpoint\": \"/2/file_requests/list/continue\",\r\n            \"actions\": [\r\n                \"Get file requests\",\r\n                \"Get file request count\"\r\n            ]\r\n        },\r\n        \"contacts.write\": {\r\n            \"test_endpoint\": \"/2/contacts/delete_manual_contacts_batch\",\r\n            \"implied_scopes\": [\r\n                \"contacts.read\"\r\n            ],\r\n            \"actions\": [\r\n                \"Remove manually added contacts\"\r\n            ]\r\n        },\r\n        \"contacts.read\": {},\r\n        \"openid\": {\r\n            \"test_endpoint\": \"/2/openid/userinfo\",\r\n            \"actions\": [\r\n                \"Get OpenID Connect user info\"\r\n            ]\r\n        },\r\n        \"profile\": {\r\n            \"actions\": [\r\n                \"Get name in user info\"\r\n            ]\r\n        },\r\n        \"email\": {\r\n            \"actions\": [\r\n                \"Get email address in user info\"\r\n            ]\r\n        }\r\n    }\r\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/elevenlabs/elevenlabs.go",
    "content": "package elevenlabs\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/google/uuid\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\n// SecretInfo hold information about key\ntype SecretInfo struct {\n\tUser                User // the owner of key\n\tValid               bool\n\tReference           string\n\tPermissions         []string             // list of Permissions assigned to the key\n\tElevenLabsResources []ElevenLabsResource // list of resources the key has access to\n\tmu                  sync.RWMutex\n}\n\n// AppendPermission safely append new permission to secret info permissions list.\nfunc (s *SecretInfo) AppendPermission(perm string) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\ts.Permissions = append(s.Permissions, perm)\n}\n\n// HasPermission safely read secret info permission list to check if passed permission exist in the list.\nfunc (s *SecretInfo) HasPermission(perm Permission) bool {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\tpermissionString, _ := perm.ToString()\n\n\treturn slices.Contains(s.Permissions, permissionString)\n}\n\n// AppendResource safely append new resource to secret info elevenlabs resource list.\nfunc (s *SecretInfo) AppendResource(resource ElevenLabsResource) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\ts.ElevenLabsResources = append(s.ElevenLabsResources, resource)\n}\n\n// User hold the information about user to whom the key belongs to\ntype User struct {\n\tID                 string\n\tName               string\n\tSubscriptionTier   string\n\tSubscriptionStatus string\n}\n\n// ElevenLabsResource hold information about the elevenlabs resource the key has access\ntype ElevenLabsResource struct {\n\tID         string\n\tName       string\n\tType       string\n\tMetadata   map[string]string\n\tPermission string\n}\n\nfunc (a Analyzer) Type() analyzers.AnalyzerType {\n\treturn analyzers.AnalyzerTypeElevenLabs\n}\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\t// check if the `key` exist in the credentials info\n\tkey, exist := credInfo[\"key\"]\n\tif !exist {\n\t\treturn nil, errors.New(\"key not found in credentials info\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\n// AnalyzePermissions check if key is valid and analyzes the permission for the key\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\t// create http client\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\tvar secretInfo = &SecretInfo{}\n\n\t// fetch user information using the key\n\tuser, err := fetchUser(client, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsecretInfo.Valid = true\n\n\t// if user is not nil, that means the key has user read permission. Set the user information in secret info user\n\t// user can only be nil when the key is valid but it does not have a user read permission\n\tif user != nil {\n\t\televenLabsUserToSecretInfoUser(*user, secretInfo)\n\t}\n\n\t// get elevenlabs resources with permissions\n\tif err := getElevenLabsResources(client, key, secretInfo); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfo, nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\t// just print the error in cli and continue as a partial success\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t}\n\n\tif info == nil {\n\t\tcolor.Red(\"[x] Error : %s\", \"No information found\")\n\t\treturn\n\t}\n\n\tif info.Valid {\n\t\tcolor.Green(\"[!] Valid ElevenLabs API key\\n\\n\")\n\t\t// print user information\n\t\tprintUser(info.User)\n\t\t// print permissions\n\t\tprintPermissions(info.Permissions)\n\t\t// print resources\n\t\tprintElevenLabsResources(info.ElevenLabsResources)\n\n\t\tcolor.Yellow(\"\\n[i] Expires: Never\")\n\t}\n}\n\n// secretInfoToAnalyzerResult translate secret info to Analyzer Result\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeElevenLabs,\n\t\tMetadata:     map[string]any{},\n\t\tBindings:     make([]analyzers.Binding, 0),\n\t}\n\n\t// for resources to be uniquely identified, we need a unique id to be appended in resource fully qualified name\n\tuniqueId := info.User.ID\n\tif uniqueId == \"\" {\n\t\tuniqueId = uuid.NewString()\n\t}\n\n\t// extract information from resource to create bindings and append to result bindings\n\tfor _, resource := range info.ElevenLabsResources {\n\t\t// if resource has permission it is binded resource\n\t\tif resource.Permission != \"\" {\n\t\t\tbinding := analyzers.Binding{\n\t\t\t\tResource: analyzers.Resource{\n\t\t\t\t\tName:               resource.Name,\n\t\t\t\t\tFullyQualifiedName: fmt.Sprintf(\"%s/%s/%s\", uniqueId, resource.Type, resource.ID), // e.g: <user_id>/Model/eleven_flash_v2_5\n\t\t\t\t\tType:               resource.Type,\n\t\t\t\t\tMetadata:           map[string]any{}, // to avoid panic\n\t\t\t\t},\n\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\tValue: resource.Permission,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tfor key, value := range resource.Metadata {\n\t\t\t\tbinding.Resource.Metadata[key] = value\n\t\t\t}\n\n\t\t\tresult.Bindings = append(result.Bindings, binding)\n\t\t} else {\n\t\t\t// if resource is missing permission it is an unbounded resource\n\t\t\tunboundedResource := analyzers.Resource{\n\t\t\t\tName:               resource.Name,\n\t\t\t\tFullyQualifiedName: fmt.Sprintf(\"%s/%s/%s\", uniqueId, resource.Type, resource.ID),\n\t\t\t\tType:               resource.Type,\n\t\t\t\tMetadata:           map[string]any{},\n\t\t\t}\n\n\t\t\tfor key, value := range resource.Metadata {\n\t\t\t\tunboundedResource.Metadata[key] = value\n\t\t\t}\n\n\t\t\tresult.UnboundedResources = append(result.UnboundedResources, unboundedResource)\n\t\t}\n\t}\n\n\tresult.Metadata[\"Valid_Key\"] = info.Valid\n\n\treturn &result\n}\n\n// fetchUser fetch elevenlabs user information associated with the key\nfunc fetchUser(client *http.Client, key string) (*User, error) {\n\tresponse, statusCode, err := makeElevenLabsRequest(client, permissionToAPIMap[UserRead], http.MethodGet, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar user UserResponse\n\n\t\tif err := json.Unmarshal(response, &user); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn &User{\n\t\t\tID:                 user.UserID,\n\t\t\tName:               user.FirstName,\n\t\t\tSubscriptionTier:   user.Subscription.Tier,\n\t\t\tSubscriptionStatus: user.Subscription.Status,\n\t\t}, nil\n\tcase http.StatusUnauthorized:\n\t\tvar errorResp ErrorResponse\n\n\t\tif err := json.Unmarshal(response, &errorResp); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif errorResp.Detail.Status == InvalidAPIKey || errorResp.Detail.Status == NotVerifiable {\n\t\t\treturn nil, errors.New(\"invalid api key\")\n\t\t} else if errorResp.Detail.Status == MissingPermissions {\n\t\t\t// key is missing user read permissions but is valid\n\t\t\treturn nil, nil\n\t\t}\n\n\t\treturn nil, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// elevenLabsUserToSecretInfoUser set the elevenlabs user information to secretInfo user\nfunc elevenLabsUserToSecretInfoUser(user User, secretInfo *SecretInfo) {\n\tsecretInfo.User = user\n\t// add user read scope to secret info\n\tsecretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[UserRead])\n\t// map resource to secret info\n\t// as user is accessible through a specific permission and has a unique id it is also a resource\n\tsecretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{\n\t\tID:         user.ID,\n\t\tName:       user.Name,\n\t\tType:       \"User\",\n\t\tPermission: PermissionStrings[UserRead],\n\t})\n}\n\n/*\ngetElevenLabsResources gather resources the key can access\n\nNote: The permissions in eleven labs is either Read or Read and Write. There is not separate permission for Write.\n*/\nfunc getElevenLabsResources(client *http.Client, key string, secretInfo *SecretInfo) error {\n\tvar (\n\t\taggregatedErrs = make([]string, 0)\n\t\terrChan        = make(chan error, 17) // buffer for 17 errors - one per API call\n\t\twg             sync.WaitGroup\n\t)\n\n\t// history\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tif err := getHistory(client, key, secretInfo); err != nil {\n\t\t\terrChan <- err\n\t\t}\n\n\t\tif err := deleteHistory(client, key, secretInfo); err != nil {\n\t\t\terrChan <- err\n\t\t}\n\t}()\n\n\t// dubbings\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tif err := deleteDubbing(client, key, secretInfo); err != nil {\n\t\t\terrChan <- err\n\t\t}\n\n\t\t// if dubbing write permission was not added\n\t\tif !secretInfo.HasPermission(DubbingWrite) {\n\t\t\tif err := getDebugging(client, key, secretInfo); err != nil {\n\t\t\t\terrChan <- err\n\t\t\t}\n\t\t}\n\t}()\n\n\t// voices\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tif err := getVoices(client, key, secretInfo); err != nil {\n\t\t\terrChan <- err\n\t\t}\n\n\t\tif err := deleteVoice(client, key, secretInfo); err != nil {\n\t\t\terrChan <- err\n\t\t}\n\t}()\n\n\t// projects\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tif err := getProjects(client, key, secretInfo); err != nil {\n\t\t\terrChan <- err\n\t\t}\n\n\t\tif err := deleteProject(client, key, secretInfo); err != nil {\n\t\t\terrChan <- err\n\t\t}\n\t}()\n\n\t// pronunciation dictionaries\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tif err := getPronunciationDictionaries(client, key, secretInfo); err != nil {\n\t\t\terrChan <- err\n\t\t}\n\n\t\tif err := removePronunciationDictionariesRule(client, key, secretInfo); err != nil {\n\t\t\terrChan <- err\n\t\t}\n\t}()\n\n\t// models\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tif err := getModels(client, key, secretInfo); err != nil {\n\t\t\terrChan <- err\n\t\t}\n\t}()\n\n\t// audio native\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tif err := updateAudioNativeProject(client, key, secretInfo); err != nil {\n\t\t\terrChan <- err\n\t\t}\n\t}()\n\n\t// workspace\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tif err := deleteInviteFromWorkspace(client, key, secretInfo); err != nil {\n\t\t\terrChan <- err\n\t\t}\n\t}()\n\n\t// speech\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tif err := textToSpeech(client, key, secretInfo); err != nil {\n\t\t\terrChan <- err\n\t\t}\n\n\t\t// voice changer\n\t\tif err := speechToSpeech(client, key, secretInfo); err != nil {\n\t\t\terrChan <- err\n\t\t}\n\t}()\n\n\t// audio isolation\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\tif err := audioIsolation(client, key, secretInfo); err != nil {\n\t\t\terrChan <- err\n\t\t}\n\t}()\n\n\t// agent\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\n\t\t// each agent can have a conversations which we get inside this function\n\t\tif err := getAgents(client, key, secretInfo); err != nil {\n\t\t\terrChan <- err\n\t\t}\n\t}()\n\n\t// wait for all API calls to finish\n\twg.Wait()\n\tclose(errChan)\n\n\t// collect all errors\n\tfor err := range errChan {\n\t\taggregatedErrs = append(aggregatedErrs, err.Error())\n\t}\n\n\tif len(aggregatedErrs) > 0 {\n\t\treturn errors.New(strings.Join(aggregatedErrs, \", \"))\n\t}\n\n\treturn nil\n}\n\n// cli print functions\nfunc printUser(user User) {\n\tcolor.Green(\"\\n[i] User:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"ID\", \"Name\", \"Subscription Tier\", \"Subscription Status\"})\n\tt.AppendRow(table.Row{color.GreenString(user.ID), color.GreenString(user.Name), color.GreenString(user.SubscriptionTier), color.GreenString(user.SubscriptionStatus)})\n\tt.Render()\n}\n\nfunc printPermissions(permissions []string) {\n\tcolor.Yellow(\"[i] Permissions:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Permission\"})\n\tfor _, permission := range permissions {\n\t\tt.AppendRow(table.Row{color.GreenString(permission)})\n\t}\n\tt.Render()\n}\n\nfunc printElevenLabsResources(resources []ElevenLabsResource) {\n\tcolor.Green(\"\\n[i] Resources:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Resource Type\", \"Resource ID\", \"Resource Name\", \"Permission\"})\n\tfor _, resource := range resources {\n\t\tt.AppendRow(table.Row{color.GreenString(resource.Type), color.GreenString(resource.ID), color.GreenString(resource.Name), color.GreenString(resource.Permission)})\n\t}\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/elevenlabs/elevenlabs_test.go",
    "content": "package elevenlabs\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed result_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tkey := testSecrets.MustGetField(\"ELEVENLABS\")\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    []byte // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid ElevenLabs full access key\",\n\t\t\tkey:     key,\n\t\t\twant:    expectedOutput,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/elevenlabs/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage elevenlabs\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    TextToSpeech Permission = iota\n    SpeechToSpeech Permission = iota\n    AudioIsolation Permission = iota\n    DubbingRead Permission = iota\n    DubbingWrite Permission = iota\n    ProjectsRead Permission = iota\n    ProjectsWrite Permission = iota\n    AudioNativeRead Permission = iota\n    AudioNativeWrite Permission = iota\n    PronunciationDictionariesRead Permission = iota\n    PronunciationDictionariesWrite Permission = iota\n    VoicesRead Permission = iota\n    VoicesWrite Permission = iota\n    ModelsRead Permission = iota\n    SpeechHistoryRead Permission = iota\n    SpeechHistoryWrite Permission = iota\n    UserRead Permission = iota\n    WorkspaceRead Permission = iota\n    WorkspaceWrite Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        TextToSpeech: \"text_to_speech\",\n        SpeechToSpeech: \"speech_to_speech\",\n        AudioIsolation: \"audio_isolation\",\n        DubbingRead: \"dubbing_read\",\n        DubbingWrite: \"dubbing_write\",\n        ProjectsRead: \"projects_read\",\n        ProjectsWrite: \"projects_write\",\n        AudioNativeRead: \"audio_native_read\",\n        AudioNativeWrite: \"audio_native_write\",\n        PronunciationDictionariesRead: \"pronunciation_dictionaries_read\",\n        PronunciationDictionariesWrite: \"pronunciation_dictionaries_write\",\n        VoicesRead: \"voices_read\",\n        VoicesWrite: \"voices_write\",\n        ModelsRead: \"models_read\",\n        SpeechHistoryRead: \"speech_history_read\",\n        SpeechHistoryWrite: \"speech_history_write\",\n        UserRead: \"user_read\",\n        WorkspaceRead: \"workspace_read\",\n        WorkspaceWrite: \"workspace_write\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"text_to_speech\": TextToSpeech,\n        \"speech_to_speech\": SpeechToSpeech,\n        \"audio_isolation\": AudioIsolation,\n        \"dubbing_read\": DubbingRead,\n        \"dubbing_write\": DubbingWrite,\n        \"projects_read\": ProjectsRead,\n        \"projects_write\": ProjectsWrite,\n        \"audio_native_read\": AudioNativeRead,\n        \"audio_native_write\": AudioNativeWrite,\n        \"pronunciation_dictionaries_read\": PronunciationDictionariesRead,\n        \"pronunciation_dictionaries_write\": PronunciationDictionariesWrite,\n        \"voices_read\": VoicesRead,\n        \"voices_write\": VoicesWrite,\n        \"models_read\": ModelsRead,\n        \"speech_history_read\": SpeechHistoryRead,\n        \"speech_history_write\": SpeechHistoryWrite,\n        \"user_read\": UserRead,\n        \"workspace_read\": WorkspaceRead,\n        \"workspace_write\": WorkspaceWrite,\n    }\n\n    PermissionIDs = map[Permission]int{\n        TextToSpeech: 1,\n        SpeechToSpeech: 2,\n        AudioIsolation: 3,\n        DubbingRead: 4,\n        DubbingWrite: 5,\n        ProjectsRead: 6,\n        ProjectsWrite: 7,\n        AudioNativeRead: 8,\n        AudioNativeWrite: 9,\n        PronunciationDictionariesRead: 10,\n        PronunciationDictionariesWrite: 11,\n        VoicesRead: 12,\n        VoicesWrite: 13,\n        ModelsRead: 14,\n        SpeechHistoryRead: 15,\n        SpeechHistoryWrite: 16,\n        UserRead: 17,\n        WorkspaceRead: 18,\n        WorkspaceWrite: 19,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: TextToSpeech,\n        2: SpeechToSpeech,\n        3: AudioIsolation,\n        4: DubbingRead,\n        5: DubbingWrite,\n        6: ProjectsRead,\n        7: ProjectsWrite,\n        8: AudioNativeRead,\n        9: AudioNativeWrite,\n        10: PronunciationDictionariesRead,\n        11: PronunciationDictionariesWrite,\n        12: VoicesRead,\n        13: VoicesWrite,\n        14: ModelsRead,\n        15: SpeechHistoryRead,\n        16: SpeechHistoryWrite,\n        17: UserRead,\n        18: WorkspaceRead,\n        19: WorkspaceWrite,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/elevenlabs/permissions.yaml",
    "content": "permissions:\n  - text_to_speech\n  - speech_to_speech\n  # - sound_generation\n  - audio_isolation\n  # - voice_generation\n  - dubbing_read\n  - dubbing_write\n  - projects_read\n  - projects_write\n  - audio_native_read\n  - audio_native_write\n  - pronunciation_dictionaries_read\n  - pronunciation_dictionaries_write\n  - voices_read\n  - voices_write\n  - models_read\n  # - models_write\n  - speech_history_read\n  - speech_history_write\n  - user_read\n  # - user_write\n  - workspace_read\n  - workspace_write\n"
  },
  {
    "path": "pkg/analyzer/analyzers/elevenlabs/requests.go",
    "content": "package elevenlabs\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"slices\"\n\t\"strings\"\n)\n\n// permissionToAPIMap contain the API endpoints for each scope/permission\n// api docs: https://elevenlabs.io/docs/api-reference/introduction\nvar permissionToAPIMap = map[Permission]string{\n\tTextToSpeech:                   \"https://api.elevenlabs.io/v1/text-to-speech/%s\",   // require voice id\n\tSpeechToSpeech:                 \"https://api.elevenlabs.io/v1/speech-to-speech/%s\", // require voice id\n\tAudioIsolation:                 \"https://api.elevenlabs.io/v1/audio-isolation\",\n\tDubbingRead:                    \"https://api.elevenlabs.io/v1/dubbing/%s\", // require dubbing id\n\tDubbingWrite:                   \"https://api.elevenlabs.io/v1/dubbing/%s\", // require dubbing id\n\tProjectsRead:                   \"https://api.elevenlabs.io/v1/projects\",\n\tProjectsWrite:                  \"https://api.elevenlabs.io/v1/projects/%s\",             // require project id\n\tAudioNativeWrite:               \"https://api.elevenlabs.io/v1/audio-native/%s/content\", // require project id\n\tPronunciationDictionariesRead:  \"https://api.elevenlabs.io/v1/pronunciation-dictionaries\",\n\tPronunciationDictionariesWrite: \"https://api.elevenlabs.io/v1/pronunciation-dictionaries/%s/remove-rules\", // require pronunciation dictionary id\n\tVoicesRead:                     \"https://api.elevenlabs.io/v1/voices\",\n\tVoicesWrite:                    \"https://api.elevenlabs.io/v1/voices/%s\", // require voice id\n\tModelsRead:                     \"https://api.elevenlabs.io/v1/models\",\n\tSpeechHistoryRead:              \"https://api.elevenlabs.io/v1/history\",\n\tSpeechHistoryWrite:             \"https://api.elevenlabs.io/v1/history/%s\", // require history item id\n\tUserRead:                       \"https://api.elevenlabs.io/v1/user\",\n\tWorkspaceWrite:                 \"https://api.elevenlabs.io/v1/workspace/invites\",\n}\n\nvar (\n\t// not exist key\n\tfakeID = \"_thou_shalt_not_exist_\"\n\t// error statuses\n\tNotVerifiable                   = \"api_key_not_verifiable\"\n\tInvalidAPIKey                   = \"invalid_api_key\"\n\tMissingPermissions              = \"missing_permissions\"\n\tDubbingNotFound                 = \"dubbing_not_found\"\n\tProjectNotFound                 = \"project_not_found\"\n\tVoiceDoesNotExist               = \"voice_does_not_exist\"\n\tInvalidSubscription             = \"invalid_subscription\"\n\tPronunciationDictionaryNotFound = \"pronunciation_dictionary_not_found\"\n\tInternalServerError             = \"internal_server_error\"\n\tInvalidProjectID                = \"invalid_project_id\"\n\tModelNotFound                   = \"model_not_found\"\n\tVoiceNotFound                   = \"voice_not_found\"\n\tInvalidContent                  = \"invalid_content\"\n)\n\n// ErrorResponse is the error response for all APIs\ntype ErrorResponse struct {\n\tDetail struct {\n\t\tStatus string `json:\"status\"`\n\t} `json:\"detail\"`\n}\n\n// UserResponse is the /user API response\ntype UserResponse struct {\n\tUserID       string `json:\"user_id\"`\n\tFirstName    string `json:\"first_name\"`\n\tSubscription struct {\n\t\tTier   string `json:\"tier\"`\n\t\tStatus string `json:\"status\"`\n\t} `json:\"subscription\"`\n}\n\n// HistoryResponse is the /history API response\ntype HistoryResponse struct {\n\tHistory []struct {\n\t\tID      string `json:\"history_item_id\"`\n\t\tModelID string `json:\"model_id\"`\n\t\tVoiceID string `json:\"voice_id\"`\n\t} `json:\"history\"`\n}\n\n// VoiceResponse is the /voices API response\ntype VoicesResponse struct {\n\tVoices []struct {\n\t\tID       string `json:\"voice_id\"`\n\t\tName     string `json:\"name\"`\n\t\tCategory string `json:\"category\"`\n\t} `json:\"voices\"`\n}\n\n// ProjectsResponse is the /projects API response\ntype ProjectsResponse struct {\n\tProjects []struct {\n\t\tID          string `json:\"project_id\"`\n\t\tName        string `json:\"name\"`\n\t\tState       string `json:\"state\"`\n\t\tAccessLevel string `json:\"access_level\"`\n\t} `json:\"projects\"`\n}\n\n// PronunciationDictionaries is the /pronunciation-dictionaries API response\ntype PronunciationDictionariesResponse struct {\n\tPronunciationDictionaries []struct {\n\t\tID   string `json:\"id\"`\n\t\tName string `json:\"name\"`\n\t} `json:\"pronunciation_dictionaries\"`\n}\n\n// Models is the /models API response\ntype ModelsResponse struct {\n\tID   string `json:\"model_id\"`\n\tName string `json:\"name\"`\n}\n\n// AgentsResponse is the /agents API response\ntype AgentsResponse struct {\n\tAgents []struct {\n\t\tID          string `json:\"agent_id\"`\n\t\tName        string `json:\"name\"`\n\t\tAccessLevel string `json:\"access_level\"`\n\t} `json:\"agents\"`\n}\n\n// ConversationResponse is the /conversation API response\ntype ConversationResponse struct {\n\tConversations []struct {\n\t\tAgentID string `json:\"agent_id\"`\n\t\tID      string `json:\"conversation_id\"`\n\t\tStatus  string `json:\"status\"`\n\t}\n}\n\n// getAPIUrl return the API Url mapped to the permission\nfunc getAPIUrl(permission Permission) string {\n\tapiUrl := permissionToAPIMap[permission]\n\tif strings.Contains(apiUrl, \"%s\") {\n\t\treturn fmt.Sprintf(apiUrl, fakeID)\n\t}\n\n\treturn apiUrl\n}\n\n// makeElevenLabsRequest send the API request to passed url with passed key as API Key and return response body and status code\nfunc makeElevenLabsRequest(client *http.Client, url, method, key string) ([]byte, int, error) {\n\t// create request\n\treq, err := http.NewRequest(method, url, http.NoBody)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\t// add key in the header\n\treq.Header.Add(\"xi-api-key\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\t/*\n\t\tthe reason to translate body to byte and does not directly return http.Response\n\t\t is if we return http.Response we cannot close the body in defer. If we do we will get an error\n\t\t when reading body outside this function\n\t*/\n\tresponseBodyByte, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn responseBodyByte, resp.StatusCode, nil\n}\n\n// makeElevenLabsRequestWithPayload sends a POST/PATCH API request to the passed URL with the given key as the API Key\n// and an optional payload. It returns the response body and status code.\nfunc makeElevenLabsRequestWithPayload(client *http.Client, url, method, contentType, key string, payload []byte) ([]byte, int, error) {\n\t// Create request with payload\n\treq, err := http.NewRequest(method, url, bytes.NewBuffer(payload))\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\t// Add headers\n\treq.Header.Add(\"xi-api-key\", key)\n\treq.Header.Add(\"Content-Type\", contentType)\n\n\t// Send the request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\t// ensure the response body is properly closed\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\t// read the response body\n\tresponseBodyByte, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn responseBodyByte, resp.StatusCode, nil\n}\n\n// getHistory get history item using the key passed and add them to secret info\nfunc getHistory(client *http.Client, key string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(SpeechHistoryRead), http.MethodGet, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar history HistoryResponse\n\n\t\tif err := json.Unmarshal(response, &history); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// add history read scope to secret info\n\t\tsecretInfo.AppendPermission(PermissionStrings[SpeechHistoryRead])\n\t\t// map resource to secret info\n\t\tfor _, historyItem := range history.History {\n\t\t\tsecretInfo.AppendResource(ElevenLabsResource{\n\t\t\t\tID:         historyItem.ID,\n\t\t\t\tName:       \"\", // no name\n\t\t\t\tType:       \"History\",\n\t\t\t\tPermission: PermissionStrings[SpeechHistoryRead],\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn handleErrorStatus(response, \"\", secretInfo, MissingPermissions)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking history read scope\", statusCode)\n\t}\n}\n\n// deleteHistory try to delete a history item. The item must not exist.\nfunc deleteHistory(client *http.Client, key string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(SpeechHistoryWrite), http.MethodDelete, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusInternalServerError:\n\t\t// for some reason if we send fake id and token has the permission, the history api return 500 error instead of 404\n\t\t// issue opened in elevenlabs-docs: https://github.com/elevenlabs/elevenlabs-docs/issues/649\n\t\treturn handleErrorStatus(response, PermissionStrings[SpeechHistoryWrite], secretInfo, InternalServerError)\n\tcase http.StatusUnauthorized:\n\t\treturn handleErrorStatus(response, \"\", secretInfo, MissingPermissions)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking history write scope\", statusCode)\n\t}\n}\n\n// deleteDubbing try to delete a dubbing. The item must not exist.\nfunc deleteDubbing(client *http.Client, key string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(DubbingWrite), http.MethodDelete, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusNotFound:\n\t\t// as we send fake id, if permission is assigned to token we must get 404 dubbing not found\n\t\tif err := handleErrorStatus(response, PermissionStrings[DubbingWrite], secretInfo, DubbingNotFound); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// add read scope of dubbing to avoid get dubbing api call\n\t\tsecretInfo.AppendPermission(PermissionStrings[DubbingRead])\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn handleErrorStatus(response, \"\", secretInfo, MissingPermissions)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking dubbing write scope\", statusCode)\n\t}\n}\n\n// getDebugging try to get a dubbing. The item must not exist.\nfunc getDebugging(client *http.Client, key string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(DubbingRead), http.MethodGet, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusNotFound:\n\t\t// as we send fake id, if permission is assigned to token we must get 404 dubbing not found\n\t\treturn handleErrorStatus(response, PermissionStrings[DubbingRead], secretInfo, DubbingNotFound)\n\tcase http.StatusUnauthorized:\n\t\treturn handleErrorStatus(response, \"\", secretInfo, MissingPermissions)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking dubbing read scope\", statusCode)\n\t}\n}\n\n// getVoices get list of voices using the key passed and add them to secret info\nfunc getVoices(client *http.Client, key string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(VoicesRead), http.MethodGet, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar voices VoicesResponse\n\n\t\tif err := json.Unmarshal(response, &voices); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// add voices read scope to secret info\n\t\tsecretInfo.AppendPermission(PermissionStrings[VoicesRead])\n\t\t// map resource to secret info\n\t\tfor _, voice := range voices.Voices {\n\t\t\tsecretInfo.AppendResource(ElevenLabsResource{\n\t\t\t\tID:         voice.ID,\n\t\t\t\tName:       voice.Name,\n\t\t\t\tType:       \"Voice\",\n\t\t\t\tPermission: PermissionStrings[VoicesRead],\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"category\": voice.Category,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn handleErrorStatus(response, \"\", secretInfo, MissingPermissions)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking voice read scope\", statusCode)\n\t}\n}\n\n// deleteVoice try to delete a voice. The item must not exist.\nfunc deleteVoice(client *http.Client, key string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(VoicesWrite), http.MethodDelete, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusBadRequest:\n\t\t// if permission was assigned to scope we should get 400 error with voice not found status\n\t\treturn handleErrorStatus(response, PermissionStrings[VoicesWrite], secretInfo, VoiceDoesNotExist)\n\tcase http.StatusUnauthorized:\n\t\treturn handleErrorStatus(response, \"\", secretInfo, MissingPermissions)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking voice write scope\", statusCode)\n\t}\n}\n\n// getProjects get list of projects using the key passed and add them to secret info\nfunc getProjects(client *http.Client, key string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(ProjectsRead), http.MethodGet, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar projects ProjectsResponse\n\n\t\tif err := json.Unmarshal(response, &projects); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// add project read scope to secret info\n\t\tsecretInfo.AppendPermission(PermissionStrings[ProjectsRead])\n\t\t// map resource to secret info\n\t\tfor _, project := range projects.Projects {\n\t\t\tsecretInfo.AppendResource(ElevenLabsResource{\n\t\t\t\tID:         project.ID,\n\t\t\t\tName:       project.Name,\n\t\t\t\tType:       \"Project\",\n\t\t\t\tPermission: PermissionStrings[ProjectsRead],\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"state\":        project.State,\n\t\t\t\t\t\"access level\": project.AccessLevel, // access level of project\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusForbidden:\n\t\t// if token has the permission but trail is free, projects are not accessible\n\t\treturn handleErrorStatus(response, PermissionStrings[ProjectsRead], secretInfo, InvalidSubscription)\n\tcase http.StatusUnauthorized:\n\t\treturn handleErrorStatus(response, \"\", secretInfo, MissingPermissions)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking projects read scope\", statusCode)\n\t}\n}\n\n// deleteProject try to delete a project. The item must not exist.\nfunc deleteProject(client *http.Client, key string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(ProjectsWrite), http.MethodDelete, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusBadRequest:\n\t\t// if permission was assigned to token we should get 400 error with project not found status\n\t\treturn handleErrorStatus(response, PermissionStrings[ProjectsWrite], secretInfo, ProjectNotFound)\n\tcase http.StatusForbidden:\n\t\t// if token has the permission but trail is free, projects are not accessible\n\t\treturn handleErrorStatus(response, PermissionStrings[ProjectsWrite], secretInfo, InvalidSubscription)\n\tcase http.StatusUnauthorized:\n\t\treturn handleErrorStatus(response, \"\", secretInfo, MissingPermissions)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking project write scope\", statusCode)\n\t}\n}\n\n// getPronunciationDictionaries get list of pronunciation dictionaries using the key passed and add them to secret info\nfunc getPronunciationDictionaries(client *http.Client, key string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(PronunciationDictionariesRead), http.MethodGet, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar PDs PronunciationDictionariesResponse\n\n\t\tif err := json.Unmarshal(response, &PDs); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// add voices read scope to secret info\n\t\tsecretInfo.AppendPermission(PermissionStrings[PronunciationDictionariesRead])\n\t\t// map resource to secret info\n\t\tfor _, pd := range PDs.PronunciationDictionaries {\n\t\t\tsecretInfo.AppendResource(ElevenLabsResource{\n\t\t\t\tID:         pd.ID,\n\t\t\t\tName:       pd.Name,\n\t\t\t\tType:       \"Pronunciation Dictionary\",\n\t\t\t\tPermission: PermissionStrings[PronunciationDictionariesRead],\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn handleErrorStatus(response, \"\", secretInfo, MissingPermissions)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking pronunciation dictionaries read scope\", statusCode)\n\t}\n}\n\n// removePronunciationDictionariesRule try to remove a rule from pronunciation dictionaries. The item must not exist.\nfunc removePronunciationDictionariesRule(client *http.Client, key string, secretInfo *SecretInfo) error {\n\t// send empty list of rule strings\n\tpayload := map[string]interface{}{\n\t\t\"rule_strings\": []string{\"\"},\n\t}\n\n\tpayloadBytes, _ := json.Marshal(payload)\n\tresponse, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(PronunciationDictionariesWrite), http.MethodPost,\n\t\t\"application/json\", key, payloadBytes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusNotFound:\n\t\t// if permission was assigned to token we should get 404 error with pronunciation_dictionary_not_found status\n\t\treturn handleErrorStatus(response, PermissionStrings[PronunciationDictionariesWrite], secretInfo, PronunciationDictionaryNotFound)\n\tcase http.StatusUnauthorized:\n\t\treturn handleErrorStatus(response, \"\", secretInfo, MissingPermissions)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking pronunciation dictionary write scope\", statusCode)\n\t}\n}\n\n// getModels list models using the key passed and add them to secret info\nfunc getModels(client *http.Client, key string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(ModelsRead), http.MethodGet, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar models []ModelsResponse\n\n\t\tif err := json.Unmarshal(response, &models); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// add models read scope to secret info\n\t\tsecretInfo.AppendPermission(PermissionStrings[ModelsRead])\n\t\t// map resource to secret info\n\t\tfor _, model := range models {\n\t\t\tsecretInfo.AppendResource(ElevenLabsResource{\n\t\t\t\tID:         model.ID,\n\t\t\t\tName:       model.Name,\n\t\t\t\tType:       \"Model\",\n\t\t\t\tPermission: PermissionStrings[ModelsRead],\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn handleErrorStatus(response, \"\", secretInfo, MissingPermissions)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking models read scope\", statusCode)\n\t}\n}\n\n// updateAudioNativeProject try to update a project content. The item must not exist.\nfunc updateAudioNativeProject(client *http.Client, key string, secretInfo *SecretInfo) error {\n\t// create a buffer to hold the multipart form data\n\tbody := &bytes.Buffer{}\n\twriter := multipart.NewWriter(body)\n\n\t// add required fields to multipart form body\n\t_ = writer.WriteField(\"auto_convert\", \"false\")\n\t_ = writer.WriteField(\"auto_publish\", \"false\")\n\t// close the writer\n\t_ = writer.Close()\n\n\tresponse, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(AudioNativeWrite), http.MethodPost,\n\t\twriter.FormDataContentType(), key, body.Bytes())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusBadRequest:\n\t\t// if the permission is assigned to token, the api should return 400 with invalid project id\n\t\tif err := handleErrorStatus(response, PermissionStrings[AudioNativeWrite], secretInfo, InvalidProjectID); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// add read permission as no separate API exist to check read audio native permission\n\t\tsecretInfo.AppendPermission(PermissionStrings[AudioNativeRead])\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn handleErrorStatus(response, \"\", secretInfo, MissingPermissions)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking audio native write scope\", statusCode)\n\t}\n}\n\n// deleteInviteFromWorkspace try to remove a invite from workspace. The item must not exist.\nfunc deleteInviteFromWorkspace(client *http.Client, key string, secretInfo *SecretInfo) error {\n\t// send fake email in payload\n\tpayload := map[string]interface{}{\n\t\t\"email\": fakeID + \"@example.com\",\n\t}\n\n\tpayloadBytes, _ := json.Marshal(payload)\n\tresponse, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(WorkspaceWrite), http.MethodDelete,\n\t\t\"application/json\", key, payloadBytes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusInternalServerError:\n\t\t// for some reason if we send fake email and token has the permission, the workspace invite api return 500 error instead of 404\n\t\tif err := handleErrorStatus(response, PermissionStrings[WorkspaceWrite], secretInfo, InternalServerError); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// add read permission as no separate API exist to check workspace read permission\n\t\tsecretInfo.AppendPermission(PermissionStrings[WorkspaceRead])\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn handleErrorStatus(response, \"\", secretInfo, MissingPermissions)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking workspace write scope\", statusCode)\n\t}\n}\n\n// textToSpeech try to convert text to speech. The model id and voice id is fake so it actually never happens.\nfunc textToSpeech(client *http.Client, key string, secretInfo *SecretInfo) error {\n\t// send fake model id in payload\n\tpayload := map[string]interface{}{\n\t\t\"text\":     \"This is trufflehog trying to check text to speech permission of the token\",\n\t\t\"model_id\": fakeID,\n\t}\n\n\tpayloadBytes, _ := json.Marshal(payload)\n\tresponse, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(TextToSpeech), http.MethodPost,\n\t\t\"application/json\", key, payloadBytes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusBadRequest:\n\t\t// if permission is assigned to token, error status will be either model not found or voice not found as we sent both fake ;)\n\t\treturn handleErrorStatus(response, PermissionStrings[TextToSpeech], secretInfo, ModelNotFound, VoiceNotFound)\n\tcase http.StatusUnauthorized:\n\t\treturn handleErrorStatus(response, \"\", secretInfo, MissingPermissions)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking text to speech scope\", statusCode)\n\t}\n}\n\n// speechToSpeech try to change a voice in speech. The model id and voice id is fake so it actually never happens.\nfunc speechToSpeech(client *http.Client, key string, secretInfo *SecretInfo) error {\n\t// create a buffer to hold the multipart form data\n\tbody := &bytes.Buffer{}\n\twriter := multipart.NewWriter(body)\n\n\t// add required fields to multipart form body\n\t_ = writer.WriteField(\"model_id\", fakeID)\n\t_ = writer.WriteField(\"seed\", \"1\")\n\t_ = writer.WriteField(\"remove_background_noise\", \"false\")\n\taudio, _ := writer.CreateFormFile(\"audio\", \"\")\n\t_, _ = audio.Write([]byte(\"This is example fake audio for api call\"))\n\t// close the writer\n\t_ = writer.Close()\n\n\tresponse, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(SpeechToSpeech), http.MethodPost,\n\t\twriter.FormDataContentType(), key, body.Bytes())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusBadRequest:\n\t\treturn handleErrorStatus(response, PermissionStrings[SpeechToSpeech], secretInfo, InvalidContent)\n\tcase http.StatusUnauthorized:\n\t\treturn handleErrorStatus(response, \"\", secretInfo, MissingPermissions)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking speech to speech scope\", statusCode)\n\t}\n}\n\n// audioIsolation try to remove background noise from a voice. The file will be corrupted so it should return an error.\nfunc audioIsolation(client *http.Client, key string, secretInfo *SecretInfo) error {\n\t// create a buffer to hold the multipart form data\n\tbody := &bytes.Buffer{}\n\twriter := multipart.NewWriter(body)\n\n\taudio, _ := writer.CreateFormFile(\"audio\", \"\")\n\t_, _ = audio.Write([]byte(\"This is example fake audio for api call\"))\n\t// close the writer\n\t_ = writer.Close()\n\n\tresponse, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(AudioIsolation), http.MethodPost,\n\t\twriter.FormDataContentType(), key, body.Bytes())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusBadRequest:\n\t\treturn handleErrorStatus(response, PermissionStrings[AudioIsolation], secretInfo, InvalidContent)\n\tcase http.StatusUnauthorized:\n\t\treturn handleErrorStatus(response, \"\", secretInfo, MissingPermissions)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking audio isolation speech scope\", statusCode)\n\t}\n}\n\n/*\ngetAgents get all user agents which are not bound with any permission\ncall APIs in pattern: agents->conversation\n*/\nfunc getAgents(client *http.Client, key string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeElevenLabsRequest(client, \"https://api.elevenlabs.io/v1/convai/agents\", http.MethodGet, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar agents AgentsResponse\n\n\t\tif err := json.Unmarshal(response, &agents); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// map resource to secret info\n\t\tfor _, agent := range agents.Agents {\n\t\t\tresource := ElevenLabsResource{\n\t\t\t\tID:         agent.ID,\n\t\t\t\tName:       agent.Name,\n\t\t\t\tType:       \"Agent\",\n\t\t\t\tPermission: \"\", // not binded with any permission\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"access level\": agent.AccessLevel,\n\t\t\t\t},\n\t\t\t}\n\t\t\tsecretInfo.AppendResource(resource)\n\t\t\t// get agent conversations\n\t\t\tif err := getConversation(client, key, agent.ID, secretInfo); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking models read scope\", statusCode)\n\t}\n}\n\n// getConversation list all agent conversations using the key and agentID passed and add them to secret info\nfunc getConversation(client *http.Client, key, agentID string, secretInfo *SecretInfo) error {\n\tapiUrl := fmt.Sprintf(\"https://api.elevenlabs.io/v1/convai/conversations?agent_id=%s\", agentID)\n\tresponse, statusCode, err := makeElevenLabsRequest(client, apiUrl, http.MethodGet, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar conversations ConversationResponse\n\n\t\tif err := json.Unmarshal(response, &conversations); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// map resource to secret info\n\t\tfor _, conversation := range conversations.Conversations {\n\t\t\tsecretInfo.AppendResource(ElevenLabsResource{\n\t\t\t\tID:         conversation.ID,\n\t\t\t\tName:       \"\", // no name\n\t\t\t\tType:       \"Conversation\",\n\t\t\t\tPermission: \"\", // not binded with any permission\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"status\": conversation.Status,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while checking models read scope\", statusCode)\n\t}\n}\n\n// handleErrorStatus handle error response, check if expected error status is in the response and add require permission to secret info\n// this is used in case where we expect error response with specific status mostly in write calls\nfunc handleErrorStatus(response []byte, permissionToAdd string, secretInfo *SecretInfo, expectedErrStatuses ...string) error {\n\t// check if status in response is what is expected to be\n\tok, err := checkErrorStatus(response, expectedErrStatuses...)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// if permission to add was passed and it was expected error status add the permission\n\tif permissionToAdd != \"\" && ok {\n\t\tsecretInfo.AppendPermission(permissionToAdd)\n\t} else if permissionToAdd != \"\" && !ok {\n\t\t// if permission to add was passed and it was unexpected error status - return error\n\t\treturn errors.New(\"unexpected error response\")\n\t}\n\n\treturn nil\n}\n\n// checkErrorStatus check if any of expected error status exist in actual API error response\nfunc checkErrorStatus(response []byte, expectedStatuses ...string) (bool, error) {\n\tvar errorResp ErrorResponse\n\n\tif err := json.Unmarshal(response, &errorResp); err != nil {\n\t\treturn false, err\n\t}\n\n\tif slices.Contains(expectedStatuses, errorResp.Detail.Status) {\n\t\treturn true, nil\n\t}\n\n\treturn false, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/elevenlabs/result_output.json",
    "content": "{\n    \"AnalyzerType\": 6,\n    \"Bindings\": [\n        {\n            \"Resource\": {\n                \"Name\": \"Ahmed\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/User/b9Rou9mHDmTYd8cdWkg2Yk4P2lq1\",\n                \"Type\": \"User\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"user_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Alice\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/Xb7hH8MSUJpSbSDYk0k2\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Aria\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/9BWtsMINqrJLrRacOk9x\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Bill\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/pqHfZKP75CvOlQylNhV4\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Brian\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/nPczCjzI2devNBz1zQrb\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Callum\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/N2lVS1w4EtoT3dr4eOWO\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Charlie\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/IKne3meq5aSn9XLyUdCD\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Charlotte\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/XB0fDUnXU5powFXDhCwa\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Chris\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/iP95p4xoKVk53GoZ742B\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Daniel\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/onwK4e9ZLuTAKqWW03F9\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Eleven English v1\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_monolingual_v1\",\n                \"Type\": \"Model\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"models_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Eleven English v2\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_english_sts_v2\",\n                \"Type\": \"Model\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"models_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Eleven Flash v2\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_flash_v2\",\n                \"Type\": \"Model\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"models_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Eleven Flash v2.5\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_flash_v2_5\",\n                \"Type\": \"Model\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"models_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Eleven Multilingual v1\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_multilingual_v1\",\n                \"Type\": \"Model\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"models_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Eleven Multilingual v2\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_multilingual_v2\",\n                \"Type\": \"Model\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"models_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Eleven Multilingual v2\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_multilingual_sts_v2\",\n                \"Type\": \"Model\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"models_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Eleven Turbo v2\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_turbo_v2\",\n                \"Type\": \"Model\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"models_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Eleven Turbo v2.5\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_turbo_v2_5\",\n                \"Type\": \"Model\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"models_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Eric\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/cjVigY5qzO86Huf0OWal\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"George\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/JBFqnCBsd6RMkjVDRZzb\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Jessica\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/cgSgspJ2msm6clMCkdW9\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Laura\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/FGY2WhTYpPnrIDTdsKH5\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Liam\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/TX3LPaxmHKxFdv7VOQHJ\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Lily\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/pFZP5JQG7iQjIQuC4Bku\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Matilda\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/XrExE9yKIg1WjnnlVkGX\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"River\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/SAz9YHcvj6GT2YYXdXww\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Roger\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/CwhRBWXzGAHq8TQ4Fs17\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Sarah\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/EXAVITQu4vr4xnSDxMaL\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Will\",\n                \"FullyQualifiedName\": \"b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/bIHbv24MWmeRgasZH58o\",\n                \"Type\": \"Voice\",\n                \"Metadata\": {\n                    \"category\": \"premade\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"voices_read\",\n                \"Parent\": null\n            }\n        }\n    ],\n    \"UnboundedResources\": null,\n    \"Metadata\": {\n        \"Valid_Key\": true\n    }\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/fastly/fastly.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go fastly\npackage fastly\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (a Analyzer) Type() analyzers.AnalyzerType {\n\treturn analyzers.AnalyzerTypeFastly\n}\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, exist := credInfo[\"key\"]\n\tif !exist {\n\t\treturn nil, fmt.Errorf(\"key not found in credential info\")\n\t}\n\n\t// analyze permissions\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// secret info to analyzer\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\t// just print the error in cli and continue as a partial success\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t}\n\n\tif info == nil {\n\t\tcolor.Red(\"[x] Error : %s\", \"No information found\")\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid Fastly API key\\n\\n\")\n\n\tif info.TokenInfo.hasGlobalScope() {\n\t\tprintUserInfo(info.UserInfo)\n\t}\n\n\tprintScopes(info.TokenInfo.Scopes)\n\n\tif len(info.Resources) > 0 {\n\t\tprintResources(info.Resources)\n\t}\n\n\tcolor.Yellow(\"\\n[i] Expires: %s\", info.TokenInfo.ExpiresAt)\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\t// create http client\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\tvar secretInfo = &SecretInfo{}\n\n\t// capture the token details\n\tif err := captureTokenInfo(client, key, secretInfo); err != nil {\n\t\treturn nil, err\n\t}\n\n\t/*\n\t\tFastly defines four types of permissions. Two of these are related specifically to purging:\n\n\t\t- If a token has either `purge_select` or `purge_all` access, it is limited to calling purge-related APIs only.\n\t\t- If a token has `global` or `global:read` access, it can call APIs that retrieve resource and user information.\n\t*/\n\n\tif !secretInfo.TokenInfo.hasGlobalScope() {\n\t\treturn secretInfo, nil\n\t}\n\n\t// capture the user information\n\tif err := captureUserInfo(client, key, secretInfo); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// capture the resources\n\tif err := captureResources(client, key, secretInfo); err != nil {\n\t\t// return secretInfo as well in case of error for partial success\n\t\treturn secretInfo, err\n\t}\n\n\treturn secretInfo, nil\n}\n\n// secretInfoToAnalyzerResult translate secret info to Analyzer Result\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeFastly,\n\t\tMetadata:     map[string]any{},\n\t\tBindings:     make([]analyzers.Binding, 0),\n\t}\n\n\t// extract information from resource to create bindings and append to result bindings\n\tfor _, resource := range info.Resources {\n\t\tbinding := analyzers.Binding{\n\t\t\tResource: *secretInfoResourceToAnalyzerResource(resource),\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: info.TokenInfo.Scope,\n\t\t\t},\n\t\t}\n\n\t\tif resource.Parent != nil {\n\t\t\tbinding.Resource.Parent = secretInfoResourceToAnalyzerResource(*resource.Parent)\n\t\t}\n\n\t\tresult.Bindings = append(result.Bindings, binding)\n\n\t}\n\n\treturn &result\n}\n\n// secretInfoResourceToAnalyzerResource translate secret info resource to analyzer resource for binding\nfunc secretInfoResourceToAnalyzerResource(resource FastlyResource) *analyzers.Resource {\n\tanalyzerRes := analyzers.Resource{\n\t\t// make fully qualified name unique\n\t\tFullyQualifiedName: resource.Type + \"/\" + resource.ID,\n\t\tName:               resource.Name,\n\t\tType:               resource.Type,\n\t\tMetadata:           map[string]any{},\n\t}\n\n\tfor key, value := range resource.Metadata {\n\t\tanalyzerRes.Metadata[key] = value\n\t}\n\n\treturn &analyzerRes\n}\n\n// cli print functions\nfunc printUserInfo(user User) {\n\tcolor.Yellow(\"[i] User Information:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"ID\", \"Name\", \"Login\", \"Role\", \"Last Active At\"})\n\tt.AppendRow(table.Row{color.GreenString(user.ID), color.GreenString(user.Name), color.GreenString(user.Login), color.GreenString(user.Role), color.GreenString(user.LastActiveAt)})\n\n\tt.Render()\n}\n\nfunc printScopes(scopes []string) {\n\tcolor.Yellow(\"[i] Scopes:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Scopes\"})\n\tfor _, scope := range scopes {\n\t\tt.AppendRow(table.Row{color.GreenString(scope)})\n\t}\n\tt.Render()\n}\n\nfunc printResources(resources []FastlyResource) {\n\tcolor.Yellow(\"[i] Resources:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Name\", \"Type\"})\n\tfor _, resource := range resources {\n\t\tt.AppendRow(table.Row{color.GreenString(resource.Name), color.GreenString(resource.Type)})\n\t}\n\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/fastly/fastly_test.go",
    "content": "package fastly\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed result_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tkey := testSecrets.MustGetField(\"FASTLYPERSONALTOKEN_TOKEN\")\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    []byte // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid fastly token\",\n\t\t\tkey:     key,\n\t\t\twant:    expectedOutput,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.FullyQualifiedName == bindings[j].Resource.FullyQualifiedName {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.FullyQualifiedName < bindings[j].Resource.FullyQualifiedName\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/fastly/models.go",
    "content": "package fastly\n\nimport \"sync\"\n\nconst (\n\t// types\n\tTypeUserToken             string = \"User Token\"\n\tTypeAutomationToken       string = \"Automation Token\"\n\tTypeService               string = \"Service\"\n\tTypeSvcVersion            string = \"Service Version\"\n\tTypeSvcVersionACL         string = \"Service Version ACL\"\n\tTypeSvcVersionDict        string = \"Service Version Dictionary\"\n\tTypeSvcVersionBackend     string = \"Service Version Backend\"\n\tTypeSvcVersionDomain      string = \"Service Version Domain\"\n\tTypeSvcVersionHealthCheck string = \"Service Version Health Check\"\n\tTypeConfigStore           string = \"Config Store\"\n\tTypeSecretStore           string = \"Secret Store\"\n\tTypeTLSPrivateKey         string = \"TLS Private Key\"\n\tTypeTLSCertificate        string = \"TLS Certificates\"\n\tTypeTLSDomain             string = \"TLS Domain\"\n\tTypeInvoice               string = \"Invoice\"\n)\n\ntype SecretInfo struct {\n\tmu sync.RWMutex\n\n\tUserInfo  User\n\tTokenInfo SelfToken\n\tResources []FastlyResource\n}\n\ntype FastlyResource struct {\n\tID       string\n\tName     string\n\tType     string\n\tMetadata map[string]string\n\tParent   *FastlyResource\n}\n\n// AppendResource append resource to secret info resource list\nfunc (s *SecretInfo) appendResource(resource FastlyResource) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\ts.Resources = append(s.Resources, resource)\n}\n\n// listResourceByType returns a list of resources matching the given type.\nfunc (s *SecretInfo) listResourceByType(resourceType string) []FastlyResource {\n\ts.mu.RLock()\n\tdefer s.mu.RUnlock()\n\n\tresources := make([]FastlyResource, 0, len(s.Resources))\n\tfor _, resource := range s.Resources {\n\t\tif resource.Type == resourceType {\n\t\t\tresources = append(resources, resource)\n\t\t}\n\t}\n\n\treturn resources\n}\n\n// API Response models\n\n// User is /current_user API Response\ntype User struct {\n\tID           string `json:\"id\"`\n\tName         string `json:\"name\"`\n\tLogin        string `json:\"login\"`\n\tRole         string `json:\"role\"`\n\tLastActiveAt string `json:\"last_active_at\"`\n}\n\n// SelfToken is /tokens/self API Response\ntype SelfToken struct {\n\tID         string   `json:\"id\"`\n\tUserID     string   `json:\"user_id\"`\n\tName       string   `json:\"name\"`\n\tLastUsedAt string   `json:\"last_used_at\"`\n\tExpiresAt  string   `json:\"expires_at\"`\n\tScope      string   `json:\"scope\"`\n\tScopes     []string `json:\"scopes\"`\n\tServices   []string `json:\"services\"`\n}\n\n// hasGlobalScope returns true if any global scope is assigned to the token\nfunc (t SelfToken) hasGlobalScope() bool {\n\tfor _, scope := range t.Scopes {\n\t\tif scope == PermissionStrings[Global] || scope == PermissionStrings[GlobalRead] {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// TokenData is /automation-tokens API Response\ntype TokenData struct {\n\tData []Token `json:\"data\"`\n}\n\n// Token is /tokens API Response\ntype Token struct {\n\tID        string `json:\"id\"`\n\tName      string `json:\"name\"`\n\tScope     string `json:\"scope\"`\n\tRole      string `json:\"role\"`\n\tExpiresAt string `json:\"expires_at\"`\n}\n\n// Service is /service API Response\ntype Service struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n\tType string `json:\"type\"`\n}\n\n// Version is /service/<id>/version API Response\ntype Version struct {\n\tNumber    int    `json:\"number\"`\n\tActive    bool   `json:\"active\"`\n\tDeployed  bool   `json:\"deployed\"`\n\tServiceID string `json:\"service_id\"`\n}\n\n// ACL is /service/<id>/version/<number>/acl API Response\ntype ACL struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\n// Dictionary is the /service/<id>/version/<number>/dictionary API Response\ntype Dictionary struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\n// Backend is the /service/<id>/version/<number>/backend API Response\ntype Backend struct {\n\tName    string `json:\"name\"`\n\tAddress string `json:\"address\"`\n\tPort    string `json:\"port\"`\n}\n\n// Domain is the /service/<id>/version/<number>/domain API Response\ntype Domain struct {\n\tName string `json:\"name\"`\n}\n\n// HealthCheck is the /service/<id>/version/<number>/healthcheck API Response\ntype HealthCheck struct {\n\tName   string `json:\"name\"`\n\tHost   string `json:\"host\"`\n\tPath   string `json:\"path\"`\n\tMethod string `json:\"method\"`\n}\n\n// ConfigStore is the /resources/stores/config API Response\ntype ConfigStore struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\n// SecretStoreData is the /resources/stores/secret API Response\ntype SecretStoreData struct {\n\tData []SecretStore `json:\"data\"`\n}\n\n// SecretStore is a single store in SecretStoreData\ntype SecretStore struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\n// TLSPrivateKeyData is the /tls/private_keys API Response\ntype TLSPrivateKeyData struct {\n\tData []TLSPrivateKey `json:\"data\"`\n}\n\n// TLSPrivateKey is the single TLS private key in TLSPrivateKeyData\ntype TLSPrivateKey struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\n// TLSCertificatesData is the /tls/certificates API Response\ntype TLSCertificatesData struct {\n\tData []TLSCertificate `json:\"data\"`\n}\n\n// TLSCertificate is the single TLS certificate in TLSCertificatesData\ntype TLSCertificate struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\n// TLSDomainsData is the /tls/domains API Response\ntype TLSDomainsData struct {\n\tData []TLSDomain `json:\"data\"`\n}\n\n// TLSDomain is the single TLS Domain in TLSDomainsData\ntype TLSDomain struct {\n\tID string `json:\"id\"`\n}\n\n// InvoicesData is the /billing/v3/invoices API Response\ntype InvoicesData struct {\n\tData []Invoice `json:\"data\"`\n}\n\n// Invoice is the single invoice in InvoicesData\ntype Invoice struct {\n\tID              string `json:\"invoice_id\"`\n\tCustomerID      string `json:\"customer_id\"`\n\tRegion          string `json:\"region\"`\n\tStatementNo     string `json:\"statement_number\"`\n\tInvoicePostedOn string `json:\"invoice_posted_on\"`\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/fastly/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage fastly\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    Global Permission = iota\n    GlobalRead Permission = iota\n    PurgeAll Permission = iota\n    PurgeSelect Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        Global: \"global\",\n        GlobalRead: \"global:read\",\n        PurgeAll: \"purge_all\",\n        PurgeSelect: \"purge_select\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"global\": Global,\n        \"global:read\": GlobalRead,\n        \"purge_all\": PurgeAll,\n        \"purge_select\": PurgeSelect,\n    }\n\n    PermissionIDs = map[Permission]int{\n        Global: 1,\n        GlobalRead: 2,\n        PurgeAll: 3,\n        PurgeSelect: 4,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: Global,\n        2: GlobalRead,\n        3: PurgeAll,\n        4: PurgeSelect,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/fastly/permissions.yaml",
    "content": "permissions:\n  - global\n  - global:read\n  - purge_all\n  - purge_select\n"
  },
  {
    "path": "pkg/analyzer/analyzers/fastly/requests.go",
    "content": "package fastly\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"sync\"\n)\n\ntype endpoint int\n\nconst (\n\t// list of endpoints\n\tselfToken endpoint = iota\n\tcurrentUser\n\tuserTokens\n\tautomationTokens\n\tservice\n\tserviceVersions\n\tserviceVersionACLs\n\tserviceVersionDictionaries\n\tserviceVersionBackends\n\tserviceVersionDomains\n\tserviceVersionHealthChecks\n\tconfigStores\n\tsecretStores\n\ttlsPrivateKeys\n\ttlsCertificates\n\ttlsDomains\n\tinvoices\n)\n\nvar (\n\tbaseURL = \"https://api.fastly.com\"\n\n\t// endpoints contain Fastly API endpoints\n\tendpoints = map[endpoint]string{\n\t\tselfToken:                  \"/tokens/self\",\n\t\tcurrentUser:                \"/current_user\",\n\t\tuserTokens:                 \"/tokens\",\n\t\tautomationTokens:           \"/automation-tokens\",\n\t\tservice:                    \"/service\",\n\t\tserviceVersions:            \"/service/%s/version\",                // require service id\n\t\tserviceVersionACLs:         \"/service/%s/version/%s/acl\",         // require service id and version number\n\t\tserviceVersionDictionaries: \"/service/%s/version/%s/dictionary\",  // require service id and version number\n\t\tserviceVersionBackends:     \"/service/%s/version/%s/backend\",     // require service id and version number\n\t\tserviceVersionDomains:      \"/service/%s/version/%s/domain\",      // require service id and version number\n\t\tserviceVersionHealthChecks: \"/service/%s/version/%s/healthcheck\", // require service id and version number\n\t\tconfigStores:               \"/resources/stores/config\",\n\t\tsecretStores:               \"/resources/stores/secret\",\n\t\ttlsPrivateKeys:             \"/tls/private_keys\",\n\t\ttlsCertificates:            \"/tls/certificates\",\n\t\ttlsDomains:                 \"/tls/domains\",\n\t\tinvoices:                   \"/billing/v3/invoices\",\n\n\t\t/*\n\t\t\tAPI:\n\t\t\t- /service/service_id/version/version_id/package (The use of this API is discouraged as per documentation due to limited availability release)\n\t\t\t- /tls/bulk/certificates (The use of this API is discouraged as per documentation due to limited availability release)\n\t\t\t- /security/workspaces (This Fastly Security API is only available to customers with access to the Next-Gen WAF product )\n\t\t\t- /events (This API just returns the account events like user logged in or user logged out etc)\n\n\t\t\tUtilities API Docs:\n\t\t\tSome of these APIs are deprecated while others return same response for everyone with a global access key.\n\t\t\t- https://www.fastly.com/documentation/reference/api/utils/\n\t\t*/\n\t}\n)\n\n// makeFastlyRequest send the API request to passed url with passed key as API Key and return response body and status code\nfunc makeFastlyRequest(client *http.Client, endpoint, key string) ([]byte, int, error) {\n\t// create request\n\treq, err := http.NewRequest(http.MethodGet, baseURL+endpoint, http.NoBody)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\t// add key in the header\n\treq.Header.Add(\"Fastly-Key\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tresponseBodyByte, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn responseBodyByte, resp.StatusCode, nil\n}\n\n// captureResources try to capture all the resource that the key can access\nfunc captureResources(client *http.Client, key string, secretInfo *SecretInfo) error {\n\tvar (\n\t\twg             sync.WaitGroup\n\t\terrAggWg       sync.WaitGroup\n\t\taggregatedErrs = make([]error, 0)\n\t\terrChan        = make(chan error, 1)\n\t)\n\n\terrAggWg.Add(1)\n\tgo func() {\n\t\tdefer errAggWg.Done()\n\t\tfor err := range errChan {\n\t\t\taggregatedErrs = append(aggregatedErrs, err)\n\t\t}\n\t}()\n\n\t// helper to launch tasks concurrently.\n\tlaunchTask := func(task func() error) {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tif err := task(); err != nil {\n\t\t\t\terrChan <- err\n\t\t\t}\n\t\t}()\n\t}\n\n\tlaunchTask(func() error { return captureAutomationTokens(client, key, secretInfo) })\n\tlaunchTask(func() error { return captureUserTokens(client, key, secretInfo) })\n\n\t// capture services and their sub resources\n\tlaunchTask(func() error {\n\t\tif err := captureServices(client, key, secretInfo); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tservices := secretInfo.listResourceByType(TypeService)\n\t\tfor _, service := range services {\n\t\t\tif err := captureSvcVersions(client, key, service, secretInfo); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\t// capture each version sub resources\n\t\tversions := secretInfo.listResourceByType(TypeSvcVersion)\n\t\tfor _, version := range versions {\n\t\t\tlaunchTask(func() error { return captureSvcVersionACLs(client, key, version, secretInfo) })\n\t\t\tlaunchTask(func() error { return captureSvcVersionDicts(client, key, version, secretInfo) })\n\t\t\tlaunchTask(func() error { return captureSvcVersionBackends(client, key, version, secretInfo) })\n\t\t\tlaunchTask(func() error { return captureSvcVersionDomains(client, key, version, secretInfo) })\n\t\t\tlaunchTask(func() error { return captureSvcVersionHealthChecks(client, key, version, secretInfo) })\n\t\t}\n\n\t\treturn nil\n\t})\n\n\tlaunchTask(func() error { return captureConfigStores(client, key, secretInfo) })\n\tlaunchTask(func() error { return captureSecretStores(client, key, secretInfo) })\n\tlaunchTask(func() error { return capturePrivateKeys(client, key, secretInfo) })\n\tlaunchTask(func() error { return captureCertificates(client, key, secretInfo) })\n\tlaunchTask(func() error { return captureTLSDomains(client, key, secretInfo) })\n\tlaunchTask(func() error { return captureInvoices(client, key, secretInfo) })\n\n\twg.Wait()\n\tclose(errChan)\n\terrAggWg.Wait()\n\n\tif len(aggregatedErrs) > 0 {\n\t\treturn errors.Join(aggregatedErrs...)\n\t}\n\n\treturn nil\n}\n\n// captureTokenInfo calls `/tokens/self` API and capture the token information in secretInfo\nfunc captureTokenInfo(client *http.Client, key string, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeFastlyRequest(client, endpoints[selfToken], key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar token SelfToken\n\n\t\tif err := json.Unmarshal(respBody, &token); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif token.ExpiresAt == \"\" {\n\t\t\ttoken.ExpiresAt = \"never\"\n\t\t}\n\n\t\tsecretInfo.TokenInfo = token\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired api key\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d for API: %s\", statusCode, endpoints[selfToken])\n\t}\n}\n\n// captureUserInfo calls `/current_user` API and capture the current user information in secretInfo\nfunc captureUserInfo(client *http.Client, key string, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeFastlyRequest(client, endpoints[currentUser], key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar user User\n\n\t\tif err := json.Unmarshal(respBody, &user); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tsecretInfo.UserInfo = user\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d for API: %s\", statusCode, endpoints[currentUser])\n\t}\n}\n\n// captureUserTokens calls `/tokens` API\nfunc captureUserTokens(client *http.Client, key string, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeFastlyRequest(client, endpoints[userTokens], key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar tokens []Token\n\n\t\tif err := json.Unmarshal(respBody, &tokens); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, token := range tokens {\n\t\t\tresource := FastlyResource{\n\t\t\t\tID:   token.ID,\n\t\t\t\tName: token.Name,\n\t\t\t\tType: TypeUserToken,\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"Scope\":      token.Scope,\n\t\t\t\t\t\"Role\":       token.Role,\n\t\t\t\t\t\"Expires At\": token.ExpiresAt,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// captureAutomationTokens calls `/automation-tokens` API\nfunc captureAutomationTokens(client *http.Client, key string, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeFastlyRequest(client, endpoints[automationTokens], key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar tokens TokenData\n\n\t\tif err := json.Unmarshal(respBody, &tokens); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, token := range tokens.Data {\n\t\t\tresource := FastlyResource{\n\t\t\t\tID:   token.ID,\n\t\t\t\tName: token.Name,\n\t\t\t\tType: TypeAutomationToken,\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"Scope\":      token.Scope,\n\t\t\t\t\t\"Role\":       token.Role,\n\t\t\t\t\t\"Expires At\": token.ExpiresAt,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// captureServices calls `/service` API\nfunc captureServices(client *http.Client, key string, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeFastlyRequest(client, endpoints[service], key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar services []Service\n\n\t\tif err := json.Unmarshal(respBody, &services); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, service := range services {\n\t\t\tresource := FastlyResource{\n\t\t\t\tID:   service.ID,\n\t\t\t\tName: service.Name,\n\t\t\t\tType: TypeService,\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"Service Type\": service.Type,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d for API: %s\", statusCode, endpoints[service])\n\t}\n}\n\n// captureSvcVersions calls `/service/<id>/version` API\nfunc captureSvcVersions(client *http.Client, key string, parentService FastlyResource, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeFastlyRequest(client, fmt.Sprintf(endpoints[serviceVersions], parentService.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar versions []Version\n\n\t\tif err := json.Unmarshal(respBody, &versions); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, version := range versions {\n\t\t\tresource := FastlyResource{\n\t\t\t\tID:       strconv.Itoa(version.Number),\n\t\t\t\tName:     parentService.ID + \"/version/\" + strconv.Itoa(version.Number), // versions has no specific name\n\t\t\t\tType:     TypeSvcVersion,\n\t\t\t\tMetadata: map[string]string{\"service_id\": version.ServiceID},\n\t\t\t\tParent:   &parentService,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// captureSvcVersionACLs calls `/service/<id>/version/<number>/acl` API\nfunc captureSvcVersionACLs(client *http.Client, key string, parentVersion FastlyResource, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeFastlyRequest(client, fmt.Sprintf(endpoints[serviceVersionACLs], parentVersion.Metadata[\"service_id\"], parentVersion.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar acls []ACL\n\n\t\tif err := json.Unmarshal(respBody, &acls); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, acl := range acls {\n\t\t\tresource := FastlyResource{\n\t\t\t\tID:     acl.ID,\n\t\t\t\tName:   acl.Name,\n\t\t\t\tType:   TypeSvcVersionACL,\n\t\t\t\tParent: &parentVersion,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// captureSvcVersionDicts calls `/service/<id>/version/<number>/dictionaries` API\nfunc captureSvcVersionDicts(client *http.Client, key string, parentVersion FastlyResource, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeFastlyRequest(client, fmt.Sprintf(endpoints[serviceVersionDictionaries], parentVersion.Metadata[\"service_id\"], parentVersion.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar dicts []Dictionary\n\n\t\tif err := json.Unmarshal(respBody, &dicts); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, dict := range dicts {\n\t\t\tresource := FastlyResource{\n\t\t\t\tID:     dict.ID,\n\t\t\t\tName:   dict.Name,\n\t\t\t\tType:   TypeSvcVersionDict,\n\t\t\t\tParent: &parentVersion,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// captureSvcVersionBackends calls `/service/<id>/version/<number>/backend` API\nfunc captureSvcVersionBackends(client *http.Client, key string, parentVersion FastlyResource, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeFastlyRequest(client, fmt.Sprintf(endpoints[serviceVersionBackends], parentVersion.Metadata[\"service_id\"], parentVersion.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar backends []Backend\n\n\t\tif err := json.Unmarshal(respBody, &backends); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, backend := range backends {\n\t\t\tresource := FastlyResource{\n\t\t\t\tID:     parentVersion.Metadata[\"service_id\"] + \"/version/\" + parentVersion.ID + \"/backend/\" + backend.Name, // no specific ID\n\t\t\t\tName:   backend.Name,\n\t\t\t\tType:   TypeSvcVersionBackend,\n\t\t\t\tParent: &parentVersion,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// captureSvcVersionDomains calls `/service/<id>/version/<number>/domain` API\nfunc captureSvcVersionDomains(client *http.Client, key string, parentVersion FastlyResource, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeFastlyRequest(client, fmt.Sprintf(endpoints[serviceVersionDomains], parentVersion.Metadata[\"service_id\"], parentVersion.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar domains []Domain\n\n\t\tif err := json.Unmarshal(respBody, &domains); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, domain := range domains {\n\t\t\tresource := FastlyResource{\n\t\t\t\tID:     parentVersion.Metadata[\"service_id\"] + \"/version/\" + parentVersion.ID + \"/domain/\" + domain.Name, // no specific ID\n\t\t\t\tName:   domain.Name,\n\t\t\t\tType:   TypeSvcVersionDomain,\n\t\t\t\tParent: &parentVersion,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// captureSvcVersionHealthChecks calls `/service/<id>/version/<number>/healthcheck` API\nfunc captureSvcVersionHealthChecks(client *http.Client, key string, parentVersion FastlyResource, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeFastlyRequest(client, fmt.Sprintf(endpoints[serviceVersionHealthChecks], parentVersion.Metadata[\"service_id\"], parentVersion.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar healthChecks []HealthCheck\n\n\t\tif err := json.Unmarshal(respBody, &healthChecks); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, healthCheck := range healthChecks {\n\t\t\tresource := FastlyResource{\n\t\t\t\tID:     parentVersion.Metadata[\"service_id\"] + \"/version/\" + parentVersion.ID + \"/healthcheck/\" + healthCheck.Name, // no specific ID\n\t\t\t\tName:   healthCheck.Name,\n\t\t\t\tType:   TypeSvcVersionHealthCheck,\n\t\t\t\tParent: &parentVersion,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// captureConfigStores calls `/resources/stores/config` API\nfunc captureConfigStores(client *http.Client, key string, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeFastlyRequest(client, endpoints[configStores], key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar configs []ConfigStore\n\n\t\tif err := json.Unmarshal(respBody, &configs); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, config := range configs {\n\t\t\tresource := FastlyResource{\n\t\t\t\tID:   config.ID,\n\t\t\t\tName: config.Name,\n\t\t\t\tType: TypeConfigStore,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// captureSecretStores calls `/resources/stores/secret` API\nfunc captureSecretStores(client *http.Client, key string, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeFastlyRequest(client, endpoints[secretStores], key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar secretStores SecretStoreData\n\n\t\tif err := json.Unmarshal(respBody, &secretStores); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, secret := range secretStores.Data {\n\t\t\tresource := FastlyResource{\n\t\t\t\tID:   secret.ID,\n\t\t\t\tName: secret.Name,\n\t\t\t\tType: TypeSecretStore,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// capturePrivateKeys calls `/tls/private_keys` API\nfunc capturePrivateKeys(client *http.Client, key string, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeFastlyRequest(client, endpoints[tlsPrivateKeys], key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar privateKeys TLSPrivateKeyData\n\n\t\tif err := json.Unmarshal(respBody, &privateKeys); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, privateKey := range privateKeys.Data {\n\t\t\tresource := FastlyResource{\n\t\t\t\tID:   privateKey.ID,\n\t\t\t\tName: privateKey.Name,\n\t\t\t\tType: TypeTLSPrivateKey,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// captureCertificates calls `/tls/certificates` API\nfunc captureCertificates(client *http.Client, key string, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeFastlyRequest(client, endpoints[tlsCertificates], key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar certData TLSCertificatesData\n\n\t\tif err := json.Unmarshal(respBody, &certData); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, cert := range certData.Data {\n\t\t\tresource := FastlyResource{\n\t\t\t\tID:   cert.ID,\n\t\t\t\tName: cert.Name,\n\t\t\t\tType: TypeTLSCertificate,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// captureTLSDomains calls `/tls/domains` API\nfunc captureTLSDomains(client *http.Client, key string, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeFastlyRequest(client, endpoints[tlsDomains], key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar domainData TLSDomainsData\n\n\t\tif err := json.Unmarshal(respBody, &domainData); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, domain := range domainData.Data {\n\t\t\tresource := FastlyResource{\n\t\t\t\tID:   domain.ID,\n\t\t\t\tName: domain.ID,\n\t\t\t\tType: TypeTLSDomain,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// captureInvoices calls `/billing/v3/invoices` API\nfunc captureInvoices(client *http.Client, key string, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeFastlyRequest(client, endpoints[invoices], key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar invoices InvoicesData\n\n\t\tif err := json.Unmarshal(respBody, &invoices); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, invoice := range invoices.Data {\n\t\t\tresource := FastlyResource{\n\t\t\t\tID:   invoice.CustomerID + \"/region/\" + invoice.Region + \"/statement/\" + invoice.StatementNo + \"/invoice/\" + invoice.ID,\n\t\t\t\tName: invoice.ID, // no specific name\n\t\t\t\tType: TypeInvoice,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/fastly/result_output.json",
    "content": "{\n    \"AnalyzerType\": 34,\n    \"Bindings\": [\n        {\n            \"Resource\": {\n                \"Name\": \"test\",\n                \"FullyQualifiedName\": \"Config Store/Q9uDqi7ODnLUrhMFifFVT4\",\n                \"Type\": \"Config Store\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"global:read global\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"centrally-decent-lynx.edgecompute.app\",\n                \"FullyQualifiedName\": \"Service Version Domain/vInh5jJ0qnGdhiCO04INR7/version/1/domain/centrally-decent-lynx.edgecompute.app\",\n                \"Type\": \"Service Version Domain\",\n                \"Metadata\": {},\n                \"Parent\": {\n                    \"Name\": \"vInh5jJ0qnGdhiCO04INR7/version/1\",\n                    \"FullyQualifiedName\": \"Service Version/1\",\n                    \"Type\": \"Service Version\",\n                    \"Metadata\": {\n                        \"service_id\": \"vInh5jJ0qnGdhiCO04INR7\"\n                    },\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"global:read global\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"centrally-decent-lynx.edgecompute.app\",\n                \"FullyQualifiedName\": \"Service Version Domain/vInh5jJ0qnGdhiCO04INR7/version/2/domain/centrally-decent-lynx.edgecompute.app\",\n                \"Type\": \"Service Version Domain\",\n                \"Metadata\": {},\n                \"Parent\": {\n                    \"Name\": \"vInh5jJ0qnGdhiCO04INR7/version/2\",\n                    \"FullyQualifiedName\": \"Service Version/2\",\n                    \"Type\": \"Service Version\",\n                    \"Metadata\": {\n                        \"service_id\": \"vInh5jJ0qnGdhiCO04INR7\"\n                    },\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"global:read global\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"centrally-decent-lynx.edgecompute.app\",\n                \"FullyQualifiedName\": \"Service Version Domain/vInh5jJ0qnGdhiCO04INR7/version/3/domain/centrally-decent-lynx.edgecompute.app\",\n                \"Type\": \"Service Version Domain\",\n                \"Metadata\": {},\n                \"Parent\": {\n                    \"Name\": \"vInh5jJ0qnGdhiCO04INR7/version/3\",\n                    \"FullyQualifiedName\": \"Service Version/3\",\n                    \"Type\": \"Service Version\",\n                    \"Metadata\": {\n                        \"service_id\": \"vInh5jJ0qnGdhiCO04INR7\"\n                    },\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"global:read global\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Detectors\",\n                \"FullyQualifiedName\": \"Service Version Health Check/vInh5jJ0qnGdhiCO04INR7/version/3/healthcheck/Detectors\",\n                \"Type\": \"Service Version Health Check\",\n                \"Metadata\": {},\n                \"Parent\": {\n                    \"Name\": \"vInh5jJ0qnGdhiCO04INR7/version/3\",\n                    \"FullyQualifiedName\": \"Service Version/3\",\n                    \"Type\": \"Service Version\",\n                    \"Metadata\": {\n                        \"service_id\": \"vInh5jJ0qnGdhiCO04INR7\"\n                    },\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"global:read global\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"yja0K1GNPRDNTA6vizIFK4/version/1\",\n                \"FullyQualifiedName\": \"Service Version/1\",\n                \"Type\": \"Service Version\",\n                \"Metadata\": {\n                    \"service_id\": \"yja0K1GNPRDNTA6vizIFK4\"\n                },\n                \"Parent\": {\n                    \"Name\": \"Truffle Security's website\",\n                    \"FullyQualifiedName\": \"Service/yja0K1GNPRDNTA6vizIFK4\",\n                    \"Type\": \"Service\",\n                    \"Metadata\": {\n                        \"Service Type\": \"vcl\"\n                    },\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"global:read global\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"vInh5jJ0qnGdhiCO04INR7/version/1\",\n                \"FullyQualifiedName\": \"Service Version/1\",\n                \"Type\": \"Service Version\",\n                \"Metadata\": {\n                    \"service_id\": \"vInh5jJ0qnGdhiCO04INR7\"\n                },\n                \"Parent\": {\n                    \"Name\": \"this is a test service\",\n                    \"FullyQualifiedName\": \"Service/vInh5jJ0qnGdhiCO04INR7\",\n                    \"Type\": \"Service\",\n                    \"Metadata\": {\n                        \"Service Type\": \"wasm\"\n                    },\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"global:read global\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"vInh5jJ0qnGdhiCO04INR7/version/2\",\n                \"FullyQualifiedName\": \"Service Version/2\",\n                \"Type\": \"Service Version\",\n                \"Metadata\": {\n                    \"service_id\": \"vInh5jJ0qnGdhiCO04INR7\"\n                },\n                \"Parent\": {\n                    \"Name\": \"this is a test service\",\n                    \"FullyQualifiedName\": \"Service/vInh5jJ0qnGdhiCO04INR7\",\n                    \"Type\": \"Service\",\n                    \"Metadata\": {\n                        \"Service Type\": \"wasm\"\n                    },\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"global:read global\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"vInh5jJ0qnGdhiCO04INR7/version/3\",\n                \"FullyQualifiedName\": \"Service Version/3\",\n                \"Type\": \"Service Version\",\n                \"Metadata\": {\n                    \"service_id\": \"vInh5jJ0qnGdhiCO04INR7\"\n                },\n                \"Parent\": {\n                    \"Name\": \"this is a test service\",\n                    \"FullyQualifiedName\": \"Service/vInh5jJ0qnGdhiCO04INR7\",\n                    \"Type\": \"Service\",\n                    \"Metadata\": {\n                        \"Service Type\": \"wasm\"\n                    },\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"global:read global\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"this is a test service\",\n                \"FullyQualifiedName\": \"Service/vInh5jJ0qnGdhiCO04INR7\",\n                \"Type\": \"Service\",\n                \"Metadata\": {\n                    \"Service Type\": \"wasm\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"global:read global\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Truffle Security's website\",\n                \"FullyQualifiedName\": \"Service/yja0K1GNPRDNTA6vizIFK4\",\n                \"Type\": \"Service\",\n                \"Metadata\": {\n                    \"Service Type\": \"vcl\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"global:read global\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"test-user-global\",\n                \"FullyQualifiedName\": \"User Token/24K13teXo9GhmaUGhwBS2V\",\n                \"Type\": \"User Token\",\n                \"Metadata\": {\n                    \"Expires At\": \"2025-12-31T19:00:00Z\",\n                    \"Role\": \"\",\n                    \"Scope\": \"global\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"global:read global\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"test-user-purge-select\",\n                \"FullyQualifiedName\": \"User Token/2782vHUyFqralr1GKmWmVF\",\n                \"Type\": \"User Token\",\n                \"Metadata\": {\n                    \"Expires At\": \"\",\n                    \"Role\": \"\",\n                    \"Scope\": \"purge_select\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"global:read global\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"test\",\n                \"FullyQualifiedName\": \"User Token/278C9jIudzPv9NC6BvZT4z\",\n                \"Type\": \"User Token\",\n                \"Metadata\": {\n                    \"Expires At\": \"2025-07-22T19:00:00Z\",\n                    \"Role\": \"\",\n                    \"Scope\": \"global:read global\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"global:read global\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"integration-test\",\n                \"FullyQualifiedName\": \"User Token/2ICO7ArmhY8OMiiOyNpXfc\",\n                \"Type\": \"User Token\",\n                \"Metadata\": {\n                    \"Expires At\": \"\",\n                    \"Role\": \"\",\n                    \"Scope\": \"global:read global\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"global:read global\",\n                \"Parent\": null\n            }\n        }\n    ],\n    \"UnboundedResources\": null,\n    \"Metadata\": {}\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/figma/endpoints.json",
    "content": "{\r\n    \"files:read\": {\r\n        \"url\": \"https://api.figma.com/v1/me\",\r\n        \"method\": \"GET\",\r\n        \"expected_status_code_with_scope\": 200,\r\n        \"expected_status_code_without_scope\": 403\r\n    },\r\n    \"library_analytics:read\": {\r\n        \"url\": \"https://api.figma.com/v1/analytics/libraries/0/component/actions\",\r\n        \"method\": \"GET\",\r\n        \"expected_status_code_with_scope\": 400,\r\n        \"expected_status_code_without_scope\": 403\r\n    },\r\n    \"file_dev_resources:write\": {\r\n        \"url\": \"https://api.figma.com/v1/dev_resources\",\r\n        \"method\": \"POST\",\r\n        \"expected_status_code_with_scope\": 400,\r\n        \"expected_status_code_without_scope\": 403\r\n    },\r\n    \"file_variables:read\": {\r\n        \"url\": \"https://api.figma.com/v1/files/0/variables/published\",\r\n        \"method\": \"GET\",\r\n        \"expected_status_code_with_scope\": 404,\r\n        \"expected_status_code_without_scope\": 403\r\n    },\r\n    \"webhooks:write\": {\r\n        \"url\": \"https://api.figma.com/v2/webhooks\",\r\n        \"method\": \"POST\",\r\n        \"expected_status_code_with_scope\": 400,\r\n        \"expected_status_code_without_scope\": 403\r\n    }\r\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/figma/expected_output.json",
    "content": "{\"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}"
  },
  {
    "path": "pkg/analyzer/analyzers/figma/figma.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go figma\n\npackage figma\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeFigma }\n\ntype ScopeStatus string\n\nconst (\n\tStatusError      ScopeStatus = \"Error\"\n\tStatusGranted    ScopeStatus = \"Granted\"\n\tStatusDenied     ScopeStatus = \"Denied\"\n\tStatusUnverified ScopeStatus = \"Unverified\"\n)\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\ttoken, ok := credInfo[\"token\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"token not found in credInfo\")\n\t}\n\tinfo, err := AnalyzePermissions(a.Cfg, token)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn MapToAnalyzerResult(info), nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, token string) {\n\tinfo, err := AnalyzePermissions(cfg, token)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid Figma Personal Access Token\\n\\n\")\n\tPrintUserAndPermissions(info)\n}\n\nfunc AnalyzePermissions(cfg *config.Config, token string) (*secretInfo, error) {\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\tallScopes := getAllScopes()\n\tscopeToEndpoints, err := getScopeEndpointsMap()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar info = &secretInfo{Scopes: map[Scope]ScopeStatus{}}\n\tfor _, scope := range allScopes {\n\t\tinfo.Scopes[scope] = StatusUnverified\n\t}\n\n\tfor _, scope := range orderedScopeList {\n\t\tendpoint, err := getScopeEndpoint(scopeToEndpoints, scope)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresp, err := callAPIEndpoint(client, token, endpoint)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer resp.Body.Close()\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tscopeStatus := determineScopeStatus(resp.StatusCode, endpoint)\n\t\tif scopeStatus == StatusGranted {\n\t\t\tif scope == ScopeFilesRead {\n\t\t\t\tif err := json.Unmarshal(body, &info.UserInfo); err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"error decoding user info from response %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tinfo.Scopes[scope] = StatusGranted\n\t\t}\n\t\t// If the token does NOT have the scope, response will include all the scopes it does have\n\t\tif scopeStatus == StatusDenied {\n\t\t\tscopes, ok := extractScopesFromError(body)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"could not extract scopes from error message\")\n\t\t\t}\n\t\t\tfor scope := range info.Scopes {\n\t\t\t\tinfo.Scopes[scope] = StatusDenied\n\t\t\t}\n\t\t\tfor _, scope := range scopes {\n\t\t\t\tinfo.Scopes[scope] = StatusGranted\n\t\t\t}\n\t\t\t// We have enough info to finish analysis\n\t\t\tbreak\n\t\t}\n\t}\n\treturn info, nil\n}\n\n// determineScopeStatus takes the API response status code and uses it along with the expected\n// status codes to dermine whether the access token has the required scope to perform that action.\n// It returns a ScopeStatus which can be Granted, Denied, or Unverified.\nfunc determineScopeStatus(statusCode int, endpoint endpoint) ScopeStatus {\n\tif statusCode == endpoint.ExpectedStatusCodeWithScope || statusCode == http.StatusOK {\n\t\treturn StatusGranted\n\t}\n\n\tif statusCode == endpoint.ExpectedStatusCodeWithoutScope {\n\t\treturn StatusDenied\n\t}\n\n\t// Can not determine scope as the expected error is unknown\n\treturn StatusUnverified\n}\n\n// Matches API response body with expected message pattern in case the token is missing a scope\n// If the responses match, we can extract all available scopes from the response msg\nfunc extractScopesFromError(body []byte) ([]Scope, bool) {\n\tfilteredBody := filterErrorResponseBody(string(body))\n\tre := regexp.MustCompile(`Invalid scope(?:\\(s\\))?: ([a-zA-Z_:, ]+)\\. This endpoint requires.*`)\n\tmatches := re.FindStringSubmatch(filteredBody)\n\tif len(matches) > 1 {\n\t\tscopes := strings.Split(matches[1], \", \")\n\t\treturn getScopesFromScopeStrings(scopes), true\n\t}\n\treturn nil, false\n}\n\n// The filterErrorResponseBody function cleans the provided \"invalid permission\" API\n// response message by removing the characters '\"', '[', ']', '\\', and '\"'.\nfunc filterErrorResponseBody(msg string) string {\n\tresult := strings.ReplaceAll(msg, \"\\\\\", \"\")\n\tresult = strings.ReplaceAll(result, \"\\\"\", \"\")\n\tresult = strings.ReplaceAll(result, \"[\", \"\")\n\treturn strings.ReplaceAll(result, \"]\", \"\")\n}\n\nfunc MapToAnalyzerResult(info *secretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeFigma,\n\t}\n\tvar permissions []analyzers.Permission\n\tfor scope, status := range info.Scopes {\n\t\tif status != StatusGranted {\n\t\t\tcontinue\n\t\t}\n\t\tpermissions = append(permissions, analyzers.Permission{Value: string(scope)})\n\t}\n\tuserResource := analyzers.Resource{\n\t\tName:               info.UserInfo.Handle,\n\t\tFullyQualifiedName: info.UserInfo.ID,\n\t\tType:               \"user\",\n\t\tMetadata: map[string]any{\n\t\t\t\"email\":   info.UserInfo.Email,\n\t\t\t\"img_url\": info.UserInfo.ImgURL,\n\t\t},\n\t}\n\n\tresult.Bindings = analyzers.BindAllPermissions(userResource, permissions...)\n\treturn &result\n}\n\nfunc PrintUserAndPermissions(info *secretInfo) {\n\tcolor.Yellow(\"[i] User Info:\")\n\tt1 := table.NewWriter()\n\tt1.SetOutputMirror(os.Stdout)\n\tt1.AppendHeader(table.Row{\"ID\", \"Handle\", \"Email\", \"Image URL\"})\n\tt1.AppendRow(table.Row{\n\t\tcolor.GreenString(info.UserInfo.ID),\n\t\tcolor.GreenString(info.UserInfo.Handle),\n\t\tcolor.GreenString(info.UserInfo.Email),\n\t\tcolor.GreenString(info.UserInfo.ImgURL),\n\t})\n\tt1.SetOutputMirror(os.Stdout)\n\tt1.Render()\n\n\tcolor.Yellow(\"\\n[i] Scopes:\")\n\tt2 := table.NewWriter()\n\tt2.AppendHeader(table.Row{\"Scope\", \"Status\", \"Actions\"})\n\tfor scope, status := range info.Scopes {\n\t\tactions := getScopeActions(scope)\n\t\trows := []table.Row{}\n\t\tfor i, action := range actions {\n\t\t\tvar scopeCell string\n\t\t\tvar statusCell string\n\t\t\tif i == 0 {\n\t\t\t\tscopeCell = color.GreenString(string(scope))\n\t\t\t\tstatusCell = color.GreenString(string(status))\n\t\t\t}\n\t\t\trows = append(rows, table.Row{scopeCell, statusCell, color.GreenString(action)})\n\t\t}\n\t\tt2.AppendRows(rows)\n\t\tt2.AppendSeparator()\n\t}\n\tt2.SetOutputMirror(os.Stdout)\n\tt2.Render()\n\tfmt.Printf(\"%s: https://www.figma.com/developers/api\\n\\n\", color.GreenString(\"Ref\"))\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/figma/figma_test.go",
    "content": "package figma\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\ttoken   string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\ttoken:   testSecrets.MustGetField(\"FIGMAPERSONALACCESSTOKEN_V2_TOKEN\"),\n\t\t\tname:    \"valid Figma Personal Access Token\",\n\t\t\twant:    string(expectedOutput),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"token\": tt.token})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/figma/models.go",
    "content": "package figma\n\ntype userInfo struct {\n\tID     string `json:\"id\"`\n\tHandle string `json:\"handle\"`\n\tImgURL string `json:\"img_url\"`\n\tEmail  string `json:\"email\"`\n}\n\ntype secretInfo struct {\n\tUserInfo userInfo\n\tScopes   map[Scope]ScopeStatus\n}\n\ntype endpoint struct {\n\tURL                            string `json:\"url\"`\n\tMethod                         string `json:\"method\"`\n\tExpectedStatusCodeWithScope    int    `json:\"expected_status_code_with_scope\"`\n\tExpectedStatusCodeWithoutScope int    `json:\"expected_status_code_without_scope\"`\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/figma/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage figma\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    FilesRead Permission = iota\n    FileVariablesRead Permission = iota\n    FileVariablesWrite Permission = iota\n    FileCommentsWrite Permission = iota\n    FileDevResourcesRead Permission = iota\n    FileDevResourcesWrite Permission = iota\n    LibraryAnalyticsRead Permission = iota\n    WebhooksWrite Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        FilesRead: \"files:read\",\n        FileVariablesRead: \"file_variables:read\",\n        FileVariablesWrite: \"file_variables:write\",\n        FileCommentsWrite: \"file_comments:write\",\n        FileDevResourcesRead: \"file_dev_resources:read\",\n        FileDevResourcesWrite: \"file_dev_resources:write\",\n        LibraryAnalyticsRead: \"library_analytics:read\",\n        WebhooksWrite: \"webhooks:write\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"files:read\": FilesRead,\n        \"file_variables:read\": FileVariablesRead,\n        \"file_variables:write\": FileVariablesWrite,\n        \"file_comments:write\": FileCommentsWrite,\n        \"file_dev_resources:read\": FileDevResourcesRead,\n        \"file_dev_resources:write\": FileDevResourcesWrite,\n        \"library_analytics:read\": LibraryAnalyticsRead,\n        \"webhooks:write\": WebhooksWrite,\n    }\n\n    PermissionIDs = map[Permission]int{\n        FilesRead: 1,\n        FileVariablesRead: 2,\n        FileVariablesWrite: 3,\n        FileCommentsWrite: 4,\n        FileDevResourcesRead: 5,\n        FileDevResourcesWrite: 6,\n        LibraryAnalyticsRead: 7,\n        WebhooksWrite: 8,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: FilesRead,\n        2: FileVariablesRead,\n        3: FileVariablesWrite,\n        4: FileCommentsWrite,\n        5: FileDevResourcesRead,\n        6: FileDevResourcesWrite,\n        7: LibraryAnalyticsRead,\n        8: WebhooksWrite,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/figma/permissions.yaml",
    "content": "permissions:\r\n  - files:read\r\n  - file_variables:read\r\n  - file_variables:write\r\n  - file_comments:write\r\n  - file_dev_resources:read\r\n  - file_dev_resources:write\r\n  - library_analytics:read\r\n  - webhooks:write\r\n"
  },
  {
    "path": "pkg/analyzer/analyzers/figma/requests.go",
    "content": "package figma\n\nimport (\n\t\"net/http\"\n)\n\nfunc callAPIEndpoint(client *http.Client, token string, endpoint endpoint) (*http.Response, error) {\n\treq, err := http.NewRequest(endpoint.Method, endpoint.URL, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treq.Header.Set(\"X-FIGMA-TOKEN\", token)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn resp, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/figma/scopes.go",
    "content": "package figma\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"errors\"\n)\n\ntype Scope string\n\nconst (\n\tScopeFilesRead             Scope = \"files:read\"\n\tScopeFileVariablesRead     Scope = \"file_variables:read\"\n\tScopeFileVariablesWrite    Scope = \"file_variables:write\"\n\tScopeFileCommentsWrite     Scope = \"file_comments:write\"\n\tScopeFileDevResourcesRead  Scope = \"file_dev_resources:read\"\n\tScopeFileDevResourcesWrite Scope = \"file_dev_resources:write\"\n\tScopeLibraryAnalyticsRead  Scope = \"library_analytics:read\"\n\tScopeWebhooksWrite         Scope = \"webhooks:write\"\n)\n\n// This list orders the scope in which they must be tested\nvar orderedScopeList = []Scope{\n\tScopeFilesRead,\n\tScopeLibraryAnalyticsRead,\n\tScopeFileDevResourcesWrite,\n\tScopeFileVariablesRead,\n\tScopeWebhooksWrite,\n}\n\nvar scopeToActions = map[Scope][]string{\n\tScopeFilesRead: {\n\t\t\"Get user info\",\n\t\t\"Read files\",\n\t\t\"Read projects\",\n\t\t\"Read users\",\n\t\t\"Read versions\",\n\t\t\"Read comments\",\n\t\t\"Read components & styles\",\n\t\t\"Read webhooks\",\n\t},\n\tScopeFileVariablesRead: {\n\t\t\"Read file variables\",\n\t},\n\tScopeFileVariablesWrite: {\n\t\t\"Write file variables\",\n\t},\n\tScopeFileCommentsWrite: {\n\t\t\"Post comments\",\n\t\t\"Delete comments\",\n\t\t\"Post comment reactions\",\n\t\t\"Delete comment reactions\",\n\t},\n\tScopeFileDevResourcesRead: {\n\t\t\"Read file dev resources\",\n\t},\n\tScopeFileDevResourcesWrite: {\n\t\t\"Write file dev resources\",\n\t},\n\tScopeLibraryAnalyticsRead: {\n\t\t\"Read design system analytics\",\n\t},\n\tScopeWebhooksWrite: {\n\t\t\"Create webhooks\",\n\t\t\"Manage webhooks\",\n\t},\n}\n\nvar scopeStringToScope map[string]Scope\n\n//go:embed endpoints.json\nvar endpointsConfig []byte\n\nfunc init() {\n\tscopeStringToScope = map[string]Scope{\n\t\tstring(ScopeFilesRead):             ScopeFilesRead,\n\t\tstring(ScopeFileVariablesRead):     ScopeFileVariablesRead,\n\t\tstring(ScopeFileVariablesWrite):    ScopeFileVariablesWrite,\n\t\tstring(ScopeFileCommentsWrite):     ScopeFileCommentsWrite,\n\t\tstring(ScopeFileDevResourcesRead):  ScopeFileDevResourcesRead,\n\t\tstring(ScopeFileDevResourcesWrite): ScopeFileDevResourcesWrite,\n\t\tstring(ScopeLibraryAnalyticsRead):  ScopeLibraryAnalyticsRead,\n\t\tstring(ScopeWebhooksWrite):         ScopeWebhooksWrite,\n\t}\n}\n\nfunc getScopeActions(scope Scope) []string {\n\treturn scopeToActions[scope]\n}\n\nfunc getScopeEndpointsMap() (map[Scope]endpoint, error) {\n\tvar scopeToEndpoints map[Scope]endpoint\n\tif err := json.Unmarshal(endpointsConfig, &scopeToEndpoints); err != nil {\n\t\treturn nil, errors.New(\"failed to unmarshal endpoints.json: \" + err.Error())\n\t}\n\treturn scopeToEndpoints, nil\n}\n\nfunc getScopeEndpoint(scopeToEndpoint map[Scope]endpoint, scope Scope) (endpoint, error) {\n\tif endpoint, ok := scopeToEndpoint[scope]; ok {\n\t\treturn endpoint, nil\n\t}\n\treturn endpoint{}, errors.New(\"invalid scope or endpoint doesn't exist\")\n}\n\nfunc getScopesFromScopeStrings(scopeStrings []string) []Scope {\n\tvar scopes []Scope\n\tfor _, scopeString := range scopeStrings {\n\t\tif scope, ok := scopeStringToScope[scopeString]; ok {\n\t\t\tscopes = append(scopes, scope)\n\t\t}\n\t}\n\treturn scopes\n}\n\nfunc getAllScopes() []Scope {\n\treturn []Scope{\n\t\tScopeFilesRead,\n\t\tScopeFileVariablesRead,\n\t\tScopeFileVariablesWrite,\n\t\tScopeFileCommentsWrite,\n\t\tScopeFileDevResourcesRead,\n\t\tScopeFileDevResourcesWrite,\n\t\tScopeLibraryAnalyticsRead,\n\t\tScopeWebhooksWrite,\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/github/classic/classic.yaml",
    "content": "permissions:\n  - repo\n  - repo:status\n  - repo_deployment\n  - public_repo\n  - repo:invite\n  - security_events\n  - workflow\n  - write:packages\n  - read:packages\n  - delete:packages\n  - admin:org\n  - write:org\n  - read:org\n  - manage_runners:org\n  - admin:public_key\n  - write:public_key\n  - read:public_key\n  - admin:repo_hook\n  - write:repo_hook\n  - read:repo_hook\n  - admin:org_hook\n  - gist\n  - notifications\n  - user\n  - read:user\n  - user:email\n  - user:follow\n  - delete_repo\n  - write:discussion\n  - read:discussion\n  - admin:enterprise\n  - manage_runners:enterprise\n  - manage_billing:enterprise\n  - read:enterprise\n  - audit_log\n  - read:audit_log\n  - codespace\n  - codespace:secrets\n  - copilot\n  - manage_billing:copilot\n  - project\n  - read:project\n  - admin:gpg_key\n  - write:gpg_key\n  - read:gpg_key\n  - admin:ssh_signing_key\n  - write:ssh_signing_key\n  - read:ssh_signing_key\n"
  },
  {
    "path": "pkg/analyzer/analyzers/github/classic/classic_permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage classic\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    Repo Permission = iota\n    RepoStatus Permission = iota\n    RepoDeployment Permission = iota\n    PublicRepo Permission = iota\n    RepoInvite Permission = iota\n    SecurityEvents Permission = iota\n    Workflow Permission = iota\n    WritePackages Permission = iota\n    ReadPackages Permission = iota\n    DeletePackages Permission = iota\n    AdminOrg Permission = iota\n    WriteOrg Permission = iota\n    ReadOrg Permission = iota\n    ManageRunnersOrg Permission = iota\n    AdminPublicKey Permission = iota\n    WritePublicKey Permission = iota\n    ReadPublicKey Permission = iota\n    AdminRepoHook Permission = iota\n    WriteRepoHook Permission = iota\n    ReadRepoHook Permission = iota\n    AdminOrgHook Permission = iota\n    Gist Permission = iota\n    Notifications Permission = iota\n    User Permission = iota\n    ReadUser Permission = iota\n    UserEmail Permission = iota\n    UserFollow Permission = iota\n    DeleteRepo Permission = iota\n    WriteDiscussion Permission = iota\n    ReadDiscussion Permission = iota\n    AdminEnterprise Permission = iota\n    ManageRunnersEnterprise Permission = iota\n    ManageBillingEnterprise Permission = iota\n    ReadEnterprise Permission = iota\n    AuditLog Permission = iota\n    ReadAuditLog Permission = iota\n    Codespace Permission = iota\n    CodespaceSecrets Permission = iota\n    Copilot Permission = iota\n    ManageBillingCopilot Permission = iota\n    Project Permission = iota\n    ReadProject Permission = iota\n    AdminGpgKey Permission = iota\n    WriteGpgKey Permission = iota\n    ReadGpgKey Permission = iota\n    AdminSshSigningKey Permission = iota\n    WriteSshSigningKey Permission = iota\n    ReadSshSigningKey Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        Repo: \"repo\",\n        RepoStatus: \"repo:status\",\n        RepoDeployment: \"repo_deployment\",\n        PublicRepo: \"public_repo\",\n        RepoInvite: \"repo:invite\",\n        SecurityEvents: \"security_events\",\n        Workflow: \"workflow\",\n        WritePackages: \"write:packages\",\n        ReadPackages: \"read:packages\",\n        DeletePackages: \"delete:packages\",\n        AdminOrg: \"admin:org\",\n        WriteOrg: \"write:org\",\n        ReadOrg: \"read:org\",\n        ManageRunnersOrg: \"manage_runners:org\",\n        AdminPublicKey: \"admin:public_key\",\n        WritePublicKey: \"write:public_key\",\n        ReadPublicKey: \"read:public_key\",\n        AdminRepoHook: \"admin:repo_hook\",\n        WriteRepoHook: \"write:repo_hook\",\n        ReadRepoHook: \"read:repo_hook\",\n        AdminOrgHook: \"admin:org_hook\",\n        Gist: \"gist\",\n        Notifications: \"notifications\",\n        User: \"user\",\n        ReadUser: \"read:user\",\n        UserEmail: \"user:email\",\n        UserFollow: \"user:follow\",\n        DeleteRepo: \"delete_repo\",\n        WriteDiscussion: \"write:discussion\",\n        ReadDiscussion: \"read:discussion\",\n        AdminEnterprise: \"admin:enterprise\",\n        ManageRunnersEnterprise: \"manage_runners:enterprise\",\n        ManageBillingEnterprise: \"manage_billing:enterprise\",\n        ReadEnterprise: \"read:enterprise\",\n        AuditLog: \"audit_log\",\n        ReadAuditLog: \"read:audit_log\",\n        Codespace: \"codespace\",\n        CodespaceSecrets: \"codespace:secrets\",\n        Copilot: \"copilot\",\n        ManageBillingCopilot: \"manage_billing:copilot\",\n        Project: \"project\",\n        ReadProject: \"read:project\",\n        AdminGpgKey: \"admin:gpg_key\",\n        WriteGpgKey: \"write:gpg_key\",\n        ReadGpgKey: \"read:gpg_key\",\n        AdminSshSigningKey: \"admin:ssh_signing_key\",\n        WriteSshSigningKey: \"write:ssh_signing_key\",\n        ReadSshSigningKey: \"read:ssh_signing_key\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"repo\": Repo,\n        \"repo:status\": RepoStatus,\n        \"repo_deployment\": RepoDeployment,\n        \"public_repo\": PublicRepo,\n        \"repo:invite\": RepoInvite,\n        \"security_events\": SecurityEvents,\n        \"workflow\": Workflow,\n        \"write:packages\": WritePackages,\n        \"read:packages\": ReadPackages,\n        \"delete:packages\": DeletePackages,\n        \"admin:org\": AdminOrg,\n        \"write:org\": WriteOrg,\n        \"read:org\": ReadOrg,\n        \"manage_runners:org\": ManageRunnersOrg,\n        \"admin:public_key\": AdminPublicKey,\n        \"write:public_key\": WritePublicKey,\n        \"read:public_key\": ReadPublicKey,\n        \"admin:repo_hook\": AdminRepoHook,\n        \"write:repo_hook\": WriteRepoHook,\n        \"read:repo_hook\": ReadRepoHook,\n        \"admin:org_hook\": AdminOrgHook,\n        \"gist\": Gist,\n        \"notifications\": Notifications,\n        \"user\": User,\n        \"read:user\": ReadUser,\n        \"user:email\": UserEmail,\n        \"user:follow\": UserFollow,\n        \"delete_repo\": DeleteRepo,\n        \"write:discussion\": WriteDiscussion,\n        \"read:discussion\": ReadDiscussion,\n        \"admin:enterprise\": AdminEnterprise,\n        \"manage_runners:enterprise\": ManageRunnersEnterprise,\n        \"manage_billing:enterprise\": ManageBillingEnterprise,\n        \"read:enterprise\": ReadEnterprise,\n        \"audit_log\": AuditLog,\n        \"read:audit_log\": ReadAuditLog,\n        \"codespace\": Codespace,\n        \"codespace:secrets\": CodespaceSecrets,\n        \"copilot\": Copilot,\n        \"manage_billing:copilot\": ManageBillingCopilot,\n        \"project\": Project,\n        \"read:project\": ReadProject,\n        \"admin:gpg_key\": AdminGpgKey,\n        \"write:gpg_key\": WriteGpgKey,\n        \"read:gpg_key\": ReadGpgKey,\n        \"admin:ssh_signing_key\": AdminSshSigningKey,\n        \"write:ssh_signing_key\": WriteSshSigningKey,\n        \"read:ssh_signing_key\": ReadSshSigningKey,\n    }\n\n    PermissionIDs = map[Permission]int{\n        Repo: 1,\n        RepoStatus: 2,\n        RepoDeployment: 3,\n        PublicRepo: 4,\n        RepoInvite: 5,\n        SecurityEvents: 6,\n        Workflow: 7,\n        WritePackages: 8,\n        ReadPackages: 9,\n        DeletePackages: 10,\n        AdminOrg: 11,\n        WriteOrg: 12,\n        ReadOrg: 13,\n        ManageRunnersOrg: 14,\n        AdminPublicKey: 15,\n        WritePublicKey: 16,\n        ReadPublicKey: 17,\n        AdminRepoHook: 18,\n        WriteRepoHook: 19,\n        ReadRepoHook: 20,\n        AdminOrgHook: 21,\n        Gist: 22,\n        Notifications: 23,\n        User: 24,\n        ReadUser: 25,\n        UserEmail: 26,\n        UserFollow: 27,\n        DeleteRepo: 28,\n        WriteDiscussion: 29,\n        ReadDiscussion: 30,\n        AdminEnterprise: 31,\n        ManageRunnersEnterprise: 32,\n        ManageBillingEnterprise: 33,\n        ReadEnterprise: 34,\n        AuditLog: 35,\n        ReadAuditLog: 36,\n        Codespace: 37,\n        CodespaceSecrets: 38,\n        Copilot: 39,\n        ManageBillingCopilot: 40,\n        Project: 41,\n        ReadProject: 42,\n        AdminGpgKey: 43,\n        WriteGpgKey: 44,\n        ReadGpgKey: 45,\n        AdminSshSigningKey: 46,\n        WriteSshSigningKey: 47,\n        ReadSshSigningKey: 48,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: Repo,\n        2: RepoStatus,\n        3: RepoDeployment,\n        4: PublicRepo,\n        5: RepoInvite,\n        6: SecurityEvents,\n        7: Workflow,\n        8: WritePackages,\n        9: ReadPackages,\n        10: DeletePackages,\n        11: AdminOrg,\n        12: WriteOrg,\n        13: ReadOrg,\n        14: ManageRunnersOrg,\n        15: AdminPublicKey,\n        16: WritePublicKey,\n        17: ReadPublicKey,\n        18: AdminRepoHook,\n        19: WriteRepoHook,\n        20: ReadRepoHook,\n        21: AdminOrgHook,\n        22: Gist,\n        23: Notifications,\n        24: User,\n        25: ReadUser,\n        26: UserEmail,\n        27: UserFollow,\n        28: DeleteRepo,\n        29: WriteDiscussion,\n        30: ReadDiscussion,\n        31: AdminEnterprise,\n        32: ManageRunnersEnterprise,\n        33: ManageBillingEnterprise,\n        34: ReadEnterprise,\n        35: AuditLog,\n        36: ReadAuditLog,\n        37: Codespace,\n        38: CodespaceSecrets,\n        39: Copilot,\n        40: ManageBillingCopilot,\n        41: Project,\n        42: ReadProject,\n        43: AdminGpgKey,\n        44: WriteGpgKey,\n        45: ReadGpgKey,\n        46: AdminSshSigningKey,\n        47: WriteSshSigningKey,\n        48: ReadSshSigningKey,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/github/classic/classictoken.go",
    "content": "//go:generate generate_permissions classic.yaml classic_permissions.go classic\n\npackage classic\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\tgh \"github.com/google/go-github/v67/github\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n)\n\nvar SCOPE_ORDER = [][]Permission{\n\t{Repo, RepoStatus, RepoDeployment, PublicRepo, RepoInvite, SecurityEvents},\n\t{Workflow},\n\t{WritePackages, ReadPackages},\n\t{DeletePackages},\n\t{AdminOrg, WriteOrg, ReadOrg, ManageRunnersOrg},\n\t{AdminPublicKey, WritePublicKey, ReadPublicKey},\n\t{AdminRepoHook, WriteRepoHook, ReadRepoHook},\n\t{AdminOrgHook},\n\t{Gist},\n\t{Notifications},\n\t{User, ReadUser, UserEmail, UserFollow},\n\t{DeleteRepo},\n\t{WriteDiscussion, ReadDiscussion},\n\t{AdminEnterprise, ManageRunnersEnterprise, ManageBillingEnterprise, ReadEnterprise},\n\t{AuditLog, ReadAuditLog},\n\t{Codespace, CodespaceSecrets},\n\t{Copilot, ManageBillingCopilot},\n\t{Project, ReadProject},\n\t{AdminGpgKey, WriteGpgKey, ReadGpgKey},\n\t{AdminSshSigningKey, WriteSshSigningKey, ReadSshSigningKey},\n}\n\nvar SCOPE_TO_SUB_SCOPE = map[Permission][]Permission{\n\tRepo:                    {RepoStatus, RepoDeployment, PublicRepo, RepoInvite, SecurityEvents},\n\tWritePackages:           {ReadPackages},\n\tAdminOrg:                {WriteOrg, ReadOrg, ManageRunnersOrg},\n\tWriteOrg:                {ReadOrg},\n\tAdminPublicKey:          {WritePublicKey, ReadPublicKey},\n\tWritePublicKey:          {ReadPublicKey},\n\tAdminRepoHook:           {WriteRepoHook, ReadRepoHook},\n\tWriteRepoHook:           {ReadRepoHook},\n\tUser:                    {ReadUser, UserEmail, UserFollow},\n\tWriteDiscussion:         {ReadDiscussion},\n\tAdminEnterprise:         {ManageRunnersEnterprise, ManageBillingEnterprise, ReadEnterprise},\n\tManageBillingEnterprise: {ReadEnterprise},\n\tAuditLog:                {ReadAuditLog},\n\tCodespace:               {CodespaceSecrets},\n\tCopilot:                 {ManageBillingCopilot},\n\tProject:                 {ReadProject},\n\tAdminGpgKey:             {WriteGpgKey, ReadGpgKey},\n\tWriteGpgKey:             {ReadGpgKey},\n\tAdminSshSigningKey:      {WriteSshSigningKey, ReadSshSigningKey},\n\tWriteSshSigningKey:      {ReadSshSigningKey},\n}\n\nfunc hasPrivateRepoAccess(scopes map[Permission]bool) bool {\n\treturn scopes[Repo]\n}\n\nfunc processScopes(headerScopesSlice []analyzers.Permission) map[Permission]bool {\n\tallScopes := make(map[Permission]bool)\n\tfor _, scope := range headerScopesSlice {\n\t\tallScopes[StringToPermission[scope.Value]] = true\n\t}\n\tfor scope := range allScopes {\n\t\tif subScopes, ok := SCOPE_TO_SUB_SCOPE[scope]; ok {\n\t\t\tfor _, subScope := range subScopes {\n\t\t\t\tallScopes[subScope] = true\n\t\t\t}\n\t\t}\n\t}\n\treturn allScopes\n}\n\nfunc AnalyzeClassicToken(client *gh.Client, meta *common.TokenMetadata) (*common.SecretInfo, error) {\n\t// Convert OauthScopes to have hierarchical permissions.\n\tmeta.OauthScopes = oauthScopesToPermissions(meta.OauthScopes...)\n\tscopes := processScopes(meta.OauthScopes)\n\n\tvar repos []*gh.Repository\n\tif hasPrivateRepoAccess(scopes) {\n\t\tvar err error\n\t\trepos, err = common.GetAllReposForUser(client)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tgists, err := common.GetAllGistsForUser(client)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &common.SecretInfo{\n\t\tMetadata: meta,\n\t\tRepos:    repos,\n\t\tGists:    gists,\n\t}, nil\n}\n\nfunc filterPrivateRepoScopes(scopes map[Permission]bool) []Permission {\n\tvar intersection []Permission\n\tprivateScopes := []Permission{Repo, RepoStatus, RepoDeployment, RepoInvite, SecurityEvents, AdminRepoHook, WriteRepoHook, ReadRepoHook}\n\n\tfor _, privScope := range privateScopes {\n\t\tif scopes[privScope] {\n\t\t\tintersection = append(intersection, privScope)\n\t\t}\n\t}\n\treturn intersection\n}\n\nfunc PrintClassicToken(cfg *config.Config, info *common.SecretInfo) {\n\tscopes := processScopes(info.Metadata.OauthScopes)\n\tif len(scopes) == 0 {\n\t\tcolor.Red(\"[x] Classic Token has no scopes\")\n\t} else {\n\t\tprintClassicGHPermissions(scopes, cfg.ShowAll)\n\t}\n\n\tprivateScopes := filterPrivateRepoScopes(scopes)\n\tif hasPrivateRepoAccess(scopes) {\n\t\tcolor.Green(\"[!] Token has scope(s) for both public and private repositories. Here's a list of all accessible repositories:\")\n\t\tcommon.PrintGitHubRepos(info.Repos)\n\t} else if len(privateScopes) > 0 {\n\t\tcolor.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))\n\t} else if scopes[PublicRepo] {\n\t\tcolor.Yellow(\"[i] Token is scoped to only public repositories. See https://github.com/%v?tab=repositories\", *info.Metadata.User.Login)\n\t} else {\n\t\tcolor.Red(\"[x] Token does not appear scoped to any specific repositories.\")\n\t}\n\tcommon.PrintGists(info.Gists, cfg.ShowAll)\n}\n\nfunc joinPermissions(perms []Permission) string {\n\tvar permStrings []string\n\tfor _, perm := range perms {\n\t\tpermStr, err := perm.ToString()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tpermStrings = append(permStrings, permStr)\n\t}\n\treturn strings.Join(permStrings, \", \")\n}\n\nfunc scopeFormatter(scope Permission, checked bool, indentation int) (string, string) {\n\tscopeStr, err := scope.ToString()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif indentation != 0 {\n\t\tscopeStr = strings.Repeat(\"  \", indentation) + scopeStr\n\t}\n\tif checked {\n\t\treturn color.GreenString(scopeStr), color.GreenString(\"true\")\n\t}\n\treturn scopeStr, \"false\"\n}\n\nfunc printClassicGHPermissions(scopes map[Permission]bool, showAll bool) {\n\tscopeCount := 0\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Scope\", \"In-Scope\"})\n\n\tfilteredScopes := make([][]Permission, 0)\n\tfor _, scopeSlice := range SCOPE_ORDER {\n\t\tfor _, scope := range scopeSlice {\n\t\t\tif scopes[scope] {\n\t\t\t\tfilteredScopes = append(filteredScopes, scopeSlice)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tvar formattedScope, status string\n\tvar indentation int\n\n\tif !showAll {\n\t\tfor _, scopeSlice := range filteredScopes {\n\t\t\tfor ind, scope := range scopeSlice {\n\t\t\t\tif ind == 0 {\n\t\t\t\t\tindentation = 0\n\t\t\t\t\tif scopes[scope] {\n\t\t\t\t\t\tscopeCount++\n\t\t\t\t\t\tformattedScope, status = scopeFormatter(scope, true, indentation)\n\t\t\t\t\t\tt.AppendRow([]any{formattedScope, status})\n\t\t\t\t\t} else {\n\n\t\t\t\t\t\tscopeStr, err := scope.ToString()\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tt.AppendRow([]any{scopeStr, \"----\"})\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tindentation = 2\n\t\t\t\t\tif scopes[scope] {\n\t\t\t\t\t\tscopeCount++\n\t\t\t\t\t\tformattedScope, status = scopeFormatter(scope, true, indentation)\n\t\t\t\t\t\tt.AppendRow([]any{formattedScope, status})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tt.AppendSeparator()\n\t\t}\n\t} else {\n\t\tfor _, scopeSlice := range SCOPE_ORDER {\n\t\t\tfor ind, scope := range scopeSlice {\n\t\t\t\tif ind == 0 {\n\t\t\t\t\tindentation = 0\n\t\t\t\t} else {\n\t\t\t\t\tindentation = 2\n\t\t\t\t}\n\t\t\t\tif scopes[scope] {\n\t\t\t\t\tscopeCount++\n\t\t\t\t\tformattedScope, status = scopeFormatter(scope, true, indentation)\n\t\t\t\t\tt.AppendRow([]any{formattedScope, status})\n\t\t\t\t} else {\n\t\t\t\t\tformattedScope, status = scopeFormatter(scope, false, indentation)\n\t\t\t\t\tt.AppendRow([]any{formattedScope, status})\n\t\t\t\t}\n\t\t\t}\n\t\t\tt.AppendSeparator()\n\t\t}\n\t}\n\n\tif scopeCount == 0 && !showAll {\n\t\tcolor.Red(\"No Scopes Found for the GitHub Token above\\n\\n\")\n\t\treturn\n\t} else if scopeCount == 0 {\n\t\tcolor.Red(\"Found No Scopes for the GitHub Token above\\n\")\n\t} else {\n\t\tcolor.Green(fmt.Sprintf(\"[!] Found %v Scope(s) for the GitHub Token above\\n\", scopeCount))\n\t}\n\tt.Render()\n\tfmt.Print(\"\\n\\n\")\n}\n\n// oauthScopesToPermissions takes a list of scopes and returns a slice of\n// permissions for it. If the scope has implied permissions, they are included\n// as children of the parent scope, and both the parent and children are\n// returned in the slice.\nfunc oauthScopesToPermissions(scopes ...analyzers.Permission) []analyzers.Permission {\n\tallPermissions := make([]analyzers.Permission, 0, len(scopes))\n\tfor _, scope := range scopes {\n\t\tallPermissions = append(allPermissions, oauthScopeToPermissions(scope.Value)...)\n\t}\n\treturn allPermissions\n}\n\n// oauthScopeToPermissions takes a given scope and returns a slice of\n// permissions for it. If the scope has implied permissions, they are included\n// as children of the parent scope, and both the parent and children are\n// returned in the slice.\nfunc oauthScopeToPermissions(scope string) []analyzers.Permission {\n\tparent := analyzers.Permission{Value: scope}\n\tperms := []analyzers.Permission{parent}\n\tsubScopes, ok := func() ([]Permission, bool) {\n\t\tid, err := PermissionFromString(scope)\n\t\tif err != nil {\n\t\t\treturn nil, false\n\t\t}\n\t\tsubScopes, ok := SCOPE_TO_SUB_SCOPE[id]\n\t\treturn subScopes, ok\n\t}()\n\tif !ok {\n\t\t// No sub-scopes, so the only permission is itself.\n\t\treturn perms\n\t}\n\t// Add all the children to the list of permissions.\n\tfor _, subScope := range subScopes {\n\t\tsubScope, _ := subScope.ToString()\n\t\tperms = append(perms, analyzers.Permission{\n\t\t\tValue:  subScope,\n\t\t\tParent: &parent,\n\t\t})\n\t}\n\treturn perms\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/github/common/github.go",
    "content": "package common\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/fatih/color\"\n\tgh \"github.com/google/go-github/v67/github\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n)\n\ntype TokenType string\n\nconst (\n\tTokenTypeFineGrainedPAT TokenType = \"Fine-Grained GitHub Personal Access Token\"\n\tTokenTypeClassicPAT     TokenType = \"Classic GitHub Personal Access Token\"\n\tTokenTypeUserToServer   TokenType = \"GitHub User-to-Server Token\"\n\tTokenTypeGitHubToken    TokenType = \"GitHub Token\"\n)\n\nfunc checkFineGrained(token string, oauthScopes []analyzers.Permission) (TokenType, bool) {\n\t// For details on token prefixes, see:\n\t// https://github.blog/2021-04-05-behind-githubs-new-authentication-token-formats/\n\n\t// Special case for ghu_ prefix tokens (ex: in a codespace) that don't have the X-OAuth-Scopes header\n\tif strings.HasPrefix(token, \"ghu_\") {\n\t\treturn TokenTypeUserToServer, true\n\t}\n\n\t// Handle github_pat_ tokens\n\tif strings.HasPrefix(token, \"github_pat\") {\n\t\treturn TokenTypeFineGrainedPAT, true\n\t}\n\n\t// Handle classic PATs\n\tif strings.HasPrefix(token, \"ghp_\") {\n\t\treturn TokenTypeClassicPAT, false\n\t}\n\n\t// Catch-all for any other types\n\t// If resp.Header \"X-OAuth-Scopes\" doesn't exist, then we have fine-grained permissions\n\tif len(oauthScopes) > 0 {\n\t\treturn TokenTypeGitHubToken, false\n\t}\n\treturn TokenTypeGitHubToken, true\n}\n\ntype Permission int\n\ntype SecretInfo struct {\n\tMetadata *TokenMetadata\n\tRepos    []*gh.Repository\n\tGists    []*gh.Gist\n\t// AccessibleRepos, RepoAccessMap, and UserAccessMap are only set if\n\t// the token has fine-grained access.\n\tAccessibleRepos []*gh.Repository\n\tRepoAccessMap   any\n\tUserAccessMap   any\n}\n\ntype TokenMetadata struct {\n\tType        TokenType\n\tFineGrained bool\n\tUser        *gh.User\n\tExpiration  time.Time\n\t// OauthScopes is only set for classic tokens.\n\tOauthScopes []analyzers.Permission\n}\n\n// GetTokenMetadata gets the username, expiration date, and x-oauth-scopes headers for a given token\n// by sending a GET request to the /user endpoint\n// Returns a response object for usage in the checkFineGrained function\nfunc GetTokenMetadata(token string, client *gh.Client) (*TokenMetadata, error) {\n\tuser, resp, err := client.Users.Get(context.Background(), \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar oauthScopes []analyzers.Permission\n\tfor _, scope := range resp.Header.Values(\"X-OAuth-Scopes\") {\n\t\tfor _, scope := range strings.Split(scope, \", \") {\n\t\t\toauthScopes = append(oauthScopes, analyzers.Permission{Value: scope})\n\t\t}\n\t}\n\ttokenType, fineGrained := checkFineGrained(token, oauthScopes)\n\n\tvar expiration time.Time\n\tif tokenType == TokenTypeClassicPAT {\n\t\t// for classic tokens, github return token expiration time in header in UTC format.\n\t\texpiration, _ = time.Parse(\"2006-01-02 15:04:05 UTC\", resp.Header.Get(\"github-authentication-token-expiration\"))\n\t} else {\n\t\texpiration, _ = time.Parse(\"2006-01-02 15:04:05 -0700\", resp.Header.Get(\"github-authentication-token-expiration\"))\n\t}\n\n\treturn &TokenMetadata{\n\t\tType:        tokenType,\n\t\tFineGrained: fineGrained,\n\t\tUser:        user,\n\t\tExpiration:  expiration,\n\t\tOauthScopes: oauthScopes,\n\t}, nil\n}\n\nfunc GetAllGistsForUser(client *gh.Client) ([]*gh.Gist, error) {\n\topt := &gh.GistListOptions{ListOptions: gh.ListOptions{PerPage: 100}}\n\tvar allGists []*gh.Gist\n\tpage := 1\n\tfor {\n\t\topt.Page = page\n\t\tgists, resp, err := client.Gists.List(context.Background(), \"\", opt)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error getting gists.\")\n\t\t\treturn nil, err\n\t\t}\n\t\tallGists = append(allGists, gists...)\n\n\t\tlinkHeader := resp.Header.Get(\"link\")\n\t\tif linkHeader == \"\" || !strings.Contains(linkHeader, `rel=\"next\"`) {\n\t\t\tbreak\n\t\t}\n\t\tpage++\n\n\t}\n\n\treturn allGists, nil\n}\n\nfunc GetAllReposForUser(client *gh.Client) ([]*gh.Repository, error) {\n\topt := &gh.RepositoryListByAuthenticatedUserOptions{ListOptions: gh.ListOptions{PerPage: 100}}\n\tvar allRepos []*gh.Repository\n\tpage := 1\n\tfor {\n\t\topt.Page = page\n\t\trepos, resp, err := client.Repositories.ListByAuthenticatedUser(context.Background(), opt)\n\t\tif err != nil {\n\t\t\tcolor.Red(\"Error getting repos.\")\n\t\t\treturn nil, err\n\t\t}\n\t\tallRepos = append(allRepos, repos...)\n\n\t\tlinkHeader := resp.Header.Get(\"link\")\n\t\tif linkHeader == \"\" || !strings.Contains(linkHeader, `rel=\"next\"`) {\n\t\t\tbreak\n\t\t}\n\t\tpage++\n\n\t}\n\treturn allRepos, nil\n}\n\nfunc PrintGitHubRepos(repos []*gh.Repository) {\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Repo Name\", \"Owner\", \"Repo Link\", \"Private\"})\n\tfor _, repo := range repos {\n\t\tif *repo.Private {\n\t\t\tgreen := color.New(color.FgGreen).SprintFunc()\n\t\t\tt.AppendRow([]interface{}{green(*repo.Name), green(*repo.Owner.Login), green(*repo.HTMLURL), green(\"true\")})\n\t\t} else {\n\t\t\tt.AppendRow([]interface{}{*repo.Name, *repo.Owner.Login, *repo.HTMLURL, *repo.Private})\n\t\t}\n\t}\n\tt.Render()\n\tfmt.Print(\"\\n\\n\")\n}\n\nfunc PrintGists(gists []*gh.Gist, showAll bool) {\n\tprivateCount := 0\n\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Gist ID\", \"Gist Link\", \"Description\", \"Private\"})\n\tfor _, gist := range gists {\n\t\tif gist == nil {\n\t\t\tcontinue\n\t\t}\n\t\tgistID := gist.GetID()\n\t\tgistLink := gist.GetHTMLURL()\n\t\tgistDescription := gist.GetDescription()\n\t\tisPublic := gist.GetPublic()\n\n\t\tif showAll && isPublic {\n\t\t\tt.AppendRow([]any{gistID, gistLink, gistDescription, \"false\"})\n\t\t} else if !isPublic {\n\t\t\tprivateCount++\n\t\t\tgreen := color.New(color.FgGreen).SprintFunc()\n\t\t\tt.AppendRow([]any{green(gistID), green(gistLink), green(gistDescription), green(\"true\")})\n\t\t}\n\t}\n\tif showAll && len(gists) == 0 {\n\t\tcolor.Red(\"[i] No Gist(s) Found\\n\")\n\t} else if showAll {\n\t\tcolor.Yellow(\"[i] Found %v Total Gist(s) (%v private)\\n\", len(gists), privateCount)\n\t\tt.Render()\n\t} else if privateCount == 0 {\n\t\tcolor.Red(\"[i] No Private Gist(s) Found\\n\")\n\t} else {\n\t\tcolor.Green(fmt.Sprintf(\"[!] Found %v Private Gist(s)\\n\", privateCount))\n\t\tt.Render()\n\t}\n\tfmt.Print(\"\\n\\n\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/github/finegrained/finegrained.go",
    "content": "//go:generate generate_permissions finegrained.yaml finegrained_permissions.go finegrained\n\npackage finegrained\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\tgh \"github.com/google/go-github/v67/github\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n)\n\nconst (\n\t// Random values for testing\n\tRANDOM_STRING   = \"FQ2pR.4voZg-gJfsqYKx_eLDNF_6BYhw8RL__\"\n\tRANDOM_USERNAME = \"d\" + \"ummy\" + \"acco\" + \"untgh\" + \"2024\"\n\tRANDOM_REPO     = \"te\" + \"st\"\n\tRANDOM_INTEGER  = 4294967289\n)\n\nvar ErrInvalid = errors.New(\"invalid\")\n\nvar repoPermFuncMap = []func(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error){\n\tgetActionsPermission,\n\tgetAdministrationPermission,\n\tgetCodeScanningAlertsPermission,\n\tgetCodespacesPermission,\n\tnotImplementedRepoPerm, // ToDo: Implement. Docs make this look org-wide...not repo-based?\n\tgetCodespacesMetadataPermission,\n\tgetCodespacesSecretsPermission,\n\tgetCommitStatusesPermission,\n\tgetContentsPermission,\n\tnotImplementedRepoPerm, // ToDo: Only supports orgs. Implement once have an org token.\n\tgetDependabotAlertsPermission,\n\tgetDependabotSecretsPermission,\n\tgetDeploymentsPermission,\n\tgetEnvironmentsPermission,\n\tgetIssuesPermission,\n\tnotImplementedRepoPerm, // Skipped until API better documented\n\tgetMetadataPermission,\n\tgetPagesPermission,\n\tgetPullRequestsPermission,\n\tgetRepoSecurityPermission,\n\tgetSecretScanningPermission,\n\tgetSecretsPermission,\n\tgetVariablesPermission,\n\tgetWebhooksPermission,\n\tnotImplementedRepoPerm, // ToDo: Skipped b/c would require us to create a release (High Risk function)\n}\n\nvar acctPermFuncMap = []func(client *gh.Client, user *gh.User) (Permission, error){\n\tgetBlockUserPermission,\n\tgetCodespacesUserPermission,\n\tgetEmailPermission,\n\tgetFollowersPermission,\n\tgetGPGKeysPermission,\n\tgetGistsPermission,\n\tgetGitKeysPermission,\n\tgetLimitsPermission,\n\tgetPlanPermission,\n\tnotImplementedAcctPerm, // Skipped until API better documented\n\tgetProfilePermission,\n\tgetSigningKeysPermission,\n\tgetStarringPermission,\n\tgetWatchingPermission,\n}\n\n// Define your custom formatter function\nfunc permissionFormatter(key, val any) (string, string) {\n\tif perm, ok := val.(Permission); ok {\n\t\tpermStr, err := perm.ToString()\n\t\tif err != nil {\n\t\t\tlog.Fatal(fmt.Errorf(\"Error converting permission to string: %v\", err))\n\t\t}\n\t\tvar permissionStr string\n\t\tswitch {\n\t\tcase strings.Contains(permStr, \"read\"):\n\t\t\tpermissionStr = \"READ_ONLY\"\n\t\tcase strings.Contains(permStr, \"write\"):\n\t\t\tpermissionStr = \"READ_WRITE\"\n\t\tdefault:\n\t\t\tpermissionStr = \"UNKNOWN\"\n\t\t}\n\n\t\tswitch permissionStr {\n\t\tcase \"READ_ONLY\":\n\t\t\tyellow := color.New(color.FgYellow).SprintFunc()\n\t\t\treturn yellow(key), yellow(permissionStr)\n\t\tcase \"READ_WRITE\":\n\t\t\tred := color.New(color.FgGreen).SprintFunc()\n\t\t\treturn red(key), red(permissionStr)\n\t\tcase \"UNKNOWN\":\n\t\t\tblue := color.New(color.FgBlue).SprintFunc()\n\t\t\treturn blue(key), blue(permissionStr)\n\t\t}\n\t}\n\treturn fmt.Sprintf(\"%v\", key), fmt.Sprintf(\"%v\", val)\n}\n\nfunc notImplementedRepoPerm(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\treturn NoAccess, nil\n}\n\n// notImplementedAcctPerm is a placeholder function that returns a \"NOT_IMPLEMENTED\" status when a GitHub account permission is not yet implemented.\nfunc notImplementedAcctPerm(client *gh.Client, user *gh.User) (Permission, error) {\n\treturn NoAccess, nil\n}\n\nfunc getMetadataPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\t// Risk: Extremely Low\n\t// -> GET request to /repos/{owner}/{repo}/collaborators\n\t_, resp, err := client.Repositories.ListCollaborators(context.Background(), *repo.Owner.Login, *repo.Name, nil)\n\tif err != nil {\n\t\tif resp.StatusCode == 403 {\n\t\t\treturn NoAccess, nil\n\t\t}\n\t\treturn Invalid, err\n\t}\n\t// If no error, then we have read access\n\n\treturn MetadataRead, nil\n}\n\nfunc getActionsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\tif *repo.Private {\n\t\t// Risk: Extremely Low\n\t\t// -> GET request to /repos/{owner}/{repo}/actions/artifacts\n\t\t_, resp, err := client.Actions.ListArtifacts(context.Background(), *repo.Owner.Login, *repo.Name, nil)\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn NoAccess, nil\n\t\tcase 200:\n\t\t\tbreak\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\n\t\t// Risk: Very, very low.\n\t\t// -> Unless the user has a workflow file named (see RANDOM_STRING above), this will always return 404 for users with READ_WRITE permissions.\n\t\t// -> POST request to /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches\n\t\tresp, err = client.Actions.CreateWorkflowDispatchEventByFileName(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, gh.CreateWorkflowDispatchEventRequest{})\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn ActionsRead, nil\n\t\tcase 404:\n\t\t\treturn ActionsWrite, nil\n\t\tcase 200:\n\t\t\tlog.Fatal(\"This shouldn't print. We are enabling a workflow based on a random string \" + RANDOM_STRING + \", which most likely doesn't exist.\")\n\t\t\treturn ActionsWrite, nil\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\t} else {\n\t\t// Will only land here if already tested one public repo and got a 403.\n\t\tif currentAccess == NoAccess {\n\t\t\treturn NoAccess, nil\n\t\t}\n\t\t// Risk: Very, very low.\n\t\t// -> Unless the user has a workflow file named (see RANDOM_STRING above), this will always return 404 for users with READ_WRITE permissions.\n\t\t// -> POST request to /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches\n\t\tresp, err := client.Actions.CreateWorkflowDispatchEventByFileName(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, gh.CreateWorkflowDispatchEventRequest{})\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn NoAccess, nil\n\t\tcase 404:\n\t\t\treturn ActionsWrite, nil\n\t\tcase 200:\n\t\t\tlog.Fatal(\"This shouldn't print. We are enabling a workflow based on a random string \" + RANDOM_STRING + \", which most likely doesn't exist.\")\n\t\t\treturn ActionsWrite, nil\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\t}\n}\n\n// Continue with the other functions using the same pattern...\n\nfunc getAdministrationPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\t// Risk: Extremely Low\n\t// -> GET request to /repos/{owner}/{repo}/actions/permissions\n\t_, resp, err := client.Repositories.GetActionsPermissions(context.Background(), *repo.Owner.Login, *repo.Name)\n\tswitch resp.StatusCode {\n\tcase 403, 404:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// Risk: Extremely Low\n\t// -> GET request to /repos/{owner}/{repo}/rulesets/rule-suites\n\treq, err := client.NewRequest(\"GET\", \"https://api.github.com/repos/\"+*repo.Owner.Login+\"/\"+*repo.Name+\"/rulesets/rule-suites\", nil)\n\tif err != nil {\n\t\treturn Invalid, err\n\t}\n\tresp, err = client.Do(context.Background(), req, nil)\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn AdministrationRead, nil\n\tcase 200:\n\t\treturn AdministrationWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getCodeScanningAlertsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\t// Risk: Extremely Low\n\t// -> GET request to /repos/{owner}/{repo}/code-scanning/alerts\n\t_, resp, err := client.CodeScanning.ListAlertsForRepo(context.Background(), *repo.Owner.Login, *repo.Name, nil)\n\tdefer resp.Body.Close()\n\n\tswitch {\n\tcase resp.StatusCode == 403:\n\t\treturn NoAccess, nil\n\tcase resp.StatusCode == 404:\n\t\tbreak\n\tcase resp.StatusCode >= 200 && resp.StatusCode <= 299:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// Risk: Very Low\n\t// -> Even if user had an alert with the number (see RANDOM_INTEGER above), this should error 422 due to the nil value passed in.\n\t// -> PATCH request to /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}\n\t_, resp, err = client.CodeScanning.UpdateAlert(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_INTEGER, nil)\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn CodeScanningAlertsRead, nil\n\tcase 422:\n\t\treturn CodeScanningAlertsWrite, nil\n\tcase 200:\n\t\tlog.Fatal(\"This should never happen. We are updating an alert with nil which should be an invalid request.\")\n\t\treturn CodeScanningAlertsWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getCodespacesPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET request to /repos/{owner}/{repo}/codespaces\n\t_, resp, err := client.Codespaces.ListInRepo(context.Background(), *repo.Owner.Login, *repo.Name, nil)\n\tswitch resp.StatusCode {\n\tcase 403, 404:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// Risk: Extremely Low\n\t// GET request to /repos/{owner}/{repo}/codespaces/permissions_check\n\treq, err := client.NewRequest(\"GET\", \"https://api.github.com/repos/\"+*repo.Owner.Login+\"/\"+*repo.Name+\"/codespaces/permissions_check\", nil)\n\tif err != nil {\n\t\treturn Invalid, err\n\t}\n\tresp, err = client.Do(context.Background(), req, nil)\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn CodespacesRead, nil\n\tcase 422:\n\t\treturn CodespacesWrite, nil\n\tcase 200:\n\t\treturn CodespacesWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getCodespacesMetadataPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET request to /repos/{owner}/{repo}/codespaces/machines\n\treq, err := client.NewRequest(\"GET\", \"https://api.github.com/repos/\"+*repo.Owner.Login+\"/\"+*repo.Name+\"/codespaces/machines\", nil)\n\tif err != nil {\n\t\treturn Invalid, err\n\t}\n\tresp, err := client.Do(context.Background(), req, nil)\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\treturn CodespacesMetadataRead, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getCodespacesSecretsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET request to /repos/{owner}/{repo}/codespaces/secrets for non-existent secret\n\t_, resp, err := client.Codespaces.GetRepoSecret(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING)\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn NoAccess, nil\n\tcase 404:\n\t\treturn CodespacesSecretsWrite, nil\n\tcase 200:\n\t\treturn CodespacesSecretsWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\n// getCommitStatusesPermission will check if we have access to commit statuses for a given repo.\n// By default, we have read-only access to commit statuses for all public repos. If only public repos exist under\n// this key's permissions, then they best we can hope for us a READ_WRITE status or an UNKNOWN status.\n// If a private repo exists, then we can check for READ_ONLY, READ_WRITE and NO_ACCESS.\nfunc getCommitStatusesPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\tif *repo.Private {\n\t\t// Risk: Extremely Low\n\t\t// GET request to /repos/{owner}/{repo}/commits/{commit_sha}/statuses\n\t\t_, resp, err := client.Repositories.ListStatuses(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, nil)\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn NoAccess, nil\n\t\tcase 404:\n\t\t\tbreak\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\t\t// At this point we have read access\n\n\t\t// Risk: Extremely Low\n\t\t// -> We're POSTing a commit status to a commit that cannot exist. This should always return 422 if valid access.\n\t\t// POST request to /repos/{owner}/{repo}/statuses/{commit_sha}\n\t\t_, resp, err = client.Repositories.CreateStatus(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, &gh.RepoStatus{})\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn CommitStatusesRead, nil\n\t\tcase 422:\n\t\t\treturn CommitStatusesWrite, nil\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\t} else {\n\t\t// Will only land here if already tested one public repo and got a 403.\n\t\tif currentAccess == NoAccess {\n\t\t\treturn NoAccess, nil\n\t\t}\n\t\t// Risk: Extremely Low\n\t\t// -> We're POSTing a commit status to a commit that cannot exist. This should always return 422 if valid access.\n\t\t// POST request to /repos/{owner}/{repo}/statuses/{commit_sha}\n\t\t_, resp, err := client.Repositories.CreateStatus(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, &gh.RepoStatus{})\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\t// All we know is we don't have READ_WRITE\n\t\t\treturn NoAccess, nil\n\t\tcase 422:\n\t\t\treturn CommitStatusesWrite, nil\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\t}\n}\n\n// getContentsPermission will check if we have access to the contents of a given repo.\n// By default, we have read-only access to the contents of all public repos. If only public repos exist under\n// this key's permissions, then they best we can hope for us a READ_WRITE status or an UNKNOWN status.\n// If a private repo exists, then we can check for READ_ONLY, READ_WRITE and NO_ACCESS.\nfunc getContentsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\tif *repo.Private {\n\t\t// Risk: Extremely Low\n\t\t// GET request to /repos/{owner}/{repo}/commits\n\t\t_, resp, err := client.Repositories.ListCommits(context.Background(), *repo.Owner.Login, *repo.Name, &gh.CommitsListOptions{})\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn NoAccess, nil\n\t\tcase 200:\n\t\t\tbreak\n\t\tcase 409:\n\t\t\tbreak\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\t\t// At this point we have read access\n\n\t\t// Risk: Low-Medium\n\t\t// -> 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.\n\t\t// PUT /repos/{owner}/{repo}/contents/{path}\n\t\t_, resp, err = client.Repositories.CreateFile(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, &gh.RepositoryContentFileOptions{})\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn ContentsRead, nil\n\t\tcase 200:\n\t\t\tlog.Fatal(\"This should never happen. We are creating a file with an invalid payload.\")\n\t\t\treturn ContentsWrite, nil\n\t\tcase 400, 422:\n\t\t\treturn ContentsWrite, nil\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\t} else {\n\t\t// Will only land here if already tested one public repo and got a 403.\n\t\tif currentAccess == NoAccess {\n\t\t\treturn NoAccess, nil\n\t\t}\n\t\t// Risk: Low-Medium\n\t\t// -> 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.\n\t\t// PUT /repos/{owner}/{repo}/contents/{path}\n\t\t_, resp, err := client.Repositories.CreateFile(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, &gh.RepositoryContentFileOptions{})\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn NoAccess, nil\n\t\tcase 200:\n\t\t\tpanic(\"This should never happen. We are creating a file with an invalid payload.\")\n\t\tcase 400, 422:\n\t\t\treturn ContentsWrite, nil\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\t}\n}\n\nfunc getDependabotAlertsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET /repos/{owner}/{repo}/dependabot/alerts\n\t_, resp, err := client.Dependabot.ListRepoAlerts(context.Background(), *repo.Owner.Login, *repo.Name, &gh.ListAlertsOptions{})\n\tdefer resp.Body.Close()\n\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// PATCH /repos/{owner}/{repo}/dependabot/alerts/{alert_number}\n\t_, resp, err = client.Dependabot.UpdateAlert(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_INTEGER, nil)\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn DependabotAlertsRead, nil\n\tcase 422, 404:\n\t\treturn DependabotAlertsWrite, nil\n\tcase 200:\n\t\tlog.Fatal(\"This should never happen. We are updating an alert with nil which should be an invalid request.\")\n\t\treturn DependabotAlertsWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getDependabotSecretsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET /repos/{owner}/{repo}/dependabot/secrets\n\t_, resp, err := client.Dependabot.ListRepoSecrets(context.Background(), *repo.Owner.Login, *repo.Name, &gh.ListOptions{})\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// Risk: Very Low\n\t// -> 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.\n\t// PUT /repos/{owner}/{repo}/dependabot/secrets/{secret_name}\n\tresp, err = client.Dependabot.CreateOrUpdateRepoSecret(context.Background(), *repo.Owner.Login, *repo.Name, &gh.DependabotEncryptedSecret{Name: RANDOM_STRING})\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn DependabotSecretsRead, nil\n\tcase 422:\n\t\treturn DependabotSecretsWrite, nil\n\tcase 201, 204:\n\t\tlog.Fatal(\"This should never happen. We are creating a secret with an invalid payload.\")\n\t\treturn DependabotSecretsWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getDeploymentsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET /repos/{owner}/{repo}/deployments\n\t_, resp, err := client.Repositories.ListDeployments(context.Background(), *repo.Owner.Login, *repo.Name, &gh.DeploymentsListOptions{})\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// Risk: Very Low\n\t// -> 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.\n\t// POST /repos/{owner}/{repo}/deployments/{deployment_id}/statuses\n\t_, resp, err = client.Repositories.CreateDeployment(context.Background(), *repo.Owner.Login, *repo.Name, &gh.DeploymentRequest{})\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn DeploymentsRead, nil\n\tcase 409, 422:\n\t\treturn DeploymentsWrite, nil\n\tcase 201, 202:\n\t\tlog.Fatal(\"This should never happen. We are creating a deployment with an invalid payload.\")\n\t\treturn DeploymentsWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getEnvironmentsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET /repos/{owner}/{repo}/environments\n\tenvResp, resp, _ := client.Repositories.ListEnvironments(context.Background(), *repo.Owner.Login, *repo.Name, &gh.EnvironmentListOptions{})\n\tif resp.StatusCode != 200 {\n\t\treturn NoAccess, nil\n\t}\n\t// If no environments exist, then we return UNKNOWN\n\tif len(envResp.Environments) == 0 {\n\t\treturn NoAccess, nil\n\t}\n\n\t// Risk: Extremely Low\n\t// GET /repositories/{repository_id}/environments/{environment_name}/variables\n\t_, resp, _ = client.Actions.ListEnvVariables(context.Background(), *repo.Owner.Login, *repo.Name, *envResp.Environments[0].Name, &gh.ListOptions{})\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, nil\n\t}\n\n\t// Risk: Very Low\n\t// -> 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.\n\t// PATCH /repositories/{repository_id}/environments/{environment_name}/variables/{variable_name}\n\tresp, err := client.Actions.UpdateEnvVariable(context.Background(), *repo.Owner.Login, *repo.Name, *envResp.Environments[0].Name, &gh.ActionsVariable{Name: RANDOM_STRING})\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn EnvironmentsRead, nil\n\tcase 422:\n\t\treturn EnvironmentsWrite, nil\n\tcase 200:\n\t\tlog.Fatal(\"This should never happen. We are updating an environment variable with an invalid payload.\")\n\t\treturn EnvironmentsWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getIssuesPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\n\tif *repo.Private {\n\n\t\t// Risk: Extremely Low\n\t\t// GET /repos/{owner}/{repo}/issues\n\t\t_, resp, err := client.Issues.ListByRepo(context.Background(), *repo.Owner.Login, *repo.Name, &gh.IssueListByRepoOptions{})\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn NoAccess, nil\n\t\tcase 200, 301:\n\t\t\tbreak\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\n\t\t// Risk: Very Low\n\t\t// -> We're editing an issue label that does not exist. Even if we did, the name would be (see RANDOM_STRING above).\n\t\t// PATCH /repos/{owner}/{repo}/labels/{name}\n\t\t_, resp, err = client.Issues.EditLabel(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, &gh.Label{})\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn IssuesRead, nil\n\t\tcase 404:\n\t\t\treturn IssuesWrite, nil\n\t\tcase 200:\n\t\t\tlog.Fatal(\"This should never happen. We are editing a label with an invalid payload.\")\n\t\t\treturn IssuesWrite, nil\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\t} else {\n\t\t// Will only land here if already tested one public repo and got a 403.\n\t\tif currentAccess == NoAccess {\n\t\t\treturn NoAccess, nil\n\t\t}\n\t\t// Risk: Very Low\n\t\t// -> We're editing an issue label that does not exist. Even if we did, the name would be (see RANDOM_STRING above).\n\t\t// PATCH /repos/{owner}/{repo}/labels/{name}\n\t\t_, resp, err := client.Issues.EditLabel(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_STRING, &gh.Label{})\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn NoAccess, nil\n\t\tcase 404:\n\t\t\treturn IssuesWrite, nil\n\t\tcase 200:\n\t\t\tlog.Fatal(\"This should never happen. We are editing a label with an invalid payload.\")\n\t\t\treturn IssuesWrite, nil\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\t}\n}\n\nfunc getPagesPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\tif *repo.Private {\n\t\t// Risk: Extremely Low\n\t\t// GET /repos/{owner}/{repo}/pages\n\t\t_, resp, err := client.Repositories.GetPagesInfo(context.Background(), *repo.Owner.Login, *repo.Name)\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn NoAccess, nil\n\t\tcase 200, 404:\n\t\t\tbreak\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\n\t\t// Risk: Very Low\n\t\t// -> We're cancelling a GitHub Pages deployment that does not exist (see RANDOM_STRING above).\n\t\t// POST /repos/{owner}/{repo}/pages/deployments/{deployment_id}/cancel\n\t\treq, err := client.NewRequest(\"POST\", \"https://api.github.com/repos/\"+*repo.Owner.Login+\"/\"+*repo.Name+\"/pages/deployments/\"+RANDOM_STRING+\"/cancel\", nil)\n\t\tif err != nil {\n\t\t\treturn Invalid, err\n\t\t}\n\t\tresp, err = client.Do(context.Background(), req, nil)\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn PagesRead, nil\n\t\tcase 404:\n\t\t\treturn PagesWrite, nil\n\t\tcase 200:\n\t\t\tlog.Fatal(\"This should never happen. We are cancelling a deployment with an invalid ID.\")\n\t\t\treturn PagesWrite, nil\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\t} else {\n\t\t// Will only land here if already tested one public repo and got a 403.\n\t\tif currentAccess == NoAccess {\n\t\t\treturn NoAccess, nil\n\t\t}\n\t\t// Risk: Very Low\n\t\t// -> We're cancelling a GitHub Pages deployment that does not exist (see RANDOM_STRING above).\n\t\t// POST /repos/{owner}/{repo}/pages/deployments/{deployment_id}/cancel\n\t\treq, err := client.NewRequest(\"POST\", \"https://api.github.com/repos/\"+*repo.Owner.Login+\"/\"+*repo.Name+\"/pages/deployments/\"+RANDOM_STRING+\"/cancel\", nil)\n\t\tif err != nil {\n\t\t\treturn Invalid, err\n\t\t}\n\t\tresp, err := client.Do(context.Background(), req, nil)\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn NoAccess, nil\n\t\tcase 404:\n\t\t\treturn PagesWrite, nil\n\t\tcase 200:\n\t\t\tlog.Fatal(\"This should never happen. We are cancelling a deployment with an invalid ID.\")\n\t\t\treturn PagesWrite, nil\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\t}\n}\n\nfunc getPullRequestsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\tif *repo.Private {\n\t\t// Risk: Extremely Low\n\t\t// GET /repos/{owner}/{repo}/pulls\n\t\t_, resp, err := client.PullRequests.List(context.Background(), *repo.Owner.Login, *repo.Name, &gh.PullRequestListOptions{})\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn NoAccess, nil\n\t\tcase 200:\n\t\t\tbreak\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\n\t\t// Risk: Very Low\n\t\t// -> We're creating a pull request with an invalid payload.\n\t\t// POST /repos/{owner}/{repo}/pulls\n\t\t_, resp, err = client.PullRequests.Create(context.Background(), *repo.Owner.Login, *repo.Name, &gh.NewPullRequest{})\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn PullRequestsRead, nil\n\t\tcase 422:\n\t\t\treturn PullRequestsWrite, nil\n\t\tcase 200:\n\t\t\tlog.Fatal(\"This should never happen. We are creating a pull request with an invalid payload.\")\n\t\t\treturn PullRequestsWrite, nil\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\t} else {\n\t\t// Will only land here if already tested one public repo and got a 403.\n\t\tif currentAccess == NoAccess {\n\t\t\treturn NoAccess, nil\n\t\t}\n\t\t// Risk: Very Low\n\t\t// -> We're creating a pull request with an invalid payload.\n\t\t// POST /repos/{owner}/{repo}/pulls\n\t\t_, resp, err := client.PullRequests.Create(context.Background(), *repo.Owner.Login, *repo.Name, &gh.NewPullRequest{})\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn NoAccess, nil\n\t\tcase 422:\n\t\t\treturn PullRequestsWrite, nil\n\t\tcase 200:\n\t\t\tlog.Fatal(\"This should never happen. We are creating a pull request with an invalid payload.\")\n\t\t\treturn PullRequestsWrite, nil\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\t}\n}\n\nfunc getRepoSecurityPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\n\tif *repo.Private {\n\t\t// Risk: Extremely Low\n\t\t// GET /repos/{owner}/{repo}/security-advisories\n\t\t_, resp, err := client.SecurityAdvisories.ListRepositorySecurityAdvisories(context.Background(), *repo.Owner.Login, *repo.Name, nil)\n\t\tswitch resp.StatusCode {\n\t\tcase 403, 404:\n\t\t\treturn NoAccess, nil\n\t\tcase 200:\n\t\t\tbreak\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\n\t\t// Risk: Very Low\n\t\t// -> We're creating a security advisory with an invalid payload.\n\t\t// POST /repos/{owner}/{repo}/security-advisories\n\t\treq, err := client.NewRequest(\"POST\", \"https://api.github.com/repos/\"+*repo.Owner.Login+\"/\"+*repo.Name+\"/security-advisories\", nil)\n\t\tif err != nil {\n\t\t\treturn Invalid, err\n\t\t}\n\t\tresp, err = client.Do(context.Background(), req, nil)\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn RepoSecurityRead, nil\n\t\tcase 422:\n\t\t\treturn RepoSecurityWrite, nil\n\t\tcase 200:\n\t\t\tlog.Fatal(\"This should never happen. We are creating a security advisory with an invalid payload.\")\n\t\t\treturn RepoSecurityWrite, nil\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\t} else {\n\t\t// Will only land here if already tested one public repo and got a 403.\n\t\tif currentAccess == NoAccess {\n\t\t\treturn NoAccess, nil\n\t\t}\n\t\t// Risk: Very Low\n\t\t// -> We're creating a security advisory with an invalid payload.\n\t\t// POST /repos/{owner}/{repo}/security-advisories\n\t\treq, err := client.NewRequest(\"POST\", \"https://api.github.com/repos/\"+*repo.Owner.Login+\"/\"+*repo.Name+\"/security-advisories\", nil)\n\t\tif err != nil {\n\t\t\treturn Invalid, err\n\t\t}\n\t\tresp, err := client.Do(context.Background(), req, nil)\n\t\tswitch resp.StatusCode {\n\t\tcase 403:\n\t\t\treturn NoAccess, nil\n\t\tcase 422:\n\t\t\treturn RepoSecurityWrite, nil\n\t\tcase 200:\n\t\t\tlog.Fatal(\"This should never happen. We are creating a security advisory with an invalid payload.\")\n\t\t\treturn RepoSecurityWrite, nil\n\t\tdefault:\n\t\t\treturn Invalid, err\n\t\t}\n\t}\n}\n\nfunc getSecretScanningPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET /repos/{owner}/{repo}/secret-scanning/alerts\n\t_, resp, err := client.SecretScanning.ListAlertsForRepo(context.Background(), *repo.Owner.Login, *repo.Name, nil)\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn NoAccess, nil\n\tcase 200, 404:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// Risk: Very Low\n\t// -> We're updating a secret scanning alert for an alert that doesn't exist.\n\t// POST /repos/{owner}/{repo}/secret-scanning/alerts\n\t_, resp, err = client.SecretScanning.UpdateAlert(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_INTEGER, &gh.SecretScanningAlertUpdateOptions{})\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn SecretScanningRead, nil\n\tcase 404, 422:\n\t\treturn SecretScanningWrite, nil\n\tcase 200:\n\t\tlog.Fatal(\"This should never happen. We are updating a secret scanning alert that doesn't exist.\")\n\t\treturn SecretScanningWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getSecretsPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET /repos/{owner}/{repo}/actions/secrets\n\t_, resp, err := client.Actions.ListRepoSecrets(context.Background(), *repo.Owner.Login, *repo.Name, &gh.ListOptions{})\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// Risk: Very Low\n\t// -> We're creating a secret with an invalid payload.\n\t// PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}\n\tresp, err = client.Actions.CreateOrUpdateRepoSecret(context.Background(), *repo.Owner.Login, *repo.Name, &gh.EncryptedSecret{Name: RANDOM_STRING})\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn SecretsRead, nil\n\tcase 422:\n\t\treturn SecretsWrite, nil\n\tcase 201, 204:\n\t\tlog.Fatal(\"This should never happen. We are creating a secret with an invalid payload.\")\n\t\treturn SecretsWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getVariablesPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET /repos/{owner}/{repo}/actions/variables\n\t_, resp, err := client.Actions.ListRepoVariables(context.Background(), *repo.Owner.Login, *repo.Name, &gh.ListOptions{})\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// Risk: Very Low\n\t// -> We're updating a variable that doesn't exist with an invalid payload.\n\t// PATCH /repos/{owner}/{repo}/actions/variables/{name}\n\tresp, err = client.Actions.UpdateRepoVariable(context.Background(), *repo.Owner.Login, *repo.Name, &gh.ActionsVariable{Name: RANDOM_STRING})\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn VariablesRead, nil\n\tcase 422:\n\t\treturn VariablesWrite, nil\n\tcase 201, 204:\n\t\tlog.Fatal(\"This should never happen. We are patching a variable with an invalid payload and no name.\")\n\t\treturn VariablesWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getWebhooksPermission(client *gh.Client, repo *gh.Repository, currentAccess Permission) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET /repos/{owner}/{repo}/hooks\n\t_, resp, err := client.Repositories.ListHooks(context.Background(), *repo.Owner.Login, *repo.Name, &gh.ListOptions{})\n\tswitch resp.StatusCode {\n\tcase 403, 404:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// Risk: Very Low\n\t// -> We're updating a webhook that doesn't exist with an invalid payload.\n\t// PATCH /repos/{owner}/{repo}/hooks/{hook_id}\n\t_, resp, err = client.Repositories.EditHook(context.Background(), *repo.Owner.Login, *repo.Name, RANDOM_INTEGER, &gh.Hook{})\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn WebhooksRead, nil\n\tcase 404:\n\t\treturn WebhooksWrite, nil\n\tcase 200:\n\t\tlog.Fatal(\"This should never happen. We are updating a webhook with an invalid payload.\")\n\t\treturn WebhooksWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\n// analyzeRepositoryPermissions will analyze the fine-grained permissions of a given permission type and return the access level.\n// This function is needed b/c in some cases a token could have permissions that are only enabled on specific repos.\n// If we only checked one repo, we wouldn't be able to tell if the token has access to a specific permission type.\n// Ex: \"Code scanning alerts\" must be enabled to tell if we have that permission.\nfunc analyzeRepositoryPermissions(client *gh.Client, repos []*gh.Repository) ([]Permission, error) {\n\tperms := make([]Permission, len(repoPermFuncMap))\n\tfor _, repo := range repos {\n\t\tfor i, permFunc := range repoPermFuncMap {\n\t\t\taccess, err := permFunc(client, repo, perms[i])\n\t\t\tif err != nil || access == Invalid {\n\t\t\t\t// TODO: Log error.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif perms[i] == Invalid || perms[i] == NoAccess {\n\t\t\t\tperms[i] = access\n\t\t\t}\n\t\t}\n\t}\n\treturn perms, nil\n}\n\nfunc getBlockUserPermission(client *gh.Client, user *gh.User) (Permission, error) {\n\t// Risk: Extremely Low\n\t// -> GET request to /user/blocks\n\t_, resp, err := client.Users.ListBlockedUsers(context.Background(), nil)\n\tswitch resp.StatusCode {\n\tcase 403, 404:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// Risk: Extremely Low\n\t// -> PUT request to /user/blocks/{username}\n\t// -> We're blocking a user that doesn't exist. See RANDOM_STRING above.\n\tresp, err = client.Users.BlockUser(context.Background(), RANDOM_STRING)\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn BlockUserRead, nil\n\tcase 404:\n\t\treturn BlockUserWrite, nil\n\tcase 204:\n\t\tlog.Fatal(\"This should never happen. We are blocking a user that doesn't exist.\")\n\t\treturn BlockUserWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getCodespacesUserPermission(client *gh.Client, user *gh.User) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET request to /user/codespaces/secrets\n\t_, resp, err := client.Codespaces.ListUserSecrets(context.Background(), nil)\n\tswitch resp.StatusCode {\n\tcase 403, 404:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// Risk: Low\n\t// PUT request to /user/codespaces/secrets/{secret_name}\n\t// Payload is invalid, so it shouldn't actually post.\n\tresp, err = client.Codespaces.CreateOrUpdateUserSecret(context.Background(), &gh.EncryptedSecret{Name: RANDOM_STRING})\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn CodespaceUserSecretsRead, nil\n\tcase 422:\n\t\treturn CodespaceUserSecretsWrite, nil\n\tcase 201, 204:\n\t\tlog.Fatal(\"This should never happen. We are creating a user secret with an invalid payload.\")\n\t\treturn CodespaceUserSecretsWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getEmailPermission(client *gh.Client, user *gh.User) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET request to /user/emails\n\t_, resp, err := client.Users.ListEmails(context.Background(), nil)\n\tswitch resp.StatusCode {\n\tcase 403, 404:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// Risk: Low\n\t// POST request to /user/emails/visibility\n\t_, resp, err = client.Users.SetEmailVisibility(context.Background(), RANDOM_STRING)\n\tswitch resp.StatusCode {\n\tcase 403, 404:\n\t\treturn EmailRead, nil\n\tcase 422:\n\t\treturn EmailWrite, nil\n\tcase 201:\n\t\tlog.Fatal(\"This should never happen. We are setting email visibility with an invalid payload.\")\n\t\treturn EmailWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getFollowersPermission(client *gh.Client, user *gh.User) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET request to /user/followers\n\t_, resp, err := client.Users.ListFollowers(context.Background(), \"\", nil)\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// Risk: Low - Medium\n\t// DELETE request to /user/followers/{username}\n\t// For the username value, we need to use a real username. So there is a super small chance that someone following\n\t// an account for RANDOM_USERNAME value will then no longer follow that account.\n\t// But we're using an account created specifically for this purpose with no activity.\n\tresp, err = client.Users.Unfollow(context.Background(), RANDOM_USERNAME)\n\tswitch resp.StatusCode {\n\tcase 403, 404:\n\t\treturn FollowersRead, nil\n\tcase 204:\n\t\treturn FollowersWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getGPGKeysPermission(client *gh.Client, user *gh.User) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET request to /user/gpg_keys\n\t_, resp, err := client.Users.ListGPGKeys(context.Background(), \"\", nil)\n\tswitch resp.StatusCode {\n\tcase 403, 404:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// Risk: Low - Medium\n\t// POST request to /user/gpg_keys\n\t// Payload is invalid, so it shouldn't actually post.\n\t_, resp, err = client.Users.CreateGPGKey(context.Background(), RANDOM_STRING)\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn GpgKeysRead, nil\n\tcase 422:\n\t\treturn GpgKeysWrite, nil\n\tcase 200, 201, 204:\n\t\tlog.Fatal(\"This should never happen. We are creating a GPG key with an invalid payload.\")\n\t\treturn GpgKeysWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getGistsPermission(client *gh.Client, user *gh.User) (Permission, error) {\n\t// Risk: Low - Medium\n\t// POST request to /gists\n\t// Payload is invalid, so it shouldn't actually post.\n\t_, resp, err := client.Gists.Create(context.Background(), &gh.Gist{})\n\tswitch resp.StatusCode {\n\tcase 403, 404:\n\t\treturn NoAccess, nil\n\tcase 422:\n\t\treturn GistsWrite, nil\n\tcase 200, 201, 204:\n\t\tlog.Fatal(\"This should never happen. We are creating a Gist with an invalid payload.\")\n\t\treturn GistsWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getGitKeysPermission(client *gh.Client, user *gh.User) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET request to /user/keys\n\t_, resp, err := client.Users.ListKeys(context.Background(), \"\", nil)\n\tswitch resp.StatusCode {\n\tcase 403, 404:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// Risk: Low - Medium\n\t// POST request to /user/keys\n\t// Payload is invalid, so it shouldn't actually post.\n\t_, resp, err = client.Users.CreateKey(context.Background(), &gh.Key{})\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn GitKeysRead, nil\n\tcase 422:\n\t\treturn GitKeysWrite, nil\n\tcase 200, 201, 204:\n\t\tlog.Fatal(\"This should never happen. We are creating a key with an invalid payload.\")\n\t\treturn GitKeysWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getLimitsPermission(client *gh.Client, user *gh.User) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET request to /user/interaction-limits\n\treq, err := client.NewRequest(\"GET\", \"https://api.github.com/user/interaction-limits\", nil)\n\tif err != nil {\n\t\treturn Invalid, err\n\t}\n\tresp, err := client.Do(context.Background(), req, nil)\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn NoAccess, nil\n\tcase 200, 204:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// Risk: Low\n\t// PUT request to /user/interaction-limits\n\t// Payload is invalid, so it shouldn't actually post.\n\treq, err = client.NewRequest(\"PUT\", \"https://api.github.com/user/interaction-limits\", nil)\n\tif err != nil {\n\t\treturn Invalid, err\n\t}\n\tresp, err = client.Do(context.Background(), req, nil)\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn LimitsRead, nil\n\tcase 422:\n\t\treturn LimitsWrite, nil\n\tcase 200, 204:\n\t\tlog.Fatal(\"This should never happen. We are setting interaction limits with an invalid payload.\")\n\t\treturn LimitsWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getPlanPermission(client *gh.Client, user *gh.User) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET request to /user/{username}/settings/billing/actions\n\t_, resp, err := client.Billing.GetActionsBillingUser(context.Background(), *user.Login)\n\tswitch resp.StatusCode {\n\tcase 403, 404:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\treturn PlanRead, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getProfilePermission(client *gh.Client, user *gh.User) (Permission, error) {\n\t// Risk: Low\n\t// POST request to /user/social_accounts\n\t// Payload is invalid, so it shouldn't actually patch.\n\treq, err := client.NewRequest(\"POST\", \"https://api.github.com/user/social_accounts\", nil)\n\tif err != nil {\n\t\treturn Invalid, err\n\t}\n\tresp, err := client.Do(context.Background(), req, nil)\n\tswitch resp.StatusCode {\n\tcase 403, 404:\n\t\treturn NoAccess, nil\n\tcase 422:\n\t\treturn ProfileWrite, nil\n\tcase 200, 201, 204:\n\t\tlog.Fatal(\"This should never happen. We are creating a social account with an invalid payload.\")\n\t\treturn ProfileWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getSigningKeysPermission(client *gh.Client, user *gh.User) (Permission, error) {\n\t// Risk: Extremely Low\n\t// GET request to /user/ssh_signing_keys\n\t_, resp, err := client.Users.ListSSHSigningKeys(context.Background(), \"\", nil)\n\tswitch resp.StatusCode {\n\tcase 403, 404:\n\t\treturn NoAccess, nil\n\tcase 200:\n\t\tbreak\n\tdefault:\n\t\treturn Invalid, err\n\t}\n\n\t// Risk: Low - Medium\n\t// POST request to /user/ssh_signing_keys\n\t// Payload is invalid, so it shouldn't actually post.\n\t_, resp, err = client.Users.CreateSSHSigningKey(context.Background(), &gh.Key{})\n\tswitch resp.StatusCode {\n\tcase 403:\n\t\treturn SigningKeysRead, nil\n\tcase 422:\n\t\treturn SigningKeysWrite, nil\n\tcase 200, 201, 204:\n\t\tlog.Fatal(\"This should never happen. We are creating a SSH key with an invalid payload.\")\n\t\treturn SigningKeysWrite, nil\n\tdefault:\n\t\treturn Invalid, err\n\t}\n}\n\nfunc getStarringPermission(client *gh.Client, user *gh.User) (Permission, error) {\n\t// Note: We can't test READ_WRITE b/c Unstar() isn't working even with READ_WRITE permissions.\n\t// Note: GET /user/starred returns the same results regardless of permissions\n\t//       but since all have the same access, we'll call it READ_ONLY for now.\n\treturn StarringRead, nil\n}\n\nfunc getWatchingPermission(client *gh.Client, user *gh.User) (Permission, error) {\n\t// Note: GET /user/subscriptions returns the same results regardless of permissions\n\t//       but since all have the same access, we'll call it READ_ONLY for now.\n\treturn WatchingRead, nil\n}\n\nfunc analyzeUserPermissions(client *gh.Client, user *gh.User) ([]Permission, error) {\n\tperms := []Permission{}\n\tfor _, permFunc := range acctPermFuncMap {\n\t\taccess, err := permFunc(client, user)\n\t\tif err != nil {\n\t\t\t// TODO: Log error.\n\t\t\tcontinue\n\t\t}\n\t\tperms = append(perms, access)\n\t}\n\n\treturn perms, nil\n}\n\nfunc AnalyzeFineGrainedToken(client *gh.Client, meta *common.TokenMetadata, shallowCheck bool) (*common.SecretInfo, error) {\n\tallRepos, err := common.GetAllReposForUser(client)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tallGists, err := common.GetAllGistsForUser(client)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\taccessibleRepos := make([]*gh.Repository, 0)\n\tfor _, repo := range allRepos {\n\t\tperm, err := getMetadataPermission(client, repo, Invalid)\n\t\tif err != nil {\n\t\t\t// TODO: Log error.\n\t\t\tcontinue\n\t\t}\n\t\tif perm != Invalid {\n\t\t\taccessibleRepos = append(accessibleRepos, repo)\n\t\t}\n\t}\n\n\trepoAccessMap := []Permission{}\n\tuserAccessMap := []Permission{}\n\n\tif !shallowCheck {\n\t\t// Check our access\n\t\tperms, err := analyzeRepositoryPermissions(client, accessibleRepos)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor _, perm := range perms {\n\t\t\tif perm != Invalid && perm != NoAccess {\n\t\t\t\trepoAccessMap = append(repoAccessMap, perm)\n\t\t\t}\n\t\t}\n\n\t\tperms, err = analyzeUserPermissions(client, meta.User)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor _, perm := range perms {\n\t\t\tif perm != Invalid && perm != NoAccess {\n\t\t\t\tuserAccessMap = append(userAccessMap, perm)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &common.SecretInfo{\n\t\tMetadata:        meta,\n\t\tRepos:           allRepos,\n\t\tGists:           allGists,\n\t\tAccessibleRepos: accessibleRepos,\n\t\tRepoAccessMap:   repoAccessMap,\n\t\tUserAccessMap:   userAccessMap,\n\t}, nil\n}\n\nfunc PrintFineGrainedToken(cfg *config.Config, info *common.SecretInfo) {\n\tif len(info.AccessibleRepos) == 0 {\n\t\t// If no repos are accessible, then we only have read access to public repos\n\t\tcolor.Red(\"[!] Repository Access: Public Repositories (read-only)\\n\")\n\t} else {\n\t\t// Print out the repos the token can access\n\t\tcolor.Green(fmt.Sprintf(\"Found %v\", len(info.AccessibleRepos)) + \" Accessible Repositor(ies) \\n\")\n\t\tcommon.PrintGitHubRepos(info.AccessibleRepos)\n\n\t\t// Print out the access map\n\t\tperms, ok := info.RepoAccessMap.([]Permission)\n\t\tif !ok {\n\t\t\tpanic(\"Repo Access Map is not of type Permission\")\n\t\t}\n\t\tprintFineGrainedPermissions(perms, cfg.ShowAll, true)\n\t}\n\n\tperms, ok := info.UserAccessMap.([]Permission)\n\tif !ok {\n\t\tpanic(\"Repo Access Map is not of type Permission\")\n\t}\n\n\tprintFineGrainedPermissions(perms, cfg.ShowAll, false)\n\tcommon.PrintGists(info.Gists, cfg.ShowAll)\n}\n\nfunc printFineGrainedPermissions(accessMap []Permission, showAll bool, repoPermissions bool) {\n\tpermissionCount := 0\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Permission Type\", \"Permission\" /* Add more column headers if needed */})\n\n\tfor _, perm := range accessMap {\n\t\tpermStr, _ := perm.ToString()\n\t\tif perm == Invalid {\n\t\t\t// don't change permissionCount\n\t\t} else {\n\t\t\tpermissionCount++\n\t\t}\n\t\tif !showAll && perm == Invalid {\n\t\t\tcontinue\n\t\t} else {\n\t\t\tk, v := permissionFormatter(permStr, perm)\n\t\t\tt.AppendRow([]any{k, v})\n\t\t}\n\t}\n\tvar permissionType string\n\tif repoPermissions {\n\t\tpermissionType = \"Repositor(ies)\"\n\t} else {\n\t\tpermissionType = \"User Account\"\n\t}\n\tif permissionCount == 0 && !showAll {\n\t\tcolor.Red(\"No Permissions Found for the %v above\\n\\n\", permissionType)\n\t\treturn\n\t} else if permissionCount == 0 {\n\t\tcolor.Red(\"Found No Permissions for the %v above\\n\", permissionType)\n\t} else {\n\t\tcolor.Green(fmt.Sprintf(\"Found %v Permission(s) for the %v above\\n\", permissionCount, permissionType))\n\t}\n\tt.Render()\n\tfmt.Print(\"\\n\\n\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/github/finegrained/finegrained.yaml",
    "content": "# 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\"\npermissions:\n  - no_access\n  - actions:read\n  - actions:write\n  - administration:read\n  - administration:write\n  - code_scanning_alerts:read\n  - code_scanning_alerts:write\n  - codespaces:read\n  - codespaces:write\n  - codespaces_lifecycle:read\n  - codespaces_lifecycle:write\n  - codespaces_metadata:read\n  - codespaces_metadata:write\n  - codespaces_secrets:read\n  - codespaces_secrets:write\n  - commit_statuses:read\n  - commit_statuses:write\n  - contents:read\n  - contents:write\n  - custom_properties:read\n  - custom_properties:write\n  - dependabot_alerts:read\n  - dependabot_alerts:write\n  - dependabot_secrets:read\n  - dependabot_secrets:write\n  - deployments:read\n  - deployments:write\n  - environments:read\n  - environments:write\n  - issues:read\n  - issues:write\n  - merge_queues:read\n  - merge_queues:write\n  - metadata:read\n  - metadata:write\n  - pages:read\n  - pages:write\n  - pull_requests:read\n  - pull_requests:write\n  - repo_security:read\n  - repo_security:write\n  - secret_scanning:read\n  - secret_scanning:write\n  - secrets:read\n  - secrets:write\n  - variables:read\n  - variables:write\n  - webhooks:read\n  - webhooks:write\n  - workflows:read\n  - workflows:write\n  - block_user:read\n  - block_user:write\n  - codespace_user_secrets:read\n  - codespace_user_secrets:write\n  - email:read\n  - email:write\n  - followers:read\n  - followers:write\n  - gpg_keys:read\n  - gpg_keys:write\n  - gists:read\n  - gists:write\n  - git_keys:read\n  - git_keys:write\n  - limits:read\n  - limits:write\n  - plan:read\n  - plan:write\n  - private_invites:read\n  - private_invites:write\n  - profile:read\n  - profile:write\n  - signing_keys:read\n  - signing_keys:write\n  - starring:read\n  - starring:write\n  - watching:read\n  - watching:write\n"
  },
  {
    "path": "pkg/analyzer/analyzers/github/finegrained/finegrained_permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage finegrained\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    NoAccess Permission = iota\n    ActionsRead Permission = iota\n    ActionsWrite Permission = iota\n    AdministrationRead Permission = iota\n    AdministrationWrite Permission = iota\n    CodeScanningAlertsRead Permission = iota\n    CodeScanningAlertsWrite Permission = iota\n    CodespacesRead Permission = iota\n    CodespacesWrite Permission = iota\n    CodespacesLifecycleRead Permission = iota\n    CodespacesLifecycleWrite Permission = iota\n    CodespacesMetadataRead Permission = iota\n    CodespacesMetadataWrite Permission = iota\n    CodespacesSecretsRead Permission = iota\n    CodespacesSecretsWrite Permission = iota\n    CommitStatusesRead Permission = iota\n    CommitStatusesWrite Permission = iota\n    ContentsRead Permission = iota\n    ContentsWrite Permission = iota\n    CustomPropertiesRead Permission = iota\n    CustomPropertiesWrite Permission = iota\n    DependabotAlertsRead Permission = iota\n    DependabotAlertsWrite Permission = iota\n    DependabotSecretsRead Permission = iota\n    DependabotSecretsWrite Permission = iota\n    DeploymentsRead Permission = iota\n    DeploymentsWrite Permission = iota\n    EnvironmentsRead Permission = iota\n    EnvironmentsWrite Permission = iota\n    IssuesRead Permission = iota\n    IssuesWrite Permission = iota\n    MergeQueuesRead Permission = iota\n    MergeQueuesWrite Permission = iota\n    MetadataRead Permission = iota\n    MetadataWrite Permission = iota\n    PagesRead Permission = iota\n    PagesWrite Permission = iota\n    PullRequestsRead Permission = iota\n    PullRequestsWrite Permission = iota\n    RepoSecurityRead Permission = iota\n    RepoSecurityWrite Permission = iota\n    SecretScanningRead Permission = iota\n    SecretScanningWrite Permission = iota\n    SecretsRead Permission = iota\n    SecretsWrite Permission = iota\n    VariablesRead Permission = iota\n    VariablesWrite Permission = iota\n    WebhooksRead Permission = iota\n    WebhooksWrite Permission = iota\n    WorkflowsRead Permission = iota\n    WorkflowsWrite Permission = iota\n    BlockUserRead Permission = iota\n    BlockUserWrite Permission = iota\n    CodespaceUserSecretsRead Permission = iota\n    CodespaceUserSecretsWrite Permission = iota\n    EmailRead Permission = iota\n    EmailWrite Permission = iota\n    FollowersRead Permission = iota\n    FollowersWrite Permission = iota\n    GpgKeysRead Permission = iota\n    GpgKeysWrite Permission = iota\n    GistsRead Permission = iota\n    GistsWrite Permission = iota\n    GitKeysRead Permission = iota\n    GitKeysWrite Permission = iota\n    LimitsRead Permission = iota\n    LimitsWrite Permission = iota\n    PlanRead Permission = iota\n    PlanWrite Permission = iota\n    PrivateInvitesRead Permission = iota\n    PrivateInvitesWrite Permission = iota\n    ProfileRead Permission = iota\n    ProfileWrite Permission = iota\n    SigningKeysRead Permission = iota\n    SigningKeysWrite Permission = iota\n    StarringRead Permission = iota\n    StarringWrite Permission = iota\n    WatchingRead Permission = iota\n    WatchingWrite Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        NoAccess: \"no_access\",\n        ActionsRead: \"actions:read\",\n        ActionsWrite: \"actions:write\",\n        AdministrationRead: \"administration:read\",\n        AdministrationWrite: \"administration:write\",\n        CodeScanningAlertsRead: \"code_scanning_alerts:read\",\n        CodeScanningAlertsWrite: \"code_scanning_alerts:write\",\n        CodespacesRead: \"codespaces:read\",\n        CodespacesWrite: \"codespaces:write\",\n        CodespacesLifecycleRead: \"codespaces_lifecycle:read\",\n        CodespacesLifecycleWrite: \"codespaces_lifecycle:write\",\n        CodespacesMetadataRead: \"codespaces_metadata:read\",\n        CodespacesMetadataWrite: \"codespaces_metadata:write\",\n        CodespacesSecretsRead: \"codespaces_secrets:read\",\n        CodespacesSecretsWrite: \"codespaces_secrets:write\",\n        CommitStatusesRead: \"commit_statuses:read\",\n        CommitStatusesWrite: \"commit_statuses:write\",\n        ContentsRead: \"contents:read\",\n        ContentsWrite: \"contents:write\",\n        CustomPropertiesRead: \"custom_properties:read\",\n        CustomPropertiesWrite: \"custom_properties:write\",\n        DependabotAlertsRead: \"dependabot_alerts:read\",\n        DependabotAlertsWrite: \"dependabot_alerts:write\",\n        DependabotSecretsRead: \"dependabot_secrets:read\",\n        DependabotSecretsWrite: \"dependabot_secrets:write\",\n        DeploymentsRead: \"deployments:read\",\n        DeploymentsWrite: \"deployments:write\",\n        EnvironmentsRead: \"environments:read\",\n        EnvironmentsWrite: \"environments:write\",\n        IssuesRead: \"issues:read\",\n        IssuesWrite: \"issues:write\",\n        MergeQueuesRead: \"merge_queues:read\",\n        MergeQueuesWrite: \"merge_queues:write\",\n        MetadataRead: \"metadata:read\",\n        MetadataWrite: \"metadata:write\",\n        PagesRead: \"pages:read\",\n        PagesWrite: \"pages:write\",\n        PullRequestsRead: \"pull_requests:read\",\n        PullRequestsWrite: \"pull_requests:write\",\n        RepoSecurityRead: \"repo_security:read\",\n        RepoSecurityWrite: \"repo_security:write\",\n        SecretScanningRead: \"secret_scanning:read\",\n        SecretScanningWrite: \"secret_scanning:write\",\n        SecretsRead: \"secrets:read\",\n        SecretsWrite: \"secrets:write\",\n        VariablesRead: \"variables:read\",\n        VariablesWrite: \"variables:write\",\n        WebhooksRead: \"webhooks:read\",\n        WebhooksWrite: \"webhooks:write\",\n        WorkflowsRead: \"workflows:read\",\n        WorkflowsWrite: \"workflows:write\",\n        BlockUserRead: \"block_user:read\",\n        BlockUserWrite: \"block_user:write\",\n        CodespaceUserSecretsRead: \"codespace_user_secrets:read\",\n        CodespaceUserSecretsWrite: \"codespace_user_secrets:write\",\n        EmailRead: \"email:read\",\n        EmailWrite: \"email:write\",\n        FollowersRead: \"followers:read\",\n        FollowersWrite: \"followers:write\",\n        GpgKeysRead: \"gpg_keys:read\",\n        GpgKeysWrite: \"gpg_keys:write\",\n        GistsRead: \"gists:read\",\n        GistsWrite: \"gists:write\",\n        GitKeysRead: \"git_keys:read\",\n        GitKeysWrite: \"git_keys:write\",\n        LimitsRead: \"limits:read\",\n        LimitsWrite: \"limits:write\",\n        PlanRead: \"plan:read\",\n        PlanWrite: \"plan:write\",\n        PrivateInvitesRead: \"private_invites:read\",\n        PrivateInvitesWrite: \"private_invites:write\",\n        ProfileRead: \"profile:read\",\n        ProfileWrite: \"profile:write\",\n        SigningKeysRead: \"signing_keys:read\",\n        SigningKeysWrite: \"signing_keys:write\",\n        StarringRead: \"starring:read\",\n        StarringWrite: \"starring:write\",\n        WatchingRead: \"watching:read\",\n        WatchingWrite: \"watching:write\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"no_access\": NoAccess,\n        \"actions:read\": ActionsRead,\n        \"actions:write\": ActionsWrite,\n        \"administration:read\": AdministrationRead,\n        \"administration:write\": AdministrationWrite,\n        \"code_scanning_alerts:read\": CodeScanningAlertsRead,\n        \"code_scanning_alerts:write\": CodeScanningAlertsWrite,\n        \"codespaces:read\": CodespacesRead,\n        \"codespaces:write\": CodespacesWrite,\n        \"codespaces_lifecycle:read\": CodespacesLifecycleRead,\n        \"codespaces_lifecycle:write\": CodespacesLifecycleWrite,\n        \"codespaces_metadata:read\": CodespacesMetadataRead,\n        \"codespaces_metadata:write\": CodespacesMetadataWrite,\n        \"codespaces_secrets:read\": CodespacesSecretsRead,\n        \"codespaces_secrets:write\": CodespacesSecretsWrite,\n        \"commit_statuses:read\": CommitStatusesRead,\n        \"commit_statuses:write\": CommitStatusesWrite,\n        \"contents:read\": ContentsRead,\n        \"contents:write\": ContentsWrite,\n        \"custom_properties:read\": CustomPropertiesRead,\n        \"custom_properties:write\": CustomPropertiesWrite,\n        \"dependabot_alerts:read\": DependabotAlertsRead,\n        \"dependabot_alerts:write\": DependabotAlertsWrite,\n        \"dependabot_secrets:read\": DependabotSecretsRead,\n        \"dependabot_secrets:write\": DependabotSecretsWrite,\n        \"deployments:read\": DeploymentsRead,\n        \"deployments:write\": DeploymentsWrite,\n        \"environments:read\": EnvironmentsRead,\n        \"environments:write\": EnvironmentsWrite,\n        \"issues:read\": IssuesRead,\n        \"issues:write\": IssuesWrite,\n        \"merge_queues:read\": MergeQueuesRead,\n        \"merge_queues:write\": MergeQueuesWrite,\n        \"metadata:read\": MetadataRead,\n        \"metadata:write\": MetadataWrite,\n        \"pages:read\": PagesRead,\n        \"pages:write\": PagesWrite,\n        \"pull_requests:read\": PullRequestsRead,\n        \"pull_requests:write\": PullRequestsWrite,\n        \"repo_security:read\": RepoSecurityRead,\n        \"repo_security:write\": RepoSecurityWrite,\n        \"secret_scanning:read\": SecretScanningRead,\n        \"secret_scanning:write\": SecretScanningWrite,\n        \"secrets:read\": SecretsRead,\n        \"secrets:write\": SecretsWrite,\n        \"variables:read\": VariablesRead,\n        \"variables:write\": VariablesWrite,\n        \"webhooks:read\": WebhooksRead,\n        \"webhooks:write\": WebhooksWrite,\n        \"workflows:read\": WorkflowsRead,\n        \"workflows:write\": WorkflowsWrite,\n        \"block_user:read\": BlockUserRead,\n        \"block_user:write\": BlockUserWrite,\n        \"codespace_user_secrets:read\": CodespaceUserSecretsRead,\n        \"codespace_user_secrets:write\": CodespaceUserSecretsWrite,\n        \"email:read\": EmailRead,\n        \"email:write\": EmailWrite,\n        \"followers:read\": FollowersRead,\n        \"followers:write\": FollowersWrite,\n        \"gpg_keys:read\": GpgKeysRead,\n        \"gpg_keys:write\": GpgKeysWrite,\n        \"gists:read\": GistsRead,\n        \"gists:write\": GistsWrite,\n        \"git_keys:read\": GitKeysRead,\n        \"git_keys:write\": GitKeysWrite,\n        \"limits:read\": LimitsRead,\n        \"limits:write\": LimitsWrite,\n        \"plan:read\": PlanRead,\n        \"plan:write\": PlanWrite,\n        \"private_invites:read\": PrivateInvitesRead,\n        \"private_invites:write\": PrivateInvitesWrite,\n        \"profile:read\": ProfileRead,\n        \"profile:write\": ProfileWrite,\n        \"signing_keys:read\": SigningKeysRead,\n        \"signing_keys:write\": SigningKeysWrite,\n        \"starring:read\": StarringRead,\n        \"starring:write\": StarringWrite,\n        \"watching:read\": WatchingRead,\n        \"watching:write\": WatchingWrite,\n    }\n\n    PermissionIDs = map[Permission]int{\n        NoAccess: 1,\n        ActionsRead: 2,\n        ActionsWrite: 3,\n        AdministrationRead: 4,\n        AdministrationWrite: 5,\n        CodeScanningAlertsRead: 6,\n        CodeScanningAlertsWrite: 7,\n        CodespacesRead: 8,\n        CodespacesWrite: 9,\n        CodespacesLifecycleRead: 10,\n        CodespacesLifecycleWrite: 11,\n        CodespacesMetadataRead: 12,\n        CodespacesMetadataWrite: 13,\n        CodespacesSecretsRead: 14,\n        CodespacesSecretsWrite: 15,\n        CommitStatusesRead: 16,\n        CommitStatusesWrite: 17,\n        ContentsRead: 18,\n        ContentsWrite: 19,\n        CustomPropertiesRead: 20,\n        CustomPropertiesWrite: 21,\n        DependabotAlertsRead: 22,\n        DependabotAlertsWrite: 23,\n        DependabotSecretsRead: 24,\n        DependabotSecretsWrite: 25,\n        DeploymentsRead: 26,\n        DeploymentsWrite: 27,\n        EnvironmentsRead: 28,\n        EnvironmentsWrite: 29,\n        IssuesRead: 30,\n        IssuesWrite: 31,\n        MergeQueuesRead: 32,\n        MergeQueuesWrite: 33,\n        MetadataRead: 34,\n        MetadataWrite: 35,\n        PagesRead: 36,\n        PagesWrite: 37,\n        PullRequestsRead: 38,\n        PullRequestsWrite: 39,\n        RepoSecurityRead: 40,\n        RepoSecurityWrite: 41,\n        SecretScanningRead: 42,\n        SecretScanningWrite: 43,\n        SecretsRead: 44,\n        SecretsWrite: 45,\n        VariablesRead: 46,\n        VariablesWrite: 47,\n        WebhooksRead: 48,\n        WebhooksWrite: 49,\n        WorkflowsRead: 50,\n        WorkflowsWrite: 51,\n        BlockUserRead: 52,\n        BlockUserWrite: 53,\n        CodespaceUserSecretsRead: 54,\n        CodespaceUserSecretsWrite: 55,\n        EmailRead: 56,\n        EmailWrite: 57,\n        FollowersRead: 58,\n        FollowersWrite: 59,\n        GpgKeysRead: 60,\n        GpgKeysWrite: 61,\n        GistsRead: 62,\n        GistsWrite: 63,\n        GitKeysRead: 64,\n        GitKeysWrite: 65,\n        LimitsRead: 66,\n        LimitsWrite: 67,\n        PlanRead: 68,\n        PlanWrite: 69,\n        PrivateInvitesRead: 70,\n        PrivateInvitesWrite: 71,\n        ProfileRead: 72,\n        ProfileWrite: 73,\n        SigningKeysRead: 74,\n        SigningKeysWrite: 75,\n        StarringRead: 76,\n        StarringWrite: 77,\n        WatchingRead: 78,\n        WatchingWrite: 79,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: NoAccess,\n        2: ActionsRead,\n        3: ActionsWrite,\n        4: AdministrationRead,\n        5: AdministrationWrite,\n        6: CodeScanningAlertsRead,\n        7: CodeScanningAlertsWrite,\n        8: CodespacesRead,\n        9: CodespacesWrite,\n        10: CodespacesLifecycleRead,\n        11: CodespacesLifecycleWrite,\n        12: CodespacesMetadataRead,\n        13: CodespacesMetadataWrite,\n        14: CodespacesSecretsRead,\n        15: CodespacesSecretsWrite,\n        16: CommitStatusesRead,\n        17: CommitStatusesWrite,\n        18: ContentsRead,\n        19: ContentsWrite,\n        20: CustomPropertiesRead,\n        21: CustomPropertiesWrite,\n        22: DependabotAlertsRead,\n        23: DependabotAlertsWrite,\n        24: DependabotSecretsRead,\n        25: DependabotSecretsWrite,\n        26: DeploymentsRead,\n        27: DeploymentsWrite,\n        28: EnvironmentsRead,\n        29: EnvironmentsWrite,\n        30: IssuesRead,\n        31: IssuesWrite,\n        32: MergeQueuesRead,\n        33: MergeQueuesWrite,\n        34: MetadataRead,\n        35: MetadataWrite,\n        36: PagesRead,\n        37: PagesWrite,\n        38: PullRequestsRead,\n        39: PullRequestsWrite,\n        40: RepoSecurityRead,\n        41: RepoSecurityWrite,\n        42: SecretScanningRead,\n        43: SecretScanningWrite,\n        44: SecretsRead,\n        45: SecretsWrite,\n        46: VariablesRead,\n        47: VariablesWrite,\n        48: WebhooksRead,\n        49: WebhooksWrite,\n        50: WorkflowsRead,\n        51: WorkflowsWrite,\n        52: BlockUserRead,\n        53: BlockUserWrite,\n        54: CodespaceUserSecretsRead,\n        55: CodespaceUserSecretsWrite,\n        56: EmailRead,\n        57: EmailWrite,\n        58: FollowersRead,\n        59: FollowersWrite,\n        60: GpgKeysRead,\n        61: GpgKeysWrite,\n        62: GistsRead,\n        63: GistsWrite,\n        64: GitKeysRead,\n        65: GitKeysWrite,\n        66: LimitsRead,\n        67: LimitsWrite,\n        68: PlanRead,\n        69: PlanWrite,\n        70: PrivateInvitesRead,\n        71: PrivateInvitesWrite,\n        72: ProfileRead,\n        73: ProfileWrite,\n        74: SigningKeysRead,\n        75: SigningKeysWrite,\n        76: StarringRead,\n        77: StarringWrite,\n        78: WatchingRead,\n        79: WatchingWrite,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/github/finegrained/finegrained_test.go",
    "content": "package finegrained\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\tgh \"github.com/google/go-github/v67/github\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\tanalyzerCommon \"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\n\tanalyzerSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"analyzers1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"finegrained - github-allrepos-actionsRW-contentsRW-issuesRW\",\n\t\t\tkey:     analyzerSecrets.MustGetField(\"GITHUB_FINEGRAINED_ALLREPOS_ACTIONS_RW_CONTENTS_RW_ISSUES_RW\"),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcfg := &config.Config{}\n\t\t\tkey := tt.key\n\t\t\tclient := gh.NewClient(analyzers.NewAnalyzeClient(cfg)).WithAuthToken(key)\n\n\t\t\tmd, err := analyzerCommon.GetTokenMetadata(key, client)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not get token metadata: %s\", err)\n\t\t\t}\n\n\t\t\t_, err = AnalyzeFineGrainedToken(client, md, cfg.Shallow)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/github/github.go",
    "content": "package github\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/fatih/color\"\n\tgh \"github.com/google/go-github/v67/github\"\n\t\"golang.org/x/time/rate\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github/classic\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github/finegrained\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n// According to GitHub's rate limiting documentation, the default rate limit for\n// authenticated requests (PAT) is 5000 requests per hour. This equates to roughly 1.39\n// requests per second. To provide some buffer, we set the rate limit to 1.25\n// requests per second with a burst of 10.\n// https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28\nvar rateLimiter = rate.NewLimiter(rate.Limit(1.25), 10)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeGitHub }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tinfo, err := AnalyzePermissions(a.Cfg, credInfo[\"key\"])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *common.SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\tresult := &analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeGitHub,\n\t\tMetadata: map[string]any{\n\t\t\t\"owner\":      info.Metadata.User.Login,\n\t\t\t\"type\":       info.Metadata.Type,\n\t\t\t\"expiration\": info.Metadata.Expiration,\n\t\t},\n\t}\n\tresult.Bindings = append(result.Bindings, secretInfoToUserBindings(info)...)\n\tresult.Bindings = append(result.Bindings, secretInfoToRepoBindings(info)...)\n\tresult.Bindings = append(result.Bindings, secretInfoToGistBindings(info)...)\n\tfor _, repo := range append(info.Repos, info.AccessibleRepos...) {\n\t\tif repo.Owner.GetType() != \"Organization\" {\n\t\t\tcontinue\n\t\t}\n\t\tname := repo.Owner.GetName()\n\t\tif name == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tresult.UnboundedResources = append(result.UnboundedResources, analyzers.Resource{\n\t\t\tName:               name,\n\t\t\tFullyQualifiedName: fmt.Sprintf(\"github.com/%s\", name),\n\t\t\tType:               \"organization\",\n\t\t})\n\t}\n\t// TODO: Unbound resources\n\t// - Repo owners\n\t// - Gist owners\n\treturn result\n}\n\nfunc secretInfoToUserBindings(info *common.SecretInfo) []analyzers.Binding {\n\treturn analyzers.BindAllPermissions(*userToResource(info.Metadata.User), info.Metadata.OauthScopes...)\n}\n\nfunc userToResource(user *gh.User) *analyzers.Resource {\n\tname := *user.Login\n\treturn &analyzers.Resource{\n\t\tName:               name,\n\t\tFullyQualifiedName: fmt.Sprintf(\"github.com/%s\", name),\n\t\tType:               strings.ToLower(*user.Type), // \"user\" or \"organization\"\n\t}\n}\n\nfunc secretInfoToRepoBindings(info *common.SecretInfo) []analyzers.Binding {\n\tvar perms []analyzers.Permission\n\tswitch info.Metadata.Type {\n\tcase common.TokenTypeClassicPAT:\n\t\tperms = info.Metadata.OauthScopes\n\tcase common.TokenTypeFineGrainedPAT:\n\t\tfineGrainedPermissions := info.RepoAccessMap.([]finegrained.Permission)\n\t\tfor _, perm := range fineGrainedPermissions {\n\t\t\tpermName, _ := perm.ToString()\n\t\t\tperms = append(perms, analyzers.Permission{Value: permName})\n\t\t}\n\tdefault:\n\t\tif len(info.Metadata.OauthScopes) > 0 {\n\t\t\tperms = info.Metadata.OauthScopes\n\t\t}\n\t}\n\n\trepos := info.Repos\n\tif len(info.AccessibleRepos) > 0 {\n\t\trepos = info.AccessibleRepos\n\t}\n\tvar bindings []analyzers.Binding\n\tfor _, repo := range repos {\n\t\tresource := analyzers.Resource{\n\t\t\tName:               *repo.Name,\n\t\t\tFullyQualifiedName: fmt.Sprintf(\"github.com/%s\", *repo.FullName),\n\t\t\tType:               \"repository\",\n\t\t\tParent:             userToResource(repo.Owner),\n\t\t}\n\t\tbindings = append(bindings, analyzers.BindAllPermissions(resource, perms...)...)\n\t}\n\treturn bindings\n}\n\nfunc secretInfoToGistBindings(info *common.SecretInfo) []analyzers.Binding {\n\tvar bindings []analyzers.Binding\n\tfor _, gist := range info.Gists {\n\t\tresource := analyzers.Resource{\n\t\t\tName:               *gist.Description,\n\t\t\tFullyQualifiedName: fmt.Sprintf(\"gist.github.com/%s/%s\", *gist.Owner.Login, *gist.ID),\n\t\t\tType:               \"gist\",\n\t\t\tParent:             userToResource(gist.Owner),\n\t\t}\n\t\tbindings = append(bindings, analyzers.BindAllPermissions(resource, info.Metadata.OauthScopes...)...)\n\t}\n\treturn bindings\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*common.SecretInfo, error) {\n\tif cfg == nil {\n\t\tcfg = &config.Config{}\n\t}\n\tclient := gh.NewClient(analyzers.NewAnalyzeClient(cfg, analyzers.WithRateLimiter(rateLimiter))).WithAuthToken(key)\n\n\tmd, err := common.GetTokenMetadata(key, client)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif md.FineGrained {\n\t\treturn finegrained.AnalyzeFineGrainedToken(client, md, cfg.Shallow)\n\t} else {\n\t\treturn classic.AnalyzeClassicToken(client, md)\n\t}\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[x] %s\", err.Error())\n\t\treturn\n\t}\n\n\tcolor.Yellow(\"[i] Token User: %v\", *info.Metadata.User.Login)\n\tif expiry := info.Metadata.Expiration; expiry.IsZero() {\n\t\tcolor.Red(\"[i] Token Expiration: does not expire\")\n\t} else {\n\t\ttimeRemaining := time.Until(expiry)\n\t\tcolor.Yellow(\"[i] Token Expiration: %v (%s remaining)\", expiry, roughHumanReadableDuration(timeRemaining))\n\t}\n\tcolor.Yellow(\"[i] Token Type: %s\\n\\n\", info.Metadata.Type)\n\n\tif info.Metadata.FineGrained {\n\t\tfinegrained.PrintFineGrainedToken(cfg, info)\n\t\treturn\n\t}\n\tclassic.PrintClassicToken(cfg, info)\n}\n\n// roughHumanReadableDuration converts a duration into a rough estimate for\n// human consumption. The larger the duration, the larger granularity is\n// returned.\nfunc roughHumanReadableDuration(d time.Duration) string {\n\tvar gran time.Duration\n\tvar unit string\n\tswitch {\n\tcase d < 1*time.Minute:\n\t\tgran = time.Second\n\t\tunit = \"second\"\n\tcase d < 1*time.Hour:\n\t\tgran = time.Minute\n\t\tunit = \"minute\"\n\tcase d < 24*time.Hour:\n\t\tgran = time.Hour\n\t\tunit = \"hour\"\n\tcase d < 4*7*24*time.Hour:\n\t\tgran = 24 * time.Hour\n\t\tunit = \"day\"\n\tcase d < 3*4*7*24*time.Hour:\n\t\tgran = 7 * 24 * time.Hour\n\t\tunit = \"week\"\n\tcase d < 5*365*24*time.Hour:\n\t\tgran = 365 * 24 * time.Hour\n\t\tunit = \"month\"\n\tdefault:\n\t\tgran = 365 * 24 * time.Hour\n\t\tunit = \"year\"\n\t}\n\tnum := d.Round(gran) / gran\n\tif num != 1 {\n\t\tunit += \"s\"\n\t}\n\treturn fmt.Sprintf(\"%d %s\", num, unit)\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/github/github_test.go",
    "content": "package github\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tanalyzerSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"analyzers1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"finegrained - github-allrepos-actionsRW-contentsRW-issuesRW\",\n\t\t\tkey:     analyzerSecrets.MustGetField(\"GITHUB_FINEGRAINED_ALLREPOS_ACTIONS_RW_CONTENTS_RW_ISSUES_RW\"),\n\t\t\twantErr: false,\n\t\t\twant: `{\n              \"AnalyzerType\": 7,\n              \"Bindings\": [\n                {\n                  \"Resource\": {\n                    \"Name\": \"private\",\n                    \"FullyQualifiedName\": \"github.com/sirdetectsalot/private\",\n                    \"Type\": \"repository\",\n                    \"Metadata\": null,\n                    \"Parent\": {\n                      \"Name\": \"sirdetectsalot\",\n                      \"FullyQualifiedName\": \"github.com/sirdetectsalot\",\n                      \"Type\": \"user\",\n                      \"Metadata\": null,\n                      \"Parent\": null\n                    }\n                  },\n                  \"Permission\": {\n                    \"Value\": \"actions:write\",\n                    \"Parent\": null\n                  }\n                },\n                {\n                  \"Resource\": {\n                    \"Name\": \"private\",\n                    \"FullyQualifiedName\": \"github.com/sirdetectsalot/private\",\n                    \"Type\": \"repository\",\n                    \"Metadata\": null,\n                    \"Parent\": {\n                      \"Name\": \"sirdetectsalot\",\n                      \"FullyQualifiedName\": \"github.com/sirdetectsalot\",\n                      \"Type\": \"user\",\n                      \"Metadata\": null,\n                      \"Parent\": null\n                    }\n                  },\n                  \"Permission\": {\n                    \"Value\": \"contents:write\",\n                    \"Parent\": null\n                  }\n                },\n                {\n                  \"Resource\": {\n                    \"Name\": \"private\",\n                    \"FullyQualifiedName\": \"github.com/sirdetectsalot/private\",\n                    \"Type\": \"repository\",\n                    \"Metadata\": null,\n                    \"Parent\": {\n                      \"Name\": \"sirdetectsalot\",\n                      \"FullyQualifiedName\": \"github.com/sirdetectsalot\",\n                      \"Type\": \"user\",\n                      \"Metadata\": null,\n                      \"Parent\": null\n                    }\n                  },\n                  \"Permission\": {\n                    \"Value\": \"deployments:read\",\n                    \"Parent\": null\n                  }\n                },\n                {\n                  \"Resource\": {\n                    \"Name\": \"private\",\n                    \"FullyQualifiedName\": \"github.com/sirdetectsalot/private\",\n                    \"Type\": \"repository\",\n                    \"Metadata\": null,\n                    \"Parent\": {\n                      \"Name\": \"sirdetectsalot\",\n                      \"FullyQualifiedName\": \"github.com/sirdetectsalot\",\n                      \"Type\": \"user\",\n                      \"Metadata\": null,\n                      \"Parent\": null\n                    }\n                  },\n                  \"Permission\": {\n                    \"Value\": \"issues:write\",\n                    \"Parent\": null\n                  }\n                },\n                {\n                  \"Resource\": {\n                    \"Name\": \"private\",\n                    \"FullyQualifiedName\": \"github.com/sirdetectsalot/private\",\n                    \"Type\": \"repository\",\n                    \"Metadata\": null,\n                    \"Parent\": {\n                      \"Name\": \"sirdetectsalot\",\n                      \"FullyQualifiedName\": \"github.com/sirdetectsalot\",\n                      \"Type\": \"user\",\n                      \"Metadata\": null,\n                      \"Parent\": null\n                    }\n                  },\n                  \"Permission\": {\n                    \"Value\": \"metadata:read\",\n                    \"Parent\": null\n                  }\n                },\n                {\n                  \"Resource\": {\n                    \"Name\": \"public\",\n                    \"FullyQualifiedName\": \"github.com/sirdetectsalot/public\",\n                    \"Type\": \"repository\",\n                    \"Metadata\": null,\n                    \"Parent\": {\n                      \"Name\": \"sirdetectsalot\",\n                      \"FullyQualifiedName\": \"github.com/sirdetectsalot\",\n                      \"Type\": \"user\",\n                      \"Metadata\": null,\n                      \"Parent\": null\n                    }\n                  },\n                  \"Permission\": {\n                    \"Value\": \"actions:write\",\n                    \"Parent\": null\n                  }\n                },\n                {\n                  \"Resource\": {\n                    \"Name\": \"public\",\n                    \"FullyQualifiedName\": \"github.com/sirdetectsalot/public\",\n                    \"Type\": \"repository\",\n                    \"Metadata\": null,\n                    \"Parent\": {\n                      \"Name\": \"sirdetectsalot\",\n                      \"FullyQualifiedName\": \"github.com/sirdetectsalot\",\n                      \"Type\": \"user\",\n                      \"Metadata\": null,\n                      \"Parent\": null\n                    }\n                  },\n                  \"Permission\": {\n                    \"Value\": \"contents:write\",\n                    \"Parent\": null\n                  }\n                },\n                {\n                  \"Resource\": {\n                    \"Name\": \"public\",\n                    \"FullyQualifiedName\": \"github.com/sirdetectsalot/public\",\n                    \"Type\": \"repository\",\n                    \"Metadata\": null,\n                    \"Parent\": {\n                      \"Name\": \"sirdetectsalot\",\n                      \"FullyQualifiedName\": \"github.com/sirdetectsalot\",\n                      \"Type\": \"user\",\n                      \"Metadata\": null,\n                      \"Parent\": null\n                    }\n                  },\n                  \"Permission\": {\n                    \"Value\": \"deployments:read\",\n                    \"Parent\": null\n                  }\n                },\n                {\n                  \"Resource\": {\n                    \"Name\": \"public\",\n                    \"FullyQualifiedName\": \"github.com/sirdetectsalot/public\",\n                    \"Type\": \"repository\",\n                    \"Metadata\": null,\n                    \"Parent\": {\n                      \"Name\": \"sirdetectsalot\",\n                      \"FullyQualifiedName\": \"github.com/sirdetectsalot\",\n                      \"Type\": \"user\",\n                      \"Metadata\": null,\n                      \"Parent\": null\n                    }\n                  },\n                  \"Permission\": {\n                    \"Value\": \"issues:write\",\n                    \"Parent\": null\n                  }\n                },\n                {\n                  \"Resource\": {\n                    \"Name\": \"public\",\n                    \"FullyQualifiedName\": \"github.com/sirdetectsalot/public\",\n                    \"Type\": \"repository\",\n                    \"Metadata\": null,\n                    \"Parent\": {\n                      \"Name\": \"sirdetectsalot\",\n                      \"FullyQualifiedName\": \"github.com/sirdetectsalot\",\n                      \"Type\": \"user\",\n                      \"Metadata\": null,\n                      \"Parent\": null\n                    }\n                  },\n                  \"Permission\": {\n                    \"Value\": \"metadata:read\",\n                    \"Parent\": null\n                  }\n                }\n              ],\n              \"UnboundedResources\": null,\n              \"Metadata\": {\n                \"owner\": \"sirdetectsalot\",\n                \"expiration\": \"2026-03-24T15:27:38+05:00\",\n                \"type\": \"Fine-Grained GitHub Personal Access Token\"\n              }\n            }`,\n\t\t},\n\t\t{\n\t\t\tname: \"v2 ghp\",\n\t\t\tkey:  testSecrets.MustGetField(\"GITHUB_VERIFIED_GHP\"),\n\t\t\twant: `{\n              \"AnalyzerType\": 7,\n              \"Bindings\": [\n                {\n                  \"Resource\": {\n                    \"Name\": \"truffle-sandbox\",\n                    \"FullyQualifiedName\": \"github.com/truffle-sandbox\",\n                    \"Type\": \"user\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                  },\n                  \"Permission\": {\n                    \"Value\": \"notifications\",\n                    \"AccessLevel\": \"\",\n                    \"Parent\": null\n                  }\n                },\n                {\n                  \"Resource\": {\n                    \"Name\": \"public gist\",\n                    \"FullyQualifiedName\": \"gist.github.com/truffle-sandbox/fecf272c606ddbc5f8486f9c44821312\",\n                    \"Type\": \"gist\",\n                    \"Metadata\": null,\n                    \"Parent\": {\n                      \"Name\": \"truffle-sandbox\",\n                      \"FullyQualifiedName\": \"github.com/truffle-sandbox\",\n                      \"Type\": \"user\",\n                      \"Metadata\": null,\n                      \"Parent\": null\n                    }\n                  },\n                  \"Permission\": {\n                    \"Value\": \"notifications\",\n                    \"Parent\": null\n                  }\n                }\n              ],\n              \"UnboundedResources\": null,\n              \"Metadata\": {\n                \"owner\": \"truffle-sandbox\",\n                \"expiration\": \"0001-01-01T00:00:00Z\",\n                \"type\": \"Classic GitHub Personal Access Token\"\n              }\n            }`,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.MarshalIndent(got, \"\", \"  \")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Marshal the expected result to JSON with indentation\n\t\t\twantJSON, err := json.MarshalIndent(wantObj, \"\", \"  \")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings and show diff if they don't match\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\tdiff := cmp.Diff(string(wantJSON), string(gotJSON))\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() mismatch (-want +got):\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/gitlab/expected_output.json",
    "content": "{\"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}}"
  },
  {
    "path": "pkg/analyzer/analyzers/gitlab/gitlab.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go gitlab\n\npackage gitlab\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\nconst (\n\tDefaultGitLabHost = \"https://gitlab.com\"\n)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeGitLab }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credInfo[\"key\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"key not found in credentialInfo\")\n\t}\n\thost, ok := credInfo[\"host\"]\n\tif !ok {\n\t\thost = DefaultGitLabHost\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, key, host)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeGitLab,\n\t\tMetadata: map[string]any{\n\t\t\t\"enterprise\": info.Metadata.Enterprise,\n\t\t},\n\t\tBindings: []analyzers.Binding{},\n\t}\n\n\t// Add user and it's permissions to bindings\n\tuserFullyQualifiedName := fmt.Sprintf(\"gitlab.com/user/%d\", info.AccessToken.UserID)\n\tuserResource := analyzers.Resource{\n\t\tName:               userFullyQualifiedName,\n\t\tFullyQualifiedName: userFullyQualifiedName,\n\t\tType:               \"user\",\n\t\tMetadata: map[string]any{\n\t\t\t\"token_name\":       info.AccessToken.Name,\n\t\t\t\"token_id\":         info.AccessToken.ID,\n\t\t\t\"token_created_at\": info.AccessToken.CreatedAt,\n\t\t\t\"token_revoked\":    info.AccessToken.Revoked,\n\t\t\t\"token_expires_at\": info.AccessToken.ExpiresAt,\n\t\t},\n\t}\n\n\tfor _, scope := range info.AccessToken.Scopes {\n\t\tresult.Bindings = append(result.Bindings, analyzers.Binding{\n\t\t\tResource: userResource,\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: scope,\n\t\t\t},\n\t\t})\n\t}\n\n\t// append project and it's permissions to bindings\n\tfor _, project := range info.Projects {\n\t\tprojectResource := analyzers.Resource{\n\t\t\tName:               project.NameWithNamespace,\n\t\t\tFullyQualifiedName: fmt.Sprintf(\"gitlab.com/project/%d\", project.ID),\n\t\t\tType:               \"project\",\n\t\t}\n\n\t\taccessLevel, ok := access_level_map[project.Permissions.ProjectAccess.AccessLevel]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tresult.Bindings = append(result.Bindings, analyzers.Binding{\n\t\t\tResource: projectResource,\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: accessLevel,\n\t\t\t},\n\t\t})\n\t}\n\n\treturn &result\n}\n\n// consider calling /api/v4/metadata to learn about gitlab instance version and whether neterrprises is enabled\n\n// we'll call /api/v4/personal_access_tokens and then filter down to scopes.\n\ntype AccessTokenJSON struct {\n\tID         int      `json:\"id\"`\n\tName       string   `json:\"name\"`\n\tRevoked    bool     `json:\"revoked\"`\n\tCreatedAt  string   `json:\"created_at\"`\n\tScopes     []string `json:\"scopes\"`\n\tLastUsedAt string   `json:\"last_used_at\"`\n\tExpiresAt  string   `json:\"expires_at\"`\n\tUserID     int      `json:\"user_id\"`\n}\n\ntype ProjectsJSON struct {\n\tID                int    `json:\"id\"`\n\tNameWithNamespace string `json:\"name_with_namespace\"`\n\tPermissions       struct {\n\t\tProjectAccess struct {\n\t\t\tAccessLevel int `json:\"access_level\"`\n\t\t} `json:\"project_access\"`\n\t} `json:\"permissions\"`\n}\n\ntype ErrorJSON struct {\n\tError string `json:\"error\"`\n\tScope string `json:\"scope\"`\n}\n\ntype MetadataJSON struct {\n\tVersion    string `json:\"version\"`\n\tEnterprise bool   `json:\"enterprise\"`\n}\n\nfunc getPersonalAccessToken(cfg *config.Config, key, host string) (AccessTokenJSON, int, error) {\n\tvar tokens AccessTokenJSON\n\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(\"GET\", fmt.Sprintf(\"%s/api/v4/personal_access_tokens/self\", host), nil)\n\tif err != nil {\n\t\treturn tokens, -1, err\n\t}\n\n\treq.Header.Set(\"Private-Token\", key)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn tokens, resp.StatusCode, err\n\t}\n\n\tdefer resp.Body.Close()\n\tif err := json.NewDecoder(resp.Body).Decode(&tokens); err != nil {\n\t\treturn tokens, resp.StatusCode, err\n\t}\n\treturn tokens, resp.StatusCode, nil\n}\n\nfunc getAccessibleProjects(cfg *config.Config, key, host string) ([]ProjectsJSON, error) {\n\tvar projects []ProjectsJSON\n\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(\"GET\", fmt.Sprintf(\"%s/api/v4/projects\", host), nil)\n\tif err != nil {\n\t\treturn projects, err\n\t}\n\n\treq.Header.Set(\"Private-Token\", key)\n\n\t// Add query parameters\n\tq := req.URL.Query()\n\tq.Add(\"min_access_level\", \"10\")\n\treq.URL.RawQuery = q.Encode()\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn projects, err\n\t}\n\n\tdefer resp.Body.Close()\n\n\tbodyBytes, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn projects, err\n\t}\n\n\tnewBody := func() io.ReadCloser {\n\t\treturn io.NopCloser(bytes.NewReader(bodyBytes))\n\t}\n\n\tif err := json.NewDecoder(newBody()).Decode(&projects); err != nil {\n\t\tvar e ErrorJSON\n\t\tif err := json.NewDecoder(newBody()).Decode(&e); err == nil {\n\t\t\treturn projects, fmt.Errorf(\"Insufficient Scope to query for projects. We need api or read_api permissions.\")\n\t\t}\n\t\treturn projects, err\n\t}\n\treturn projects, nil\n}\n\nfunc getMetadata(cfg *config.Config, key, host string) (MetadataJSON, error) {\n\tvar metadata MetadataJSON\n\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(\"GET\", fmt.Sprintf(\"%s/api/v4/metadata\", host), nil)\n\tif err != nil {\n\t\treturn metadata, err\n\t}\n\n\treq.Header.Set(\"Private-Token\", key)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn metadata, err\n\t}\n\n\tdefer resp.Body.Close()\n\n\tbodyBytes, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn metadata, err\n\t}\n\n\tnewBody := func() io.ReadCloser {\n\t\treturn io.NopCloser(bytes.NewReader(bodyBytes))\n\t}\n\n\tif err := json.NewDecoder(newBody()).Decode(&metadata); err != nil {\n\t\treturn metadata, err\n\t}\n\n\tif metadata.Version == \"\" {\n\t\tvar e ErrorJSON\n\t\tif err := json.NewDecoder(newBody()).Decode(&e); err != nil {\n\t\t\treturn metadata, err\n\t\t}\n\t\treturn metadata, fmt.Errorf(\"Insufficient Scope to query for metadata. We need read_user, ai_features, api or read_api permissions.\")\n\t}\n\n\treturn metadata, nil\n}\n\ntype SecretInfo struct {\n\tAccessToken AccessTokenJSON\n\tMetadata    MetadataJSON\n\tProjects    []ProjectsJSON\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string, host string) (*SecretInfo, error) {\n\t// get personal_access_tokens accessible\n\ttoken, statusCode, err := getPersonalAccessToken(cfg, key, host)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif statusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"Invalid GitLab Access Token\")\n\t}\n\n\tmeta, err := getMetadata(cfg, key, host)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tprojects, err := getAccessibleProjects(cfg, key, host)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &SecretInfo{\n\t\tAccessToken: token,\n\t\tMetadata:    meta,\n\t\tProjects:    projects,\n\t}, nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key, DefaultGitLabHost)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error: %s\", err)\n\t\treturn\n\t}\n\n\t// print token info\n\tprintTokenInfo(info.AccessToken)\n\n\t// print gitlab instance metadata\n\tif info.Metadata.Version != \"\" {\n\t\tprintMetadata(info.Metadata)\n\t}\n\n\t// print token permissions\n\tprintTokenPermissions(info.AccessToken)\n\n\t// print repos accessible\n\tif len(info.Projects) > 0 {\n\t\tprintProjects(info.Projects)\n\t}\n}\n\nfunc getRemainingTime(t string) string {\n\ttargetTime, err := time.Parse(\"2006-01-02\", t)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\n\t// Get the current time\n\tcurrentTime := time.Now()\n\n\t// Calculate the duration until the target time\n\tdurationUntilTarget := targetTime.Sub(currentTime)\n\tdurationUntilTarget = durationUntilTarget.Truncate(time.Minute)\n\n\t// Print the duration\n\treturn fmt.Sprintf(\"%v\", durationUntilTarget)\n}\n\nfunc printTokenInfo(token AccessTokenJSON) {\n\tcolor.Green(\"[!] Valid GitLab Access Token\\n\\n\")\n\tcolor.Green(\"Token Name: %s\\n\", token.Name)\n\tcolor.Green(\"Created At: %s\\n\", token.CreatedAt)\n\tcolor.Green(\"Last Used At: %s\\n\", token.LastUsedAt)\n\tcolor.Green(\"User ID: %d\\n\", token.UserID)\n\tcolor.Green(\"Expires At: %s  (%v remaining)\\n\\n\", token.ExpiresAt, getRemainingTime(token.ExpiresAt))\n\tif token.Revoked {\n\t\tcolor.Red(\"Token Revoked: %v\\n\", token.Revoked)\n\t}\n}\n\nfunc printMetadata(metadata MetadataJSON) {\n\tcolor.Green(\"[i] GitLab Instance Metadata\\n\")\n\tcolor.Green(\"Version: %s\\n\", metadata.Version)\n\tcolor.Green(\"Enterprise: %v\\n\\n\", metadata.Enterprise)\n}\n\nfunc printTokenPermissions(token AccessTokenJSON) {\n\tcolor.Green(\"[i] Token Permissions\\n\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Scope\", \"Access\" /* Add more column headers if needed */})\n\tfor _, scope := range token.Scopes {\n\t\tt.AppendRow([]any{color.GreenString(scope), color.GreenString(gitlab_scopes[scope])})\n\t}\n\tt.SetColumnConfigs([]table.ColumnConfig{\n\t\t{Number: 2, WidthMax: 100}, // Limit the width of the third column (Description) to 20 characters\n\t})\n\tt.Render()\n}\n\nfunc printProjects(projects []ProjectsJSON) {\n\tcolor.Green(\"\\n[i] Accessible Projects\\n\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Project\", \"Access Level\" /* Add more column headers if needed */})\n\tfor _, project := range projects {\n\t\taccess := access_level_map[project.Permissions.ProjectAccess.AccessLevel]\n\t\tif project.Permissions.ProjectAccess.AccessLevel == 50 {\n\t\t\taccess = color.GreenString(access)\n\t\t} else if project.Permissions.ProjectAccess.AccessLevel >= 30 {\n\t\t\taccess = color.YellowString(access)\n\t\t} else {\n\t\t\taccess = color.RedString(access)\n\t\t}\n\t\tt.AppendRow([]any{color.GreenString(project.NameWithNamespace), access})\n\t}\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/gitlab/gitlab_test.go",
    "content": "package gitlab\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid gitlab access token\",\n\t\t\tkey:     testSecrets.MustGetField(\"GITLABV2\"),\n\t\t\twant:    string(expectedOutput),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = \\n%s\", gotIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/gitlab/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage gitlab\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    Api Permission = iota\n    ReadUser Permission = iota\n    ReadApi Permission = iota\n    ReadRepository Permission = iota\n    WriteRepository Permission = iota\n    ReadRegistry Permission = iota\n    WriteRegistry Permission = iota\n    Sudo Permission = iota\n    AdminMode Permission = iota\n    CreateRunner Permission = iota\n    ManageRunner Permission = iota\n    AiFeatures Permission = iota\n    K8sProxy Permission = iota\n    ReadServicePing Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        Api: \"api\",\n        ReadUser: \"read_user\",\n        ReadApi: \"read_api\",\n        ReadRepository: \"read_repository\",\n        WriteRepository: \"write_repository\",\n        ReadRegistry: \"read_registry\",\n        WriteRegistry: \"write_registry\",\n        Sudo: \"sudo\",\n        AdminMode: \"admin_mode\",\n        CreateRunner: \"create_runner\",\n        ManageRunner: \"manage_runner\",\n        AiFeatures: \"ai_features\",\n        K8sProxy: \"k8s_proxy\",\n        ReadServicePing: \"read_service_ping\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"api\": Api,\n        \"read_user\": ReadUser,\n        \"read_api\": ReadApi,\n        \"read_repository\": ReadRepository,\n        \"write_repository\": WriteRepository,\n        \"read_registry\": ReadRegistry,\n        \"write_registry\": WriteRegistry,\n        \"sudo\": Sudo,\n        \"admin_mode\": AdminMode,\n        \"create_runner\": CreateRunner,\n        \"manage_runner\": ManageRunner,\n        \"ai_features\": AiFeatures,\n        \"k8s_proxy\": K8sProxy,\n        \"read_service_ping\": ReadServicePing,\n    }\n\n    PermissionIDs = map[Permission]int{\n        Api: 1,\n        ReadUser: 2,\n        ReadApi: 3,\n        ReadRepository: 4,\n        WriteRepository: 5,\n        ReadRegistry: 6,\n        WriteRegistry: 7,\n        Sudo: 8,\n        AdminMode: 9,\n        CreateRunner: 10,\n        ManageRunner: 11,\n        AiFeatures: 12,\n        K8sProxy: 13,\n        ReadServicePing: 14,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: Api,\n        2: ReadUser,\n        3: ReadApi,\n        4: ReadRepository,\n        5: WriteRepository,\n        6: ReadRegistry,\n        7: WriteRegistry,\n        8: Sudo,\n        9: AdminMode,\n        10: CreateRunner,\n        11: ManageRunner,\n        12: AiFeatures,\n        13: K8sProxy,\n        14: ReadServicePing,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/gitlab/permissions.yaml",
    "content": "permissions:\n  - api\n  - read_user\n  - read_api\n  - read_repository\n  - write_repository\n  - read_registry\n  - write_registry\n  - sudo\n  - admin_mode\n  - create_runner\n  - manage_runner\n  - ai_features\n  - k8s_proxy\n  - read_service_ping\n"
  },
  {
    "path": "pkg/analyzer/analyzers/gitlab/scopes.go",
    "content": "package gitlab\n\nvar gitlab_scopes = map[string]string{\n\t\"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.\",\n\t\"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.\",\n\t\"read_api\":          \"Grants read access to the API, including all groups and projects, the container registry, and the package registry.\",\n\t\"read_repository\":   \"Grants read-only access to repositories on private projects using Git-over-HTTP or the Repository Files API.\",\n\t\"write_repository\":  \"Grants read-write access to repositories on private projects using Git-over-HTTP (not using the API).\",\n\t\"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.\",\n\t\"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.\",\n\t\"sudo\":              \"Grants permission to perform API actions as any user in the system, when authenticated as an administrator.\",\n\t\"admin_mode\":        \"Grants permission to perform API actions as an administrator, when Admin Mode is enabled. (Introduced in GitLab 15.8.)\",\n\t\"create_runner\":     \"Grants permission to create runners.\",\n\t\"manage_runner\":     \"Grants permission to manage runners.\",\n\t\"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.\",\n\t\"k8s_proxy\":         \"Grants permission to perform Kubernetes API calls using the agent for Kubernetes.\",\n\t\"read_service_ping\": \"Grant access to download Service Ping payload through the API when authenticated as an admin use. (Introduced in GitLab 16.8.\",\n}\n\nvar access_level_map = map[int]string{\n\t0:  \"No access\",\n\t5:  \"Minimal access\",\n\t10: \"Guest\",\n\t20: \"Reporter\",\n\t30: \"Developer\",\n\t40: \"Maintainer\",\n\t50: \"Owner\",\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/groq/groq.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go groq\npackage groq\n\nimport (\n\t\"errors\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\n// SecretInfo hold the information about the groq key\ntype SecretInfo struct {\n\tValid         bool\n\tReference     string\n\tGroqResources []GroqResource\n\tPermissions   []string\n\tMisc          map[string]string\n}\n\n// GroqResource is a single groq resource which can be accessed with groq api key\ntype GroqResource struct {\n\tID         string\n\tName       string\n\tType       string\n\tPermission string\n\tMetadata   map[string]string\n}\n\n// appendGroqResource append the single groq resource to secretinfo groqresources list\nfunc (s *SecretInfo) appendGroqResource(resource GroqResource) {\n\ts.GroqResources = append(s.GroqResources, resource)\n}\n\n// updateMetadata safely update the metadata of the groq resource\nfunc (g GroqResource) updateMetadata(key, value string) {\n\tif g.Metadata == nil {\n\t\tg.Metadata = map[string]string{}\n\t}\n\n\tg.Metadata[key] = value\n}\n\nfunc (a Analyzer) Type() analyzers.AnalyzerType {\n\treturn analyzers.AnalyzerTypeGroq\n}\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, exist := credInfo[\"key\"]\n\tif !exist {\n\t\treturn nil, errors.New(\"key not found in credentials info\")\n\t}\n\n\tsecretInfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfoToAnalyzerResult(secretInfo), nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\t// just print the error in cli and continue as a partial success\n\t\tcolor.Red(\"[x] Invalid Groq API key\\n\")\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t\treturn\n\t}\n\n\tif info == nil {\n\t\tcolor.Red(\"[x] Error : %s\", \"No information found\")\n\t\treturn\n\t}\n\n\tcolor.Green(\"[i] Valid Groq API key\\n\")\n\tcolor.Yellow(\"\\n[i] Permission: Full Access\\n\")\n\n\tif len(info.GroqResources) > 0 {\n\t\tprintGroqResources(info.GroqResources)\n\t}\n\n\tcolor.Yellow(\"\\n[!] Expires: Never\")\n}\n\nfunc AnalyzePermissions(cfg *config.Config, apiKey string) (*SecretInfo, error) {\n\t// create a HTTP client\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\tvar secretInfo = &SecretInfo{Valid: true}\n\n\tif err := captureBatches(client, apiKey, secretInfo); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := captureFiles(client, apiKey, secretInfo); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfo, nil\n}\n\n// secretInfoToAnalyzerResult translate secret info to Analyzer Result\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeGroq,\n\t\tMetadata:     map[string]any{\"Valid_Key\": info.Valid},\n\t\tBindings:     make([]analyzers.Binding, len(info.GroqResources)),\n\t}\n\n\t// extract information to create bindings and append to result bindings\n\tfor _, groqResource := range info.GroqResources {\n\t\tbinding := analyzers.Binding{\n\t\t\tResource: analyzers.Resource{\n\t\t\t\tName:               groqResource.Name,\n\t\t\t\tFullyQualifiedName: groqResource.ID,\n\t\t\t\tType:               groqResource.Type,\n\t\t\t\tMetadata:           map[string]any{},\n\t\t\t},\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: groqResource.Permission,\n\t\t\t},\n\t\t}\n\n\t\tfor key, value := range groqResource.Metadata {\n\t\t\tbinding.Resource.Metadata[key] = value\n\t\t}\n\n\t\tresult.Bindings = append(result.Bindings, binding)\n\t}\n\n\treturn &result\n}\n\nfunc printGroqResources(resources []GroqResource) {\n\tcolor.Green(\"\\n[i] Resources:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Name\", \"Type\"})\n\tfor _, resource := range resources {\n\t\tt.AppendRow(table.Row{color.GreenString(resource.Name), color.GreenString(resource.Type)})\n\t}\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/groq/groq_test.go",
    "content": "package groq\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tapiKey := testSecrets.MustGetField(\"GROQ\")\n\n\ttests := []struct {\n\t\tname    string\n\t\tapiKey  string\n\t\twant    string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid dockerhub credentials\",\n\t\t\tapiKey:  apiKey,\n\t\t\twant:    `{\"AnalyzerType\":2,\"Bindings\":[],\"UnboundedResources\":null,\"Metadata\":{\"Valid_Key\":true}}`,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.apiKey})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\tfmt.Println(string(gotJSON))\n\n\t\t\t// compare the JSON strings\n\t\t\tif string(gotJSON) != string(tt.want) {\n\t\t\t\t// pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(tt.want, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/groq/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage groq\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    FullAccess Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        FullAccess: \"full_access\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"full_access\": FullAccess,\n    }\n\n    PermissionIDs = map[Permission]int{\n        FullAccess: 1,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: FullAccess,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/groq/permissions.yaml",
    "content": "permissions:\n  - full_access # by default groq api key has full access\n"
  },
  {
    "path": "pkg/analyzer/analyzers/groq/requests.go",
    "content": "package groq\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"time\"\n)\n\nvar (\n\tpermissionErr       = \"permissions_error\"\n\tnotAvailableForPlan = \"not_available_for_plan\"\n)\n\n// errorResponse is the response from groq APIs in case of any error\ntype errorResponse struct {\n\tError struct {\n\t\tMessage string `json:\"message\"`\n\t\tType    string `json:\"type\"`\n\t\tCode    string `json:\"code\"`\n\t} `json:\"error\"`\n}\n\n// listBatchesResponse is the response of /v1/batches API\ntype listBatchesResponse struct {\n\tData []batch `json:\"data\"`\n}\n\n// batch represent a single batch inside batches list\ntype batch struct {\n\tID          string `json:\"id\"`\n\tObject      string `json:\"object\"`\n\tEndpoint    string `json:\"endpoint\"`\n\tInputFileID string `json:\"input_file_id\"`\n\tStatus      string `json:\"status\"`\n\tExpiresAt   int64  `json:\"expires_at\"`\n}\n\n// listBatchesResponse is the response of /v1/files API\ntype listFilesResponse struct {\n\tData []file `json:\"data\"`\n}\n\n// file represents a single file object inside files list\ntype file struct {\n\tID        string `json:\"id\"`\n\tObject    string `json:\"object\"`\n\tCreatedAt int64  `json:\"created_at\"`\n\tFilename  string `json:\"filename\"`\n\tPurpose   string `json:\"purpose\"`\n}\n\nfunc isPermissionError(err errorResponse) bool {\n\t// has permissions error or not available for the plan subscribed\n\tif err.Error.Type == permissionErr && err.Error.Code == notAvailableForPlan {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\n// makeGroqRequest send the API request to passed url with passed key as API Key and return response body and status code\nfunc makeGroqRequest(client *http.Client, url, key string) ([]byte, int, error) {\n\t// create request\n\treq, err := http.NewRequest(http.MethodGet, url, http.NoBody)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\t// add required keys in the header\n\treq.Header.Set(\"Authorization\", \"Bearer \"+key)\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tresponseBodyByte, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn responseBodyByte, resp.StatusCode, nil\n}\n\n// docs: https://console.groq.com/docs/api-reference#batches-list\nfunc captureBatches(client *http.Client, key string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeGroqRequest(client, \"https://api.groq.com/openai/v1/batches\", key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar batches listBatchesResponse\n\n\t\tif err := json.Unmarshal(response, &batches); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, batch := range batches.Data {\n\t\t\tresource := GroqResource{\n\t\t\t\tID:         batch.ID,\n\t\t\t\tName:       batch.ID, // no specific name for batch\n\t\t\t\tType:       batch.Object,\n\t\t\t\tPermission: PermissionStrings[FullAccess],\n\t\t\t}\n\n\t\t\tresource.updateMetadata(\"status\", batch.Status)\n\t\t\tresource.updateMetadata(\"endpoint\", batch.Endpoint)\n\t\t\tresource.updateMetadata(\"input file id\", batch.InputFileID)\n\t\t\tresource.updateMetadata(\"expires at\", time.Unix(batch.ExpiresAt, 0).UTC().Format(\"2006-01-02 15:04:05 UTC\"))\n\n\t\t\tsecretInfo.appendGroqResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusForbidden:\n\t\tvar errResp errorResponse\n\n\t\tif err := json.Unmarshal(response, &errResp); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif isPermissionError(errResp) {\n\t\t\treturn nil\n\t\t}\n\n\t\treturn fmt.Errorf(\"unexpected error: %s\", errResp.Error.Message)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// docs: https://console.groq.com/docs/api-reference#files-list\nfunc captureFiles(client *http.Client, key string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeGroqRequest(client, \"https://api.groq.com/openai/v1/files\", key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar files listFilesResponse\n\n\t\tif err := json.Unmarshal(response, &files); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, file := range files.Data {\n\t\t\tresource := GroqResource{\n\t\t\t\tID:         file.ID,\n\t\t\t\tName:       file.Filename,\n\t\t\t\tType:       file.Object,\n\t\t\t\tPermission: PermissionStrings[FullAccess],\n\t\t\t}\n\n\t\t\tresource.updateMetadata(\"purpose\", file.Purpose)\n\t\t\tresource.updateMetadata(\"created at\", time.Unix(file.CreatedAt, 0).UTC().Format(\"2006-01-02 15:04:05 UTC\"))\n\n\t\t\tsecretInfo.appendGroqResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusForbidden:\n\t\tvar errResp errorResponse\n\n\t\tif err := json.Unmarshal(response, &errResp); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif isPermissionError(errResp) {\n\t\t\treturn nil\n\t\t}\n\n\t\treturn fmt.Errorf(\"unexpected error: %s\", errResp.Error.Message)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/huggingface/huggingface.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go huggingface\n\npackage huggingface\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nconst (\n\tFINEGRAINED = \"fineGrained\"\n\tWRITE       = \"write\"\n\tREAD        = \"read\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeHuggingFace }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credInfo[\"key\"]\n\tif !ok || key == \"\" {\n\t\treturn nil, fmt.Errorf(\"key not found in credentialInfo\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc bakeUnboundedResources(tokenJSON HFTokenJSON) []analyzers.Resource {\n\tunboundedResources := make([]analyzers.Resource, len(tokenJSON.Orgs))\n\tfor idx, org := range tokenJSON.Orgs {\n\t\tunboundedResources[idx] = analyzers.Resource{\n\t\t\tName:               org.Name,\n\t\t\tFullyQualifiedName: \"huggingface.com/user/\" + tokenJSON.Username + \"/organization/\" + org.Name,\n\t\t\tType:               \"organization\",\n\t\t\tMetadata: map[string]interface{}{\n\t\t\t\t\"role\":          org.Role,\n\t\t\t\t\"is_enterprise\": org.IsEnterprise,\n\t\t\t},\n\t\t}\n\t}\n\treturn unboundedResources\n}\n\nfunc bakeUnfineGrainedBindings(allModels []Model, tokenJSON HFTokenJSON) []analyzers.Binding {\n\tbindings := make([]analyzers.Binding, len(allModels))\n\n\tfor idx, model := range allModels {\n\t\t// Add Read Privs to All Models\n\t\tmodelResource := analyzers.Resource{\n\t\t\tName:               model.Name,\n\t\t\tFullyQualifiedName: \"huggingface.com/model/\" + model.ID,\n\t\t\tType:               \"model\",\n\t\t\tMetadata: map[string]interface{}{\n\t\t\t\t\"private\": model.Private,\n\t\t\t},\n\t\t}\n\n\t\t// means both read & write permission for the model\n\t\taccessLevel := string(analyzers.READ)\n\t\tif tokenJSON.Auth.AccessToken.Type == WRITE {\n\t\t\taccessLevel = string(analyzers.WRITE)\n\t\t}\n\t\tbindings[idx] = analyzers.Binding{\n\t\t\tResource: modelResource,\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: string(accessLevel),\n\t\t\t},\n\t\t}\n\t}\n\treturn bindings\n}\n\n// finegrained scopes are grouped by org, user or model.\nfunc bakefineGrainedBindings(allModels []Model, tokenJSON HFTokenJSON) []analyzers.Binding {\n\t// this section will extract the relevant permissions for each entity and store them in a map\n\tvar nameToPermissions = make(map[string]analyzers.Permission)\n\tfor _, permission := range tokenJSON.Auth.AccessToken.FineGrained.Scoped {\n\t\tprivs := analyzers.Permission{\n\t\t\tValue: string(analyzers.NONE),\n\t\t}\n\t\tfor _, perm := range permission.Permissions {\n\t\t\tif perm == \"repo.content.read\" {\n\t\t\t\tprivs.Value = string(analyzers.READ)\n\t\t\t} else if perm == \"repo.write\" {\n\t\t\t\tprivs.Value = string(analyzers.WRITE)\n\t\t\t}\n\t\t}\n\t\tif permission.Entity.Type == \"user\" || permission.Entity.Type == \"org\" {\n\t\t\tnameToPermissions[permission.Entity.Name] = privs\n\t\t} else if permission.Entity.Type == \"model\" {\n\t\t\tnameToPermissions[modelNameLookup(allModels, permission.Entity.ID)] = privs\n\t\t}\n\t}\n\n\tbindings := make([]analyzers.Binding, len(allModels))\n\tfor idx, model := range allModels {\n\n\t\t// Add Read Privs to All Models\n\t\tmodelResource := analyzers.Resource{\n\t\t\tName:               model.Name,\n\t\t\tFullyQualifiedName: \"huggingface.com/model/\" + model.ID,\n\t\t\tType:               \"model\",\n\t\t\tMetadata: map[string]interface{}{\n\t\t\t\t\"private\": model.Private,\n\t\t\t},\n\t\t}\n\n\t\tvar perm analyzers.Permission\n\t\t// get username/orgname for each model and apply those permissions\n\t\tmodelUsername := strings.Split(model.Name, \"/\")[0]\n\t\tif permissions, ok := nameToPermissions[modelUsername]; ok {\n\t\t\tperm = permissions\n\t\t}\n\t\t// override model permissions with repo-specific permissions\n\t\tif permissions, ok := nameToPermissions[model.Name]; ok {\n\t\t\tperm = permissions\n\t\t}\n\n\t\tbindings[idx] = analyzers.Binding{\n\t\t\tResource:   modelResource,\n\t\t\tPermission: perm,\n\t\t}\n\t}\n\treturn bindings\n}\n\nfunc bakeOrganizationBindings(tokenJSON HFTokenJSON) []analyzers.Binding {\n\t// check if there are any org permissions\n\t// if so, save them as a map. Only need to do this once\n\t// even if multiple orgs b/c as of 6/6/24, users can only define one set of scopes\n\t// for all orgs referenced on an access token\n\torgPermissions := map[string]struct{}{}\n\tvar orgResource *analyzers.Resource = nil\n\tfor _, permission := range tokenJSON.Auth.AccessToken.FineGrained.Scoped {\n\t\tif permission.Entity.Type == \"org\" {\n\t\t\torgResource = &analyzers.Resource{\n\t\t\t\tName:               permission.Entity.Name,\n\t\t\t\tFullyQualifiedName: \"hugggingface.com/organization/\" + permission.Entity.ID,\n\t\t\t\tType:               \"organization\",\n\t\t\t}\n\t\t\tfor _, perm := range permission.Permissions {\n\t\t\t\torgPermissions[perm] = struct{}{}\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\tbindings := make([]analyzers.Binding, 0)\n\t// check if there are any org permissions\n\tif orgResource == nil {\n\t\treturn bindings\n\t}\n\n\tfor _, permission := range org_scopes_order {\n\t\tfor key, value := range org_scopes[permission] {\n\t\t\tif _, ok := orgPermissions[key]; ok {\n\t\t\t\tbindings = append(bindings, analyzers.Binding{\n\t\t\t\t\tResource: *orgResource,\n\t\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\t\tValue: value,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\treturn bindings\n}\n\nfunc bakeUserBindings(tokenJSON HFTokenJSON) []analyzers.Binding {\n\tbindings := make([]analyzers.Binding, 0)\n\t// build a map of all user permissions\n\tusers := map[string]struct{}{}\n\tuserPermissions := map[string]struct{}{}\n\tfor _, permission := range tokenJSON.Auth.AccessToken.FineGrained.Scoped {\n\t\tif permission.Entity.Type == \"user\" {\n\t\t\tusers[permission.Entity.Name] = struct{}{}\n\t\t\tfor _, perm := range permission.Permissions {\n\t\t\t\tuserPermissions[perm] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\n\t// global permissions only apply to user tokens as of 6/6/24\n\t// but there would be a naming collision in the scopes document\n\t// so we prepend \"global.\" to the key and then add to the map\n\tfor _, permission := range tokenJSON.Auth.AccessToken.FineGrained.Global {\n\t\tuserPermissions[\"global.\"+permission] = struct{}{}\n\t}\n\n\t// check if there are any user permissions\n\tif len(userPermissions) == 0 {\n\t\treturn bindings\n\t}\n\n\tuserResource := analyzers.Resource{\n\t\tName:               tokenJSON.Name,\n\t\tFullyQualifiedName: \"huggingface.com/user/\" + tokenJSON.Username,\n\t\tType:               \"user\",\n\t}\n\tfor _, permission := range user_scopes_order {\n\t\tfor key, value := range user_scopes[permission] {\n\t\t\tif _, ok := userPermissions[key]; ok {\n\t\t\t\tbindings = append(bindings, analyzers.Binding{\n\t\t\t\t\tResource: userResource,\n\t\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\t\tValue: value,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\treturn bindings\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeHuggingFace,\n\t\tMetadata: map[string]interface{}{\n\t\t\t\"username\":   info.Token.Username,\n\t\t\t\"name\":       info.Token.Name,\n\t\t\t\"token_name\": info.Token.Auth.AccessToken.Name,\n\t\t\t\"token_type\": info.Token.Auth.AccessToken.Type,\n\t\t},\n\t}\n\n\tif len(info.Token.Orgs) > 0 {\n\t\tresult.UnboundedResources = bakeUnboundedResources(info.Token)\n\t}\n\n\tresult.Bindings = make([]analyzers.Binding, 0)\n\n\tif info.Token.Auth.AccessToken.Type == FINEGRAINED {\n\t\tresult.Bindings = append(result.Bindings, bakefineGrainedBindings(info.Models, info.Token)...)\n\t\tresult.Bindings = append(result.Bindings, bakeOrganizationBindings(info.Token)...)\n\t\tresult.Bindings = append(result.Bindings, bakeUserBindings(info.Token)...)\n\t} else {\n\t\tresult.Bindings = append(result.Bindings, bakeUnfineGrainedBindings(info.Models, info.Token)...)\n\t}\n\n\treturn &result\n}\n\n// HFTokenJSON is the struct for the HF /whoami-v2 API JSON response\ntype HFTokenJSON struct {\n\tUsername string `json:\"name\"`\n\tName     string `json:\"fullname\"`\n\tOrgs     []struct {\n\t\tName         string `json:\"name\"`\n\t\tRole         string `json:\"roleInOrg\"`\n\t\tIsEnterprise bool   `json:\"isEnterprise\"`\n\t} `json:\"orgs\"`\n\tAuth struct {\n\t\tAccessToken struct {\n\t\t\tName        string `json:\"displayName\"`\n\t\t\tType        string `json:\"role\"`\n\t\t\tCreatedAt   string `json:\"createdAt\"`\n\t\t\tFineGrained struct {\n\t\t\t\tGlobal []string `json:\"global\"`\n\t\t\t\tScoped []struct {\n\t\t\t\t\tEntity struct {\n\t\t\t\t\t\tType string `json:\"type\"`\n\t\t\t\t\t\tName string `json:\"name\"`\n\t\t\t\t\t\tID   string `json:\"_id\"`\n\t\t\t\t\t} `json:\"entity\"`\n\t\t\t\t\tPermissions []string `json:\"permissions\"`\n\t\t\t\t} `json:\"scoped\"`\n\t\t\t} `json:\"fineGrained\"`\n\t\t}\n\t} `json:\"auth\"`\n}\n\ntype Permissions struct {\n\tRead  bool\n\tWrite bool\n}\n\ntype Model struct {\n\tName        string `json:\"id\"`\n\tID          string `json:\"_id\"`\n\tPrivate     bool   `json:\"private\"`\n\tPermissions Permissions\n}\n\n// getModelsByAuthor calls the HF API /models endpoint with the author query param\n// returns a list of models and an error\nfunc getModelsByAuthor(cfg *config.Config, key string, author string) ([]Model, error) {\n\tvar modelsJSON []Model\n\n\t// create a new request\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(\"GET\", \"https://huggingface.co/api/models\", nil)\n\tif err != nil {\n\t\treturn modelsJSON, err\n\t}\n\n\t// Add bearer token\n\treq.Header.Add(\"Authorization\", \"Bearer \"+key)\n\n\t// Add author param\n\tq := req.URL.Query()\n\tq.Add(\"author\", author)\n\treq.URL.RawQuery = q.Encode()\n\n\t// send the request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn modelsJSON, err\n\t}\n\n\t// defer the response body closing\n\tdefer resp.Body.Close()\n\n\t// read response\n\tif err := json.NewDecoder(resp.Body).Decode(&modelsJSON); err != nil {\n\t\treturn modelsJSON, err\n\t}\n\treturn modelsJSON, nil\n}\n\n// getTokenInfo calls the HF API /whoami-v2 endpoint to get the token info\n// returns the token info, a boolean indicating token validity, and an error\nfunc getTokenInfo(cfg *config.Config, key string) (HFTokenJSON, bool, error) {\n\tvar tokenJSON HFTokenJSON\n\n\t// create a new request\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(\"GET\", \"https://huggingface.co/api/whoami-v2\", nil)\n\tif err != nil {\n\t\treturn tokenJSON, false, err\n\t}\n\n\t// Add bearer token\n\treq.Header.Add(\"Authorization\", \"Bearer \"+key)\n\n\t// send the request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn tokenJSON, false, err\n\t}\n\n\t// check if the response is 200\n\tif resp.StatusCode != 200 {\n\t\treturn tokenJSON, false, nil\n\t}\n\n\t// defer the response body closing\n\tdefer resp.Body.Close()\n\n\t// read response\n\tif err := json.NewDecoder(resp.Body).Decode(&tokenJSON); err != nil {\n\t\treturn tokenJSON, true, err\n\t}\n\treturn tokenJSON, true, nil\n}\n\ntype SecretInfo struct {\n\tToken  HFTokenJSON\n\tModels []Model\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\t// get token info\n\ttoken, success, err := getTokenInfo(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !success {\n\t\treturn nil, fmt.Errorf(\"Invalid HuggingFace Access Token\")\n\t}\n\n\t// get all models by username\n\tvar allModels []Model\n\tuserModels, err := getModelsByAuthor(cfg, key, token.Username)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tallModels = append(allModels, userModels...)\n\n\t// get all models from all orgs\n\tfor _, org := range token.Orgs {\n\t\torgModels, err := getModelsByAuthor(cfg, key, org.Name)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tallModels = append(allModels, orgModels...)\n\t}\n\n\treturn &SecretInfo{\n\t\tToken:  token,\n\t\tModels: allModels,\n\t}, nil\n}\n\n// AnalyzeAndPrintPermissions prints the permissions of a HuggingFace API key\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error: %s\", err.Error())\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid HuggingFace Access Token\\n\\n\")\n\n\t// print user info\n\tcolor.Yellow(\"[i] Username: \" + info.Token.Username)\n\tcolor.Yellow(\"[i] Name: \" + info.Token.Name)\n\tcolor.Yellow(\"[i] Token Name: \" + info.Token.Auth.AccessToken.Name)\n\tcolor.Yellow(\"[i] Token Type: \" + info.Token.Auth.AccessToken.Type)\n\n\t// print org info\n\tprintOrgs(info.Token)\n\n\t// print accessible models\n\tprintAccessibleModels(info.Models, info.Token)\n\n\tif info.Token.Auth.AccessToken.Type == FINEGRAINED {\n\t\t// print org permissions\n\t\tprintOrgPermissions(info.Token)\n\n\t\t// print user permissions\n\t\tprintUserPermissions(info.Token)\n\t}\n}\n\n// printUserPermissions prints the user permissions\n// only applies to fine-grained tokens\nfunc printUserPermissions(tokenJSON HFTokenJSON) {\n\tcolor.Green(\"\\n[i] User Permissions:\")\n\n\t// build a map of all user permissions\n\tuserPermissions := map[string]struct{}{}\n\tfor _, permission := range tokenJSON.Auth.AccessToken.FineGrained.Scoped {\n\t\tif permission.Entity.Type == \"user\" {\n\t\t\tfor _, perm := range permission.Permissions {\n\t\t\t\tuserPermissions[perm] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\n\t// global permissions only apply to user tokens as of 6/6/24\n\t// but there would be a naming collision in the scopes document\n\t// so we prepend \"global.\" to the key and then add to the map\n\tfor _, permission := range tokenJSON.Auth.AccessToken.FineGrained.Global {\n\t\tuserPermissions[\"global.\"+permission] = struct{}{}\n\t}\n\n\t// check if there are any user permissions\n\tif len(userPermissions) == 0 {\n\t\tcolor.Red(\"\\tNo user permissions scoped.\")\n\t\treturn\n\t}\n\n\t// print the user permissions\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Category\", \"Permission\", \"In-Scope\"})\n\n\tfor _, permission := range user_scopes_order {\n\t\tt.AppendRow([]interface{}{permission, \"---\", \"---\"})\n\t\tfor key, value := range user_scopes[permission] {\n\t\t\tif _, ok := userPermissions[key]; ok {\n\t\t\t\tt.AppendRow([]interface{}{\"\", color.GreenString(value), color.GreenString(\"True\")})\n\t\t\t} else {\n\t\t\t\tt.AppendRow([]interface{}{\"\", value, \"False\"})\n\t\t\t}\n\t\t}\n\t}\n\tt.Render()\n}\n\n// printOrgPermissions prints the organization permissions\n// only applies to fine-grained tokens\nfunc printOrgPermissions(tokenJSON HFTokenJSON) {\n\tcolor.Green(\"\\n[i] Organization Permissions:\")\n\n\t// check if there are any org permissions\n\t// if so, save them as a map. Only need to do this once\n\t// even if multiple orgs b/c as of 6/6/24, users can only define one set of scopes\n\t// for all orgs referenced on an access token\n\torgScoped := false\n\torgPermissions := map[string]struct{}{}\n\tfor _, permission := range tokenJSON.Auth.AccessToken.FineGrained.Scoped {\n\t\tif permission.Entity.Type == \"org\" {\n\t\t\torgScoped = true\n\t\t\tfor _, perm := range permission.Permissions {\n\t\t\t\torgPermissions[perm] = struct{}{}\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// check if there are any org permissions\n\tif !orgScoped {\n\t\tcolor.Red(\"\\tNo organization permissions scoped.\")\n\t\treturn\n\t}\n\n\t// print the org permissions\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Category\", \"Permission\", \"In-Scope\"})\n\n\tfor _, permission := range org_scopes_order {\n\t\tt.AppendRow([]interface{}{permission, \"---\", \"---\"})\n\t\tfor key, value := range org_scopes[permission] {\n\t\t\tif _, ok := orgPermissions[key]; ok {\n\t\t\t\tt.AppendRow([]interface{}{\"\", color.GreenString(value), color.GreenString(\"True\")})\n\t\t\t} else {\n\t\t\t\tt.AppendRow([]interface{}{\"\", value, \"False\"})\n\t\t\t}\n\t\t}\n\t}\n\tt.Render()\n}\n\n// printOrgs prints the organizations the user is a member of\nfunc printOrgs(tokenJSON HFTokenJSON) {\n\tcolor.Green(\"\\n[i] Organizations:\")\n\n\tif len(tokenJSON.Orgs) == 0 {\n\t\tcolor.Yellow(\"\\tNo organizations found.\")\n\t\treturn\n\t}\n\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Name\", \"Role\", \"Is Enterprise\"})\n\tfor _, org := range tokenJSON.Orgs {\n\t\tenterprise := \"\"\n\t\trole := \"\"\n\t\tif org.IsEnterprise {\n\t\t\tenterprise = color.New(color.FgGreen).Sprint(\"True\")\n\t\t} else {\n\t\t\tenterprise = \"False\"\n\t\t}\n\t\tif org.Role == \"admin\" {\n\t\t\trole = color.New(color.FgGreen).Sprint(\"Admin\")\n\t\t} else {\n\t\t\trole = org.Role\n\t\t}\n\t\tt.AppendRow([]interface{}{color.GreenString(org.Name), role, enterprise})\n\t}\n\tt.Render()\n}\n\n// modelNameLookup is a helper function to lookup model name by _id\nfunc modelNameLookup(models []Model, id string) string {\n\tfor _, model := range models {\n\t\tif model.ID == id {\n\t\t\treturn model.Name\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// printAccessibleModels adds permissions as needed to each model\n//\n//\tand then calls the printModelsTable function\nfunc printAccessibleModels(allModels []Model, tokenJSON HFTokenJSON) {\n\tcolor.Green(\"\\n[i] Accessible Models:\")\n\n\tif tokenJSON.Auth.AccessToken.Type != FINEGRAINED {\n\t\t// Add Read Privs to All Models\n\t\tfor idx := range allModels {\n\t\t\tallModels[idx].Permissions.Read = true\n\t\t}\n\t\t// Add Write Privs to All Models if Write Access\n\t\tif tokenJSON.Auth.AccessToken.Type == WRITE {\n\t\t\tfor idx := range allModels {\n\t\t\t\tallModels[idx].Permissions.Write = true\n\t\t\t}\n\t\t}\n\t\t// Print Models Table\n\t\tprintModelsTable(allModels)\n\t\treturn\n\t}\n\n\t// finegrained scopes are grouped by org, user or model.\n\t// this section will extract the relevant permissions for each entity and store them in a map\n\tvar nameToPermissions = make(map[string]Permissions)\n\tfor _, permission := range tokenJSON.Auth.AccessToken.FineGrained.Scoped {\n\t\tread := false\n\t\twrite := false\n\t\tfor _, perm := range permission.Permissions {\n\t\t\tif perm == \"repo.content.read\" {\n\t\t\t\tread = true\n\t\t\t} else if perm == \"repo.write\" {\n\t\t\t\twrite = true\n\t\t\t}\n\t\t}\n\t\tif permission.Entity.Type == \"user\" || permission.Entity.Type == \"org\" {\n\t\t\tnameToPermissions[permission.Entity.Name] = Permissions{Read: read, Write: write}\n\t\t} else if permission.Entity.Type == \"model\" {\n\t\t\tnameToPermissions[modelNameLookup(allModels, permission.Entity.ID)] = Permissions{Read: read, Write: write}\n\t\t}\n\t}\n\n\t// apply permissions to all models\n\tfor idx := range allModels {\n\t\t// get username/orgname for each model and apply those permissions\n\t\tmodelUsername := strings.Split(allModels[idx].Name, \"/\")[0]\n\t\tif permissions, ok := nameToPermissions[modelUsername]; ok {\n\t\t\tallModels[idx].Permissions = permissions\n\t\t}\n\t\t// override model permissions with repo-specific permissions\n\t\tif permissions, ok := nameToPermissions[allModels[idx].Name]; ok {\n\t\t\tallModels[idx].Permissions = permissions\n\t\t}\n\t}\n\n\t// Print Models Table\n\tprintModelsTable(allModels)\n}\n\n// printModelsTable prints the models table\nfunc printModelsTable(models []Model) {\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Model\", \"Private\", \"Read\", \"Write\"})\n\tfor _, model := range models {\n\t\tvar name, read, write, private string\n\t\tif model.Permissions.Read {\n\t\t\tread = color.New(color.FgGreen).Sprint(\"True\")\n\t\t} else {\n\t\t\tread = \"False\"\n\t\t}\n\t\tif model.Permissions.Write {\n\t\t\twrite = color.New(color.FgGreen).Sprint(\"True\")\n\t\t} else {\n\t\t\twrite = \"False\"\n\t\t}\n\t\tif model.Private {\n\t\t\tprivate = color.New(color.FgGreen).Sprint(\"True\")\n\t\t\tname = color.New(color.FgGreen).Sprint(model.Name)\n\t\t} else {\n\t\t\tprivate = \"False\"\n\t\t\tname = model.Name\n\t\t}\n\t\tt.AppendRow([]interface{}{name, private, read, write})\n\t}\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/huggingface/huggingface_test.go",
    "content": "package huggingface\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"valid Huggingface key\",\n\t\t\tkey:  testSecrets.MustGetField(\"HUGGINGFACE\"),\n\t\t\twant: `{\n\t\t\t\t\"AnalyzerType\":6,\n\t\t\t\t\"Bindings\":[\n\t\t\t\t   {\n\t\t\t\t\t  \"Resource\":{\n\t\t\t\t\t\t \"Name\":\"zubairkhan/test\",\n\t\t\t\t\t\t \"FullyQualifiedName\": \"huggingface.com/model/64d8220c0d879296892ab835\",\n\t\t\t\t\t\t \"Type\":\"model\",\n\t\t\t\t\t\t \"Metadata\":{\n\t\t\t\t\t\t\t\"private\":false\n\t\t\t\t\t\t },\n\t\t\t\t\t\t \"Parent\":null\n\t\t\t\t\t  },\n\t\t\t\t\t  \"Permission\":{\n\t\t\t\t\t\t \"Value\":\"Read\",\n\t\t\t\t\t\t \"Parent\":null\n\t\t\t\t\t  }\n\t\t\t\t   },\n\t\t\t\t   {\n\t\t\t\t\t  \"Resource\":{\n\t\t\t\t\t\t \"Name\":\"zubairkhan/first_repo\",\n\t\t\t\t\t\t \"FullyQualifiedName\": \"huggingface.com/model/64d82349a787c9bc7bbb2ab4\",\n\t\t\t\t\t\t \"Type\":\"model\",\n\t\t\t\t\t\t \"Metadata\":{\n\t\t\t\t\t\t\t\"private\":true\n\t\t\t\t\t\t },\n\t\t\t\t\t\t \"Parent\":null\n\t\t\t\t\t  },\n\t\t\t\t\t  \"Permission\":{\n\t\t\t\t\t\t \"Value\":\"Read\",\n\t\t\t\t\t\t \"Parent\":null\n\t\t\t\t\t  }\n\t\t\t\t   }\n\t\t\t\t],\n\t\t\t\t\"UnboundedResources\":null,\n\t\t\t\t\"Metadata\":{\n\t\t\t\t   \"name\":\"Zubair Khan\",\n\t\t\t\t   \"token_name\":\"another_one\",\n\t\t\t\t   \"token_type\":\"read\",\n\t\t\t\t   \"username\":\"zubairkhan\"\n\t\t\t\t}\n\t\t\t }`,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/huggingface/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage huggingface\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    Read Permission = iota\n    Write Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        Read: \"read\",\n        Write: \"write\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"read\": Read,\n        \"write\": Write,\n    }\n\n    PermissionIDs = map[Permission]int{\n        Read: 1,\n        Write: 2,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: Read,\n        2: Write,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/huggingface/permissions.yaml",
    "content": "permissions:\n - read\n - write"
  },
  {
    "path": "pkg/analyzer/analyzers/huggingface/scopes.go",
    "content": "package huggingface\n\n//nolint:unused\nvar repo_scopes = map[string]string{\n\t\"repo.content.read\": \"Read access to contents\",\n\t\"discussion.write\":  \"Interact with discussions / Open pull requests\",\n\t\"repo.write\":        \"Write access to contents/settings\",\n}\n\nvar org_scopes_order = []string{\n\t\"Repos\",\n\t\"Collections\",\n\t\"Inference endpoints\",\n\t\"Org settings\",\n}\n\nvar org_scopes = map[string]map[string]string{\n\t\"Repos\": {\n\t\t\"repo.content.read\": \"Read access to contents of all repos\",\n\t\t\"discussion.write\":  \"Interact with discussions / Open pull requests on all repos\",\n\t\t\"repo.write\":        \"Write access to contents/settings of all repos\",\n\t},\n\t\"Collections\": {\n\t\t\"collection.read\":  \"Read access to all collections\",\n\t\t\"collection.write\": \"Write access to all collections\",\n\t},\n\t\"Inference endpoints\": {\n\t\t\"inference.endpoints.infer.write\": \"Make calls to inference endpoints\",\n\t\t\"inference.endpoints.write\":       \"Manage inference endpoints\",\n\t},\n\t\"Org settings\": {\n\t\t\"org.read\":  \"Read access to organization's settings\",\n\t\t\"org.write\": \"Write access to organization's settings / member management\",\n\t},\n}\n\nvar user_scopes_order = []string{\n\t\"Billing\",\n\t\"Collections\",\n\t\"Discussions & Posts\",\n\t\"Inference\",\n\t\"Repos\",\n\t\"Webhooks\",\n}\n\nvar user_scopes = map[string]map[string]string{\n\t\"Billing\": {\n\t\t\"user.billing.read\": \"Read access to user's billing usage\",\n\t},\n\t\"Collections\": {\n\t\t\"collection.read\":  \"Read access to all collections under user's namespace\",\n\t\t\"collection.write\": \"Write access to all collections under user's namespace\",\n\t},\n\t\"Discussions & Posts\": {\n\t\t// Note: prepending global. to scopes that are nested under \"global\" in fine-grained permissions JSON\n\t\t// otherwise they would overlap with user scopes under the \"scoped\" JSON\n\t\t\"discussion.write\":        \"Interact with discussions / Open pull requests on repos under user's namespace\",\n\t\t\"global.discussion.write\": \"Interact with discussions / Open pull requests on external repos\",\n\t\t\"global.post.write\":       \"Interact with posts\",\n\t},\n\t\"Inference\": {\n\t\t\"global.inference.serverless.write\": \"Make calls to the serverless Inference API\",\n\t\t\"inference.endpoints.infer.write\":   \"Make calls to inference endpoints\",\n\t\t\"inference.endpoints.write\":         \"Manage inference endpoints\",\n\t},\n\t\"Repos\": {\n\t\t\"repo.content.read\": \"Read access to contents of all repos under user's namespace\",\n\t\t\"repo.write\":        \"Write access to contents/settings of all repos under user's namespace\",\n\t},\n\t\"Webhooks\": {\n\t\t\"user.webhooks.read\":  \"Access webhooks data\",\n\t\t\"user.webhooks.write\": \"Create and manage webhooks\",\n\t},\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/jira/jira.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go jira\n\npackage jira\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"slices\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (a Analyzer) Type() analyzers.AnalyzerType {\n\treturn analyzers.AnalyzerTypeJira\n}\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\ttoken, exist := credInfo[\"token\"]\n\tif !exist {\n\t\treturn nil, fmt.Errorf(\"token not found in credential info\")\n\t}\n\tdomain, exist := credInfo[\"domain\"]\n\tif !exist {\n\t\treturn nil, fmt.Errorf(\"domain not found in credential info\")\n\t}\n\temail, exist := credInfo[\"email\"]\n\tif !exist {\n\t\treturn nil, fmt.Errorf(\"email not found in credential info\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, token, domain, email)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, domain, email, token string) {\n\tinfo, err := AnalyzePermissions(cfg, token, domain, email)\n\tif err != nil {\n\t\t// just print the error in cli and continue as a partial success\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t} else {\n\t\tcolor.Green(\"[!] Valid Jira API token\\n\\n\")\n\t}\n\n\tif info == nil {\n\t\tcolor.Red(\"[x] Error : %s\", \"No information found\")\n\t\treturn\n\t}\n\n\tprintUserInfo(info.UserInfo)\n\tprintPermissions(info.Permissions)\n\tprintResources(info.Resources)\n}\n\nfunc AnalyzePermissions(cfg *config.Config, token, domain, email string) (*SecretInfo, error) {\n\t// create http client\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\tvar secretInfo = &SecretInfo{}\n\n\t// capture the user information\n\tif err := captureUserInfo(client, token, domain, email, secretInfo); err != nil {\n\t\treturn nil, err\n\t}\n\n\tbody, _, err := capturePermissions(client, domain, email, token)\n\tif err != nil {\n\t\treturn secretInfo, fmt.Errorf(\"failed to check permissions: %w\", err)\n\t}\n\n\tvar permissionsResp JiraPermissionsResponse\n\tif err := json.Unmarshal(body, &permissionsResp); err != nil {\n\t\treturn secretInfo, fmt.Errorf(\"failed to unmarshal permissions response: %w\", err)\n\t}\n\n\tvar grantedPermissions []string\n\tfor key, perm := range permissionsResp.Permissions {\n\t\tif perm.HavePermission {\n\t\t\tgrantedPermissions = append(grantedPermissions, key)\n\t\t}\n\t}\n\tslices.Sort(grantedPermissions)\n\tsecretInfo.Permissions = grantedPermissions\n\n\t// capture the resources\n\tif err := captureResources(client, domain, email, token, secretInfo, grantedPermissions); err != nil {\n\t\t// return secretInfo as well in case of error for partial success\n\t\treturn secretInfo, err\n\t}\n\n\treturn secretInfo, nil\n}\n\n// secretInfoToAnalyzerResult translate secret info to Analyzer Result\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeJira,\n\t\tMetadata:     map[string]any{},\n\t\tBindings:     make([]analyzers.Binding, 0),\n\t}\n\n\tfor _, resource := range info.Resources {\n\t\tfor _, perm := range resource.Permissions {\n\t\t\tbinding := analyzers.Binding{\n\t\t\t\tResource: *secretInfoResourceToAnalyzerResource(resource),\n\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\tValue: perm,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tif resource.Parent != nil {\n\t\t\t\tbinding.Resource.Parent = secretInfoResourceToAnalyzerResource(*resource.Parent)\n\t\t\t}\n\n\t\t\tresult.Bindings = append(result.Bindings, binding)\n\t\t}\n\t}\n\n\treturn &result\n}\n\n// secretInfoResourceToAnalyzerResource translate secret info resource to analyzer resource for binding\nfunc secretInfoResourceToAnalyzerResource(resource JiraResource) *analyzers.Resource {\n\tanalyzerRes := analyzers.Resource{\n\t\t// make fully qualified name unique\n\t\tFullyQualifiedName: resource.Type + \"/\" + resource.ID,\n\t\tName:               resource.Name,\n\t\tType:               resource.Type,\n\t\tMetadata:           map[string]any{},\n\t}\n\n\tfor key, value := range resource.Metadata {\n\t\tanalyzerRes.Metadata[key] = value\n\t}\n\n\treturn &analyzerRes\n}\n\n// cli print functions\nfunc printUserInfo(user JiraUser) {\n\tif user.AccountID == \"\" {\n\t\tcolor.Red(\"[x] No user information found\")\n\t\treturn\n\t}\n\tcolor.Yellow(\"[i] User Information:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"ID\", \"Name\", \"Account Type\", \"Email\", \"Active\"})\n\tt.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))})\n\n\tt.Render()\n}\n\nfunc printPermissions(permissions []string) {\n\tif len(permissions) == 0 {\n\t\tcolor.Red(\"[x] No permissions found\")\n\t\treturn\n\t}\n\tcolor.Yellow(\"[i] Permissions:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Permission\"})\n\tfor _, scope := range permissions {\n\t\tt.AppendRow(table.Row{color.GreenString(scope)})\n\t}\n\tt.Render()\n}\n\nfunc printResources(resources []JiraResource) {\n\tif len(resources) == 0 {\n\t\tcolor.Red(\"[x] No resources found\")\n\t\treturn\n\t}\n\tcolor.Yellow(\"[i] Resources:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Name\", \"Type\"})\n\tfor _, resource := range resources {\n\t\tt.AppendRow(table.Row{color.GreenString(resource.Name), color.GreenString(resource.Type)})\n\t}\n\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/jira/jira_test.go",
    "content": "package jira\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed result_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"analyzers1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tjiraDomain := testSecrets.MustGetField(\"JIRA_DOMAIN_ANALYZE\")\n\tjiraEmail := testSecrets.MustGetField(\"JIRA_EMAIL_ANALYZE\")\n\tjiraToken := testSecrets.MustGetField(\"JIRA_TOKEN_ANALYZE\")\n\n\ttests := []struct {\n\t\tname    string\n\t\tdomain  string\n\t\temail   string\n\t\ttoken   string\n\t\twant    []byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid jira token\",\n\t\t\tdomain:  jiraDomain,\n\t\t\temail:   jiraEmail,\n\t\t\ttoken:   jiraToken,\n\t\t\twant:    expectedOutput,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid jira token\",\n\t\t\tdomain:  jiraDomain,\n\t\t\temail:   jiraEmail,\n\t\t\ttoken:   \"invalid\",\n\t\t\twant:    nil,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"token\": tt.token, \"domain\": tt.domain, \"email\": tt.email})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif tt.wantErr {\n\t\t\t\tif got != nil {\n\t\t\t\t\tt.Errorf(\"Analyzer.Analyze() got = %v, want nil\", got)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.FullyQualifiedName == bindings[j].Resource.FullyQualifiedName {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.FullyQualifiedName < bindings[j].Resource.FullyQualifiedName\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/jira/models.go",
    "content": "package jira\n\nimport (\n\t\"sync\"\n)\n\nconst (\n\tResourceTypeProject     = \"Project\"\n\tResourceTypeBoard       = \"Board\"\n\tResourceTypeGroup       = \"Group\"\n\tResourceTypeIssue       = \"Issue\"\n\tResourceTypeUser        = \"User\"\n\tResourceTypeAuditRecord = \"AuditRecord\"\n)\n\nvar ResourcePermissions = map[string][]Permission{\n\tResourceTypeProject: {\n\t\tAdminister,\n\t\tBrowseProjects,\n\t\tAdministerProjects,\n\t\tCreateProject,\n\t\tEditIssueLayout,\n\t\tViewDevTools,\n\t\tViewAggregatedData,\n\t\tSystemAdmin,\n\t},\n\tResourceTypeIssue: {\n\t\tAdminister,\n\t\tAddComments,\n\t\tAssignIssues,\n\t\tCloseIssues,\n\t\tCreateAttachments,\n\t\tCreateIssues,\n\t\tDeleteIssues,\n\t\tDeleteAllAttachments,\n\t\tDeleteAllComments,\n\t\tDeleteAllWorklogs,\n\t\tDeleteOwnAttachments,\n\t\tDeleteOwnComments,\n\t\tDeleteOwnWorklogs,\n\t\tEditAllComments,\n\t\tEditAllWorklogs,\n\t\tEditIssues,\n\t\tEditOwnComments,\n\t\tEditOwnWorklogs,\n\t\tLinkIssues,\n\t\tManageWatchers,\n\t\tModifyReporter,\n\t\tMoveIssues,\n\t\tResolveIssues,\n\t\tScheduleIssues,\n\t\tSetIssueSecurity,\n\t\tSystemAdmin,\n\t\tTransitionIssues,\n\t\tUnarchiveIssues,\n\t\tViewVotersAndWatchers,\n\t\tWorkOnIssues,\n\t},\n\tResourceTypeBoard: {\n\t\tAdminister,\n\t\tManageSprintsPermission,\n\t\tBrowseProjects,\n\t\tSystemAdmin,\n\t\tViewAggregatedData,\n\t},\n\tResourceTypeUser: {\n\t\tAssignableUser,\n\t\tSystemAdmin,\n\t\tUserPicker,\n\t},\n\tResourceTypeGroup: {\n\t\tAdminister,\n\t\tSystemAdmin,\n\t},\n\tResourceTypeAuditRecord: {\n\t\tAdminister,\n\t\tSystemAdmin,\n\t},\n}\n\ntype SecretInfo struct {\n\tmu sync.RWMutex\n\n\tUserInfo    JiraUser\n\tPermissions []string\n\tResources   []JiraResource\n}\n\n// JiraUser represents the response from /myself API\ntype JiraUser struct {\n\tAccountID    string `json:\"accountId\"`\n\tAccountType  string `json:\"accountType\"`\n\tDisplayName  string `json:\"displayName\"`\n\tEmailAddress string `json:\"emailAddress\"`\n\tActive       bool   `json:\"active\"`\n\tTimeZone     string `json:\"timeZone\"`\n\tLocale       string `json:\"locale\"`\n\tSelf         string `json:\"self\"`\n}\n\ntype JiraResource struct {\n\tID          string\n\tName        string\n\tType        string\n\tMetadata    map[string]string\n\tParent      *JiraResource\n\tPermissions []string\n}\n\nfunc (s *SecretInfo) appendResource(resource JiraResource, resourceType string) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\tif perms, ok := ResourcePermissions[resourceType]; ok {\n\t\tfor _, p := range perms {\n\t\t\tif userPerms[p] {\n\t\t\t\tresource.Permissions = append(resource.Permissions, PermissionStrings[p])\n\t\t\t}\n\t\t}\n\t}\n\n\ts.Resources = append(s.Resources, resource)\n}\n\ntype JiraPermissionsResponse struct {\n\tPermissions map[string]JiraPermission `json:\"permissions\"`\n}\n\ntype JiraPermission struct {\n\tID             string `json:\"id\"`\n\tKey            string `json:\"key\"`\n\tName           string `json:\"name\"`\n\tType           string `json:\"type\"`\n\tDescription    string `json:\"description\"`\n\tHavePermission bool   `json:\"havePermission\"`\n}\n\ntype ProjectSearchResponse struct {\n\tMaxResults int           `json:\"maxResults\"`\n\tTotal      int           `json:\"total\"`\n\tIsLast     bool          `json:\"isLast\"`\n\tValues     []JiraProject `json:\"values\"`\n}\n\ntype JiraProject struct {\n\tID             string `json:\"id\"`\n\tKey            string `json:\"key\"`\n\tName           string `json:\"name\"`\n\tProjectTypeKey string `json:\"projectTypeKey\"`\n\tIsPrivate      bool   `json:\"isPrivate\"`\n\tUUID           string `json:\"uuid\"`\n}\n\ntype JiraIssue struct {\n\tIssues []struct {\n\t\tID     string `json:\"id\"`\n\t\tKey    string `json:\"key\"`\n\t\tFields struct {\n\t\t\tSummary string `json:\"summary\"`\n\t\t\tStatus  struct {\n\t\t\t\tName string `json:\"name\"`\n\t\t\t} `json:\"status\"`\n\t\t\tIssueType struct {\n\t\t\t\tName string `json:\"name\"`\n\t\t\t} `json:\"issuetype\"`\n\t\t} `json:\"fields\"`\n\t} `json:\"issues\"`\n}\n\ntype JiraBoard struct {\n\tValues []struct {\n\t\tID        int    `json:\"id\"`\n\t\tName      string `json:\"name\"`\n\t\tType      string `json:\"type\"`\n\t\tSelf      string `json:\"self\"`\n\t\tIsPrivate bool   `json:\"isPrivate\"`\n\t\tLocation  struct {\n\t\t\tProjectID      int    `json:\"projectId\"`\n\t\t\tDisplayName    string `json:\"displayName\"`\n\t\t\tProjectName    string `json:\"projectName\"`\n\t\t\tProjectKey     string `json:\"projectKey\"`\n\t\t\tProjectTypeKey string `json:\"projectTypeKey\"`\n\t\t\tAvatarURI      string `json:\"avatarURI\"`\n\t\t\tName           string `json:\"name\"`\n\t\t} `json:\"location\"`\n\t} `json:\"values\"`\n}\n\ntype JiraGroup struct {\n\tTotal  int `json:\"total\"`\n\tGroups []struct {\n\t\tName    string `json:\"name\"`\n\t\tHTML    string `json:\"html\"`\n\t\tGroupID string `json:\"groupId\"`\n\t\tLabels  []struct {\n\t\t\tText  string `json:\"text\"`\n\t\t\tTitle string `json:\"title\"`\n\t\t\tType  string `json:\"type\"`\n\t\t} `json:\"labels\"`\n\t} `json:\"groups\"`\n}\n\ntype AuditRecord struct {\n\tOffset  int `json:\"offset\"`\n\tLimit   int `json:\"limit\"`\n\tTotal   int `json:\"total\"`\n\tRecords []struct {\n\t\tID            int    `json:\"id\"`\n\t\tSummary       string `json:\"summary\"`\n\t\tCreated       string `json:\"created\"`\n\t\tCategory      string `json:\"category\"`\n\t\tEventSource   string `json:\"eventSource\"`\n\t\tRemoteAddress string `json:\"remoteAddress,omitempty\"`\n\t\tAuthorKey     string `json:\"authorKey,omitempty\"`\n\t\tAuthorAccount string `json:\"authorAccountId,omitempty\"`\n\n\t\tObjectItem struct {\n\t\t\tID         string `json:\"id,omitempty\"`\n\t\t\tName       string `json:\"name\"`\n\t\t\tTypeName   string `json:\"typeName\"`\n\t\t\tParentID   string `json:\"parentId,omitempty\"`\n\t\t\tParentName string `json:\"parentName,omitempty\"`\n\t\t} `json:\"objectItem\"`\n\n\t\tAssociatedItems []struct {\n\t\t\tID         string `json:\"id\"`\n\t\t\tName       string `json:\"name\"`\n\t\t\tTypeName   string `json:\"typeName\"`\n\t\t\tParentID   string `json:\"parentId\"`\n\t\t\tParentName string `json:\"parentName\"`\n\t\t} `json:\"associatedItems\"`\n\n\t\tChangedValues []struct {\n\t\t\tFieldName string `json:\"fieldName\"`\n\t\t\tChangedTo string `json:\"changedTo\"`\n\t\t} `json:\"changedValues\"`\n\t} `json:\"records\"`\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/jira/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage jira\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    AddComments Permission = iota\n    Administer Permission = iota\n    AdministerProjects Permission = iota\n    AssignableUser Permission = iota\n    AssignIssues Permission = iota\n    BrowseProjects Permission = iota\n    BulkChange Permission = iota\n    CloseIssues Permission = iota\n    CreateAttachments Permission = iota\n    CreateIssues Permission = iota\n    CreateProject Permission = iota\n    CreateSharedObjects Permission = iota\n    DeleteAllAttachments Permission = iota\n    DeleteAllComments Permission = iota\n    DeleteAllWorklogs Permission = iota\n    DeleteIssues Permission = iota\n    DeleteOwnAttachments Permission = iota\n    DeleteOwnComments Permission = iota\n    DeleteOwnWorklogs Permission = iota\n    EditAllComments Permission = iota\n    EditAllWorklogs Permission = iota\n    EditIssues Permission = iota\n    EditIssueLayout Permission = iota\n    EditOwnComments Permission = iota\n    EditOwnWorklogs Permission = iota\n    EditWorkflow Permission = iota\n    LinkIssues Permission = iota\n    ManageGroupFilterSubscriptions Permission = iota\n    ManageSprintsPermission Permission = iota\n    ManageWatchers Permission = iota\n    ModifyReporter Permission = iota\n    MoveIssues Permission = iota\n    ResolveIssues Permission = iota\n    ScheduleIssues Permission = iota\n    SetIssueSecurity Permission = iota\n    SystemAdmin Permission = iota\n    TransitionIssues Permission = iota\n    UnarchiveIssues Permission = iota\n    UserPicker Permission = iota\n    ViewAggregatedData Permission = iota\n    ViewDevTools Permission = iota\n    ViewReadonlyWorkflow Permission = iota\n    ViewVotersAndWatchers Permission = iota\n    WorkOnIssues Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        AddComments: \"add_comments\",\n        Administer: \"administer\",\n        AdministerProjects: \"administer_projects\",\n        AssignableUser: \"assignable_user\",\n        AssignIssues: \"assign_issues\",\n        BrowseProjects: \"browse_projects\",\n        BulkChange: \"bulk_change\",\n        CloseIssues: \"close_issues\",\n        CreateAttachments: \"create_attachments\",\n        CreateIssues: \"create_issues\",\n        CreateProject: \"create_project\",\n        CreateSharedObjects: \"create_shared_objects\",\n        DeleteAllAttachments: \"delete_all_attachments\",\n        DeleteAllComments: \"delete_all_comments\",\n        DeleteAllWorklogs: \"delete_all_worklogs\",\n        DeleteIssues: \"delete_issues\",\n        DeleteOwnAttachments: \"delete_own_attachments\",\n        DeleteOwnComments: \"delete_own_comments\",\n        DeleteOwnWorklogs: \"delete_own_worklogs\",\n        EditAllComments: \"edit_all_comments\",\n        EditAllWorklogs: \"edit_all_worklogs\",\n        EditIssues: \"edit_issues\",\n        EditIssueLayout: \"edit_issue_layout\",\n        EditOwnComments: \"edit_own_comments\",\n        EditOwnWorklogs: \"edit_own_worklogs\",\n        EditWorkflow: \"edit_workflow\",\n        LinkIssues: \"link_issues\",\n        ManageGroupFilterSubscriptions: \"manage_group_filter_subscriptions\",\n        ManageSprintsPermission: \"manage_sprints_permission\",\n        ManageWatchers: \"manage_watchers\",\n        ModifyReporter: \"modify_reporter\",\n        MoveIssues: \"move_issues\",\n        ResolveIssues: \"resolve_issues\",\n        ScheduleIssues: \"schedule_issues\",\n        SetIssueSecurity: \"set_issue_security\",\n        SystemAdmin: \"system_admin\",\n        TransitionIssues: \"transition_issues\",\n        UnarchiveIssues: \"unarchive_issues\",\n        UserPicker: \"user_picker\",\n        ViewAggregatedData: \"view_aggregated_data\",\n        ViewDevTools: \"view_dev_tools\",\n        ViewReadonlyWorkflow: \"view_readonly_workflow\",\n        ViewVotersAndWatchers: \"view_voters_and_watchers\",\n        WorkOnIssues: \"work_on_issues\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"add_comments\": AddComments,\n        \"administer\": Administer,\n        \"administer_projects\": AdministerProjects,\n        \"assignable_user\": AssignableUser,\n        \"assign_issues\": AssignIssues,\n        \"browse_projects\": BrowseProjects,\n        \"bulk_change\": BulkChange,\n        \"close_issues\": CloseIssues,\n        \"create_attachments\": CreateAttachments,\n        \"create_issues\": CreateIssues,\n        \"create_project\": CreateProject,\n        \"create_shared_objects\": CreateSharedObjects,\n        \"delete_all_attachments\": DeleteAllAttachments,\n        \"delete_all_comments\": DeleteAllComments,\n        \"delete_all_worklogs\": DeleteAllWorklogs,\n        \"delete_issues\": DeleteIssues,\n        \"delete_own_attachments\": DeleteOwnAttachments,\n        \"delete_own_comments\": DeleteOwnComments,\n        \"delete_own_worklogs\": DeleteOwnWorklogs,\n        \"edit_all_comments\": EditAllComments,\n        \"edit_all_worklogs\": EditAllWorklogs,\n        \"edit_issues\": EditIssues,\n        \"edit_issue_layout\": EditIssueLayout,\n        \"edit_own_comments\": EditOwnComments,\n        \"edit_own_worklogs\": EditOwnWorklogs,\n        \"edit_workflow\": EditWorkflow,\n        \"link_issues\": LinkIssues,\n        \"manage_group_filter_subscriptions\": ManageGroupFilterSubscriptions,\n        \"manage_sprints_permission\": ManageSprintsPermission,\n        \"manage_watchers\": ManageWatchers,\n        \"modify_reporter\": ModifyReporter,\n        \"move_issues\": MoveIssues,\n        \"resolve_issues\": ResolveIssues,\n        \"schedule_issues\": ScheduleIssues,\n        \"set_issue_security\": SetIssueSecurity,\n        \"system_admin\": SystemAdmin,\n        \"transition_issues\": TransitionIssues,\n        \"unarchive_issues\": UnarchiveIssues,\n        \"user_picker\": UserPicker,\n        \"view_aggregated_data\": ViewAggregatedData,\n        \"view_dev_tools\": ViewDevTools,\n        \"view_readonly_workflow\": ViewReadonlyWorkflow,\n        \"view_voters_and_watchers\": ViewVotersAndWatchers,\n        \"work_on_issues\": WorkOnIssues,\n    }\n\n    PermissionIDs = map[Permission]int{\n        AddComments: 1,\n        Administer: 2,\n        AdministerProjects: 3,\n        AssignableUser: 4,\n        AssignIssues: 5,\n        BrowseProjects: 6,\n        BulkChange: 7,\n        CloseIssues: 8,\n        CreateAttachments: 9,\n        CreateIssues: 10,\n        CreateProject: 11,\n        CreateSharedObjects: 12,\n        DeleteAllAttachments: 13,\n        DeleteAllComments: 14,\n        DeleteAllWorklogs: 15,\n        DeleteIssues: 16,\n        DeleteOwnAttachments: 17,\n        DeleteOwnComments: 18,\n        DeleteOwnWorklogs: 19,\n        EditAllComments: 20,\n        EditAllWorklogs: 21,\n        EditIssues: 22,\n        EditIssueLayout: 23,\n        EditOwnComments: 24,\n        EditOwnWorklogs: 25,\n        EditWorkflow: 26,\n        LinkIssues: 27,\n        ManageGroupFilterSubscriptions: 28,\n        ManageSprintsPermission: 29,\n        ManageWatchers: 30,\n        ModifyReporter: 31,\n        MoveIssues: 32,\n        ResolveIssues: 33,\n        ScheduleIssues: 34,\n        SetIssueSecurity: 35,\n        SystemAdmin: 36,\n        TransitionIssues: 37,\n        UnarchiveIssues: 38,\n        UserPicker: 39,\n        ViewAggregatedData: 40,\n        ViewDevTools: 41,\n        ViewReadonlyWorkflow: 42,\n        ViewVotersAndWatchers: 43,\n        WorkOnIssues: 44,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: AddComments,\n        2: Administer,\n        3: AdministerProjects,\n        4: AssignableUser,\n        5: AssignIssues,\n        6: BrowseProjects,\n        7: BulkChange,\n        8: CloseIssues,\n        9: CreateAttachments,\n        10: CreateIssues,\n        11: CreateProject,\n        12: CreateSharedObjects,\n        13: DeleteAllAttachments,\n        14: DeleteAllComments,\n        15: DeleteAllWorklogs,\n        16: DeleteIssues,\n        17: DeleteOwnAttachments,\n        18: DeleteOwnComments,\n        19: DeleteOwnWorklogs,\n        20: EditAllComments,\n        21: EditAllWorklogs,\n        22: EditIssues,\n        23: EditIssueLayout,\n        24: EditOwnComments,\n        25: EditOwnWorklogs,\n        26: EditWorkflow,\n        27: LinkIssues,\n        28: ManageGroupFilterSubscriptions,\n        29: ManageSprintsPermission,\n        30: ManageWatchers,\n        31: ModifyReporter,\n        32: MoveIssues,\n        33: ResolveIssues,\n        34: ScheduleIssues,\n        35: SetIssueSecurity,\n        36: SystemAdmin,\n        37: TransitionIssues,\n        38: UnarchiveIssues,\n        39: UserPicker,\n        40: ViewAggregatedData,\n        41: ViewDevTools,\n        42: ViewReadonlyWorkflow,\n        43: ViewVotersAndWatchers,\n        44: WorkOnIssues,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/jira/permissions.yaml",
    "content": "permissions:\n  - add_comments\n  - administer\n  - administer_projects\n  - assignable_user\n  - assign_issues\n  - browse_projects\n  - bulk_change\n  - close_issues\n  - create_attachments\n  - create_issues\n  - create_project\n  - create_shared_objects\n  - delete_all_attachments\n  - delete_all_comments\n  - delete_all_worklogs\n  - delete_issues\n  - delete_own_attachments\n  - delete_own_comments\n  - delete_own_worklogs\n  - edit_all_comments\n  - edit_all_worklogs\n  - edit_issues\n  - edit_issue_layout\n  - edit_own_comments\n  - edit_own_worklogs\n  - edit_workflow\n  - link_issues\n  - manage_group_filter_subscriptions\n  - manage_sprints_permission\n  - manage_watchers\n  - modify_reporter\n  - move_issues\n  - resolve_issues\n  - schedule_issues\n  - set_issue_security\n  - system_admin\n  - transition_issues\n  - unarchive_issues\n  - user_picker\n  - view_aggregated_data\n  - view_dev_tools\n  - view_readonly_workflow\n  - view_voters_and_watchers\n  - work_on_issues\n"
  },
  {
    "path": "pkg/analyzer/analyzers/jira/requests.go",
    "content": "package jira\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n)\n\ntype endpoint int\n\nconst (\n\t// list of endpoints\n\tmySelf endpoint = iota\n\tmyPermissions\n\tgetAllProjects\n\tsearchIssues\n\tgetAllBoards\n\tgetAllUsers\n\tfindGroups\n\tgetAuditRecords\n)\n\nvar (\n\tbaseURL = \"https://%s/rest\"\n\n\t// endpoints contain Jira API endpoints\n\tendpoints = map[endpoint]string{\n\t\tmySelf:          \"myself\",\n\t\tmyPermissions:   \"mypermissions\",\n\t\tsearchIssues:    \"search/jql\",\n\t\tgetAllProjects:  \"project/search\",\n\t\tgetAllBoards:    \"board\",\n\t\tgetAllUsers:     \"users/search\",\n\t\tfindGroups:      \"groups/picker\",\n\t\tgetAuditRecords: \"auditing/record\",\n\t}\n\n\tuserPerms = make(map[Permission]bool)\n)\n\n// buildBasicAuthHeader constructs the Basic Auth header\nfunc buildBasicAuthHeader(email, token string) string {\n\tauth := fmt.Sprintf(\"%s:%s\", email, token)\n\treturn \"Basic \" + base64.StdEncoding.EncodeToString([]byte(auth))\n}\n\n// makeJiraRequest send the API request to passed url with passed key as API Key and return response body and status code\nfunc makeJiraRequest(client *http.Client, endpoint, email, token string) ([]byte, int, error) {\n\t// create request\n\treq, err := http.NewRequest(http.MethodGet, endpoint, http.NoBody)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treq.Header.Set(\"Accept\", \"application/json\")\n\treq.Header.Set(\"Authorization\", buildBasicAuthHeader(email, token))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tresponseBodyByte, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn responseBodyByte, resp.StatusCode, nil\n}\n\nfunc capturePermissions(client *http.Client, domain, email, token string) ([]byte, int, error) {\n\tvar allPermissions []string\n\tfor _, key := range PermissionStrings {\n\t\tallPermissions = append(allPermissions, strings.ToUpper(key))\n\t}\n\n\tquery := url.Values{}\n\tquery.Set(\"permissions\", strings.Join(allPermissions, \",\"))\n\n\tendpoint := fmt.Sprintf(\"%s/api/3/%s?%s\", fmt.Sprintf(baseURL, domain), endpoints[myPermissions], query.Encode())\n\n\treturn makeJiraRequest(client, endpoint, email, token)\n}\n\n// captureResources try to capture all the resource that the key can access\nfunc captureResources(client *http.Client, domain, email, token string, secretInfo *SecretInfo, grantedPermissions []string) error {\n\tfor _, p := range grantedPermissions {\n\t\tuserPerms[StringToPermission[strings.ToLower(p)]] = true\n\t}\n\n\tvar (\n\t\twg             sync.WaitGroup\n\t\terrAggWg       sync.WaitGroup\n\t\taggregatedErrs = make([]error, 0)\n\t\terrChan        = make(chan error, 1)\n\t)\n\n\terrAggWg.Add(1)\n\tgo func() {\n\t\tdefer errAggWg.Done()\n\t\tfor err := range errChan {\n\t\t\taggregatedErrs = append(aggregatedErrs, err)\n\t\t}\n\t}()\n\n\tlaunchTask := func(task func() error) {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tif err := task(); err != nil {\n\t\t\t\terrChan <- err\n\t\t\t}\n\t\t}()\n\t}\n\n\tprojects, err := captureProjects(client, domain, email, token, secretInfo)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to capture projects: %w\", err)\n\t}\n\tif projects != nil {\n\t\tfor _, proj := range projects.Values {\n\t\t\tlaunchTask(func() error {\n\t\t\t\treturn captureIssues(client, domain, email, token, proj.Key, secretInfo)\n\t\t\t})\n\t\t}\n\t}\n\tlaunchTask(func() error { return captureBoards(client, domain, email, token, secretInfo) })\n\tlaunchTask(func() error { return captureUsers(client, domain, email, token, secretInfo) })\n\tlaunchTask(func() error { return captureGroups(client, domain, email, token, secretInfo) })\n\tlaunchTask(func() error { return captureAuditLogs(client, domain, email, token, secretInfo) })\n\n\twg.Wait()\n\tclose(errChan)\n\terrAggWg.Wait()\n\n\tif len(aggregatedErrs) > 0 {\n\t\treturn errors.Join(aggregatedErrs...)\n\t}\n\n\treturn nil\n}\n\n// captureUserInfo calls `/myself` API and store the current user information in secretInfo\nfunc captureUserInfo(client *http.Client, token, domain, email string, secretInfo *SecretInfo) error {\n\tendPoint := fmt.Sprintf(\"%s/api/3/%s\", fmt.Sprintf(baseURL, domain), endpoints[mySelf])\n\trespBody, statusCode, err := makeJiraRequest(client, endPoint, email, token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar user JiraUser\n\n\t\tif err := json.Unmarshal(respBody, &user); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tsecretInfo.UserInfo = user\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn fmt.Errorf(\"invalid email or api token\")\n\tcase http.StatusNotFound:\n\t\treturn fmt.Errorf(\"domain not found: %s\", domain)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d for API: %s\", statusCode, endpoints[mySelf])\n\t}\n}\n\nfunc captureProjects(client *http.Client, domain, email, token string, secretInfo *SecretInfo) (*ProjectSearchResponse, error) {\n\tendpoint := fmt.Sprintf(\"%s/api/3/%s\", fmt.Sprintf(baseURL, domain), endpoints[getAllProjects])\n\tbody, statusCode, err := makeJiraRequest(client, endpoint, email, token)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := handleStatusCode(statusCode, endpoint); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar resp ProjectSearchResponse\n\tif err := json.Unmarshal(body, &resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal project response: %w\", err)\n\t}\n\n\tfor _, proj := range resp.Values {\n\t\tresource := JiraResource{\n\t\t\tID:   proj.ID,\n\t\t\tName: proj.Name,\n\t\t\tType: ResourceTypeProject,\n\t\t\tMetadata: map[string]string{\n\t\t\t\t\"Key\":     proj.Key,\n\t\t\t\t\"UUID\":    proj.UUID,\n\t\t\t\t\"Private\": strconv.FormatBool(proj.IsPrivate),\n\t\t\t\t\"TypeKey\": proj.ProjectTypeKey,\n\t\t\t},\n\t\t}\n\n\t\tsecretInfo.appendResource(resource, ResourceTypeProject)\n\t}\n\n\treturn &resp, nil\n}\n\nfunc captureIssues(client *http.Client, domain, email, token, projectKey string, secretInfo *SecretInfo) error {\n\tpath := fmt.Sprintf(\"api/3/%s\", endpoints[searchIssues])\n\tquery := fmt.Sprintf(\"jql=project=%s&fields=issuetype,summary,status\", projectKey)\n\tendpoint := fmt.Sprintf(\"%s/%s?%s\", fmt.Sprintf(baseURL, domain), path, query)\n\n\tbody, statusCode, err := makeJiraRequest(client, endpoint, email, token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := handleStatusCode(statusCode, endpoint); err != nil {\n\t\treturn err\n\t}\n\n\tvar issueResp JiraIssue\n\tif err := json.Unmarshal(body, &issueResp); err != nil {\n\t\treturn fmt.Errorf(\"failed to unmarshal issue response: %w\", err)\n\t}\n\n\tfor _, issue := range issueResp.Issues {\n\t\tissueResource := JiraResource{\n\t\t\tID:   issue.ID,\n\t\t\tName: issue.Key,\n\t\t\tType: issue.Fields.IssueType.Name,\n\t\t\tMetadata: map[string]string{\n\t\t\t\t\"Summary\": issue.Fields.Summary,\n\t\t\t\t\"Status\":  issue.Fields.Status.Name,\n\t\t\t\t\"Project\": projectKey,\n\t\t\t},\n\t\t}\n\n\t\tsecretInfo.appendResource(issueResource, ResourceTypeIssue)\n\t}\n\n\treturn nil\n}\n\nfunc captureBoards(client *http.Client, domain, email, token string, secretInfo *SecretInfo) error {\n\tendpoint := fmt.Sprintf(\"%s/agile/1.0/%s\", fmt.Sprintf(baseURL, domain), endpoints[getAllBoards])\n\n\tbody, statusCode, err := makeJiraRequest(client, endpoint, email, token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := handleStatusCode(statusCode, endpoint); err != nil {\n\t\treturn err\n\t}\n\n\tvar boardResp JiraBoard\n\tif err := json.Unmarshal(body, &boardResp); err != nil {\n\t\treturn fmt.Errorf(\"failed to unmarshal board response: %w\", err)\n\t}\n\n\tfor _, board := range boardResp.Values {\n\t\tboardResource := JiraResource{\n\t\t\tID:   fmt.Sprintf(\"%d\", board.ID),\n\t\t\tName: board.Name,\n\t\t\tType: ResourceTypeBoard,\n\t\t\tMetadata: map[string]string{\n\t\t\t\t\"BoardType\":    board.Type,\n\t\t\t\t\"IsPrivate\":    strconv.FormatBool(board.IsPrivate),\n\t\t\t\t\"ProjectID\":    fmt.Sprintf(\"%d\", board.Location.ProjectID),\n\t\t\t\t\"ProjectKey\":   board.Location.ProjectKey,\n\t\t\t\t\"ProjectName\":  board.Location.ProjectName,\n\t\t\t\t\"ProjectType\":  board.Location.ProjectTypeKey,\n\t\t\t\t\"DisplayName\":  board.Location.DisplayName,\n\t\t\t\t\"AvatarURI\":    board.Location.AvatarURI,\n\t\t\t\t\"BoardSelfURL\": board.Self,\n\t\t\t},\n\t\t}\n\t\tsecretInfo.appendResource(boardResource, ResourceTypeBoard)\n\t}\n\n\treturn nil\n}\n\nfunc captureUsers(client *http.Client, domain, email, token string, secretInfo *SecretInfo) error {\n\tendpoint := fmt.Sprintf(\"%s/api/3/%s\", fmt.Sprintf(baseURL, domain), endpoints[getAllUsers])\n\n\tbody, statusCode, err := makeJiraRequest(client, endpoint, email, token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := handleStatusCode(statusCode, endpoint); err != nil {\n\t\treturn err\n\t}\n\n\tvar users []JiraUser\n\tif err := json.Unmarshal(body, &users); err != nil {\n\t\treturn fmt.Errorf(\"failed to unmarshal user response: %w\", err)\n\t}\n\n\tfor _, user := range users {\n\t\tuserResource := JiraResource{\n\t\t\tID:   user.AccountID,\n\t\t\tName: user.DisplayName,\n\t\t\tType: ResourceTypeUser,\n\t\t\tMetadata: map[string]string{\n\t\t\t\t\"Email\":       user.EmailAddress,\n\t\t\t\t\"AccountType\": user.AccountType,\n\t\t\t\t\"Active\":      strconv.FormatBool(user.Active),\n\t\t\t\t\"SelfURL\":     user.Self,\n\t\t\t},\n\t\t}\n\t\tif user.AccountType != \"app\" {\n\t\t\tsecretInfo.appendResource(userResource, ResourceTypeUser)\n\t\t}\n\n\t}\n\n\treturn nil\n}\n\nfunc captureGroups(client *http.Client, domain, email, token string, secretInfo *SecretInfo) error {\n\tendpoint := fmt.Sprintf(\"%s/api/3/%s\", fmt.Sprintf(baseURL, domain), endpoints[findGroups])\n\n\tbody, statusCode, err := makeJiraRequest(client, endpoint, email, token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := handleStatusCode(statusCode, endpoint); err != nil {\n\t\treturn err\n\t}\n\n\tvar groupResp JiraGroup\n\tif err := json.Unmarshal(body, &groupResp); err != nil {\n\t\treturn fmt.Errorf(\"failed to unmarshal group response: %w\", err)\n\t}\n\n\tfor _, group := range groupResp.Groups {\n\t\tmetadata := map[string]string{\n\t\t\t\"HTML\": group.HTML,\n\t\t}\n\t\tif len(group.Labels) > 0 {\n\t\t\tfor i, label := range group.Labels {\n\t\t\t\tmetadata[fmt.Sprintf(\"Label%d_Text\", i)] = label.Text\n\t\t\t\tmetadata[fmt.Sprintf(\"Label%d_Title\", i)] = label.Title\n\t\t\t\tmetadata[fmt.Sprintf(\"Label%d_Type\", i)] = label.Type\n\t\t\t}\n\t\t}\n\n\t\tgroupResource := JiraResource{\n\t\t\tID:       group.GroupID,\n\t\t\tName:     group.Name,\n\t\t\tType:     ResourceTypeGroup,\n\t\t\tMetadata: metadata,\n\t\t}\n\n\t\tsecretInfo.appendResource(groupResource, ResourceTypeGroup)\n\t}\n\n\treturn nil\n}\n\nfunc captureAuditLogs(client *http.Client, domain, email, token string, secretInfo *SecretInfo) error {\n\tendpoint := fmt.Sprintf(\"%s/api/3/%s\", fmt.Sprintf(baseURL, domain), endpoints[getAuditRecords])\n\n\tbody, statusCode, err := makeJiraRequest(client, endpoint, email, token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := handleStatusCode(statusCode, endpoint); err != nil {\n\t\treturn err\n\t}\n\n\tvar auditResp AuditRecord\n\tif err := json.Unmarshal(body, &auditResp); err != nil {\n\t\treturn fmt.Errorf(\"failed to unmarshal audit logs: %w\", err)\n\t}\n\n\tfor _, record := range auditResp.Records {\n\t\tmetadata := map[string]string{\n\t\t\t\"Summary\":  record.Summary,\n\t\t\t\"Created\":  record.Created,\n\t\t\t\"Category\": record.Category,\n\t\t\t\"Type\":     record.ObjectItem.TypeName,\n\t\t\t\"Object\":   record.ObjectItem.Name,\n\t\t}\n\n\t\tif record.AuthorAccount != \"\" {\n\t\t\tmetadata[\"AuthorAccountID\"] = record.AuthorAccount\n\t\t}\n\t\tif record.RemoteAddress != \"\" {\n\t\t\tmetadata[\"RemoteAddress\"] = record.RemoteAddress\n\t\t}\n\n\t\tfor i, item := range record.AssociatedItems {\n\t\t\tmetadata[fmt.Sprintf(\"AssociatedItem%d_Name\", i)] = item.Name\n\t\t\tmetadata[fmt.Sprintf(\"AssociatedItem%d_Type\", i)] = item.TypeName\n\t\t}\n\n\t\tfor i, change := range record.ChangedValues {\n\t\t\tmetadata[fmt.Sprintf(\"ChangedField%d_Name\", i)] = change.FieldName\n\t\t\tmetadata[fmt.Sprintf(\"ChangedField%d_To\", i)] = change.ChangedTo\n\t\t}\n\n\t\tresource := JiraResource{\n\t\t\tID:       fmt.Sprintf(\"%d\", record.ID),\n\t\t\tName:     record.Summary,\n\t\t\tType:     ResourceTypeAuditRecord,\n\t\t\tMetadata: metadata,\n\t\t}\n\n\t\tsecretInfo.appendResource(resource, ResourceTypeAuditRecord)\n\t}\n\n\treturn nil\n}\n\nfunc handleStatusCode(statusCode int, endpoint string) error {\n\tswitch {\n\tcase statusCode == http.StatusOK:\n\t\treturn nil\n\tcase statusCode == http.StatusBadRequest:\n\t\treturn fmt.Errorf(\"bad request for API: %s\", endpoint)\n\tcase statusCode == http.StatusUnauthorized, statusCode == http.StatusForbidden,\n\t\tstatusCode == http.StatusNotFound, statusCode == http.StatusConflict:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d for API: %s\", statusCode, endpoint)\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/jira/result_output.json",
    "content": "{\n    \"AnalyzerType\": 42,\n    \"Bindings\": [\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10000\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"development\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Includes development summary panel information used in JQL\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Dev Summary Custom Field\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"development\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10001\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Team\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Team\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Team\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10002\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Organizations\",\n            \"ChangedField1_Name\": \"Description\",\n            \"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.\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Organizations\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Organizations\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10003\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Approvers\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Contains users needed for approval. This custom field was created by Jira Service Desk.\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"User Picker (multiple users)\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Approvers\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10004\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Impact\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Select List (single choice)\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Impact\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10005\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Change type\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Select List (single choice)\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Change type\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10006\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Change risk\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Select List (single choice)\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Change risk\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10007\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Change reason\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Choose the reason for the change request\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Select List (single choice)\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Change reason\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10008\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Actual start\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Enter when the change actually started.\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Date Time Picker\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Actual start\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10009\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Actual end\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Enter when the change actually ended.\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Date Time Picker\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Actual end\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10010\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Request Type\",\n            \"ChangedField1_Name\": \"Description\",\n            \"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.\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Customer Request Type Custom Field\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Request Type\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10011\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Epic Name\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Provide a short name to identify this epic.\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Name of Epic\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Epic Name\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10012\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Epic Status\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Epic Status field for Jira Software use only.\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Status of Epic\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Epic Status\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10013\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Epic Color\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Epic Color field for Jira Software use only.\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Color of Epic\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Epic Color\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10014\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Epic Link\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Choose an epic to assign this issue to.\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Epic Link Relationship\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Epic Link\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10015\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Start date\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Allows the planned start date for a piece of work to be set.\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Date Picker\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Start date\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10016\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Story point estimate\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Measurement of complexity and/or size of a requirement.\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Number Field\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Story point estimate\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10017\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Issue color\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Issue color\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Issue color\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10018\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Parent Link\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Parent Link\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Parent Link\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field updated\",\n          \"FullyQualifiedName\": \"AuditRecord/10019\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Parent Link\",\n            \"Summary\": \"Custom field updated\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field updated\",\n          \"FullyQualifiedName\": \"AuditRecord/10020\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Story point estimate\",\n            \"Summary\": \"Custom field updated\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10021\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Rank\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Global rank field for Jira Software use only.\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Global Rank\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Rank\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10022\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Sprint\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Jira Software sprint field\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Jira Sprint Field\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Sprint\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10023\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Flagged\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Allows to flag issues with impediments.\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Checkboxes\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Flagged\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field context created\",\n          \"FullyQualifiedName\": \"AuditRecord/10024\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"custom field context\",\n            \"ChangedField0_Name\": \"Associated to project(s)\",\n            \"ChangedField0_To\": \"All projects\",\n            \"ChangedField1_Name\": \"Associated to issue type(s)\",\n            \"ChangedField1_To\": \"All issue types\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"customfield_10022\",\n            \"Summary\": \"Field context created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10025\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Target start\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"The targeted start date. This custom field is created and required by Advanced Roadmaps for Jira.\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Target start\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Target start\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field context created\",\n          \"FullyQualifiedName\": \"AuditRecord/10026\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"custom field context\",\n            \"ChangedField0_Name\": \"Associated to project(s)\",\n            \"ChangedField0_To\": \"All projects\",\n            \"ChangedField1_Name\": \"Associated to issue type(s)\",\n            \"ChangedField1_To\": \"All issue types\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"customfield_10023\",\n            \"Summary\": \"Field context created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10027\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Target end\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"The targeted end date. This custom field is created and required by Advanced Roadmaps for Jira.\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Target end\",\n            \"Created\": \"2025-05-05T10:25:48.747+0000\",\n            \"Object\": \"Target end\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Global permission added\",\n          \"FullyQualifiedName\": \"AuditRecord/10028\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"permissions\",\n            \"ChangedField0_Name\": \"Permission\",\n            \"ChangedField0_To\": \"Administer Jira\",\n            \"ChangedField1_Name\": \"Group name\",\n            \"ChangedField1_To\": \"org-admins\",\n            \"ChangedField2_Name\": \"Group\",\n            \"ChangedField2_To\": \"f94760f8-4a5e-49da-ac1e-0d4281e86aa1\",\n            \"Created\": \"2025-05-20T09:11:40.435+0000\",\n            \"Object\": \"Global Permissions\",\n            \"Summary\": \"Global permission added\",\n            \"Type\": \"PERMISSIONS\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Global permission added\",\n          \"FullyQualifiedName\": \"AuditRecord/10029\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"permissions\",\n            \"ChangedField0_Name\": \"Permission\",\n            \"ChangedField0_To\": \"Administer Jira\",\n            \"ChangedField1_Name\": \"Group name\",\n            \"ChangedField1_To\": \"jira-admins-shaider\",\n            \"ChangedField2_Name\": \"Group\",\n            \"ChangedField2_To\": \"e06e77c1-dea1-42ba-a119-ed1189826165\",\n            \"Created\": \"2025-05-20T09:11:40.465+0000\",\n            \"Object\": \"Global Permissions\",\n            \"Summary\": \"Global permission added\",\n            \"Type\": \"PERMISSIONS\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field context created\",\n          \"FullyQualifiedName\": \"AuditRecord/10030\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"custom field context\",\n            \"ChangedField0_Name\": \"Associated to project(s)\",\n            \"ChangedField0_To\": \"All projects\",\n            \"ChangedField1_Name\": \"Associated to issue type(s)\",\n            \"ChangedField1_To\": \"All issue types\",\n            \"Created\": \"2025-05-20T09:11:41.700+0000\",\n            \"Object\": \"customfield_10026\",\n            \"Summary\": \"Field context created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10031\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"[CHART] Date of First Response\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Date of First Response\",\n            \"Created\": \"2025-05-20T09:11:41.715+0000\",\n            \"Object\": \"[CHART] Date of First Response\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field context created\",\n          \"FullyQualifiedName\": \"AuditRecord/10032\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"custom field context\",\n            \"ChangedField0_Name\": \"Associated to project(s)\",\n            \"ChangedField0_To\": \"All projects\",\n            \"ChangedField1_Name\": \"Associated to issue type(s)\",\n            \"ChangedField1_To\": \"All issue types\",\n            \"Created\": \"2025-05-20T09:11:41.812+0000\",\n            \"Object\": \"customfield_10027\",\n            \"Summary\": \"Field context created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10033\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"[CHART] Time in Status\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Time in Status\",\n            \"Created\": \"2025-05-20T09:11:41.830+0000\",\n            \"Object\": \"[CHART] Time in Status\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field context created\",\n          \"FullyQualifiedName\": \"AuditRecord/10034\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"custom field context\",\n            \"ChangedField0_Name\": \"Associated to project(s)\",\n            \"ChangedField0_To\": \"All projects\",\n            \"ChangedField1_Name\": \"Associated to issue type(s)\",\n            \"ChangedField1_To\": \"All issue types\",\n            \"Created\": \"2025-05-20T09:11:43.498+0000\",\n            \"Object\": \"customfield_10029\",\n            \"Summary\": \"Field context created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field context created\",\n          \"FullyQualifiedName\": \"AuditRecord/10035\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"custom field context\",\n            \"ChangedField0_Name\": \"Associated to project(s)\",\n            \"ChangedField0_To\": \"All projects\",\n            \"ChangedField1_Name\": \"Associated to issue type(s)\",\n            \"ChangedField1_To\": \"All issue types\",\n            \"Created\": \"2025-05-20T09:11:43.499+0000\",\n            \"Object\": \"customfield_10031\",\n            \"Summary\": \"Field context created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field context created\",\n          \"FullyQualifiedName\": \"AuditRecord/10036\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"custom field context\",\n            \"ChangedField0_Name\": \"Associated to project(s)\",\n            \"ChangedField0_To\": \"All projects\",\n            \"ChangedField1_Name\": \"Associated to issue type(s)\",\n            \"ChangedField1_To\": \"All issue types\",\n            \"Created\": \"2025-05-20T09:11:43.499+0000\",\n            \"Object\": \"customfield_10028\",\n            \"Summary\": \"Field context created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field context created\",\n          \"FullyQualifiedName\": \"AuditRecord/10037\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"custom field context\",\n            \"ChangedField0_Name\": \"Associated to project(s)\",\n            \"ChangedField0_To\": \"All projects\",\n            \"ChangedField1_Name\": \"Associated to issue type(s)\",\n            \"ChangedField1_To\": \"All issue types\",\n            \"Created\": \"2025-05-20T09:11:43.499+0000\",\n            \"Object\": \"customfield_10030\",\n            \"Summary\": \"Field context created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10038\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Open forms\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"The number of open forms on the issue\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Open forms\",\n            \"Created\": \"2025-05-20T09:11:43.520+0000\",\n            \"Object\": \"Open forms\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10039\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Submitted forms\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"The number of submitted forms on the issue\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Submitted forms\",\n            \"Created\": \"2025-05-20T09:11:43.520+0000\",\n            \"Object\": \"Submitted forms\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10040\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Total forms\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"The total number of forms on the issue\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Total forms\",\n            \"Created\": \"2025-05-20T09:11:43.523+0000\",\n            \"Object\": \"Total forms\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10041\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Locked forms\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"The number of locked forms on the issue\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Locked forms\",\n            \"Created\": \"2025-05-20T09:11:43.536+0000\",\n            \"Object\": \"Locked forms\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Issue type created\",\n          \"FullyQualifiedName\": \"AuditRecord/10042\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"issue types\",\n            \"Created\": \"2025-05-20T09:12:06.211+0000\",\n            \"Object\": \"Epic\",\n            \"Summary\": \"Issue type created\",\n            \"Type\": \"ISSUE_TYPE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Issue type created\",\n          \"FullyQualifiedName\": \"AuditRecord/10043\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"issue types\",\n            \"Created\": \"2025-05-20T09:12:06.332+0000\",\n            \"Object\": \"Subtask\",\n            \"Summary\": \"Issue type created\",\n            \"Type\": \"ISSUE_TYPE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Issue type created\",\n          \"FullyQualifiedName\": \"AuditRecord/10044\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"issue types\",\n            \"Created\": \"2025-05-20T09:12:06.400+0000\",\n            \"Object\": \"Task\",\n            \"Summary\": \"Issue type created\",\n            \"Type\": \"ISSUE_TYPE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Issue type created\",\n          \"FullyQualifiedName\": \"AuditRecord/10045\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"issue types\",\n            \"Created\": \"2025-05-20T09:12:06.468+0000\",\n            \"Object\": \"Story\",\n            \"Summary\": \"Issue type created\",\n            \"Type\": \"ISSUE_TYPE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10046\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"development\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Includes development summary panel information used in JQL\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Dev Summary Custom Field\",\n            \"Created\": \"2025-05-20T09:12:09.092+0000\",\n            \"Object\": \"development\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10047\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Design\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Custom field that stores design information for JQL\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Design\",\n            \"Created\": \"2025-05-20T09:12:09.180+0000\",\n            \"Object\": \"Design\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10048\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Vulnerability\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Custom field that stores vulnerability information for JQL\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Vulnerability\",\n            \"Created\": \"2025-05-20T09:12:09.257+0000\",\n            \"Object\": \"Vulnerability\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Group created\",\n          \"FullyQualifiedName\": \"AuditRecord/10049\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:12:11.379+0000\",\n            \"Object\": \"jira-users-shaider\",\n            \"Summary\": \"Group created\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Group created\",\n          \"FullyQualifiedName\": \"AuditRecord/10050\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:12:11.397+0000\",\n            \"Object\": \"org-admins\",\n            \"Summary\": \"Group created\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Group created\",\n          \"FullyQualifiedName\": \"AuditRecord/10051\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:12:11.399+0000\",\n            \"Object\": \"atlassian-addons-admin\",\n            \"Summary\": \"Group created\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Group created\",\n          \"FullyQualifiedName\": \"AuditRecord/10052\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:12:11.408+0000\",\n            \"Object\": \"jira-admins-shaider\",\n            \"Summary\": \"Group created\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Group created\",\n          \"FullyQualifiedName\": \"AuditRecord/10053\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:12:11.408+0000\",\n            \"Object\": \"jira-user-access-admins-shaider\",\n            \"Summary\": \"Group created\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Group created\",\n          \"FullyQualifiedName\": \"AuditRecord/10054\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:12:11.414+0000\",\n            \"Object\": \"system-administrators\",\n            \"Summary\": \"Group created\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10055\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"d1e68c57-d711-4b14-a2c2-ef4bd13e60b9\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:12:11.414+0000\",\n            \"Object\": \"jira-users-shaider\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10056\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"d1e68c57-d711-4b14-a2c2-ef4bd13e60b9\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:12:11.407+0000\",\n            \"Object\": \"org-admins\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10057\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"d1e68c57-d711-4b14-a2c2-ef4bd13e60b9\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T09:12:11.405+0000\",\n            \"Object\": \"d1e68c57-d711-4b14-a2c2-ef4bd13e60b9\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Workflow created\",\n          \"FullyQualifiedName\": \"AuditRecord/10058\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"workflows\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Software workflow for project SCRUM\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"\",\n            \"Created\": \"2025-05-20T09:12:11.729+0000\",\n            \"Object\": \"Software workflow for project SCRUM\",\n            \"Summary\": \"Workflow created\",\n            \"Type\": \"WORKFLOW\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10059\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"My Scrum Project\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"ChangedField0_To\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Created\": \"2025-05-20T09:12:14.634+0000\",\n            \"Object\": \"Administrator\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Sprint created\",\n          \"FullyQualifiedName\": \"AuditRecord/10060\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"sprints\",\n            \"Created\": \"2025-05-20T09:12:19.817+0000\",\n            \"Object\": \"SCRUM Sprint 1\",\n            \"Summary\": \"Sprint created\",\n            \"Type\": \"SPRINT\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field removed from Screen\",\n          \"FullyQualifiedName\": \"AuditRecord/10061\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"screens\",\n            \"ChangedField0_Name\": \"Field Removed\",\n            \"ChangedField0_To\": \"\",\n            \"Created\": \"2025-05-20T09:12:20.260+0000\",\n            \"Object\": \"SCRUM-Story\",\n            \"Summary\": \"Field removed from Screen\",\n            \"Type\": \"SCREEN\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field updated in Screen\",\n          \"FullyQualifiedName\": \"AuditRecord/10062\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"screens\",\n            \"ChangedField0_Name\": \"Field Updated\",\n            \"ChangedField0_To\": \"Resolution,Reporter,Summary,Flagged,Labels,Attachment,Restrict to,Rank,Assignee,Story point estimate,Linked Issues,Issue Type,Team,Sprint,Description,development,Parent\",\n            \"Created\": \"2025-05-20T09:12:20.282+0000\",\n            \"Object\": \"SCRUM-Story\",\n            \"Summary\": \"Field updated in Screen\",\n            \"Type\": \"SCREEN\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field removed from Screen\",\n          \"FullyQualifiedName\": \"AuditRecord/10063\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"screens\",\n            \"ChangedField0_Name\": \"Field Removed\",\n            \"ChangedField0_To\": \"\",\n            \"Created\": \"2025-05-20T09:12:20.363+0000\",\n            \"Object\": \"SCRUM-Task\",\n            \"Summary\": \"Field removed from Screen\",\n            \"Type\": \"SCREEN\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field updated in Screen\",\n          \"FullyQualifiedName\": \"AuditRecord/10064\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"screens\",\n            \"ChangedField0_Name\": \"Field Updated\",\n            \"ChangedField0_To\": \"Flagged,Issue Type,Labels,Attachment,Team,Rank,Story point estimate,development,Parent,Reporter,Restrict to,Description,Assignee,Summary,Linked Issues,Resolution,Sprint\",\n            \"Created\": \"2025-05-20T09:12:20.383+0000\",\n            \"Object\": \"SCRUM-Task\",\n            \"Summary\": \"Field updated in Screen\",\n            \"Type\": \"SCREEN\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field removed from Screen\",\n          \"FullyQualifiedName\": \"AuditRecord/10065\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"screens\",\n            \"ChangedField0_Name\": \"Field Removed\",\n            \"ChangedField0_To\": \"\",\n            \"Created\": \"2025-05-20T09:12:20.464+0000\",\n            \"Object\": \"SCRUM - Subtask\",\n            \"Summary\": \"Field removed from Screen\",\n            \"Type\": \"SCREEN\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field updated in Screen\",\n          \"FullyQualifiedName\": \"AuditRecord/10066\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"screens\",\n            \"ChangedField0_Name\": \"Field Updated\",\n            \"ChangedField0_To\": \"Labels,Attachment,Restrict to,Assignee,Linked Issues,Issue Type,Sprint,development,Rank,Reporter,Summary,Flagged,Story point estimate,Description,Team,Parent,Resolution\",\n            \"Created\": \"2025-05-20T09:12:20.478+0000\",\n            \"Object\": \"SCRUM - Subtask\",\n            \"Summary\": \"Field updated in Screen\",\n            \"Type\": \"SCREEN\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field configuration scheme updated\",\n          \"FullyQualifiedName\": \"AuditRecord/10067\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Issue Type\",\n            \"ChangedField0_To\": \"Task\",\n            \"ChangedField1_Name\": \"Field Configuration\",\n            \"ChangedField1_To\": \"LEARNJIRA-10005\",\n            \"Created\": \"2025-05-20T09:12:28.876+0000\",\n            \"Object\": \"Field Configuration Scheme for Project LEARNJIRA\",\n            \"Summary\": \"Field configuration scheme updated\",\n            \"Type\": \"SCHEME\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Issue type created\",\n          \"FullyQualifiedName\": \"AuditRecord/10068\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"issue types\",\n            \"Created\": \"2025-05-20T09:12:29.155+0000\",\n            \"Object\": \"Task\",\n            \"Summary\": \"Issue type created\",\n            \"Type\": \"ISSUE_TYPE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Workflow created\",\n          \"FullyQualifiedName\": \"AuditRecord/10069\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"workflows\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Software workflow for project 10001\",\n            \"Created\": \"2025-05-20T09:12:29.792+0000\",\n            \"Object\": \"Software workflow for project 10001\",\n            \"Summary\": \"Workflow created\",\n            \"Type\": \"WORKFLOW\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field configuration scheme updated\",\n          \"FullyQualifiedName\": \"AuditRecord/10070\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Issue Type\",\n            \"ChangedField0_To\": \"Epic\",\n            \"ChangedField1_Name\": \"Field Configuration\",\n            \"ChangedField1_To\": \"LEARNJIRA-10006\",\n            \"Created\": \"2025-05-20T09:12:30.456+0000\",\n            \"Object\": \"Field Configuration Scheme for Project LEARNJIRA\",\n            \"Summary\": \"Field configuration scheme updated\",\n            \"Type\": \"SCHEME\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Issue type created\",\n          \"FullyQualifiedName\": \"AuditRecord/10071\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"issue types\",\n            \"Created\": \"2025-05-20T09:12:30.746+0000\",\n            \"Object\": \"Epic\",\n            \"Summary\": \"Issue type created\",\n            \"Type\": \"ISSUE_TYPE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field configuration scheme updated\",\n          \"FullyQualifiedName\": \"AuditRecord/10072\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Issue Type\",\n            \"ChangedField0_To\": \"Subtask\",\n            \"ChangedField1_Name\": \"Field Configuration\",\n            \"ChangedField1_To\": \"LEARNJIRA-10007\",\n            \"Created\": \"2025-05-20T09:12:31.363+0000\",\n            \"Object\": \"Field Configuration Scheme for Project LEARNJIRA\",\n            \"Summary\": \"Field configuration scheme updated\",\n            \"Type\": \"SCHEME\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Issue type created\",\n          \"FullyQualifiedName\": \"AuditRecord/10073\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"issue types\",\n            \"Created\": \"2025-05-20T09:12:31.461+0000\",\n            \"Object\": \"Subtask\",\n            \"Summary\": \"Issue type created\",\n            \"Type\": \"ISSUE_TYPE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project created\",\n          \"FullyQualifiedName\": \"AuditRecord/10074\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"d1e68c57-d711-4b14-a2c2-ef4bd13e60b9\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n            \"ChangedField1_Name\": \"Key\",\n            \"ChangedField1_To\": \"LEARNJIRA\",\n            \"ChangedField2_Name\": \"Description\",\n            \"ChangedField2_To\": \"\",\n            \"ChangedField3_Name\": \"Project lead\",\n            \"ChangedField3_To\": \"d1e68c57-d711-4b14-a2c2-ef4bd13e60b9\",\n            \"ChangedField4_Name\": \"Default Assignee\",\n            \"ChangedField4_To\": \"Unassigned\",\n            \"Created\": \"2025-05-20T09:12:31.995+0000\",\n            \"Object\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n            \"Summary\": \"Project created\",\n            \"Type\": \"PROJECT\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10075\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Story Points\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Measurement of complexity and/or size of a requirement.\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Number Field\",\n            \"Created\": \"2025-05-20T09:12:34.016+0000\",\n            \"Object\": \"Story Points\",\n            \"RemoteAddress\": \"10.20.110.172\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10076\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"ChangedField0_To\": \"60e5a86a471e61006a4c51fd\",\n            \"Created\": \"2025-05-20T09:16:46.236+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10077\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"My Scrum Project\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"ChangedField0_To\": \"60e5a86a471e61006a4c51fd\",\n            \"Created\": \"2025-05-20T09:16:46.289+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10078\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"60e5a86a471e61006a4c51fd\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:16:46.636+0000\",\n            \"Object\": \"jira-users-shaider\",\n            \"RemoteAddress\": \"10.26.66.143\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10079\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"ChangedField0_To\": \"557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 60e5a86a471e61006a4c51fd\",\n            \"Created\": \"2025-05-20T09:16:49.651+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10080\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"My Scrum Project\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"ChangedField0_To\": \"557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 60e5a86a471e61006a4c51fd\",\n            \"Created\": \"2025-05-20T09:16:49.683+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10081\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:16:50.382+0000\",\n            \"Object\": \"jira-users-shaider\",\n            \"RemoteAddress\": \"10.26.66.143\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10082\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"ChangedField0_To\": \"5b6c7b3afbc68529c6c47967, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 60e5a86a471e61006a4c51fd\",\n            \"Created\": \"2025-05-20T09:16:52.052+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10083\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"My Scrum Project\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"ChangedField0_To\": \"557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5b6c7b3afbc68529c6c47967, 60e5a86a471e61006a4c51fd\",\n            \"Created\": \"2025-05-20T09:16:52.107+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10084\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"5b6c7b3afbc68529c6c47967\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:16:52.761+0000\",\n            \"Object\": \"jira-users-shaider\",\n            \"RemoteAddress\": \"10.26.66.143\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10085\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"ChangedField0_To\": \"5b6c7b3afbc68529c6c47967, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 60e5a86a471e61006a4c51fd, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077\",\n            \"Created\": \"2025-05-20T09:16:55.592+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10086\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"My Scrum Project\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"ChangedField0_To\": \"557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5b6c7b3afbc68529c6c47967, 60e5a86a471e61006a4c51fd, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077\",\n            \"Created\": \"2025-05-20T09:16:55.622+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10087\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"557058:f58131cb-b67d-43c7-b30d-6b58d40bd077\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:16:56.370+0000\",\n            \"Object\": \"jira-admins-shaider\",\n            \"RemoteAddress\": \"10.16.132.66\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10088\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"557058:f58131cb-b67d-43c7-b30d-6b58d40bd077\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:16:56.411+0000\",\n            \"Object\": \"jira-users-shaider\",\n            \"RemoteAddress\": \"10.16.132.66\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10089\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"ChangedField0_To\": \"5b6c7b3afbc68529c6c47967, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 60e5a86a471e61006a4c51fd, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077, 5d53f3cbc6b9320d9ea5bdc2\",\n            \"Created\": \"2025-05-20T09:16:57.962+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10090\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"My Scrum Project\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"ChangedField0_To\": \"557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5b6c7b3afbc68529c6c47967, 60e5a86a471e61006a4c51fd, 5d53f3cbc6b9320d9ea5bdc2, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077\",\n            \"Created\": \"2025-05-20T09:16:57.988+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10091\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"5d53f3cbc6b9320d9ea5bdc2\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:16:58.595+0000\",\n            \"Object\": \"jira-users-shaider\",\n            \"RemoteAddress\": \"10.26.105.37\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10092\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"ChangedField0_To\": \"5b6c7b3afbc68529c6c47967, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 60e5a86a471e61006a4c51fd, 5cb4ae0e4b97ab11a18e00c7, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077, 5d53f3cbc6b9320d9ea5bdc2\",\n            \"Created\": \"2025-05-20T09:17:00.720+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10093\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"My Scrum Project\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"ChangedField0_To\": \"5cb4ae0e4b97ab11a18e00c7, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5b6c7b3afbc68529c6c47967, 60e5a86a471e61006a4c51fd, 5d53f3cbc6b9320d9ea5bdc2, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077\",\n            \"Created\": \"2025-05-20T09:17:00.751+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10094\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"5cb4ae0e4b97ab11a18e00c7\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:17:01.225+0000\",\n            \"Object\": \"jira-admins-shaider\",\n            \"RemoteAddress\": \"10.16.132.66\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10095\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"5cb4ae0e4b97ab11a18e00c7\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:17:01.927+0000\",\n            \"Object\": \"jira-users-shaider\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10096\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"ChangedField0_To\": \"5b6c7b3afbc68529c6c47967, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5cb4ae0e4b97ab11a18e00c7, 60e5a86a471e61006a4c51fd, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077, 5d53f3cbc6b9320d9ea5bdc2, 557058:0867a421-a9ee-4659-801a-bc0ee4a4487e\",\n            \"Created\": \"2025-05-20T09:17:03.279+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10097\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"My Scrum Project\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"ChangedField0_To\": \"5cb4ae0e4b97ab11a18e00c7, 557058:0867a421-a9ee-4659-801a-bc0ee4a4487e, 557058:214cdd6a-ff93-4d8b-838b-62dfcf1a2a71, 5b6c7b3afbc68529c6c47967, 60e5a86a471e61006a4c51fd, 5d53f3cbc6b9320d9ea5bdc2, 557058:f58131cb-b67d-43c7-b30d-6b58d40bd077\",\n            \"Created\": \"2025-05-20T09:17:03.306+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10098\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"557058:0867a421-a9ee-4659-801a-bc0ee4a4487e\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:17:03.773+0000\",\n            \"Object\": \"jira-users-shaider\",\n            \"RemoteAddress\": \"10.26.105.37\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10099\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"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\",\n            \"Created\": \"2025-05-20T09:17:06.320+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10100\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"My Scrum Project\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"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\",\n            \"Created\": \"2025-05-20T09:17:06.357+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10101\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"a71b105a-ea02-4b84-a162-64b8abccda81\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:17:06.383+0000\",\n            \"Object\": \"atlassian-addons-admin\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10102\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"a71b105a-ea02-4b84-a162-64b8abccda81\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T09:17:06.386+0000\",\n            \"Object\": \"a71b105a-ea02-4b84-a162-64b8abccda81\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10103\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"a71b105a-ea02-4b84-a162-64b8abccda81\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:17:06.821+0000\",\n            \"Object\": \"jira-users-shaider\",\n            \"RemoteAddress\": \"10.26.72.216\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10104\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"a71b105a-ea02-4b84-a162-64b8abccda81\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:17:07.034+0000\",\n            \"Object\": \"jira-admins-shaider\",\n            \"RemoteAddress\": \"10.16.132.66\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10105\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"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\",\n            \"Created\": \"2025-05-20T09:17:08.228+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10106\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"My Scrum Project\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"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\",\n            \"Created\": \"2025-05-20T09:17:08.256+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10107\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"c6a62781-e4f9-484c-baa8-0ee189f25039\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T09:17:08.450+0000\",\n            \"Object\": \"c6a62781-e4f9-484c-baa8-0ee189f25039\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10108\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"c6a62781-e4f9-484c-baa8-0ee189f25039\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:17:09.139+0000\",\n            \"Object\": \"jira-users-shaider\",\n            \"RemoteAddress\": \"10.26.66.143\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10109\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"e89f88c7-0ba9-4908-b2cc-b45f11ec5ca7\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T09:17:11.866+0000\",\n            \"Object\": \"e89f88c7-0ba9-4908-b2cc-b45f11ec5ca7\",\n            \"RemoteAddress\": \"10.26.72.216\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10110\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"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\",\n            \"Created\": \"2025-05-20T09:17:11.960+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10111\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"My Scrum Project\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"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\",\n            \"Created\": \"2025-05-20T09:17:11.992+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User removed from group\",\n          \"FullyQualifiedName\": \"AuditRecord/10112\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"e89f88c7-0ba9-4908-b2cc-b45f11ec5ca7\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:17:12.169+0000\",\n            \"Object\": \"atlassian-addons-admin\",\n            \"Summary\": \"User removed from group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10113\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"e89f88c7-0ba9-4908-b2cc-b45f11ec5ca7\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T09:17:12.191+0000\",\n            \"Object\": \"e89f88c7-0ba9-4908-b2cc-b45f11ec5ca7\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10114\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"e89f88c7-0ba9-4908-b2cc-b45f11ec5ca7\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:17:12.446+0000\",\n            \"Object\": \"atlassian-addons-admin\",\n            \"RemoteAddress\": \"10.16.132.66\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10115\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"e89f88c7-0ba9-4908-b2cc-b45f11ec5ca7\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:17:12.511+0000\",\n            \"Object\": \"jira-admins-shaider\",\n            \"RemoteAddress\": \"10.16.132.66\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10116\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"e89f88c7-0ba9-4908-b2cc-b45f11ec5ca7\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:17:12.644+0000\",\n            \"Object\": \"jira-users-shaider\",\n            \"RemoteAddress\": \"10.26.66.143\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10117\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"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\",\n            \"Created\": \"2025-05-20T09:17:14.744+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10118\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"My Scrum Project\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"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\",\n            \"Created\": \"2025-05-20T09:17:14.775+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10119\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"5cf112d31552030f1e3a5905\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:17:15.565+0000\",\n            \"Object\": \"jira-users-shaider\",\n            \"RemoteAddress\": \"10.16.149.189\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10120\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"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\",\n            \"Created\": \"2025-05-20T09:17:18.574+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Project roles changed\",\n          \"FullyQualifiedName\": \"AuditRecord/10121\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"My Scrum Project\",\n            \"AssociatedItem0_Type\": \"PROJECT\",\n            \"Category\": \"projects\",\n            \"ChangedField0_Name\": \"Users\",\n            \"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\",\n            \"Created\": \"2025-05-20T09:17:18.605+0000\",\n            \"Object\": \"atlassian-addons-project-access\",\n            \"Summary\": \"Project roles changed\",\n            \"Type\": \"PROJECT_ROLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10122\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"630db2cd9796033b256bc349\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:17:19.009+0000\",\n            \"Object\": \"jira-admins-shaider\",\n            \"RemoteAddress\": \"10.26.66.143\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10123\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"630db2cd9796033b256bc349\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-20T09:17:19.052+0000\",\n            \"Object\": \"jira-users-shaider\",\n            \"RemoteAddress\": \"10.16.132.66\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field context created\",\n          \"FullyQualifiedName\": \"AuditRecord/10124\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"custom field context\",\n            \"ChangedField0_Name\": \"Associated to project(s)\",\n            \"ChangedField0_To\": \"All projects\",\n            \"ChangedField1_Name\": \"Associated to issue type(s)\",\n            \"ChangedField1_To\": \"All issue types\",\n            \"Created\": \"2025-05-20T09:17:19.186+0000\",\n            \"Object\": \"com.atlassian.atlas.jira__project-key\",\n            \"Summary\": \"Field context created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10125\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Project overview key\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Key of project overview connected via Atlassian Home for Jira Cloud\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Project overview key\",\n            \"Created\": \"2025-05-20T09:17:19.206+0000\",\n            \"Object\": \"Project overview key\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Field context created\",\n          \"FullyQualifiedName\": \"AuditRecord/10126\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"custom field context\",\n            \"ChangedField0_Name\": \"Associated to project(s)\",\n            \"ChangedField0_To\": \"All projects\",\n            \"ChangedField1_Name\": \"Associated to issue type(s)\",\n            \"ChangedField1_To\": \"All issue types\",\n            \"Created\": \"2025-05-20T09:17:19.372+0000\",\n            \"Object\": \"com.atlassian.atlas.jira__project-status\",\n            \"Summary\": \"Field context created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Custom field created\",\n          \"FullyQualifiedName\": \"AuditRecord/10127\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"Category\": \"fields\",\n            \"ChangedField0_Name\": \"Name\",\n            \"ChangedField0_To\": \"Project overview status\",\n            \"ChangedField1_Name\": \"Description\",\n            \"ChangedField1_To\": \"Status of project overview connected via Atlassian Home for Jira Cloud\",\n            \"ChangedField2_Name\": \"Type\",\n            \"ChangedField2_To\": \"Project overview status\",\n            \"Created\": \"2025-05-20T09:17:19.389+0000\",\n            \"Object\": \"Project overview status\",\n            \"Summary\": \"Custom field created\",\n            \"Type\": \"CUSTOM_FIELD\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10160\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"cbefd655-3c82-41d7-993b-1e39524f1a70\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:16.406+0000\",\n            \"Object\": \"cbefd655-3c82-41d7-993b-1e39524f1a70\",\n            \"RemoteAddress\": \"10.26.109.230\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10161\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"127e0d88-4090-4621-9ba6-e821b3337030\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:18.580+0000\",\n            \"Object\": \"127e0d88-4090-4621-9ba6-e821b3337030\",\n            \"RemoteAddress\": \"10.26.69.20\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10162\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"fd5c50d8-5ffe-45a5-8f0f-710d02cfd080\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:19.363+0000\",\n            \"Object\": \"fd5c50d8-5ffe-45a5-8f0f-710d02cfd080\",\n            \"RemoteAddress\": \"10.26.124.174\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10163\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"c0183bc4-5673-42a3-a769-1c91715c92c6\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:20.260+0000\",\n            \"Object\": \"c0183bc4-5673-42a3-a769-1c91715c92c6\",\n            \"RemoteAddress\": \"10.26.124.174\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10164\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"08251f11-274a-43e5-8672-dd60e972654a\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:21.481+0000\",\n            \"Object\": \"08251f11-274a-43e5-8672-dd60e972654a\",\n            \"RemoteAddress\": \"10.16.140.214\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10165\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"1c0e598f-5a5e-49bf-b0aa-6251ac808027\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:22.698+0000\",\n            \"Object\": \"1c0e598f-5a5e-49bf-b0aa-6251ac808027\",\n            \"RemoteAddress\": \"10.26.124.174\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10166\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"72f57329-34af-40e2-a217-40128d049fa9\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:24.162+0000\",\n            \"Object\": \"72f57329-34af-40e2-a217-40128d049fa9\",\n            \"RemoteAddress\": \"10.26.72.252\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10167\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"b73e79da-3732-42dc-98f8-5cfe2765fbcf\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:25.105+0000\",\n            \"Object\": \"b73e79da-3732-42dc-98f8-5cfe2765fbcf\",\n            \"RemoteAddress\": \"10.26.72.223\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10168\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"dd62e8db-f985-461c-8957-630cba36dca3\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:25.787+0000\",\n            \"Object\": \"dd62e8db-f985-461c-8957-630cba36dca3\",\n            \"RemoteAddress\": \"10.26.69.20\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10169\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"95850660-2865-498a-a78c-ea40add0f257\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:27.277+0000\",\n            \"Object\": \"95850660-2865-498a-a78c-ea40add0f257\",\n            \"RemoteAddress\": \"10.26.124.174\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10170\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"2b683cd5-f6b2-4d41-8383-6987df3c5337\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:28.110+0000\",\n            \"Object\": \"2b683cd5-f6b2-4d41-8383-6987df3c5337\",\n            \"RemoteAddress\": \"10.26.109.230\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10171\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"55e4b181-4a42-45ed-bcc3-0c0576b8a709\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:29.603+0000\",\n            \"Object\": \"55e4b181-4a42-45ed-bcc3-0c0576b8a709\",\n            \"RemoteAddress\": \"10.26.72.252\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10172\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"ec58c96c-2129-4781-81d9-199189807ea5\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:31.305+0000\",\n            \"Object\": \"ec58c96c-2129-4781-81d9-199189807ea5\",\n            \"RemoteAddress\": \"10.16.130.180\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10173\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"ef289a7b-3601-4d0d-903c-eee4a53695b5\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:31.429+0000\",\n            \"Object\": \"ef289a7b-3601-4d0d-903c-eee4a53695b5\",\n            \"RemoteAddress\": \"10.26.69.20\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10174\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"adbe0dd1-5afb-44cb-a662-16151eaa2ee2\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:32.418+0000\",\n            \"Object\": \"adbe0dd1-5afb-44cb-a662-16151eaa2ee2\",\n            \"RemoteAddress\": \"10.26.72.252\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10175\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"491b809f-064b-48a2-9d98-8940cce0fa81\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:34.218+0000\",\n            \"Object\": \"491b809f-064b-48a2-9d98-8940cce0fa81\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10176\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"0b0ad180-899d-4f27-a526-ce558ee2b454\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:34.807+0000\",\n            \"Object\": \"0b0ad180-899d-4f27-a526-ce558ee2b454\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10177\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"ef5f609c-8acd-4cdd-a867-caea2cb04201\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:36.413+0000\",\n            \"Object\": \"ef5f609c-8acd-4cdd-a867-caea2cb04201\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10178\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"a47aa1f1-5663-48cc-ab44-50a0a18f7efd\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:37.039+0000\",\n            \"Object\": \"a47aa1f1-5663-48cc-ab44-50a0a18f7efd\",\n            \"RemoteAddress\": \"10.26.72.252\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10179\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"3696b761-cc6f-481e-9a33-08b3367b7bce\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:38.456+0000\",\n            \"Object\": \"3696b761-cc6f-481e-9a33-08b3367b7bce\",\n            \"RemoteAddress\": \"10.16.142.95\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10180\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"15439766-f128-4fcb-b55a-bce2d3179d6c\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:40.015+0000\",\n            \"Object\": \"15439766-f128-4fcb-b55a-bce2d3179d6c\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10181\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"d4576f35-c3fc-466e-afaa-24dca7167c91\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:40.500+0000\",\n            \"Object\": \"d4576f35-c3fc-466e-afaa-24dca7167c91\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10182\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"73a7eb7c-322b-449c-9a9b-4eca296ac805\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:41.451+0000\",\n            \"Object\": \"73a7eb7c-322b-449c-9a9b-4eca296ac805\",\n            \"RemoteAddress\": \"10.16.140.214\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10183\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"bd41f0b5-f733-47f1-bae7-ab71a923f129\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:42.752+0000\",\n            \"Object\": \"bd41f0b5-f733-47f1-bae7-ab71a923f129\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10184\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"6fbf8958-93f4-4cb4-9bcc-8f3756bcd2c2\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:44.141+0000\",\n            \"Object\": \"6fbf8958-93f4-4cb4-9bcc-8f3756bcd2c2\",\n            \"RemoteAddress\": \"10.26.124.174\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10185\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"06425cbc-036a-4da4-bcb9-659f0e1478cf\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:45.192+0000\",\n            \"Object\": \"06425cbc-036a-4da4-bcb9-659f0e1478cf\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10186\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"6fa0ac8d-61b5-4954-b81f-8d32a9c3ac30\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:45.976+0000\",\n            \"Object\": \"6fa0ac8d-61b5-4954-b81f-8d32a9c3ac30\",\n            \"RemoteAddress\": \"10.26.69.20\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10187\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"3d59bbf5-af46-4cc4-8dd5-7869dd05a0bd\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:47.373+0000\",\n            \"Object\": \"3d59bbf5-af46-4cc4-8dd5-7869dd05a0bd\",\n            \"RemoteAddress\": \"10.26.113.166\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10188\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"e842d31a-bc10-4448-af5f-db6bf6999f7e\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:48.431+0000\",\n            \"Object\": \"e842d31a-bc10-4448-af5f-db6bf6999f7e\",\n            \"RemoteAddress\": \"10.26.124.174\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10189\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"05c895af-5335-46ca-9c2d-2a73e2b360a8\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:49.654+0000\",\n            \"Object\": \"05c895af-5335-46ca-9c2d-2a73e2b360a8\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10190\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"bbba649f-2d48-4661-82c0-dfa3393a3215\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:51.717+0000\",\n            \"Object\": \"bbba649f-2d48-4661-82c0-dfa3393a3215\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10191\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"a21b45de-4a21-4c77-a920-6cb658e0d2d5\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:51.735+0000\",\n            \"Object\": \"a21b45de-4a21-4c77-a920-6cb658e0d2d5\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10192\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"f956eab6-78f6-4bde-918d-23273d2674ec\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:52.550+0000\",\n            \"Object\": \"f956eab6-78f6-4bde-918d-23273d2674ec\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10193\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"f1fb816a-9862-4424-80bc-c6d8503efd96\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:53.870+0000\",\n            \"Object\": \"f1fb816a-9862-4424-80bc-c6d8503efd96\",\n            \"RemoteAddress\": \"10.26.69.20\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10194\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"5788ffcd-e20c-43e5-b4e1-af981de34331\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:55.333+0000\",\n            \"Object\": \"5788ffcd-e20c-43e5-b4e1-af981de34331\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10195\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"b25c7d8e-dee0-4211-8d81-554a49bf29e6\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:56.566+0000\",\n            \"Object\": \"b25c7d8e-dee0-4211-8d81-554a49bf29e6\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10196\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"d6257f38-b615-47ac-ba65-a87cf6a2b862\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:57.923+0000\",\n            \"Object\": \"d6257f38-b615-47ac-ba65-a87cf6a2b862\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10197\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"d651c2e1-ff94-4f17-9238-0b428a652875\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:56:58.830+0000\",\n            \"Object\": \"d651c2e1-ff94-4f17-9238-0b428a652875\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10198\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"cb3435fb-aa28-47bf-b4ec-74639485ccd1\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-20T19:57:00.662+0000\",\n            \"Object\": \"cb3435fb-aa28-47bf-b4ec-74639485ccd1\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Issue type scheme updated\",\n          \"FullyQualifiedName\": \"AuditRecord/10226\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"issue type scheme\",\n            \"Created\": \"2025-05-26T10:19:43.125+0000\",\n            \"Object\": \"Default Issue Type Scheme\",\n            \"RemoteAddress\": \"10.20.41.84\",\n            \"Summary\": \"Issue type scheme updated\",\n            \"Type\": \"ISSUE_TYPE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Issue type created\",\n          \"FullyQualifiedName\": \"AuditRecord/10227\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"issue types\",\n            \"Created\": \"2025-05-26T10:19:43.153+0000\",\n            \"Object\": \"Story\",\n            \"RemoteAddress\": \"10.20.41.84\",\n            \"Summary\": \"Issue type created\",\n            \"Type\": \"ISSUE_TYPE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Sprint created\",\n          \"FullyQualifiedName\": \"AuditRecord/10228\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"sprints\",\n            \"Created\": \"2025-05-26T10:41:38.965+0000\",\n            \"Object\": \"SCRUM Sprint 2\",\n            \"RemoteAddress\": \"10.20.108.78\",\n            \"Summary\": \"Sprint created\",\n            \"Type\": \"SPRINT\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Sprint deleted\",\n          \"FullyQualifiedName\": \"AuditRecord/10229\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"sprints\",\n            \"Created\": \"2025-05-26T10:42:18.259+0000\",\n            \"Object\": \"SCRUM Sprint 2\",\n            \"RemoteAddress\": \"10.20.41.84\",\n            \"Summary\": \"Sprint deleted\",\n            \"Type\": \"SPRINT\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Sprint started\",\n          \"FullyQualifiedName\": \"AuditRecord/10230\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"sprints\",\n            \"Created\": \"2025-05-26T10:44:01.559+0000\",\n            \"Object\": \"SCRUM Sprint 1\",\n            \"RemoteAddress\": \"10.22.111.74\",\n            \"Summary\": \"Sprint started\",\n            \"Type\": \"SPRINT\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Sprint updated\",\n          \"FullyQualifiedName\": \"AuditRecord/10231\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AuthorAccountID\": \"712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n            \"Category\": \"sprints\",\n            \"Created\": \"2025-05-26T10:44:01.564+0000\",\n            \"Object\": \"SCRUM Sprint 1\",\n            \"RemoteAddress\": \"10.22.111.74\",\n            \"Summary\": \"Sprint updated\",\n            \"Type\": \"SPRINT\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User created\",\n          \"FullyQualifiedName\": \"AuditRecord/10259\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"cf5b456d-6059-48dc-995a-04e18681dc21\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"user management\",\n            \"ChangedField0_Name\": \"Active / Inactive\",\n            \"ChangedField0_To\": \"Active\",\n            \"Created\": \"2025-05-29T11:12:19.464+0000\",\n            \"Object\": \"cf5b456d-6059-48dc-995a-04e18681dc21\",\n            \"Summary\": \"User created\",\n            \"Type\": \"USER\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"User added to group\",\n          \"FullyQualifiedName\": \"AuditRecord/10260\",\n          \"Type\": \"AuditRecord\",\n          \"Metadata\": {\n            \"AssociatedItem0_Name\": \"cf5b456d-6059-48dc-995a-04e18681dc21\",\n            \"AssociatedItem0_Type\": \"USER\",\n            \"Category\": \"group management\",\n            \"Created\": \"2025-05-29T11:12:19.567+0000\",\n            \"Object\": \"jira-users-shaider\",\n            \"Summary\": \"User added to group\",\n            \"Type\": \"GROUP\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM board\",\n          \"FullyQualifiedName\": \"Board/1\",\n          \"Type\": \"Board\",\n          \"Metadata\": {\n            \"AvatarURI\": \"https://shaider.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10425?size=small\",\n            \"BoardSelfURL\": \"https://shaider.atlassian.net/rest/agile/1.0/board/1\",\n            \"BoardType\": \"simple\",\n            \"DisplayName\": \"My Scrum Project (SCRUM)\",\n            \"IsPrivate\": \"false\",\n            \"ProjectID\": \"10000\",\n            \"ProjectKey\": \"SCRUM\",\n            \"ProjectName\": \"My Scrum Project\",\n            \"ProjectType\": \"software\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM board\",\n          \"FullyQualifiedName\": \"Board/1\",\n          \"Type\": \"Board\",\n          \"Metadata\": {\n            \"AvatarURI\": \"https://shaider.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10425?size=small\",\n            \"BoardSelfURL\": \"https://shaider.atlassian.net/rest/agile/1.0/board/1\",\n            \"BoardType\": \"simple\",\n            \"DisplayName\": \"My Scrum Project (SCRUM)\",\n            \"IsPrivate\": \"false\",\n            \"ProjectID\": \"10000\",\n            \"ProjectKey\": \"SCRUM\",\n            \"ProjectName\": \"My Scrum Project\",\n            \"ProjectType\": \"software\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"browse_projects\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM board\",\n          \"FullyQualifiedName\": \"Board/1\",\n          \"Type\": \"Board\",\n          \"Metadata\": {\n            \"AvatarURI\": \"https://shaider.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10425?size=small\",\n            \"BoardSelfURL\": \"https://shaider.atlassian.net/rest/agile/1.0/board/1\",\n            \"BoardType\": \"simple\",\n            \"DisplayName\": \"My Scrum Project (SCRUM)\",\n            \"IsPrivate\": \"false\",\n            \"ProjectID\": \"10000\",\n            \"ProjectKey\": \"SCRUM\",\n            \"ProjectName\": \"My Scrum Project\",\n            \"ProjectType\": \"software\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"manage_sprints_permission\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"(Learn) Jira Premium benefits in 5 min\",\n          \"FullyQualifiedName\": \"Board/2\",\n          \"Type\": \"Board\",\n          \"Metadata\": {\n            \"AvatarURI\": \"https://shaider.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10411?size=small\",\n            \"BoardSelfURL\": \"https://shaider.atlassian.net/rest/agile/1.0/board/2\",\n            \"BoardType\": \"simple\",\n            \"DisplayName\": \"(Learn) Jira Premium benefits in 5 min 👋 (LEARNJIRA)\",\n            \"IsPrivate\": \"false\",\n            \"ProjectID\": \"10001\",\n            \"ProjectKey\": \"LEARNJIRA\",\n            \"ProjectName\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n            \"ProjectType\": \"software\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"(Learn) Jira Premium benefits in 5 min\",\n          \"FullyQualifiedName\": \"Board/2\",\n          \"Type\": \"Board\",\n          \"Metadata\": {\n            \"AvatarURI\": \"https://shaider.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10411?size=small\",\n            \"BoardSelfURL\": \"https://shaider.atlassian.net/rest/agile/1.0/board/2\",\n            \"BoardType\": \"simple\",\n            \"DisplayName\": \"(Learn) Jira Premium benefits in 5 min 👋 (LEARNJIRA)\",\n            \"IsPrivate\": \"false\",\n            \"ProjectID\": \"10001\",\n            \"ProjectKey\": \"LEARNJIRA\",\n            \"ProjectName\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n            \"ProjectType\": \"software\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"browse_projects\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"(Learn) Jira Premium benefits in 5 min\",\n          \"FullyQualifiedName\": \"Board/2\",\n          \"Type\": \"Board\",\n          \"Metadata\": {\n            \"AvatarURI\": \"https://shaider.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10411?size=small\",\n            \"BoardSelfURL\": \"https://shaider.atlassian.net/rest/agile/1.0/board/2\",\n            \"BoardType\": \"simple\",\n            \"DisplayName\": \"(Learn) Jira Premium benefits in 5 min 👋 (LEARNJIRA)\",\n            \"IsPrivate\": \"false\",\n            \"ProjectID\": \"10001\",\n            \"ProjectKey\": \"LEARNJIRA\",\n            \"ProjectName\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n            \"ProjectType\": \"software\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"manage_sprints_permission\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"add_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"assign_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"close_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"link_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"manage_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"modify_reporter\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"move_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"resolve_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"schedule_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"set_issue_security\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"transition_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"unarchive_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"view_voters_and_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-2\",\n          \"FullyQualifiedName\": \"Epic/10034\",\n          \"Type\": \"Epic\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Notification Service\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"work_on_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"system-administrators\",\n          \"FullyQualifiedName\": \"Group/183f64ca-3ef5-469a-82cb-a84da8b11cbd\",\n          \"Type\": \"Group\",\n          \"Metadata\": {\n            \"HTML\": \"system-administrators\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"jira-users-shaider\",\n          \"FullyQualifiedName\": \"Group/6b44ab2c-c950-4bd7-8e51-a0e35ea95bce\",\n          \"Type\": \"Group\",\n          \"Metadata\": {\n            \"HTML\": \"jira-users-shaider\",\n            \"Label0_Text\": \"Jira Software\",\n            \"Label0_Title\": \"Users added to this group will be given access to \\u003cstrong\\u003eJira Software\\u003c/strong\\u003e\",\n            \"Label0_Type\": \"SINGLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"jira-user-access-admins-shaider\",\n          \"FullyQualifiedName\": \"Group/7c9b5ee2-fe05-4b6e-9c88-d6d4887caf7c\",\n          \"Type\": \"Group\",\n          \"Metadata\": {\n            \"HTML\": \"jira-user-access-admins-shaider\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"jira-admins-shaider\",\n          \"FullyQualifiedName\": \"Group/e06e77c1-dea1-42ba-a119-ed1189826165\",\n          \"Type\": \"Group\",\n          \"Metadata\": {\n            \"HTML\": \"jira-admins-shaider\",\n            \"Label0_Text\": \"Admin\",\n            \"Label0_Title\": \"Users added to this group will be given administrative access\",\n            \"Label0_Type\": \"ADMIN\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"atlassian-addons-admin\",\n          \"FullyQualifiedName\": \"Group/f04dd022-c413-4790-aed4-0c7b5167ec31\",\n          \"Type\": \"Group\",\n          \"Metadata\": {\n            \"HTML\": \"atlassian-addons-admin\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"org-admins\",\n          \"FullyQualifiedName\": \"Group/f94760f8-4a5e-49da-ac1e-0d4281e86aa1\",\n          \"Type\": \"Group\",\n          \"Metadata\": {\n            \"HTML\": \"org-admins\",\n            \"Label0_Text\": \"Admin\",\n            \"Label0_Title\": \"Users added to this group will be given administrative access\",\n            \"Label0_Type\": \"ADMIN\",\n            \"Label1_Text\": \"Jira Software\",\n            \"Label1_Title\": \"Users added to this group will be given access to \\u003cstrong\\u003eJira Software\\u003c/strong\\u003e\",\n            \"Label1_Type\": \"SINGLE\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"My Scrum Project\",\n          \"FullyQualifiedName\": \"Project/10000\",\n          \"Type\": \"Project\",\n          \"Metadata\": {\n            \"Key\": \"SCRUM\",\n            \"Private\": \"false\",\n            \"TypeKey\": \"software\",\n            \"UUID\": \"6fc39a42-a49a-4ba6-8fe0-d0e23787eb39\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"My Scrum Project\",\n          \"FullyQualifiedName\": \"Project/10000\",\n          \"Type\": \"Project\",\n          \"Metadata\": {\n            \"Key\": \"SCRUM\",\n            \"Private\": \"false\",\n            \"TypeKey\": \"software\",\n            \"UUID\": \"6fc39a42-a49a-4ba6-8fe0-d0e23787eb39\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer_projects\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"My Scrum Project\",\n          \"FullyQualifiedName\": \"Project/10000\",\n          \"Type\": \"Project\",\n          \"Metadata\": {\n            \"Key\": \"SCRUM\",\n            \"Private\": \"false\",\n            \"TypeKey\": \"software\",\n            \"UUID\": \"6fc39a42-a49a-4ba6-8fe0-d0e23787eb39\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"browse_projects\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"My Scrum Project\",\n          \"FullyQualifiedName\": \"Project/10000\",\n          \"Type\": \"Project\",\n          \"Metadata\": {\n            \"Key\": \"SCRUM\",\n            \"Private\": \"false\",\n            \"TypeKey\": \"software\",\n            \"UUID\": \"6fc39a42-a49a-4ba6-8fe0-d0e23787eb39\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_project\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"My Scrum Project\",\n          \"FullyQualifiedName\": \"Project/10000\",\n          \"Type\": \"Project\",\n          \"Metadata\": {\n            \"Key\": \"SCRUM\",\n            \"Private\": \"false\",\n            \"TypeKey\": \"software\",\n            \"UUID\": \"6fc39a42-a49a-4ba6-8fe0-d0e23787eb39\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_issue_layout\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"My Scrum Project\",\n          \"FullyQualifiedName\": \"Project/10000\",\n          \"Type\": \"Project\",\n          \"Metadata\": {\n            \"Key\": \"SCRUM\",\n            \"Private\": \"false\",\n            \"TypeKey\": \"software\",\n            \"UUID\": \"6fc39a42-a49a-4ba6-8fe0-d0e23787eb39\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"view_dev_tools\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n          \"FullyQualifiedName\": \"Project/10001\",\n          \"Type\": \"Project\",\n          \"Metadata\": {\n            \"Key\": \"LEARNJIRA\",\n            \"Private\": \"false\",\n            \"TypeKey\": \"software\",\n            \"UUID\": \"05b93f07-2de4-4a25-82ce-0ec40b666b3b\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n          \"FullyQualifiedName\": \"Project/10001\",\n          \"Type\": \"Project\",\n          \"Metadata\": {\n            \"Key\": \"LEARNJIRA\",\n            \"Private\": \"false\",\n            \"TypeKey\": \"software\",\n            \"UUID\": \"05b93f07-2de4-4a25-82ce-0ec40b666b3b\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer_projects\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n          \"FullyQualifiedName\": \"Project/10001\",\n          \"Type\": \"Project\",\n          \"Metadata\": {\n            \"Key\": \"LEARNJIRA\",\n            \"Private\": \"false\",\n            \"TypeKey\": \"software\",\n            \"UUID\": \"05b93f07-2de4-4a25-82ce-0ec40b666b3b\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"browse_projects\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n          \"FullyQualifiedName\": \"Project/10001\",\n          \"Type\": \"Project\",\n          \"Metadata\": {\n            \"Key\": \"LEARNJIRA\",\n            \"Private\": \"false\",\n            \"TypeKey\": \"software\",\n            \"UUID\": \"05b93f07-2de4-4a25-82ce-0ec40b666b3b\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_project\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n          \"FullyQualifiedName\": \"Project/10001\",\n          \"Type\": \"Project\",\n          \"Metadata\": {\n            \"Key\": \"LEARNJIRA\",\n            \"Private\": \"false\",\n            \"TypeKey\": \"software\",\n            \"UUID\": \"05b93f07-2de4-4a25-82ce-0ec40b666b3b\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_issue_layout\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"(Learn) Jira Premium benefits in 5 min 👋\",\n          \"FullyQualifiedName\": \"Project/10001\",\n          \"Type\": \"Project\",\n          \"Metadata\": {\n            \"Key\": \"LEARNJIRA\",\n            \"Private\": \"false\",\n            \"TypeKey\": \"software\",\n            \"UUID\": \"05b93f07-2de4-4a25-82ce-0ec40b666b3b\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"view_dev_tools\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"add_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"assign_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"close_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"link_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"manage_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"modify_reporter\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"move_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"resolve_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"schedule_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"set_issue_security\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"transition_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"unarchive_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"view_voters_and_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-6\",\n          \"FullyQualifiedName\": \"Story/10038\",\n          \"Type\": \"Story\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Story\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"work_on_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"add_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"assign_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"close_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"link_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"manage_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"modify_reporter\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"move_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"resolve_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"schedule_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"set_issue_security\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"transition_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"unarchive_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"view_voters_and_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-5\",\n          \"FullyQualifiedName\": \"Subtask/10037\",\n          \"Type\": \"Subtask\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"In Progress\",\n            \"Summary\": \"first sub-task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"work_on_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"add_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"assign_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"close_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"link_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"manage_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"modify_reporter\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"move_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"resolve_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"schedule_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"set_issue_security\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"transition_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"unarchive_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"view_voters_and_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-1\",\n          \"FullyQualifiedName\": \"Task/10000\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Security \\u0026 permissions: How to control who can edit or or manage projects 🚧\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"work_on_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"add_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"assign_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"close_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"link_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"manage_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"modify_reporter\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"move_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"resolve_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"schedule_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"set_issue_security\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"transition_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"unarchive_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"view_voters_and_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-2\",\n          \"FullyQualifiedName\": \"Task/10001\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Plans: How to use detailed roadmaps to plan out your work 📍\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"work_on_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"add_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"assign_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"close_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"link_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"manage_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"modify_reporter\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"move_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"resolve_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"schedule_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"set_issue_security\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"transition_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"unarchive_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"view_voters_and_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"LEARNJIRA-3\",\n          \"FullyQualifiedName\": \"Task/10002\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"LEARNJIRA\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Atlassian Intelligence: How to work smarter with AI 🤖\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"work_on_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"add_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"assign_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"close_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"link_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"manage_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"modify_reporter\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"move_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"resolve_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"schedule_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"set_issue_security\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"transition_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"unarchive_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"view_voters_and_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-1\",\n          \"FullyQualifiedName\": \"Task/10033\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"My First Task\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"work_on_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"add_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"assign_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"close_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"link_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"manage_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"modify_reporter\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"move_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"resolve_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"schedule_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"set_issue_security\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"transition_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"unarchive_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"view_voters_and_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-3\",\n          \"FullyQualifiedName\": \"Task/10035\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"backend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"work_on_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"add_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"assign_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"close_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"link_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"manage_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"modify_reporter\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"move_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"resolve_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"schedule_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"set_issue_security\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"transition_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"unarchive_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"view_voters_and_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-4\",\n          \"FullyQualifiedName\": \"Task/10036\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"frontend functionality for Notification feature\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"work_on_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"add_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"administer\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"assign_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"close_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"create_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_attachments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"delete_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_all_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_comments\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"edit_own_worklogs\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"link_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"manage_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"modify_reporter\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"move_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"resolve_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"schedule_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"set_issue_security\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"transition_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"unarchive_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"view_voters_and_watchers\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"SCRUM-7\",\n          \"FullyQualifiedName\": \"Task/10039\",\n          \"Type\": \"Task\",\n          \"Metadata\": {\n            \"Project\": \"SCRUM\",\n            \"Status\": \"To Do\",\n            \"Summary\": \"Issue created via API\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"work_on_issues\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Shahzad Haider\",\n          \"FullyQualifiedName\": \"User/712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n          \"Type\": \"User\",\n          \"Metadata\": {\n            \"AccountType\": \"atlassian\",\n            \"Active\": \"true\",\n            \"Email\": \"shahzadhaider@folio3.com\",\n            \"SelfURL\": \"https://shaider.atlassian.net/rest/api/3/user?accountId=712020:71808a62-5c16-479c-9ebd-35742afb57fa\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"assignable_user\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"Shahzad Haider\",\n          \"FullyQualifiedName\": \"User/712020:71808a62-5c16-479c-9ebd-35742afb57fa\",\n          \"Type\": \"User\",\n          \"Metadata\": {\n            \"AccountType\": \"atlassian\",\n            \"Active\": \"true\",\n            \"Email\": \"shahzadhaider@folio3.com\",\n            \"SelfURL\": \"https://shaider.atlassian.net/rest/api/3/user?accountId=712020:71808a62-5c16-479c-9ebd-35742afb57fa\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"user_picker\",\n          \"Parent\": null\n        }\n      }\n    ],\n    \"UnboundedResources\": null,\n    \"Metadata\": {\n      \n    }\n  }"
  },
  {
    "path": "pkg/analyzer/analyzers/launchdarkly/launchdarkly.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go launchdarkly\npackage launchdarkly\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (a Analyzer) Type() analyzers.AnalyzerType {\n\treturn analyzers.AnalyzerTypeLaunchDarkly\n}\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\t// check if the `key` exist in the credentials info\n\tkey, exist := credInfo[\"key\"]\n\tif !exist {\n\t\treturn nil, errors.New(\"key not found in credentials info\")\n\t}\n\n\tif isSDKKey(key) {\n\t\treturn nil, errors.New(\"sdk keys cannot be analyzed\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, token string) {\n\tif isSDKKey(token) {\n\t\tcolor.Yellow(\"\\n[!] The Provided key is an SDK Key. SDK Keys are sensitive but used to configure LaunchDarkly SDKs\")\n\t\tcolor.Green(\"\\n[i] Docs: https://launchdarkly.com/docs/home/account/environment/settings#copy-and-reset-sdk-credentials-for-an-environment\")\n\n\t\treturn\n\t}\n\n\tinfo, err := AnalyzePermissions(cfg, token)\n\tif err != nil {\n\t\t// just print the error in cli and continue as a partial success\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t}\n\n\tif info == nil {\n\t\tcolor.Red(\"[x] Error : %s\", \"No information found\")\n\t\treturn\n\t}\n\n\tcolor.Green(\"[i] Valid LaunchDarkly Token\\n\")\n\tprintUser(info.User)\n\tprintPermissionsType(info.User.Token)\n\tprintResources(info.Resources)\n\n\tcolor.Yellow(\"\\n[!] Expires: Never\")\n}\n\n// AnalyzePermissions will collect all the scopes assigned to token along with resource it can access\nfunc AnalyzePermissions(cfg *config.Config, token string) (*SecretInfo, error) {\n\t// create the http client\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\tvar secretInfo = &SecretInfo{}\n\n\t// capture user information in secretInfo\n\tif err := CaptureUserInformation(client, token, secretInfo); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to fetch caller identity: %v\", err)\n\t}\n\n\t// capture resources in secretInfo\n\tif err := CaptureResources(client, token, secretInfo); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to fetch resources: %v\", err)\n\t}\n\n\treturn secretInfo, nil\n}\n\n// secretInfoToAnalyzerResult translate secret info to Analyzer Result\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeLaunchDarkly,\n\t\tMetadata:     map[string]any{},\n\t\tBindings:     make([]analyzers.Binding, 0),\n\t}\n\n\t// extract information from resource to create bindings and append to result bindings\n\tfor _, resource := range info.Resources {\n\t\tbinding := analyzers.Binding{\n\t\t\tResource: *secretInfoResourceToAnalyzerResource(resource),\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: getPermissionType(info.User.Token),\n\t\t\t},\n\t\t}\n\n\t\tif resource.ParentResource != nil {\n\t\t\tbinding.Resource.Parent = secretInfoResourceToAnalyzerResource(*resource.ParentResource)\n\t\t}\n\n\t\tresult.Bindings = append(result.Bindings, binding)\n\n\t}\n\n\treturn &result\n}\n\n// secretInfoResourceToAnalyzerResource translate secret info resource to analyzer resource for binding\nfunc secretInfoResourceToAnalyzerResource(resource Resource) *analyzers.Resource {\n\tanalyzerRes := analyzers.Resource{\n\t\tFullyQualifiedName: resource.ID,\n\t\tName:               resource.Name,\n\t\tType:               resource.Type,\n\t\tMetadata:           map[string]any{},\n\t}\n\n\tfor key, value := range resource.MetaData {\n\t\tanalyzerRes.Metadata[key] = value\n\t}\n\n\treturn &analyzerRes\n}\n\n// getPermissionType return what type of permission is assigned to token\nfunc getPermissionType(token Token) string {\n\tswitch {\n\tcase token.Role != \"\":\n\t\treturn token.Role\n\tcase token.hasInlineRole():\n\t\treturn \"Inline Policy\"\n\tcase token.hasCustomRoles():\n\t\treturn \"Custom Roles\"\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n\n// printUser print User information from secret info to cli\nfunc printUser(user User) {\n\t// print caller information\n\tcolor.Green(\"\\n[i] User Information:\")\n\tcallerTable := table.NewWriter()\n\tcallerTable.SetOutputMirror(os.Stdout)\n\tcallerTable.AppendHeader(table.Row{\"Account ID\", \"Member ID\", \"Name\", \"Email\", \"Role\"})\n\tcallerTable.AppendRow(table.Row{color.GreenString(user.AccountID), color.GreenString(user.MemberID),\n\t\tcolor.GreenString(user.Name), color.GreenString(user.Email), color.GreenString(user.Role)})\n\n\tcallerTable.Render()\n\n\t// print token information\n\tcolor.Green(\"\\n[i] Token Information\")\n\ttokenTable := table.NewWriter()\n\ttokenTable.SetOutputMirror(os.Stdout)\n\n\ttokenTable.AppendHeader(table.Row{\"ID\", \"Name\", \"Role\", \"Is Service Token\", \"Default API Version\",\n\t\t\"No of Custom Roles Assigned\", \"Has Inline Policy\"})\n\n\ttokenTable.AppendRow(table.Row{color.GreenString(user.Token.ID), color.GreenString(user.Token.Name), color.GreenString(user.Token.Role),\n\t\tcolor.GreenString(fmt.Sprintf(\"%t\", user.Token.IsServiceToken)), color.GreenString(fmt.Sprintf(\"%d\", user.Token.APIVersion)),\n\t\tcolor.GreenString(fmt.Sprintf(\"%d\", len(user.Token.CustomRoles))), color.GreenString(fmt.Sprintf(\"%t\", user.Token.hasInlineRole()))})\n\n\ttokenTable.Render()\n\n\t// print custom roles information\n\tif !user.Token.hasCustomRoles() {\n\t\treturn\n\t}\n\n\t// print token information\n\tcolor.Green(\"\\n[i] Custom Roles Assigned to Token\")\n\trolesTable := table.NewWriter()\n\trolesTable.SetOutputMirror(os.Stdout)\n\trolesTable.AppendHeader(table.Row{\"ID\", \"Key\", \"Name\", \"Base Permission\", \"Assigned to members\", \"Assigned to teams\"})\n\tfor _, customRole := range user.Token.CustomRoles {\n\t\trolesTable.AppendRow(table.Row{color.GreenString(customRole.ID), color.GreenString(customRole.Key), color.GreenString(customRole.Name),\n\t\t\tcolor.GreenString(customRole.BasePermission), color.GreenString(fmt.Sprintf(\"%d\", customRole.AssignedToMembers)),\n\t\t\tcolor.GreenString(fmt.Sprintf(\"%d\", customRole.AssignedToTeams))})\n\t}\n\trolesTable.Render()\n}\n\n// printPermissionsType print permissions type token has\nfunc printPermissionsType(token Token) {\n\t// print permission type. It can be either admin, writer, reader or has inline policy or any custom roles assigned\n\tcolor.Green(\"\\n[i] Permission Type: %s\", getPermissionType(token))\n}\n\nfunc printResources(resources []Resource) {\n\t// print resources\n\tcolor.Green(\"\\n[i] Resources:\")\n\tcallerTable := table.NewWriter()\n\tcallerTable.SetOutputMirror(os.Stdout)\n\tcallerTable.AppendHeader(table.Row{\"Name\", \"Type\"})\n\tfor _, resource := range resources {\n\t\tcallerTable.AppendRow(table.Row{color.GreenString(resource.Name), color.GreenString(resource.Type)})\n\t}\n\tcallerTable.Render()\n}\n\n// isSDKKey check if the key provided is an SDK Key or not\nfunc isSDKKey(key string) bool {\n\treturn strings.HasPrefix(key, \"sdk-\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/launchdarkly/launchdarkly_test.go",
    "content": "package launchdarkly\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed result_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tkey := testSecrets.MustGetField(\"LAUNCHDARKLY_TOKEN\")\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    []byte // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid LaunchDarkly token\",\n\t\t\tkey:     key,\n\t\t\twant:    expectedOutput,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/launchdarkly/models.go",
    "content": "package launchdarkly\n\nimport \"sync\"\n\nvar (\n\tMetadataKey = \"key\"\n\n\t// resource types\n\tapplicationKey  = \"Application\"\n\trepositoryKey   = \"Repository\"\n\tprojectKey      = \"Project\"\n\tenvironmentKey  = \"Environment\"\n\texperimentKey   = \"Experiment\"\n\tholdoutsKey     = \"Holdout\"\n\tmembersKey      = \"Member\"\n\tdestinationsKey = \"Destination\"\n\ttemplatesKey    = \"Templates\"\n\tteamsKey        = \"Teams\"\n\twebhooksKey     = \"Webhooks\"\n\tfeatureFlagsKey = \"Feature Flags\"\n)\n\ntype SecretInfo struct {\n\tUser        User\n\tPermissions []string\n\n\tmu        sync.RWMutex\n\tResources []Resource\n}\n\n// User is the information about the user to whom the token belongs\ntype User struct {\n\tAccountID string // account id. It is the owner id of token as well\n\tMemberID  string\n\tName      string\n\tRole      string // role of caller\n\tEmail     string\n\tToken     Token\n}\n\n// Token is the token details\ntype Token struct {\n\tID             string       // id of the token\n\tName           string       // name of the token\n\tCustomRoles    []CustomRole // custom roles assigned to the token\n\tInlineRole     []Policy     // any policy statements maybe used in place of a built-in custom role\n\tRole           string       // role of token\n\tIsServiceToken bool         // is a service token or not\n\tAPIVersion     int          // default api version assigned to the token\n}\n\n// CustomRole is a flexible policies providing fine-grained access control to everything in launch darkly\ntype CustomRole struct {\n\tID                string\n\tKey               string\n\tName              string\n\tPolices           []Policy\n\tBasePermission    string\n\tAssignedToMembers int\n\tAssignedToTeams   int\n}\n\n// policy is a set of statements\ntype Policy struct {\n\tResources    []string\n\tNotResources []string\n\tActions      []string\n\tNotActions   []string\n\tEffect       string\n}\n\ntype Resource struct {\n\tID             string\n\tName           string\n\tPermission     string\n\tType           string\n\tParentResource *Resource\n\tMetaData       map[string]string\n}\n\n// appendResource append resource to secret info resources list\nfunc (s *SecretInfo) appendResource(resource Resource) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\ts.Resources = append(s.Resources, resource)\n}\n\n// listResourceByType returns a list of resources matching the given type.\nfunc (s *SecretInfo) listResourceByType(resourceType string) []Resource {\n\ts.mu.RLock()\n\tdefer s.mu.RUnlock()\n\n\tresources := make([]Resource, 0, len(s.Resources))\n\tfor _, resource := range s.Resources {\n\t\tif resource.Type == resourceType {\n\t\t\tresources = append(resources, resource)\n\t\t}\n\t}\n\n\treturn resources\n}\n\n// hasCustomRoles check if token has any custom roles assigned\nfunc (t Token) hasCustomRoles() bool {\n\treturn len(t.CustomRoles) > 0\n}\n\n// hasInlineRole check if token has any inline roles\nfunc (t Token) hasInlineRole() bool {\n\treturn len(t.InlineRole) > 0\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/launchdarkly/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage launchdarkly\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    Admin Permission = iota\n    Writer Permission = iota\n    Reader Permission = iota\n    Inlinepolicy Permission = iota\n    Customroles Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        Admin: \"admin\",\n        Writer: \"writer\",\n        Reader: \"reader\",\n        Inlinepolicy: \"inlinepolicy\",\n        Customroles: \"customroles\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"admin\": Admin,\n        \"writer\": Writer,\n        \"reader\": Reader,\n        \"inlinepolicy\": Inlinepolicy,\n        \"customroles\": Customroles,\n    }\n\n    PermissionIDs = map[Permission]int{\n        Admin: 1,\n        Writer: 2,\n        Reader: 3,\n        Inlinepolicy: 4,\n        Customroles: 5,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: Admin,\n        2: Writer,\n        3: Reader,\n        4: Inlinepolicy,\n        5: Customroles,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/launchdarkly/permissions.yaml",
    "content": "permissions:\n  - admin\n  - writer\n  - reader\n  - inlinepolicy\n  - customroles"
  },
  {
    "path": "pkg/analyzer/analyzers/launchdarkly/requests.go",
    "content": "package launchdarkly\n\nimport (\n\t\"context\"\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n)\n\nconst defaultTimeout = 5 * time.Second\n\nvar (\n\tbaseURL = \"https://app.launchdarkly.com/api\"\n\n\tendpoints = map[string]string{\n\t\t// user information APIs\n\t\t\"callerIdentity\": \"/v2/caller-identity\",\n\t\t\"getToken\":       \"/v2/tokens/%s\", // require token id\n\t\t\"getRole\":        \"/v2/roles/%s\",  // require role id\n\t\t// resource APIs\n\t\tapplicationKey:  \"/v2/applications\",\n\t\trepositoryKey:   \"/v2/code-refs/repositories\",\n\t\tprojectKey:      \"/v2/projects\",\n\t\tenvironmentKey:  \"/v2/projects/%s/environments\",                // require project key\n\t\tfeatureFlagsKey: \"/v2/flags/%s\",                                // require project key\n\t\texperimentKey:   \"/v2/projects/%s/environments/%s/experiments\", // require project key and env key\n\t\tholdoutsKey:     \"/v2/projects/%s/environments/%s/holdouts\",    // require project key and env key\n\t\tmembersKey:      \"/v2/members\",\n\t\tdestinationsKey: \"/v2/destinations\",\n\t\ttemplatesKey:    \"/v2/templates\",\n\t\tteamsKey:        \"/v2/teams\",\n\t\twebhooksKey:     \"/v2/webhooks\",\n\t\t/*\n\t\t\tTODO:\n\t\t\trelease pipelines: https://launchdarkly.com/docs/api/release-pipelines-beta/get-all-release-pipelines (Beta)\n\t\t\tinsight deployments: https://launchdarkly.com/docs/api/insights-deployments-beta/get-deployments (Beta)\n\t\t\tdelivery configuration: https://launchdarkly.com/docs/api/integration-delivery-configurations-beta/get-integration-delivery-configuration-by-environment (Beta)\n\t\t\tmetrics: https://launchdarkly.com/docs/api/metrics-beta/get-metric-groups (Beta)\n\t\t*/\n\t}\n)\n\n// applicationsResponse is the response of /v2/applications API\ntype applicationsResponse struct {\n\tItems []struct {\n\t\tKey        string `json:\"key\"`\n\t\tName       string `json:\"name\"`\n\t\tKind       string `json:\"kind\"`\n\t\tMaintainer struct {\n\t\t\tEmail string `json:\"email\"`\n\t\t} `json:\"_maintainer\"`\n\t} `json:\"items\"`\n}\n\n// repositoriesResponse is the response of /v2/code-refs/repositories API\ntype repositoriesResponse struct {\n\tItems []struct {\n\t\tName          string `json:\"name\"`\n\t\tType          string `json:\"type\"`\n\t\tDefaultBranch string `json:\"defaultBranch\"`\n\t\tSourceLink    string `json:\"sourceLink\"`\n\t\tVersion       int    `json:\"version\"`\n\t} `json:\"items\"`\n}\n\n// projectsResponse is the response of /v2/projects API\ntype projectsResponse struct {\n\tItems []struct {\n\t\tID   string `json:\"_id\"`\n\t\tKey  string `json:\"key\"`\n\t\tName string `json:\"name\"`\n\t} `json:\"items\"`\n}\n\n// featureFlagsResponse is the response of /v2/flags/<project_id> API\ntype featureFlagsResponse struct {\n\tItems []struct {\n\t\tKey  string `json:\"key\"`\n\t\tName string `json:\"name\"`\n\t\tKind string `json:\"kind\"`\n\t} `json:\"items\"`\n}\n\n// environmentsResponse is the response of /v2/projects/<proj_key>/environments API\ntype environmentsResponse struct {\n\tItems []struct {\n\t\tID   string `json:\"_id\"`\n\t\tKey  string `json:\"key\"`\n\t\tName string `json:\"name\"`\n\t} `json:\"items\"`\n}\n\n// experimentResponse is the response of /v2/projects/<proj_id>/env/<env_id>/experiments\ntype experimentResponse struct {\n\tItems []struct {\n\t\tID           string `json:\"_id\"`\n\t\tKey          string `json:\"key\"`\n\t\tName         string `json:\"name\"`\n\t\tMaintainerID string `json:\"_maintainerId\"`\n\t} `json:\"items\"`\n}\n\n// membersResponse is the response of /v2/members API\ntype membersResponse struct {\n\tItems []struct {\n\t\tID        string `json:\"_id\"`\n\t\tRole      string `json:\"role\"`\n\t\tEmail     string `json:\"email\"`\n\t\tFirstName string `json:\"firstName\"`\n\t\tLastName  string `json:\"lastName\"`\n\t} `json:\"items\"`\n}\n\n// holdoutsResponse is the response of /v2/projects/<project_id>/environments/<env_id>/holdouts API\ntype holdoutsResponse struct {\n\tItems []struct {\n\t\tID     string `json:\"_id\"`\n\t\tName   string `json:\"name\"`\n\t\tKey    string `json:\"key\"`\n\t\tStatus string `json:\"status\"`\n\t} `json:\"items\"`\n}\n\n// destinationsResponse is the response of /v2/destinations API\ntype destinationsResponse struct {\n\tItems []struct {\n\t\tID      string `json:\"_id\"`\n\t\tName    string `json:\"name\"`\n\t\tKind    string `json:\"kind\"`\n\t\tVersion int    `json:\"version\"`\n\t} `json:\"items\"`\n}\n\n// templatesResponse is the response of /v2/templates API\ntype templatesResponse struct {\n\tItems []struct {\n\t\tID   string `json:\"_id\"`\n\t\tKey  string `json:\"_key\"`\n\t\tName string `json:\"name\"`\n\t} `json:\"items\"`\n}\n\n// teamsResponse is the response of /v2/teams API\ntype teamsResponse struct {\n\tItems []struct {\n\t\tKey   string `json:\"key\"`\n\t\tName  string `json:\"name\"`\n\t\tRoles struct {\n\t\t\tTotalCount int `json:\"totalCount\"`\n\t\t} `json:\"roles\"`\n\t\tMembers struct {\n\t\t\tTotalCount int `json:\"totalCount\"`\n\t\t} `json:\"members\"`\n\t\tProjects struct {\n\t\t\tTotalCount int `json:\"totalCount\"`\n\t\t} `json:\"projects\"`\n\t} `json:\"items\"`\n}\n\n// webhooksResponse is the response of /v2/webhooks API\ntype webhooksResponse struct {\n\tItems []struct {\n\t\tID   string `json:\"_id\"`\n\t\tName string `json:\"name\"`\n\t\tUrl  string `json:\"url\"`\n\t} `json:\"items\"`\n}\n\n// makeLaunchDarklyRequest send the HTTP GET API request to passed url with passed token and return response body and status code\nfunc makeLaunchDarklyRequest(client *http.Client, endpoint, token string) ([]byte, int, error) {\n\tctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)\n\tdefer cancel()\n\n\t// create request\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+endpoint, http.NoBody)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\t// add required keys in the header\n\treq.Header.Set(\"Authorization\", token)\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tresponseBodyByte, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn responseBodyByte, resp.StatusCode, nil\n}\n\nfunc CaptureResources(client *http.Client, token string, secretInfo *SecretInfo) error {\n\tvar (\n\t\twg             sync.WaitGroup\n\t\terrAggWg       sync.WaitGroup\n\t\taggregatedErrs = make([]error, 0)\n\t\terrChan        = make(chan error, 1)\n\t)\n\n\terrAggWg.Add(1)\n\tgo func() {\n\t\tdefer errAggWg.Done()\n\t\tfor err := range errChan {\n\t\t\taggregatedErrs = append(aggregatedErrs, err)\n\t\t}\n\t}()\n\n\t// helper to launch tasks concurrently.\n\tlaunchTask := func(task func() error) {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tif err := task(); err != nil {\n\t\t\t\terrChan <- err\n\t\t\t}\n\t\t}()\n\t}\n\n\t// capture top-level resources\n\tlaunchTask(func() error { return captureApplications(client, token, secretInfo) })\n\tlaunchTask(func() error { return captureRepositories(client, token, secretInfo) })\n\n\t// capture projects\n\tlaunchTask(func() error {\n\t\tif err := captureProjects(client, token, secretInfo); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// capture project sub resources\n\t\tprojects := secretInfo.listResourceByType(projectKey)\n\t\tfor _, proj := range projects {\n\t\t\tlaunchTask(func() error { return captureProjectFeatureFlags(client, token, proj, secretInfo) })\n\t\t\tlaunchTask(func() error { return captureProjectEnv(client, token, proj, secretInfo) })\n\t\t}\n\n\t\treturn nil\n\t})\n\n\tlaunchTask(func() error { return captureMembers(client, token, secretInfo) })\n\tlaunchTask(func() error { return captureDestinations(client, token, secretInfo) })\n\tlaunchTask(func() error { return captureTemplates(client, token, secretInfo) })\n\tlaunchTask(func() error { return captureTeams(client, token, secretInfo) })\n\tlaunchTask(func() error { return captureWebhooks(client, token, secretInfo) })\n\n\twg.Wait()\n\tclose(errChan)\n\terrAggWg.Wait()\n\n\tif len(aggregatedErrs) > 0 {\n\t\treturn errors.Join(aggregatedErrs...)\n\t}\n\n\treturn nil\n}\n\n// docs: https://launchdarkly.com/docs/api/applications-beta/get-applications\nfunc captureApplications(client *http.Client, token string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeLaunchDarklyRequest(client, endpoints[applicationKey], token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar applications = applicationsResponse{}\n\n\t\tif err := json.Unmarshal(response, &applications); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, application := range applications.Items {\n\t\t\tresource := Resource{\n\t\t\t\tID:   fmt.Sprintf(\"launchdarkly/app/%s\", application.Key),\n\t\t\t\tName: application.Name,\n\t\t\t\tType: applicationKey,\n\t\t\t\tMetaData: map[string]string{\n\t\t\t\t\t\"Maintainer Email\": application.Maintainer.Email,\n\t\t\t\t\t\"Kind\":             application.Kind,\n\t\t\t\t\tMetadataKey:        application.Key,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// docs: https://launchdarkly.com/docs/api/code-references/get-repositories\nfunc captureRepositories(client *http.Client, token string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeLaunchDarklyRequest(client, endpoints[repositoryKey], token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar repositories = repositoriesResponse{}\n\n\t\tif err := json.Unmarshal(response, &repositories); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, repository := range repositories.Items {\n\t\t\tresource := Resource{\n\t\t\t\tID:   fmt.Sprintf(\"%s/repo/%s/%d\", repository.Type, repository.Name, repository.Version), // no unique id exist, so we make one\n\t\t\t\tName: repository.Name,\n\t\t\t\tType: repositoryKey,\n\t\t\t\tMetaData: map[string]string{\n\t\t\t\t\t\"Default branch\": repository.DefaultBranch,\n\t\t\t\t\t\"Version\":        strconv.Itoa(repository.Version),\n\t\t\t\t\t\"Source link\":    repository.SourceLink,\n\t\t\t\t\tMetadataKey:      repositoryKey,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// docs: https://launchdarkly.com/docs/api/projects/get-projects\nfunc captureProjects(client *http.Client, token string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeLaunchDarklyRequest(client, endpoints[projectKey], token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar projects = projectsResponse{}\n\n\t\tif err := json.Unmarshal(response, &projects); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, project := range projects.Items {\n\t\t\tsecretInfo.appendResource(Resource{\n\t\t\t\tID:   fmt.Sprintf(\"launchdarkly/proj/%s\", project.ID),\n\t\t\t\tName: project.Name,\n\t\t\t\tType: projectKey,\n\t\t\t\tMetaData: map[string]string{\n\t\t\t\t\tMetadataKey: project.Key,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// docs: https://launchdarkly.com/docs/api/feature-flags/get-feature-flags\nfunc captureProjectFeatureFlags(client *http.Client, token string, parent Resource, secretInfo *SecretInfo) error {\n\tprojectKey, exist := parent.MetaData[MetadataKey]\n\tif !exist {\n\t\treturn errors.New(\"project key not found\")\n\t}\n\n\tresponse, statusCode, err := makeLaunchDarklyRequest(client, fmt.Sprintf(endpoints[featureFlagsKey], projectKey), token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar flags = featureFlagsResponse{}\n\n\t\tif err := json.Unmarshal(response, &flags); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, flag := range flags.Items {\n\t\t\tresource := Resource{\n\t\t\t\tID:   fmt.Sprintf(\"launchdarkly/proj/%s/flag/%s\", projectKey, flag.Key),\n\t\t\t\tName: flag.Name,\n\t\t\t\tType: featureFlagsKey,\n\t\t\t\tMetaData: map[string]string{\n\t\t\t\t\t\"Kind\": flag.Kind,\n\t\t\t\t},\n\t\t\t\tParentResource: &parent,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// docs: https://launchdarkly.com/docs/api/environments/get-environments-by-project\nfunc captureProjectEnv(client *http.Client, token string, parent Resource, secretInfo *SecretInfo) error {\n\tprojectKey, exist := parent.MetaData[MetadataKey]\n\tif !exist {\n\t\treturn errors.New(\"project key not found\")\n\t}\n\n\tresponse, statusCode, err := makeLaunchDarklyRequest(client, fmt.Sprintf(endpoints[environmentKey], projectKey), token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar envs = environmentsResponse{}\n\n\t\tif err := json.Unmarshal(response, &envs); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, env := range envs.Items {\n\t\t\tresource := Resource{\n\t\t\t\tID:   fmt.Sprintf(\"launchdarkly/%s/env/%s\", projectKey, env.ID),\n\t\t\t\tName: env.Name,\n\t\t\t\tType: environmentKey,\n\t\t\t\tMetaData: map[string]string{\n\t\t\t\t\tMetadataKey: env.Key,\n\t\t\t\t},\n\t\t\t\tParentResource: &parent,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\n\t\t\t// capture project env child resources\n\t\t\tif err := captureProjectEnvExperiments(client, token, projectKey, resource, secretInfo); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif err := captureProjectHoldouts(client, token, projectKey, resource, secretInfo); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// docs: https://launchdarkly.com/docs/api/experiments/get-experiments\nfunc captureProjectEnvExperiments(client *http.Client, token string, projectKey string, parent Resource, secretInfo *SecretInfo) error {\n\tenvKey, exist := parent.MetaData[MetadataKey]\n\tif !exist {\n\t\treturn errors.New(\"env key not found\")\n\t}\n\n\tresponse, statusCode, err := makeLaunchDarklyRequest(client, fmt.Sprintf(endpoints[experimentKey], projectKey, envKey), token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar exps = experimentResponse{}\n\n\t\tif err := json.Unmarshal(response, &exps); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, exp := range exps.Items {\n\t\t\tresource := Resource{\n\t\t\t\tID:   fmt.Sprintf(\"launchdarkly/%s/env/%s/exp/%s\", projectKey, envKey, exp.ID),\n\t\t\t\tName: exp.Name,\n\t\t\t\tType: experimentKey,\n\t\t\t\tMetaData: map[string]string{\n\t\t\t\t\tMetadataKey:    exp.Key,\n\t\t\t\t\t\"Maintiner ID\": exp.MaintainerID,\n\t\t\t\t},\n\t\t\t\tParentResource: &parent,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound:\n\t\treturn nil\n\tcase http.StatusTooManyRequests:\n\t\ttime.Sleep(1 * time.Second)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// docs: https://launchdarkly.com/docs/api/holdouts-beta/get-all-holdouts\nfunc captureProjectHoldouts(client *http.Client, token string, projectKey string, parent Resource, secretInfo *SecretInfo) error {\n\tenvKey, exist := parent.MetaData[MetadataKey]\n\tif !exist {\n\t\treturn errors.New(\"env key not found\")\n\t}\n\n\tresponse, statusCode, err := makeLaunchDarklyRequest(client, fmt.Sprintf(endpoints[holdoutsKey], projectKey, envKey), token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar holdouts = holdoutsResponse{}\n\n\t\tif err := json.Unmarshal(response, &holdouts); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, holdout := range holdouts.Items {\n\t\t\tresource := Resource{\n\t\t\t\tID:   fmt.Sprintf(\"launchdarkly/%s/env/%s/holdout/%s\", projectKey, envKey, holdout.ID),\n\t\t\t\tName: holdout.Name,\n\t\t\t\tType: holdoutsKey,\n\t\t\t\tMetaData: map[string]string{\n\t\t\t\t\t\"Status\":    holdout.Status,\n\t\t\t\t\tholdoutsKey: holdout.Key,\n\t\t\t\t},\n\t\t\t\tParentResource: &parent,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// docs: https://launchdarkly.com/docs/api/account-members/get-members\nfunc captureMembers(client *http.Client, token string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeLaunchDarklyRequest(client, endpoints[membersKey], token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar members = membersResponse{}\n\n\t\tif err := json.Unmarshal(response, &members); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, member := range members.Items {\n\t\t\tresource := Resource{\n\t\t\t\tID:   fmt.Sprintf(\"launchdarkly/member/%s\", member.ID),\n\t\t\t\tName: member.FirstName + \" \" + member.LastName,\n\t\t\t\tType: membersKey,\n\t\t\t\tMetaData: map[string]string{\n\t\t\t\t\t\"Role\":  member.Role,\n\t\t\t\t\t\"Email\": member.Email,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// docs: https://launchdarkly.com/docs/api/data-export-destinations/get-destinations\nfunc captureDestinations(client *http.Client, token string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeLaunchDarklyRequest(client, endpoints[destinationsKey], token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar destinations = destinationsResponse{}\n\n\t\tif err := json.Unmarshal(response, &destinations); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, destination := range destinations.Items {\n\t\t\tresource := Resource{\n\t\t\t\tID:   fmt.Sprintf(\"launchdarkly/destination/%s\", destination.ID),\n\t\t\t\tName: destination.Name,\n\t\t\t\tType: destinationsKey,\n\t\t\t\tMetaData: map[string]string{\n\t\t\t\t\t\"Kind\":    destination.Kind,\n\t\t\t\t\t\"Version\": strconv.Itoa(destination.Version),\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// docs: https://launchdarkly.com/docs/api/workflow-templates/get-workflow-templates\nfunc captureTemplates(client *http.Client, token string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeLaunchDarklyRequest(client, endpoints[templatesKey], token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar templates = templatesResponse{}\n\n\t\tif err := json.Unmarshal(response, &templates); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, template := range templates.Items {\n\t\t\tresource := Resource{\n\t\t\t\tID:   fmt.Sprintf(\"launchdarkly/templates/%s\", template.ID),\n\t\t\t\tName: template.Name,\n\t\t\t\tType: templatesKey,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// docs: https://launchdarkly.com/docs/api/teams/get-teams\nfunc captureTeams(client *http.Client, token string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeLaunchDarklyRequest(client, endpoints[teamsKey], token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar teams = teamsResponse{}\n\n\t\tif err := json.Unmarshal(response, &teams); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, team := range teams.Items {\n\t\t\tresource := Resource{\n\t\t\t\tID:   fmt.Sprintf(\"launchdarkly/teams/%s\", team.Key),\n\t\t\t\tName: team.Name,\n\t\t\t\tType: teamsKey,\n\t\t\t\tMetaData: map[string]string{\n\t\t\t\t\t\"Total Roles Count\":    strconv.Itoa(team.Roles.TotalCount),\n\t\t\t\t\t\"Total Memvers Count\":  strconv.Itoa(team.Members.TotalCount),\n\t\t\t\t\t\"Total Projects Count\": strconv.Itoa(team.Projects.TotalCount),\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// docs: https://launchdarkly.com/docs/api/webhooks/get-all-webhooks\nfunc captureWebhooks(client *http.Client, token string, secretInfo *SecretInfo) error {\n\tresponse, statusCode, err := makeLaunchDarklyRequest(client, endpoints[webhooksKey], token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar webhooks = webhooksResponse{}\n\n\t\tif err := json.Unmarshal(response, &webhooks); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, webhook := range webhooks.Items {\n\t\t\tresource := Resource{\n\t\t\t\tID:   fmt.Sprintf(\"launchdarkly/webhooks/%s\", webhook.ID),\n\t\t\t\tName: webhook.Name,\n\t\t\t\tType: webhooksKey,\n\t\t\t}\n\n\t\t\tsecretInfo.appendResource(resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/launchdarkly/result_output.json",
    "content": "{\n    \"AnalyzerType\": 31,\n    \"Bindings\": [\n        {\n            \"Resource\": {\n                \"Name\": \"Production\",\n                \"FullyQualifiedName\": \"launchdarkly/default/env/61543c5956be602355624871\",\n                \"Type\": \"Environment\",\n                \"Metadata\": {\n                    \"key\": \"production\"\n                },\n                \"Parent\": {\n                    \"Name\": \"secretscanner\",\n                    \"FullyQualifiedName\": \"launchdarkly/proj/61543c5956be60235562486e\",\n                    \"Type\": \"Project\",\n                    \"Metadata\": {\n                        \"key\": \"default\"\n                    },\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"admin\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Roxanne Tampus\",\n                \"FullyQualifiedName\": \"launchdarkly/member/61543c5956be60235562486f\",\n                \"Type\": \"Member\",\n                \"Metadata\": {\n                    \"Email\": \"knightmoverchan@gmail.com\",\n                    \"Role\": \"owner\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"admin\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Test\",\n                \"FullyQualifiedName\": \"launchdarkly/default/env/61543c5956be602355624870\",\n                \"Type\": \"Environment\",\n                \"Metadata\": {\n                    \"key\": \"test\"\n                },\n                \"Parent\": {\n                    \"Name\": \"secretscanner\",\n                    \"FullyQualifiedName\": \"launchdarkly/proj/61543c5956be60235562486e\",\n                    \"Type\": \"Project\",\n                    \"Metadata\": {\n                        \"key\": \"default\"\n                    },\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"admin\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"secretscanner\",\n                \"FullyQualifiedName\": \"launchdarkly/proj/61543c5956be60235562486e\",\n                \"Type\": \"Project\",\n                \"Metadata\": {\n                    \"key\": \"default\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"admin\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"secretscanner\",\n                \"FullyQualifiedName\": \"launchdarkly/proj/default/flag/secretscanner\",\n                \"Type\": \"Feature Flags\",\n                \"Metadata\": {\n                    \"Kind\": \"boolean\"\n                },\n                \"Parent\": {\n                    \"Name\": \"secretscanner\",\n                    \"FullyQualifiedName\": \"launchdarkly/proj/61543c5956be60235562486e\",\n                    \"Type\": \"Project\",\n                    \"Metadata\": {\n                        \"key\": \"default\"\n                    },\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"admin\",\n                \"Parent\": null\n            }\n        }\n    ],\n    \"UnboundedResources\": null,\n    \"Metadata\": {}\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/launchdarkly/user.go",
    "content": "/*\nuser.go file is all related to calling APIs to get user and token information and formatting them to secretInfo User.\n\nIt calls 3 APIs:\n  - /v2/caller-identity\n  - /v2/tokens/<id> (with token id from previous api response)\n  - /v2/roles/<role_id> (if custom role id is present in tokens) (more than one role can be assigned to token as well)\n\nit formats all these responses into one User struct for secretInfo.\n*/\npackage launchdarkly\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// callerIdentityResponse is /v2/caller-identity API response\ntype callerIdentityResponse struct {\n\tAccountID    string `json:\"accountId\"`\n\tTokenName    string `json:\"tokenName\"`\n\tTokenID      string `json:\"tokenId\"`\n\tMemberID     string `json:\"memberId\"`\n\tServiceToken bool   `json:\"serviceToken\"`\n}\n\n// tokenResponse is the /v2/tokens/<id> API response\ntype tokenResponse struct {\n\tOwnerID           string                `json:\"ownerId\"`\n\tMember            tokenMemberResponse   `json:\"_member\"`\n\tName              string                `json:\"name\"`\n\tCustomRoleIDs     []string              `json:\"customRoleIds,omitempty\"`\n\tInlineRole        []tokenPolicyResponse `json:\"inlineRole,omitempty\"`\n\tRole              string                `json:\"role\"`\n\tServiceToken      bool                  `json:\"serviceToken\"`\n\tDefaultAPIVersion int                   `json:\"defaultApiVersion\"`\n}\n\n// _member object in token response\ntype tokenMemberResponse struct {\n\tFirstName string `json:\"firstName\"`\n\tLastName  string `json:\"lastName\"`\n\tRole      string `json:\"role\"`\n\tEmail     string `json:\"email\"`\n}\n\n// inlineRole object in token response\ntype tokenPolicyResponse struct {\n\tEffect       string   `json:\"effect,omitempty\"`\n\tResources    []string `json:\"resources,omitempty\"`\n\tNotResources []string `json:\"notResources,omitempty\"`\n\tActions      []string `json:\"actions,omitempty\"`\n\tNotActions   []string `json:\"notActions,omitempty\"`\n}\n\n// customRoleResponse is the /v2/roles/<role_id> API response\ntype customRoleResponse struct {\n\tID             string                `json:\"_id\"`\n\tKey            string                `json:\"key\"`\n\tName           string                `json:\"name\"`\n\tPolicy         []tokenPolicyResponse `json:\"policy\"`\n\tBasePermission string                `json:\"basePermissions\"`\n\tAssignedTo     struct {\n\t\tMembersCount int `json:\"membersCount\"`\n\t\tTeamsCount   int `json:\"teamsCount\"`\n\t} `json:\"assignedTo\"`\n}\n\n/*\nCaptureUserInformation call following three APIs:\n  - /v2/caller-identity\n  - /v2/tokens/<token_id> (token_id from previous API response)\n  - /v2/roles/<role_id> (roles_id from previous API response if exist)\n\nIt format all responses into one secret info User\n*/\nfunc CaptureUserInformation(client *http.Client, token string, secretInfo *SecretInfo) error {\n\tcaller, err := getCallerIdentity(client, token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttokenDetails, err := getToken(client, caller.TokenID, token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcustomRoles, err := getCustomRole(client, tokenDetails.CustomRoleIDs, token)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\taddUserToSecretInfo(caller, tokenDetails, customRoles, secretInfo)\n\n\treturn nil\n}\n\n// getCallerIdentity call /v2/caller-identity API and return response\nfunc getCallerIdentity(client *http.Client, token string) (*callerIdentityResponse, error) {\n\tresponse, statusCode, err := makeLaunchDarklyRequest(client, endpoints[\"callerIdentity\"], token)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar caller = &callerIdentityResponse{}\n\n\t\tif err := json.Unmarshal(response, caller); err != nil {\n\t\t\treturn caller, err\n\t\t}\n\n\t\treturn caller, nil\n\tcase http.StatusUnauthorized:\n\t\treturn nil, errors.New(\"invalid token; failed to get caller information\")\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// getToken call /v2/tokens/<token_id> API and return response\nfunc getToken(client *http.Client, tokenID, token string) (*tokenResponse, error) {\n\tresponse, statusCode, err := makeLaunchDarklyRequest(client, fmt.Sprintf(endpoints[\"getToken\"], tokenID), token)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar token tokenResponse\n\n\t\tif err := json.Unmarshal(response, &token); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn &token, nil\n\tcase http.StatusUnauthorized:\n\t\treturn nil, errors.New(\"invalid token; failed to get token information\")\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\n// getCustomRole call /v2/roles/<role_id> API  for all IDs passed and return list of responses\nfunc getCustomRole(client *http.Client, customRoleIDs []string, token string) ([]customRoleResponse, error) {\n\tvar customRoles []customRoleResponse\n\n\tfor _, customRoleID := range customRoleIDs {\n\t\tresponse, statusCode, err := makeLaunchDarklyRequest(client, fmt.Sprintf(endpoints[\"getRole\"], customRoleID), token)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tswitch statusCode {\n\t\tcase http.StatusOK:\n\t\t\tvar customRole customRoleResponse\n\n\t\t\tif err := json.Unmarshal(response, &customRole); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tcustomRoles = append(customRoles, customRole)\n\t\tcase http.StatusUnauthorized:\n\t\t\treturn nil, nil\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t\t}\n\t}\n\n\treturn customRoles, nil\n}\n\n// makeCallerIdentity take caller, tokenDetails, and customRoles and return secret info CallerIdentity\nfunc addUserToSecretInfo(caller *callerIdentityResponse, tokenDetails *tokenResponse, customRoles []customRoleResponse, secretInfo *SecretInfo) {\n\tuser := User{\n\t\tAccountID: caller.AccountID,\n\t\tMemberID:  caller.MemberID,\n\t\tName:      tokenDetails.Member.FirstName + \" \" + tokenDetails.Member.LastName,\n\t\tRole:      tokenDetails.Member.Role,\n\t\tEmail:     tokenDetails.Member.Email,\n\t\tToken: Token{\n\t\t\tID:             caller.TokenID,\n\t\t\tName:           tokenDetails.Name,\n\t\t\tRole:           tokenDetails.Role,\n\t\t\tAPIVersion:     tokenDetails.DefaultAPIVersion,\n\t\t\tIsServiceToken: tokenDetails.ServiceToken,\n\t\t\tInlineRole:     toPolicy(tokenDetails.InlineRole),\n\t\t\tCustomRoles:    toCustomRoles(customRoles),\n\t\t},\n\t}\n\n\tsecretInfo.User = user\n}\n\n// toPolicy convert inlinePolicy from token response to secret info caller identity policy\nfunc toPolicy(inlinePolices []tokenPolicyResponse) []Policy {\n\tvar policies = make([]Policy, 0)\n\n\tfor _, inlinePolicy := range inlinePolices {\n\t\tpolicies = append(policies, Policy{\n\t\t\tResources:    inlinePolicy.Resources,\n\t\t\tNotResources: inlinePolicy.NotResources,\n\t\t\tActions:      inlinePolicy.Actions,\n\t\t\tNotActions:   inlinePolicy.NotActions,\n\t\t\tEffect:       inlinePolicy.Effect,\n\t\t})\n\t}\n\n\treturn policies\n}\n\n// toCustomRoles convert customRole from token response to secret info caller identity custom role\nfunc toCustomRoles(roles []customRoleResponse) []CustomRole {\n\tvar customRoles = make([]CustomRole, 0)\n\tfor _, role := range roles {\n\t\tcustomRoles = append(customRoles, CustomRole{\n\t\t\tID:                role.ID,\n\t\t\tKey:               role.Key,\n\t\t\tName:              role.Name,\n\t\t\tPolices:           toPolicy(role.Policy),\n\t\t\tBasePermission:    role.BasePermission,\n\t\t\tAssignedToMembers: role.AssignedTo.MembersCount,\n\t\t\tAssignedToTeams:   role.AssignedTo.TeamsCount,\n\t\t})\n\t}\n\n\treturn customRoles\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mailchimp/expected_output.json",
    "content": "{\"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}"
  },
  {
    "path": "pkg/analyzer/analyzers/mailchimp/mailchimp.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go mailchimp\npackage mailchimp\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nconst BASE_URL = \"https://%s.api.mailchimp.com/3.0\"\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeMailchimp }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credInfo[\"key\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"key not found in credentialInfo\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType:       analyzers.AnalyzerTypeMailchimp,\n\t\tBindings:           make([]analyzers.Binding, 0, len(StringToPermission)),\n\t\tUnboundedResources: make([]analyzers.Resource, 0, len(info.Domains.Domains)),\n\t}\n\n\taccountResource := analyzers.Resource{\n\t\tName:               info.Metadata.AccountName,\n\t\tFullyQualifiedName: \"mailchimp.com/account/\" + info.Metadata.AccountID,\n\t\tType:               \"account\",\n\t\tMetadata: map[string]any{\n\t\t\t\"email\":             info.Metadata.Email,\n\t\t\t\"role\":              info.Metadata.Role,\n\t\t\t\"member_since\":      info.Metadata.MemberSince,\n\t\t\t\"pricing_plan\":      info.Metadata.PricingPlan,\n\t\t\t\"account_timezone\":  info.Metadata.AccountTimezone,\n\t\t\t\"last_login\":        info.Metadata.LastLogin,\n\t\t\t\"total_subscribers\": info.Metadata.TotalSubscribers,\n\t\t},\n\t}\n\n\tfor perm := range StringToPermission {\n\t\tresult.Bindings = append(result.Bindings, analyzers.Binding{\n\t\t\tResource: accountResource,\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: perm,\n\t\t\t},\n\t\t})\n\t}\n\n\tfor _, domain := range info.Domains.Domains {\n\t\tresult.UnboundedResources = append(result.UnboundedResources, analyzers.Resource{\n\t\t\tName:               domain.Domain,\n\t\t\tFullyQualifiedName: \"mailchimp.com/domain/\" + domain.Domain,\n\t\t\tType:               \"domain\",\n\t\t\tMetadata: map[string]any{\n\t\t\t\t\"verified\":      domain.Verified,\n\t\t\t\t\"authenticated\": domain.Authenticated,\n\t\t\t},\n\t\t\tParent: &accountResource,\n\t\t})\n\t}\n\n\treturn &result\n}\n\ntype MetadataJSON struct {\n\tAccountID       string `json:\"account_id\"`\n\tAccountName     string `json:\"account_name\"`\n\tEmail           string `json:\"email\"`\n\tFirstName       string `json:\"first_name\"`\n\tLastName        string `json:\"last_name\"`\n\tRole            string `json:\"role\"`\n\tMemberSince     string `json:\"member_since\"`\n\tPricingPlan     string `json:\"pricing_plan_type\"`\n\tAccountTimezone string `json:\"account_timezone\"`\n\tContact         struct {\n\t\tCompany  string `json:\"company\"`\n\t\tAddress1 string `json:\"addr1\"`\n\t\tAddress2 string `json:\"addr2\"`\n\t\tCity     string `json:\"city\"`\n\t\tState    string `json:\"state\"`\n\t\tZip      string `json:\"zip\"`\n\t\tCountry  string `json:\"country\"`\n\t} `json:\"contact\"`\n\tLastLogin        string `json:\"last_login\"`\n\tTotalSubscribers int    `json:\"total_subscribers\"`\n}\n\ntype DomainsJSON struct {\n\tDomains []Domain `json:\"domains\"`\n}\n\ntype Domain struct {\n\tDomain        string `json:\"domain\"`\n\tAuthenticated bool   `json:\"authenticated\"`\n\tVerified      bool   `json:\"verified\"`\n}\n\nfunc getMetadata(cfg *config.Config, key string) (MetadataJSON, error) {\n\tvar metadata MetadataJSON\n\n\t// extract datacenter\n\tkeySplit := strings.Split(key, \"-\")\n\tif len(keySplit) != 2 {\n\t\treturn metadata, nil\n\t}\n\tdatacenter := keySplit[1]\n\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(\"GET\", fmt.Sprintf(BASE_URL, datacenter), nil)\n\tif err != nil {\n\t\treturn metadata, err\n\t}\n\n\treq.SetBasicAuth(\"anystring\", key)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn metadata, err\n\t}\n\n\tdefer resp.Body.Close()\n\n\tif err := json.NewDecoder(resp.Body).Decode(&metadata); err != nil {\n\t\treturn metadata, err\n\t}\n\n\treturn metadata, nil\n}\n\nfunc getDomains(cfg *config.Config, key string) (DomainsJSON, error) {\n\tvar domains DomainsJSON\n\n\t// extract datacenter\n\tkeySplit := strings.Split(key, \"-\")\n\tif len(keySplit) != 2 {\n\t\treturn domains, nil\n\t}\n\tdatacenter := keySplit[1]\n\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(\"GET\", fmt.Sprintf(BASE_URL, datacenter)+\"/verified-domains\", nil)\n\tif err != nil {\n\t\treturn domains, err\n\t}\n\n\treq.SetBasicAuth(\"anystring\", key)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn domains, err\n\t}\n\n\tdefer resp.Body.Close()\n\n\tif err := json.NewDecoder(resp.Body).Decode(&domains); err != nil {\n\t\treturn domains, err\n\t}\n\n\treturn domains, nil\n}\n\ntype SecretInfo struct {\n\tMetadata MetadataJSON\n\tDomains  DomainsJSON\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\t// get metadata\n\tmetadata, err := getMetadata(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif metadata.AccountID == \"\" {\n\t\treturn nil, fmt.Errorf(\"Invalid Mailchimp API key\")\n\t}\n\n\t// get sending domains\n\tdomains, err := getDomains(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &SecretInfo{\n\t\tMetadata: metadata,\n\t\tDomains:  domains,\n\t}, nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error: %s\", err.Error())\n\t\treturn\n\t}\n\n\tprintMetadata(info.Metadata)\n\n\t// print full api key permissions\n\tcolor.Green(\"\\n[i] Permissions: Full Access\\n\\n\")\n\n\t// print sending domains\n\tif len(info.Domains.Domains) > 0 {\n\t\tprintDomains(info.Domains)\n\t} else {\n\t\tcolor.Yellow(\"[i] No sending domains found\\n\")\n\t}\n}\n\nfunc printMetadata(metadata MetadataJSON) {\n\tcolor.Green(\"[!] Valid Mailchimp API key\\n\\n\")\n\n\t// print table with account info\n\tcolor.Yellow(\"[i] Mailchimp Account Info:\\n\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendRow([]any{(\"Account Name\"), color.GreenString(\"%s\", metadata.AccountName)})\n\tt.AppendRow([]any{(\"Company Name\"), color.GreenString(\"%s\", metadata.Contact.Company)})\n\tt.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)})\n\tt.AppendRow([]any{(\"Total Subscribers\"), color.GreenString(\"%d\", metadata.TotalSubscribers)})\n\tt.Render()\n\n\t// print user info\n\tcolor.Yellow(\"\\n[i] Mailchimp User Info:\\n\")\n\tt = table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendRow([]any{(\"User Name\"), color.GreenString(\"%s %s\", metadata.FirstName, metadata.LastName)})\n\tt.AppendRow([]any{(\"User Email\"), color.GreenString(\"%s\", metadata.Email)})\n\tt.AppendRow([]any{(\"User Role\"), color.GreenString(\"%s\", metadata.Role)})\n\tt.AppendRow([]any{(\"Last Login\"), color.GreenString(\"%s\", metadata.LastLogin)})\n\tt.AppendRow([]any{(\"Member Since\"), color.GreenString(\"%s\", metadata.MemberSince)})\n\tt.Render()\n}\n\nfunc printDomains(domains DomainsJSON) {\n\tcolor.Yellow(\"\\n[i] Sending Domains:\\n\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Domain\", \"Enabled and Verified\"})\n\tfor _, domain := range domains.Domains {\n\t\tauthenticated := \"\"\n\t\tif domain.Authenticated && domain.Verified {\n\t\t\tauthenticated = color.GreenString(\"Yes\")\n\t\t} else {\n\t\t\tauthenticated = color.RedString(\"No\")\n\t\t}\n\t\tt.AppendRow([]any{color.GreenString(domain.Domain), authenticated})\n\t}\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mailchimp/mailchimp_test.go",
    "content": "package mailchimp\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expected_output []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid Mailchimp key\",\n\t\t\tkey:     testSecrets.MustGetField(\"MAILCHIMP\"),\n\t\t\twant:    string(expected_output),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.Marshal(got)\n\t\t\t\t// gotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mailchimp/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage mailchimp\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    InviteUsers Permission = iota\n    RevokeAccountAccess Permission = iota\n    SetUserAccessLevel Permission = iota\n    Require2FactorAuthentication Permission = iota\n    ChangeBillingInformation Permission = iota\n    ChangeCompanyOrganizationName Permission = iota\n    AddOrAccessApiKeys Permission = iota\n    CheckReconnectIntegrations Permission = iota\n    ReferralProgram Permission = iota\n    AccountExport Permission = iota\n    CloseAccount Permission = iota\n    AddFilesToContentStudio Permission = iota\n    OptInToReceiveEmailsFromMailchimp Permission = iota\n    CreateAudiences Permission = iota\n    ViewAudiences Permission = iota\n    AudienceExport Permission = iota\n    AudienceImport Permission = iota\n    AddContacts Permission = iota\n    DeleteContacts Permission = iota\n    ViewSegments Permission = iota\n    EditAudienceSettings Permission = iota\n    ArchiveContacts Permission = iota\n    CreateOrImportTemplates Permission = iota\n    EditTemplates Permission = iota\n    CreateEmails Permission = iota\n    EditEmails Permission = iota\n    SendPublishEmails Permission = iota\n    PauseUnpublishEmails Permission = iota\n    DeleteEmails Permission = iota\n    SubmitSmsMarketingApplication Permission = iota\n    CreateSendSmsMmsMessages Permission = iota\n    PurchaseSmsCredits Permission = iota\n    ViewEmailReports Permission = iota\n    ViewSmsReports Permission = iota\n    ViewAbuseReports Permission = iota\n    ViewEmailStatistics Permission = iota\n    UseConversations Permission = iota\n    ViewEmailRecipients Permission = iota\n    TopLocations Permission = iota\n    EmailContactDetails Permission = iota\n    EmailOpenDetails Permission = iota\n    ECommerceProductActivity Permission = iota\n    DomainPerformance Permission = iota\n    CreateYourWebsite Permission = iota\n    PublishUnpublishYourWebsite Permission = iota\n    ViewReport Permission = iota\n    CreateALandingPage Permission = iota\n    PublishUnpublishALandingPage Permission = iota\n    ReplicateALandingPage Permission = iota\n    VerifyADomain Permission = iota\n    ConnectADomain Permission = iota\n    CreateCustomerJourney Permission = iota\n    ViewCustomerJourney Permission = iota\n    EditCustomerJourney Permission = iota\n    TurnOnPauseTurnBackOn Permission = iota\n    ViewMessages Permission = iota\n    LeaveComments Permission = iota\n    SendMessages Permission = iota\n    ToggleUserNotifications Permission = iota\n    CreateSurvey Permission = iota\n    EditSurvey Permission = iota\n    PublishSurvey Permission = iota\n    DeleteSurvey Permission = iota\n    CreateForm Permission = iota\n    EditForm Permission = iota\n    PublishForm Permission = iota\n    DeleteForm Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        InviteUsers: \"invite_users\",\n        RevokeAccountAccess: \"revoke_account_access\",\n        SetUserAccessLevel: \"set_user_access_level\",\n        Require2FactorAuthentication: \"require_2_factor_authentication\",\n        ChangeBillingInformation: \"change_billing_information\",\n        ChangeCompanyOrganizationName: \"change_company_organization_name\",\n        AddOrAccessApiKeys: \"add_or_access_api_keys\",\n        CheckReconnectIntegrations: \"check_reconnect_integrations\",\n        ReferralProgram: \"referral_program\",\n        AccountExport: \"account_export\",\n        CloseAccount: \"close_account\",\n        AddFilesToContentStudio: \"add_files_to_content_studio\",\n        OptInToReceiveEmailsFromMailchimp: \"opt_in_to_receive_emails_from_mailchimp\",\n        CreateAudiences: \"create_audiences\",\n        ViewAudiences: \"view_audiences\",\n        AudienceExport: \"audience_export\",\n        AudienceImport: \"audience_import\",\n        AddContacts: \"add_contacts\",\n        DeleteContacts: \"delete_contacts\",\n        ViewSegments: \"view_segments\",\n        EditAudienceSettings: \"edit_audience_settings\",\n        ArchiveContacts: \"archive_contacts\",\n        CreateOrImportTemplates: \"create_or_import_templates\",\n        EditTemplates: \"edit_templates\",\n        CreateEmails: \"create_emails\",\n        EditEmails: \"edit_emails\",\n        SendPublishEmails: \"send_publish_emails\",\n        PauseUnpublishEmails: \"pause_unpublish_emails\",\n        DeleteEmails: \"delete_emails\",\n        SubmitSmsMarketingApplication: \"submit_sms_marketing_application\",\n        CreateSendSmsMmsMessages: \"create_send_sms_mms_messages\",\n        PurchaseSmsCredits: \"purchase_sms_credits\",\n        ViewEmailReports: \"view_email_reports\",\n        ViewSmsReports: \"view_sms_reports\",\n        ViewAbuseReports: \"view_abuse_reports\",\n        ViewEmailStatistics: \"view_email_statistics\",\n        UseConversations: \"use_conversations\",\n        ViewEmailRecipients: \"view_email_recipients\",\n        TopLocations: \"top_locations\",\n        EmailContactDetails: \"email_contact_details\",\n        EmailOpenDetails: \"email_open_details\",\n        ECommerceProductActivity: \"e_commerce_product_activity\",\n        DomainPerformance: \"domain_performance\",\n        CreateYourWebsite: \"create_your_website\",\n        PublishUnpublishYourWebsite: \"publish_unpublish_your_website\",\n        ViewReport: \"view_report\",\n        CreateALandingPage: \"create_a_landing_page\",\n        PublishUnpublishALandingPage: \"publish_unpublish_a_landing_page\",\n        ReplicateALandingPage: \"replicate_a_landing_page\",\n        VerifyADomain: \"verify_a_domain\",\n        ConnectADomain: \"connect_a_domain\",\n        CreateCustomerJourney: \"create_customer_journey\",\n        ViewCustomerJourney: \"view_customer_journey\",\n        EditCustomerJourney: \"edit_customer_journey\",\n        TurnOnPauseTurnBackOn: \"turn_on_pause_turn_back_on\",\n        ViewMessages: \"view_messages\",\n        LeaveComments: \"leave_comments\",\n        SendMessages: \"send_messages\",\n        ToggleUserNotifications: \"toggle_user_notifications\",\n        CreateSurvey: \"create_survey\",\n        EditSurvey: \"edit_survey\",\n        PublishSurvey: \"publish_survey\",\n        DeleteSurvey: \"delete_survey\",\n        CreateForm: \"create_form\",\n        EditForm: \"edit_form\",\n        PublishForm: \"publish_form\",\n        DeleteForm: \"delete_form\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"invite_users\": InviteUsers,\n        \"revoke_account_access\": RevokeAccountAccess,\n        \"set_user_access_level\": SetUserAccessLevel,\n        \"require_2_factor_authentication\": Require2FactorAuthentication,\n        \"change_billing_information\": ChangeBillingInformation,\n        \"change_company_organization_name\": ChangeCompanyOrganizationName,\n        \"add_or_access_api_keys\": AddOrAccessApiKeys,\n        \"check_reconnect_integrations\": CheckReconnectIntegrations,\n        \"referral_program\": ReferralProgram,\n        \"account_export\": AccountExport,\n        \"close_account\": CloseAccount,\n        \"add_files_to_content_studio\": AddFilesToContentStudio,\n        \"opt_in_to_receive_emails_from_mailchimp\": OptInToReceiveEmailsFromMailchimp,\n        \"create_audiences\": CreateAudiences,\n        \"view_audiences\": ViewAudiences,\n        \"audience_export\": AudienceExport,\n        \"audience_import\": AudienceImport,\n        \"add_contacts\": AddContacts,\n        \"delete_contacts\": DeleteContacts,\n        \"view_segments\": ViewSegments,\n        \"edit_audience_settings\": EditAudienceSettings,\n        \"archive_contacts\": ArchiveContacts,\n        \"create_or_import_templates\": CreateOrImportTemplates,\n        \"edit_templates\": EditTemplates,\n        \"create_emails\": CreateEmails,\n        \"edit_emails\": EditEmails,\n        \"send_publish_emails\": SendPublishEmails,\n        \"pause_unpublish_emails\": PauseUnpublishEmails,\n        \"delete_emails\": DeleteEmails,\n        \"submit_sms_marketing_application\": SubmitSmsMarketingApplication,\n        \"create_send_sms_mms_messages\": CreateSendSmsMmsMessages,\n        \"purchase_sms_credits\": PurchaseSmsCredits,\n        \"view_email_reports\": ViewEmailReports,\n        \"view_sms_reports\": ViewSmsReports,\n        \"view_abuse_reports\": ViewAbuseReports,\n        \"view_email_statistics\": ViewEmailStatistics,\n        \"use_conversations\": UseConversations,\n        \"view_email_recipients\": ViewEmailRecipients,\n        \"top_locations\": TopLocations,\n        \"email_contact_details\": EmailContactDetails,\n        \"email_open_details\": EmailOpenDetails,\n        \"e_commerce_product_activity\": ECommerceProductActivity,\n        \"domain_performance\": DomainPerformance,\n        \"create_your_website\": CreateYourWebsite,\n        \"publish_unpublish_your_website\": PublishUnpublishYourWebsite,\n        \"view_report\": ViewReport,\n        \"create_a_landing_page\": CreateALandingPage,\n        \"publish_unpublish_a_landing_page\": PublishUnpublishALandingPage,\n        \"replicate_a_landing_page\": ReplicateALandingPage,\n        \"verify_a_domain\": VerifyADomain,\n        \"connect_a_domain\": ConnectADomain,\n        \"create_customer_journey\": CreateCustomerJourney,\n        \"view_customer_journey\": ViewCustomerJourney,\n        \"edit_customer_journey\": EditCustomerJourney,\n        \"turn_on_pause_turn_back_on\": TurnOnPauseTurnBackOn,\n        \"view_messages\": ViewMessages,\n        \"leave_comments\": LeaveComments,\n        \"send_messages\": SendMessages,\n        \"toggle_user_notifications\": ToggleUserNotifications,\n        \"create_survey\": CreateSurvey,\n        \"edit_survey\": EditSurvey,\n        \"publish_survey\": PublishSurvey,\n        \"delete_survey\": DeleteSurvey,\n        \"create_form\": CreateForm,\n        \"edit_form\": EditForm,\n        \"publish_form\": PublishForm,\n        \"delete_form\": DeleteForm,\n    }\n\n    PermissionIDs = map[Permission]int{\n        InviteUsers: 1,\n        RevokeAccountAccess: 2,\n        SetUserAccessLevel: 3,\n        Require2FactorAuthentication: 4,\n        ChangeBillingInformation: 5,\n        ChangeCompanyOrganizationName: 6,\n        AddOrAccessApiKeys: 7,\n        CheckReconnectIntegrations: 8,\n        ReferralProgram: 9,\n        AccountExport: 10,\n        CloseAccount: 11,\n        AddFilesToContentStudio: 12,\n        OptInToReceiveEmailsFromMailchimp: 13,\n        CreateAudiences: 14,\n        ViewAudiences: 15,\n        AudienceExport: 16,\n        AudienceImport: 17,\n        AddContacts: 18,\n        DeleteContacts: 19,\n        ViewSegments: 20,\n        EditAudienceSettings: 21,\n        ArchiveContacts: 22,\n        CreateOrImportTemplates: 23,\n        EditTemplates: 24,\n        CreateEmails: 25,\n        EditEmails: 26,\n        SendPublishEmails: 27,\n        PauseUnpublishEmails: 28,\n        DeleteEmails: 29,\n        SubmitSmsMarketingApplication: 30,\n        CreateSendSmsMmsMessages: 31,\n        PurchaseSmsCredits: 32,\n        ViewEmailReports: 33,\n        ViewSmsReports: 34,\n        ViewAbuseReports: 35,\n        ViewEmailStatistics: 36,\n        UseConversations: 37,\n        ViewEmailRecipients: 38,\n        TopLocations: 39,\n        EmailContactDetails: 40,\n        EmailOpenDetails: 41,\n        ECommerceProductActivity: 42,\n        DomainPerformance: 43,\n        CreateYourWebsite: 44,\n        PublishUnpublishYourWebsite: 45,\n        ViewReport: 46,\n        CreateALandingPage: 47,\n        PublishUnpublishALandingPage: 48,\n        ReplicateALandingPage: 49,\n        VerifyADomain: 50,\n        ConnectADomain: 51,\n        CreateCustomerJourney: 52,\n        ViewCustomerJourney: 53,\n        EditCustomerJourney: 54,\n        TurnOnPauseTurnBackOn: 55,\n        ViewMessages: 56,\n        LeaveComments: 57,\n        SendMessages: 58,\n        ToggleUserNotifications: 59,\n        CreateSurvey: 60,\n        EditSurvey: 61,\n        PublishSurvey: 62,\n        DeleteSurvey: 63,\n        CreateForm: 64,\n        EditForm: 65,\n        PublishForm: 66,\n        DeleteForm: 67,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: InviteUsers,\n        2: RevokeAccountAccess,\n        3: SetUserAccessLevel,\n        4: Require2FactorAuthentication,\n        5: ChangeBillingInformation,\n        6: ChangeCompanyOrganizationName,\n        7: AddOrAccessApiKeys,\n        8: CheckReconnectIntegrations,\n        9: ReferralProgram,\n        10: AccountExport,\n        11: CloseAccount,\n        12: AddFilesToContentStudio,\n        13: OptInToReceiveEmailsFromMailchimp,\n        14: CreateAudiences,\n        15: ViewAudiences,\n        16: AudienceExport,\n        17: AudienceImport,\n        18: AddContacts,\n        19: DeleteContacts,\n        20: ViewSegments,\n        21: EditAudienceSettings,\n        22: ArchiveContacts,\n        23: CreateOrImportTemplates,\n        24: EditTemplates,\n        25: CreateEmails,\n        26: EditEmails,\n        27: SendPublishEmails,\n        28: PauseUnpublishEmails,\n        29: DeleteEmails,\n        30: SubmitSmsMarketingApplication,\n        31: CreateSendSmsMmsMessages,\n        32: PurchaseSmsCredits,\n        33: ViewEmailReports,\n        34: ViewSmsReports,\n        35: ViewAbuseReports,\n        36: ViewEmailStatistics,\n        37: UseConversations,\n        38: ViewEmailRecipients,\n        39: TopLocations,\n        40: EmailContactDetails,\n        41: EmailOpenDetails,\n        42: ECommerceProductActivity,\n        43: DomainPerformance,\n        44: CreateYourWebsite,\n        45: PublishUnpublishYourWebsite,\n        46: ViewReport,\n        47: CreateALandingPage,\n        48: PublishUnpublishALandingPage,\n        49: ReplicateALandingPage,\n        50: VerifyADomain,\n        51: ConnectADomain,\n        52: CreateCustomerJourney,\n        53: ViewCustomerJourney,\n        54: EditCustomerJourney,\n        55: TurnOnPauseTurnBackOn,\n        56: ViewMessages,\n        57: LeaveComments,\n        58: SendMessages,\n        59: ToggleUserNotifications,\n        60: CreateSurvey,\n        61: EditSurvey,\n        62: PublishSurvey,\n        63: DeleteSurvey,\n        64: CreateForm,\n        65: EditForm,\n        66: PublishForm,\n        67: DeleteForm,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mailchimp/permissions.yaml",
    "content": "permissions:\n  - invite_users\n  - revoke_account_access\n  - set_user_access_level\n  - require_2_factor_authentication\n  - change_billing_information\n  - change_company_organization_name\n  - add_or_access_api_keys\n  - check_reconnect_integrations\n  - referral_program\n  - account_export\n  - close_account\n  - add_files_to_content_studio\n  - opt_in_to_receive_emails_from_mailchimp\n  - create_audiences\n  - view_audiences\n  - audience_export\n  - audience_import\n  - add_contacts\n  - delete_contacts\n  - view_segments\n  - edit_audience_settings\n  - archive_contacts\n  - create_or_import_templates\n  - edit_templates\n  - create_emails\n  - edit_emails\n  - send_publish_emails\n  - pause_unpublish_emails\n  - delete_emails\n  - submit_sms_marketing_application\n  - create_send_sms_mms_messages\n  - purchase_sms_credits\n  - view_email_reports\n  - view_sms_reports\n  - view_abuse_reports\n  - view_email_statistics\n  - use_conversations\n  - view_email_recipients\n  - top_locations\n  - email_contact_details\n  - email_open_details\n  - e_commerce_product_activity\n  - domain_performance\n  - create_your_website\n  - publish_unpublish_your_website\n  - view_report\n  - create_a_landing_page\n  - publish_unpublish_a_landing_page\n  - replicate_a_landing_page\n  - verify_a_domain\n  - connect_a_domain\n  - create_customer_journey\n  - view_customer_journey\n  - edit_customer_journey\n  - turn_on_pause_turn_back_on\n  - view_messages\n  - leave_comments\n  - send_messages\n  - toggle_user_notifications\n  - create_survey\n  - edit_survey\n  - publish_survey\n  - delete_survey\n  - create_form\n  - edit_form\n  - publish_form\n  - delete_form\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mailgun/expected_output.json",
    "content": "{\n   \"AnalyzerType\": 8,\n   \"Bindings\": [\n      {\n         \"Resource\": {\n            \"Name\": \"sandbox19e49763d44e498e850589ea7d54bd82.mailgun.org\",\n            \"FullyQualifiedName\": \"mailgun/6478cb31d026c112819856cd/sandbox19e49763d44e498e850589ea7d54bd82.mailgun.org\",\n            \"Type\": \"domain\",\n            \"Metadata\": {\n               \"created_at\": \"Thu, 01 Jun 2023 16:45:37 GMT\",\n               \"is_disabled\": false,\n               \"state\": \"active\",\n               \"type\": \"sandbox\"\n            },\n            \"Parent\": null\n         },\n         \"Permission\": {\n            \"Value\": \"full_access\",\n            \"Parent\": null\n         }\n      }\n   ],\n   \"UnboundedResources\": null,\n   \"Metadata\": null\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/mailgun/mailgun.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go mailgun\npackage mailgun\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\ntype SecretInfo struct {\n\tID        string // key id\n\tUserName  string\n\tType      string // type of key\n\tRole      string // key role\n\tExpiresAt string // key expiry time if any\n\tDomains   []Domain\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeMailgun }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credInfo[\"key\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"key not found in credentialInfo\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeMailgun,\n\t\tBindings:     make([]analyzers.Binding, len(info.Domains)),\n\t}\n\n\tfor idx, domain := range info.Domains {\n\t\tresult.Bindings[idx] = analyzers.Binding{\n\t\t\tResource: analyzers.Resource{\n\t\t\t\tName:               domain.URL,\n\t\t\t\tFullyQualifiedName: \"mailgun/\" + domain.ID + \"/\" + domain.URL,\n\t\t\t\tType:               \"domain\",\n\t\t\t\tMetadata: map[string]any{\n\t\t\t\t\t\"created_at\":  domain.CreatedAt,\n\t\t\t\t\t\"type\":        domain.Type,\n\t\t\t\t\t\"state\":       domain.State,\n\t\t\t\t\t\"is_disabled\": domain.IsDisabled,\n\t\t\t\t},\n\t\t\t},\n\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: PermissionStrings[FullAccess],\n\t\t\t},\n\t\t}\n\t}\n\treturn &result\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, apiKey string) {\n\tinfo, err := AnalyzePermissions(cfg, apiKey)\n\tif err != nil {\n\t\tcolor.Red(\"[x] %s\", err.Error())\n\t\treturn\n\t}\n\n\tcolor.Green(\"[i] Valid Mailgun API key\\n\\n\")\n\tprintKeyInfo(info)\n\tprintDomains(info.Domains)\n\tcolor.Yellow(\"[i] Permissions: Full Access\\n\\n\")\n}\n\nfunc AnalyzePermissions(cfg *config.Config, apiKey string) (*SecretInfo, error) {\n\tvar secretInfo SecretInfo\n\n\tvar client = analyzers.NewAnalyzeClient(cfg)\n\n\tif err := getDomains(client, apiKey, &secretInfo); err != nil {\n\t\treturn &secretInfo, err\n\t}\n\n\tif err := getKeys(client, apiKey, &secretInfo); err != nil {\n\t\treturn &secretInfo, err\n\t}\n\n\treturn &secretInfo, nil\n}\n\nfunc printKeyInfo(info *SecretInfo) {\n\tif info.ID == \"\" {\n\t\tcolor.Red(\"[i] Key information not found\")\n\t\treturn\n\t}\n\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Key ID\", \"UserName/Requester\", \"Key Type\", \"Expires At\", \"Role\"})\n\tt.AppendRow(table.Row{info.ID, info.UserName, info.Type, info.ExpiresAt, info.Role})\n\tt.Render()\n}\nfunc printDomains(domains []Domain) {\n\tif len(domains) == 0 {\n\t\tcolor.Red(\"[i] No domains found\")\n\t\treturn\n\t}\n\n\tcolor.Yellow(\"[i] Found %d domain(s)\", len(domains))\n\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Domain\", \"Type\", \"State\", \"Created At\", \"Disabled\"})\n\n\tfor _, domain := range domains {\n\t\tvar colorFunc func(format string, a ...interface{}) string\n\t\tswitch {\n\t\tcase domain.IsDisabled:\n\t\t\tcolorFunc = color.RedString\n\t\tcase domain.Type == \"sandbox\" || domain.State == \"unverified\":\n\t\t\tcolorFunc = color.YellowString\n\t\tdefault:\n\t\t\tcolorFunc = color.GreenString\n\t\t}\n\n\t\tt.AppendRow([]interface{}{\n\t\t\tcolorFunc(domain.URL),\n\t\t\tcolorFunc(domain.Type),\n\t\t\tcolorFunc(domain.State),\n\t\t\tcolorFunc(domain.CreatedAt),\n\t\t\tcolorFunc(strconv.FormatBool(domain.IsDisabled)),\n\t\t})\n\t}\n\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mailgun/mailgun_test.go",
    "content": "package mailgun\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid Mailgun key\",\n\t\t\tkey:     testSecrets.MustGetField(\"NEW_MAILGUN_TOKEN_ACTIVE\"),\n\t\t\twant:    string(expectedOutput),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mailgun/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage mailgun\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    Read Permission = iota\n    Write Permission = iota\n    FullAccess Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        Read: \"read\",\n        Write: \"write\",\n        FullAccess: \"full_access\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"read\": Read,\n        \"write\": Write,\n        \"full_access\": FullAccess,\n    }\n\n    PermissionIDs = map[Permission]int{\n        Read: 1,\n        Write: 2,\n        FullAccess: 3,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: Read,\n        2: Write,\n        3: FullAccess,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mailgun/permissions.yaml",
    "content": "permissions:\n  - read\n  - write\n  - full_access\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mailgun/requests.go",
    "content": "package mailgun\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// DomainsJSON is /domains API response\ntype DomainsJSON struct {\n\tItems      []Domain `json:\"items\"`\n\tTotalCount int      `json:\"total_count\"`\n}\n\n// Domain is a single mailgun domain details\ntype Domain struct {\n\tID         string `json:\"id\"`\n\tURL        string `json:\"name\"`\n\tIsDisabled bool   `json:\"is_disabled\"`\n\tType       string `json:\"type\"`\n\tState      string `json:\"state\"`\n\tCreatedAt  string `json:\"created_at\"`\n}\n\n// KeysJSON is /v1/keys API response\ntype KeysJSON struct {\n\tItems      []Key `json:\"items\"`\n\tTotalCount int   `json:\"total_count\"`\n}\n\n// Key is a single mailgun Key details\ntype Key struct {\n\tID        string `json:\"id\"`\n\tRequester string `json:\"requestor\"`\n\tUserName  string `json:\"user_name\"`\n\tRole      string `json:\"role\"`\n\tType      string `json:\"kind\"`\n\tExpiresAt string `json:\"expires_at\"`\n}\n\n// getDomains list all domains\nfunc getDomains(client *http.Client, apiKey string, secretInfo *SecretInfo) error {\n\tvar domainsJSON DomainsJSON\n\n\treq, err := http.NewRequest(\"GET\", \"https://api.mailgun.net/v4/domains\", nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treq.SetBasicAuth(\"api\", apiKey)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\treturn fmt.Errorf(\"invalid Mailgun API key\")\n\t}\n\n\terr = json.NewDecoder(resp.Body).Decode(&domainsJSON)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// populate secretInfo with domains\n\tsecretInfo.Domains = append(secretInfo.Domains, domainsJSON.Items...)\n\n\treturn nil\n}\n\nfunc getKeys(client *http.Client, apiKey string, secretInfo *SecretInfo) error {\n\tvar keysJSON KeysJSON\n\n\treq, err := http.NewRequest(\"GET\", \"https://api.mailgun.net/v1/keys\", nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treq.SetBasicAuth(\"api\", apiKey)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\treturn fmt.Errorf(\"invalid Mailgun API key\")\n\t}\n\n\terr = json.NewDecoder(resp.Body).Decode(&keysJSON)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// populate secretInfo with key details\n\tfor _, key := range keysJSON.Items {\n\t\t// filter the exact key which we are analyzing\n\t\t// ID is actually the suffix of actual apiKeys\n\t\tif strings.Contains(apiKey, key.ID) {\n\t\t\tkeyToSecretInfo(key, secretInfo)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc keyToSecretInfo(key Key, secretInfo *SecretInfo) {\n\tsecretInfo.ID = key.ID\n\tif key.UserName != \"\" {\n\t\tsecretInfo.UserName = key.UserName\n\t} else {\n\t\tsecretInfo.UserName = key.Requester\n\t}\n\n\tsecretInfo.Role = key.Role\n\tsecretInfo.Type = key.Type\n\tif secretInfo.ExpiresAt != \"\" {\n\t\tsecretInfo.ExpiresAt = key.ExpiresAt\n\t} else {\n\t\tsecretInfo.ExpiresAt = \"Never\"\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/monday/monday.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go monday\npackage monday\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\ntype SecretInfo struct {\n\tUser      Me\n\tAccount   Account\n\tResources []MondayResource\n}\n\nfunc (s *SecretInfo) appendResource(resource MondayResource) {\n\ts.Resources = append(s.Resources, resource)\n}\n\ntype MondayResource struct {\n\tID       string\n\tName     string\n\tType     string\n\tMetaData map[string]string\n\tParent   *MondayResource\n}\n\nfunc (a Analyzer) Type() analyzers.AnalyzerType {\n\treturn analyzers.AnalyzerTypeMonday\n}\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, exist := credInfo[\"key\"]\n\tif !exist {\n\t\treturn nil, errors.New(\"key not found in credentials info\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\t// just print the error in cli and continue as a partial success\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t}\n\n\tif info == nil {\n\t\tcolor.Red(\"[x] Error : %s\", \"No information found\")\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid Monday Personal Access Token\\n\\n\")\n\t// print user information\n\tprintUser(info.User)\n\tprintResources(info.Resources)\n\n\tcolor.Yellow(\"\\n[i] Expires: Never\")\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\t// create http client\n\tclient := analyzers.NewAnalyzeClientUnrestricted(cfg)\n\n\tvar secretInfo = &SecretInfo{}\n\n\t// captureMondayData make a query to graphql API of monday to fetch all data and store it in secret info\n\tif err := captureMondayData(client, key, secretInfo); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfo, nil\n}\n\n// secretInfoToAnalyzerResult translate secret info to Analyzer Result\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeMonday,\n\t\tMetadata:     map[string]any{},\n\t\tBindings:     make([]analyzers.Binding, 0),\n\t}\n\n\t// extract information from resource to create bindings and append to result bindings\n\tfor _, resource := range info.Resources {\n\t\tbinding := analyzers.Binding{\n\t\t\tResource: analyzers.Resource{\n\t\t\t\tName:               resource.Name,\n\t\t\t\tFullyQualifiedName: fmt.Sprintf(\"%s/%s\", resource.Type, resource.ID), // e.g: Board/123\n\t\t\t\tType:               resource.Type,\n\t\t\t\tMetadata:           map[string]any{}, // to avoid panic\n\t\t\t},\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: PermissionStrings[FullAccess], // token always has full access\n\t\t\t},\n\t\t}\n\n\t\tfor key, value := range resource.MetaData {\n\t\t\tbinding.Resource.Metadata[key] = value\n\t\t}\n\n\t\tresult.Bindings = append(result.Bindings, binding)\n\n\t}\n\n\treturn &result\n}\n\n// cli print functions\nfunc printUser(user Me) {\n\tcolor.Green(\"\\n[i] User Information:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"ID\", \"Name\", \"Email\", \"Title\", \"Is Admin\", \"Is Guest\"})\n\tt.AppendRow(table.Row{color.GreenString(user.ID), color.GreenString(user.Name), color.GreenString(user.Email),\n\t\tcolor.GreenString(user.Title), color.GreenString(fmt.Sprintf(\"%t\", user.IsAdmin)), color.GreenString(fmt.Sprintf(\"%t\", user.IsGuest))})\n\tt.Render()\n}\n\nfunc printResources(resources []MondayResource) {\n\tcolor.Green(\"\\n[i] Resources:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Name\", \"Type\"})\n\tfor _, resource := range resources {\n\t\tt.AppendRow(table.Row{color.GreenString(resource.Name), color.GreenString(resource.Type)})\n\t}\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/monday/monday_test.go",
    "content": "package monday\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed result_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"analyzers1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tkey := testSecrets.MustGetField(\"MONDAY_PAT\")\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    []byte // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid monday personal access token\",\n\t\t\tkey:     key,\n\t\t\twant:    expectedOutput,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/monday/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage monday\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    FullAccess Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        FullAccess: \"full_access\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"full_access\": FullAccess,\n    }\n\n    PermissionIDs = map[Permission]int{\n        FullAccess: 1,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: FullAccess,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/monday/permissions.yaml",
    "content": "permissions:\n  - full_access\n"
  },
  {
    "path": "pkg/analyzer/analyzers/monday/query.go",
    "content": "package monday\n\nimport (\n\t\"bytes\"\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n)\n\n//go:embed query.graphql\nvar requestQuery string\n\nconst (\n\t// resource types\n\tTypeBoard       = \"Board\"\n\tTypeBoardGroup  = \"Board Group\"\n\tTypeBoardColumn = \"Board Column\"\n\tTypeDoc         = \"Document\"\n\tTypeFolder      = \"Folder\"\n\tTypeTag         = \"Tag\"\n\tTypeTeam        = \"Team\"\n\tTypeWorkspace   = \"Workspace\"\n)\n\ntype Request struct {\n\tQuery string `json:\"query\"`\n}\n\n// Response is the Monday Graphql API response in case of success\ntype Response struct {\n\tData Data `json:\"data\"`\n}\n\ntype Data struct {\n\tMe         Me          `json:\"me\"`\n\tAccount    Account     `json:\"account\"`\n\tUsers      []User      `json:\"users\"`\n\tBoards     []Board     `json:\"boards\"`\n\tDocs       []Doc       `json:\"docs\"`\n\tFolders    []EntityRef `json:\"folders\"`\n\tTags       []EntityRef `json:\"tags\"`\n\tTeams      []EntityRef `json:\"teams\"`\n\tWorkspaces []Workspace `json:\"workspaces\"`\n}\n\ntype EntityRef struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\ntype Me struct {\n\tID         string      `json:\"id\"`\n\tName       string      `json:\"name\"`\n\tEmail      string      `json:\"email\"`\n\tTitle      string      `json:\"title\"`\n\tIsAdmin    bool        `json:\"is_admin\"`\n\tIsGuest    bool        `json:\"is_guest\"`\n\tIsViewOnly bool        `json:\"is_view_only\"`\n\tIsPending  bool        `json:\"is_pending\"`\n\tIsVerified bool        `json:\"is_verified\"`\n\tTeams      []EntityRef `json:\"teams\"`\n}\n\ntype Account struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n\tSlug string `json:\"slug\"`\n\tTier string `json:\"tier\"`\n}\n\ntype User struct {\n\tEmail   string  `json:\"email\"`\n\tAccount Account `json:\"account\"`\n}\n\ntype Board struct {\n\tID          string      `json:\"id\"`\n\tName        string      `json:\"name\"`\n\tState       string      `json:\"state\"`\n\tPermissions string      `json:\"permissions\"`\n\tGroups      []Group     `json:\"groups\"`\n\tColumns     []Column    `json:\"column\"`\n\tOwners      []EntityRef `json:\"owner\"`\n}\n\ntype Group struct {\n\tTitle string `json:\"title\"`\n\tID    string `json:\"id\"`\n}\n\ntype Column struct {\n\tID    string `json:\"id\"`\n\tTitle string `json:\"title\"`\n\tType  string `json:\"type\"`\n}\n\ntype Doc struct {\n\tID        string    `json:\"id\"`\n\tObjectID  string    `json:\"object_id\"`\n\tName      string    `json:\"name\"`\n\tCreatedBy EntityRef `json:\"created_by\"`\n}\n\ntype Workspace struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n\tKind string `json:\"kind\"`\n}\n\n// captureMondayData send a request to Monday graphql API to get all data and capture it in secret info\nfunc captureMondayData(client *http.Client, key string, secretInfo *SecretInfo) error {\n\tjsonData, err := json.Marshal(Request{Query: requestQuery})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treq, err := http.NewRequest(http.MethodPost, \"https://api.monday.com/v2\", bytes.NewBuffer(jsonData))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Authorization\", key)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tvar apiResponse Response\n\n\t\tif err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// capture details in secret info\n\t\tresponseToSecretInfo(apiResponse, secretInfo)\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"expired/invalid access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\n// responseToSecretInfo translate api response to secret info\nfunc responseToSecretInfo(apiResponse Response, secretInfo *SecretInfo) {\n\tsecretInfo.User = apiResponse.Data.Me\n\tsecretInfo.Account = apiResponse.Data.Account\n\n\tprocessBoards(apiResponse.Data.Boards, secretInfo)\n\tprocessDocs(apiResponse.Data.Docs, secretInfo)\n\tprocessSimpleEntities(apiResponse.Data.Folders, TypeFolder, secretInfo)\n\tprocessSimpleEntities(apiResponse.Data.Tags, TypeTag, secretInfo)\n\tprocessSimpleEntities(apiResponse.Data.Teams, TypeTeam, secretInfo)\n\tprocessWorkspaces(apiResponse.Data.Workspaces, secretInfo)\n}\n\nfunc processBoards(boards []Board, secretInfo *SecretInfo) {\n\tfor _, board := range boards {\n\t\tboardResource := MondayResource{\n\t\t\tID:   board.ID,\n\t\t\tName: board.Name,\n\t\t\tType: TypeBoard,\n\t\t\tMetaData: map[string]string{\n\t\t\t\t\"state\":       board.State,\n\t\t\t\t\"permissions\": board.Permissions,\n\t\t\t},\n\t\t}\n\n\t\tsecretInfo.appendResource(boardResource)\n\n\t\t// sub resources of board\n\t\tfor _, group := range board.Groups {\n\t\t\tsecretInfo.appendResource(MondayResource{\n\t\t\t\tID:     group.ID,\n\t\t\t\tName:   group.Title,\n\t\t\t\tType:   TypeBoardGroup,\n\t\t\t\tParent: &boardResource,\n\t\t\t})\n\t\t}\n\n\t\tfor _, column := range board.Columns {\n\t\t\tsecretInfo.appendResource(MondayResource{\n\t\t\t\tID:   column.ID,\n\t\t\t\tName: column.Title,\n\t\t\t\tType: TypeBoardColumn,\n\t\t\t\tMetaData: map[string]string{\n\t\t\t\t\t\"Column Type\": column.Type,\n\t\t\t\t},\n\t\t\t\tParent: &boardResource,\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc processDocs(docs []Doc, secretInfo *SecretInfo) {\n\tfor _, doc := range docs {\n\t\tsecretInfo.appendResource(MondayResource{\n\t\t\tID:   doc.ID,\n\t\t\tName: doc.Name,\n\t\t\tType: TypeDoc,\n\t\t\tMetaData: map[string]string{\n\t\t\t\t\"created_by\": doc.CreatedBy.Name,\n\t\t\t},\n\t\t})\n\t}\n}\n\nfunc processSimpleEntities(entities []EntityRef, entityType string, secretInfo *SecretInfo) {\n\tfor _, entity := range entities {\n\t\tsecretInfo.appendResource(MondayResource{\n\t\t\tID:   entity.ID,\n\t\t\tName: entity.Name,\n\t\t\tType: entityType,\n\t\t})\n\t}\n}\n\nfunc processWorkspaces(workspaces []Workspace, secretInfo *SecretInfo) {\n\tfor _, workspace := range workspaces {\n\t\tsecretInfo.appendResource(MondayResource{\n\t\t\tID:   workspace.ID,\n\t\t\tName: workspace.Name,\n\t\t\tType: TypeWorkspace,\n\t\t\tMetaData: map[string]string{\n\t\t\t\t\"workspace_kind\": workspace.Kind,\n\t\t\t},\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/monday/query.graphql",
    "content": "{\n  me {\n    id\n    name\n    email\n    title\n    is_admin\n    is_guest\n    is_view_only\n    is_pending\n    is_verified\n    teams {\n      id\n      name\n    }\n  }\n  account {\n    id\n    name\n    slug\n    tier\n  }\n  users {\n    email\n    account {\n      name\n      id\n    }\n  }\n  boards {\n    id\n    name\n    state\n    permissions\n    groups {\n      title\n      id\n    }\n    columns {\n      id\n      title\n      type\n    }\n    owners {\n      id\n      name\n    }\n  }\n  docs {\n    id\n    object_id\n    name\n    created_by {\n      id\n      name\n    }\n  }\n  folders {\n    name\n    id\n  }\n  tags {\n    id\n    name\n  }\n  teams {\n    id\n    name\n  }\n  workspaces {\n    id\n    name\n    kind\n  }\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/monday/result_output.json",
    "content": "{\n    \"AnalyzerType\": 35,\n    \"Bindings\": [\n        {\n            \"Resource\": {\n                \"Name\": \"All Tasks\",\n                \"FullyQualifiedName\": \"Board Group/topics\",\n                \"Type\": \"Board Group\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Bugs Queue\",\n                \"FullyQualifiedName\": \"Board/2007387485\",\n                \"Type\": \"Board\",\n                \"Metadata\": {\n                    \"permissions\": \"everyone\",\n                    \"state\": \"active\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Development Work\",\n                \"FullyQualifiedName\": \"Board Group/new_group24572\",\n                \"Type\": \"Board Group\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Epics\",\n                \"FullyQualifiedName\": \"Board/2007387484\",\n                \"Type\": \"Board\",\n                \"Metadata\": {\n                    \"permissions\": \"everyone\",\n                    \"state\": \"active\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Epics Backlog\",\n                \"FullyQualifiedName\": \"Board Group/new_group\",\n                \"Type\": \"Board Group\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Getting Started\",\n                \"FullyQualifiedName\": \"Board/2007387480\",\n                \"Type\": \"Board\",\n                \"Metadata\": {\n                    \"permissions\": \"everyone\",\n                    \"state\": \"active\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Getting Started\",\n                \"FullyQualifiedName\": \"Document/1126907\",\n                \"Type\": \"Document\",\n                \"Metadata\": {\n                    \"created_by\": \"Truffle Security Detectors\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Group Title\",\n                \"FullyQualifiedName\": \"Board Group/topics\",\n                \"Type\": \"Board Group\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Incoming  Bugs\",\n                \"FullyQualifiedName\": \"Board Group/topics\",\n                \"Type\": \"Board Group\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Managed in sprints\",\n                \"FullyQualifiedName\": \"Board Group/new_group\",\n                \"Type\": \"Board Group\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"My Team\",\n                \"FullyQualifiedName\": \"Workspace/1857558\",\n                \"Type\": \"Workspace\",\n                \"Metadata\": {\n                    \"workspace_kind\": \"open\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Q1 2025\",\n                \"FullyQualifiedName\": \"Board Group/new_group313\",\n                \"Type\": \"Board Group\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Resolved\",\n                \"FullyQualifiedName\": \"Board Group/group_title\",\n                \"Type\": \"Board Group\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Retrospectives\",\n                \"FullyQualifiedName\": \"Board/2007387481\",\n                \"Type\": \"Board\",\n                \"Metadata\": {\n                    \"permissions\": \"everyone\",\n                    \"state\": \"active\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Sprint 1\",\n                \"FullyQualifiedName\": \"Board Group/group_title\",\n                \"Type\": \"Board Group\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Sprints\",\n                \"FullyQualifiedName\": \"Board Group/topics\",\n                \"Type\": \"Board Group\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Sprints\",\n                \"FullyQualifiedName\": \"Board/2007387482\",\n                \"Type\": \"Board\",\n                \"Metadata\": {\n                    \"permissions\": \"everyone\",\n                    \"state\": \"active\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Sprints order\",\n                \"FullyQualifiedName\": \"Board/2007387483\",\n                \"Type\": \"Board\",\n                \"Metadata\": {\n                    \"permissions\": \"everyone\",\n                    \"state\": \"active\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Team OSS team\",\n                \"FullyQualifiedName\": \"Folder/6205823\",\n                \"Type\": \"Folder\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        }\n    ],\n    \"UnboundedResources\": null,\n    \"Metadata\": {}\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/mux/expected_output.json",
    "content": "{\"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}"
  },
  {
    "path": "pkg/analyzer/analyzers/mux/models.go",
    "content": "package mux\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n)\n\ntype ResourceType string\n\nconst (\n\tResourceTypeVideo  ResourceType = \"video\"\n\tResourceTypeData   ResourceType = \"data\"\n\tResourceTypeSystem ResourceType = \"system\"\n)\n\ntype permissionTestConfig struct {\n\tTests []permissionTest `json:\"tests\"`\n}\n\ntype permissionTest struct {\n\tResourceType    ResourceType `json:\"resource_type\"`\n\tPermission      string       `json:\"permission\"`\n\tEndpoint        string       `json:\"endpoint\"`\n\tMethod          string       `json:\"method\"`\n\tValidStatusCode int          `json:\"valid_status_code\"`\n}\n\nfunc (test permissionTest) testPermission(client *http.Client, key string, secret string) (bool, error) {\n\t_, statusCode, err := makeAPIRequest(client, key, secret, test.Method, test.Endpoint)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tswitch statusCode {\n\tcase test.ValidStatusCode:\n\t\treturn true, nil\n\tcase http.StatusNotFound:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\ntype secretInfo struct {\n\tPermissions map[ResourceType]Permission\n\tAssets      []asset\n\tAnnotations []annotation\n\tSigningKeys []signingKey\n}\n\nfunc (info *secretInfo) addPermission(resourceType ResourceType, permission string) {\n\tif info.Permissions == nil {\n\t\tinfo.Permissions = map[ResourceType]Permission{}\n\t}\n\tif perm := info.Permissions[resourceType]; perm == FullAccess {\n\t\treturn\n\t}\n\n\tif permission == \"read\" {\n\t\tinfo.Permissions[resourceType] = Read\n\t} else if permission == \"write\" {\n\t\tinfo.Permissions[resourceType] = FullAccess\n\t}\n}\n\nfunc (info *secretInfo) hasPermission(resourceType ResourceType, permission Permission) bool {\n\tperm, exists := info.Permissions[resourceType]\n\tif !exists {\n\t\treturn false\n\t}\n\treturn perm == permission || perm == FullAccess\n}\n\n// Resource structs\n\ntype track struct {\n\tID       string  `json:\"id\"`\n\tName     string  `json:\"name\"`\n\tType     string  `json:\"type\"`\n\tDuration float64 `json:\"duration\"`\n\tStatus   string  `json:\"status\"`\n\tPrimary  bool    `json:\"primary\"`\n\n\tTextType     string `json:\"text_type\"`\n\tTextSource   string `json:\"text_source\"`\n\tLanguageCode string `json:\"language_code\"`\n\n\tMaxWidth     int     `json:\"max_width\"`\n\tMaxHeight    int     `json:\"max_height\"`\n\tMaxFrameRate float64 `json:\"max_frame_rate\"`\n\tMaxChannels  int     `json:\"max_channels\"`\n}\n\ntype playbackID struct {\n\tID     string `json:\"id\"`\n\tPolicy string `json:\"policy\"`\n}\n\ntype meta struct {\n\tTitle      string `json:\"title\"`\n\tExternalID string `json:\"external_id\"`\n\tCreatorID  string `json:\"creator_id\"`\n}\n\ntype asset struct {\n\tID           string       `json:\"id\"`\n\tDuration     float64      `json:\"duration\"`\n\tStatus       string       `json:\"status\"`\n\tVideoQuality string       `json:\"video_quality\"`\n\tMP4Support   string       `json:\"mp4_support\"`\n\tAspectRatio  string       `json:\"aspect_ratio\"`\n\tTracks       []track      `json:\"tracks\"`\n\tPlaybackIDs  []playbackID `json:\"playback_ids\"`\n\tMeta         meta         `json:\"meta\"`\n\tCreatedAt    string       `json:\"created_at\"`\n}\n\ntype annotation struct {\n\tSubPropertyID string `json:\"sub_property_id\"`\n\tNote          string `json:\"note\"`\n\tID            string `json:\"id\"`\n\tDate          string `json:\"date\"`\n}\n\ntype signingKey struct {\n\tID        string `json:\"id\"`\n\tCreatedAt string `json:\"created_at\"`\n}\n\n// API response structs\n\ntype assetListResponse struct {\n\tData []asset `json:\"data\"`\n}\n\ntype annotationListResponse struct {\n\tData []annotation `json:\"data\"`\n}\n\ntype signingKeyListResponse struct {\n\tData []signingKey `json:\"data\"`\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mux/mux.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go mux\npackage mux\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\n\t_ \"embed\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\n//go:embed tests.json\nvar testsConfig []byte\n\nfunc readTestsConfig() (*permissionTestConfig, error) {\n\tvar config permissionTestConfig\n\tif err := json.Unmarshal(testsConfig, &config); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal tests config: %w\", err)\n\t}\n\treturn &config, nil\n}\n\nfunc (a Analyzer) Type() analyzers.AnalyzerType {\n\treturn analyzers.AnalyzerTypeMux\n}\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, exist := credInfo[\"key\"]\n\tif !exist {\n\t\treturn nil, errors.New(\"key not found in credentials info\")\n\t}\n\tsecret, exist := credInfo[\"secret\"]\n\tif !exist {\n\t\treturn nil, errors.New(\"secret not found in credentials info\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, key, secret)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string, secret string) {\n\tinfo, err := AnalyzePermissions(cfg, key, secret)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Invalid Mux Key or Secret\\n\")\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t\treturn\n\t}\n\n\tif info == nil {\n\t\tcolor.Red(\"[x] Error : %s\", \"No information found\")\n\t\treturn\n\t}\n\n\tcolor.Green(\"[i] Valid Mux API Key and Secret\\n\")\n\tprintResourcesAndPermissions(info)\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string, secret string) (*secretInfo, error) {\n\tclient := analyzers.NewAnalyzeClientUnrestricted(cfg)\n\tsecretInfo := &secretInfo{}\n\tif err := testAllPermissions(client, secretInfo, key, secret); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := populateAllResources(client, secretInfo, key, secret); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfo, nil\n}\n\nfunc secretInfoToAnalyzerResult(info *secretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tbindings := []analyzers.Binding{}\n\treadAccessPermission := analyzers.Permission{\n\t\tValue: PermissionStrings[Read],\n\t}\n\tfullAccessPermission := analyzers.Permission{\n\t\tValue: PermissionStrings[FullAccess],\n\t}\n\n\tvideoResourcePermission := readAccessPermission\n\tif info.hasPermission(ResourceTypeVideo, FullAccess) {\n\t\tvideoResourcePermission = fullAccessPermission\n\t}\n\n\tdataResourcePermission := readAccessPermission\n\tif info.hasPermission(ResourceTypeData, FullAccess) {\n\t\tdataResourcePermission = fullAccessPermission\n\t}\n\n\tsystemResourcePermission := readAccessPermission\n\tif info.hasPermission(ResourceTypeSystem, FullAccess) {\n\t\tsystemResourcePermission = fullAccessPermission\n\t}\n\n\t// Binding all Mux Video Assets\n\tfor _, asset := range info.Assets {\n\t\tassetResource := createAssetResource(asset)\n\t\ttrackResources := createAssetTrackResources(asset, &assetResource)\n\t\tplaybackIDResources := createAssetPlaybackIDResources(asset, &assetResource)\n\n\t\tfor _, resource := range trackResources {\n\t\t\tbindings = append(bindings, createBinding(&resource, videoResourcePermission))\n\t\t}\n\t\tfor _, resource := range playbackIDResources {\n\t\t\tbindings = append(bindings, createBinding(&resource, videoResourcePermission))\n\t\t}\n\n\t\tbindings = append(bindings, createBinding(&assetResource, videoResourcePermission))\n\t}\n\n\t// Binding all Mux Data Annotations\n\tfor _, annotation := range info.Annotations {\n\t\tannotationResource := createAnnotationResource(annotation)\n\t\tbindings = append(bindings, createBinding(&annotationResource, dataResourcePermission))\n\t}\n\n\t// Binding all Mux System Signing Keys\n\tfor _, signingKey := range info.SigningKeys {\n\t\tsigningKeyResource := createSigningKeyResource(signingKey)\n\t\tbindings = append(bindings, createBinding(&signingKeyResource, systemResourcePermission))\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeMux,\n\t\tMetadata:     nil,\n\t\tBindings:     bindings,\n\t}\n\treturn &result\n}\n\nfunc createBinding(resource *analyzers.Resource, permission analyzers.Permission) analyzers.Binding {\n\treturn analyzers.Binding{\n\t\tResource:   *resource,\n\t\tPermission: permission,\n\t}\n}\n\nfunc printResourcesAndPermissions(info *secretInfo) {\n\tcolor.Yellow(\"\\n[i] Permissions:\")\n\tt1 := table.NewWriter()\n\tt1.AppendHeader(table.Row{\"Resource Category\", \"Access Level\", \"Resource List\"})\n\n\tfor idx, resource := range muxResourcesMap[ResourceTypeVideo] {\n\t\tcategory, access := \"\", \"\"\n\t\tif idx == 0 {\n\t\t\tcategory = \"Mux Video\"\n\t\t\taccess = getAccessLevelStringFromPermission(info.Permissions[ResourceTypeVideo])\n\t\t}\n\t\tt1.AppendRow(table.Row{\n\t\t\tcolor.GreenString(category),\n\t\t\tcolor.GreenString(access),\n\t\t\tcolor.GreenString(resource),\n\t\t})\n\t}\n\tt1.AppendSeparator()\n\n\tfor idx, resource := range muxResourcesMap[ResourceTypeData] {\n\t\tcategory, access := \"\", \"\"\n\t\tif idx == 0 {\n\t\t\tcategory = \"Mux Data\"\n\t\t\taccess = getAccessLevelStringFromPermission(info.Permissions[ResourceTypeData])\n\t\t}\n\t\tt1.AppendRow(table.Row{\n\t\t\tcolor.GreenString(category),\n\t\t\tcolor.GreenString(access),\n\t\t\tcolor.GreenString(resource),\n\t\t})\n\t}\n\tt1.AppendSeparator()\n\n\tfor idx, resource := range muxResourcesMap[ResourceTypeSystem] {\n\t\tcategory, access := \"\", \"\"\n\t\tif idx == 0 {\n\t\t\tcategory = \"Mux System\"\n\t\t\taccess = getAccessLevelStringFromPermission(info.Permissions[ResourceTypeSystem])\n\t\t}\n\t\tt1.AppendRow(table.Row{\n\t\t\tcolor.GreenString(category),\n\t\t\tcolor.GreenString(access),\n\t\t\tcolor.GreenString(resource),\n\t\t})\n\t}\n\tt1.SetOutputMirror(os.Stdout)\n\tt1.Render()\n\n\tcolor.Yellow(\"\\n[i] Resources:\")\n\n\tif info.hasPermission(ResourceTypeVideo, Read) {\n\t\tprintMuxVideoResources(info)\n\t}\n\n\tif info.hasPermission(ResourceTypeData, Read) {\n\t\tprintMuxDataResources(info)\n\t}\n\n\tif info.hasPermission(ResourceTypeSystem, Read) {\n\t\tprintMuxSystemResources(info)\n\t}\n\n\tfmt.Printf(\"%s: https://www.mux.com/docs/api-reference\\n\\n\", color.GreenString(\"Ref\"))\n}\n\nfunc printMuxVideoResources(info *secretInfo) {\n\tt1 := table.NewWriter()\n\tt1.SetTitle(\"Assets\")\n\tt1.AppendHeader(table.Row{\"ID\", \"Title\", \"Duration\", \"Status\", \"Creator ID\", \"External ID\", \"Created At\"})\n\n\tt2 := table.NewWriter()\n\tt2.SetTitle(\"Asset Tracks\")\n\tt2.AppendHeader(table.Row{\"Asset ID\", \"ID\", \"Name\", \"Type\", \"Duration\", \"Status\", \"Primary\"})\n\n\tt3 := table.NewWriter()\n\tt3.SetTitle(\"Asset Playback IDs\")\n\tt3.AppendHeader(table.Row{\"Asset ID\", \"ID\", \"Policy\"})\n\n\tfor _, asset := range info.Assets {\n\t\tt1.AppendRow(table.Row{\n\t\t\tcolor.GreenString(asset.ID),\n\t\t\tcolor.GreenString(asset.Meta.Title),\n\t\t\tcolor.GreenString(fmt.Sprintf(\"%.2fs\", asset.Duration)),\n\t\t\tcolor.GreenString(asset.Status),\n\t\t\tcolor.GreenString(asset.Meta.CreatorID),\n\t\t\tcolor.GreenString(asset.Meta.ExternalID),\n\t\t\tcolor.GreenString(asset.CreatedAt),\n\t\t})\n\t\tt1.AppendSeparator()\n\t\tfor _, track := range asset.Tracks {\n\t\t\tt2.AppendRow(table.Row{\n\t\t\t\tcolor.GreenString(asset.ID),\n\t\t\t\tcolor.GreenString(track.ID),\n\t\t\t\tcolor.GreenString(track.Name),\n\t\t\t\tcolor.GreenString(track.Type),\n\t\t\t\tcolor.GreenString(fmt.Sprintf(\"%.2fs\", track.Duration)),\n\t\t\t\tcolor.GreenString(track.Status),\n\t\t\t\tcolor.GreenString(fmt.Sprintf(\"%t\", track.Primary)),\n\t\t\t})\n\t\t\tt2.AppendSeparator()\n\t\t}\n\t\tfor _, playbackID := range asset.PlaybackIDs {\n\t\t\tt3.AppendRow(table.Row{\n\t\t\t\tcolor.GreenString(asset.ID),\n\t\t\t\tcolor.GreenString(playbackID.ID),\n\t\t\t\tcolor.GreenString(playbackID.Policy),\n\t\t\t})\n\t\t\tt3.AppendSeparator()\n\t\t}\n\t}\n\tt1.SetOutputMirror(os.Stdout)\n\tt1.Render()\n\n\tt2.SetOutputMirror(os.Stdout)\n\tt2.Render()\n\n\tt3.SetOutputMirror(os.Stdout)\n\tt3.Render()\n}\n\nfunc printMuxDataResources(info *secretInfo) {\n\tt1 := table.NewWriter()\n\tt1.SetTitle(\"Annotations\")\n\tt1.AppendHeader(table.Row{\"ID\", \"Note\", \"Date\", \"Sub Property ID\"})\n\tfor _, annotation := range info.Annotations {\n\t\tt1.AppendRow(table.Row{\n\t\t\tcolor.GreenString(annotation.ID),\n\t\t\tcolor.GreenString(annotation.Note),\n\t\t\tcolor.GreenString(annotation.Date),\n\t\t\tcolor.GreenString(annotation.SubPropertyID),\n\t\t})\n\t}\n\tt1.SetOutputMirror(os.Stdout)\n\tt1.Render()\n}\n\nfunc printMuxSystemResources(info *secretInfo) {\n\tt1 := table.NewWriter()\n\tt1.SetTitle(\"Signing Keys\")\n\tt1.AppendHeader(table.Row{\"ID\", \"Created At\"})\n\tfor _, signingKey := range info.SigningKeys {\n\t\tt1.AppendRow(table.Row{\n\t\t\tcolor.GreenString(signingKey.ID),\n\t\t\tcolor.GreenString(signingKey.CreatedAt),\n\t\t})\n\t}\n\tt1.SetOutputMirror(os.Stdout)\n\tt1.Render()\n}\n\nfunc getAccessLevelStringFromPermission(permission Permission) string {\n\tswitch permission {\n\tcase Read:\n\t\treturn \"Read\"\n\tcase FullAccess:\n\t\treturn \"Read & Write\"\n\tdefault:\n\t\treturn \"None\"\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mux/mux_test.go",
    "content": "package mux\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"analyzers1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tkey := testSecrets.MustGetField(\"MUX_KEY\")\n\tsecret := testSecrets.MustGetField(\"MUX_SECRET\")\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\tsecret  string\n\t\twant    string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid mux credentials\",\n\t\t\tkey:     key,\n\t\t\tsecret:  secret,\n\t\t\twant:    string(expectedOutput),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\n\t\t\t\t\"key\":    tt.key,\n\t\t\t\t\"secret\": tt.secret,\n\t\t\t})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// compare the JSON strings\n\t\t\tif string(gotJSON) != string(tt.want) {\n\t\t\t\t// pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(tt.want, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mux/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage mux\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    Read Permission = iota\n    FullAccess Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        Read: \"read\",\n        FullAccess: \"full_access\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"read\": Read,\n        \"full_access\": FullAccess,\n    }\n\n    PermissionIDs = map[Permission]int{\n        Read: 1,\n        FullAccess: 2,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: Read,\n        2: FullAccess,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mux/permissions.yaml",
    "content": "permissions:\r\n  - read\r\n  - full_access\r\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mux/requests.go",
    "content": "package mux\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n)\n\nconst muxAPIBaseURL = \"https://api.mux.com\"\n\nfunc makeAPIRequest(client *http.Client, key, secret, method, endpoint string) ([]byte, int, error) {\n\treq, err := http.NewRequest(method, muxAPIBaseURL+\"/\"+endpoint, nil)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treq.SetBasicAuth(key, secret)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn body, res.StatusCode, nil\n}\n\nfunc testAllPermissions(client *http.Client, info *secretInfo, key string, secret string) error {\n\ttestsConfig, err := readTestsConfig()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, test := range testsConfig.Tests {\n\t\thasPermission, err := test.testPermission(client, key, secret)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !hasPermission {\n\t\t\tcontinue\n\t\t}\n\t\tinfo.addPermission(test.ResourceType, test.Permission)\n\t}\n\n\treturn nil\n}\n\nfunc populateAllResources(client *http.Client, info *secretInfo, key string, secret string) error {\n\tif info.hasPermission(ResourceTypeVideo, Read) {\n\t\tif err := populateAssets(client, info, key, secret); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif info.hasPermission(ResourceTypeData, Read) {\n\t\tif err := populateAnnotations(client, info, key, secret); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif info.hasPermission(ResourceTypeSystem, Read) {\n\t\tif err := populateSigningKeys(client, info, key, secret); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc populateAssets(client *http.Client, info *secretInfo, key string, secret string) error {\n\tconst limit = 100\n\n\tfor page := 1; ; page++ {\n\t\turl := fmt.Sprintf(\"/video/v1/assets?limit=%d&page=%d&timeframe[]=100:days\", limit, page)\n\t\tbody, statusCode, err := makeAPIRequest(client, key, secret, http.MethodGet, url)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif statusCode != http.StatusOK {\n\t\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t\t}\n\n\t\tresp := assetListResponse{}\n\t\tif err := json.Unmarshal(body, &resp); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to unmarshal data: %w\", err)\n\t\t}\n\t\tif len(resp.Data) == 0 {\n\t\t\tbreak\n\t\t}\n\t\tinfo.Assets = append(info.Assets, resp.Data...)\n\t}\n\treturn nil\n}\n\nfunc populateAnnotations(client *http.Client, info *secretInfo, key string, secret string) error {\n\tconst limit = 100\n\n\tfor page := 1; ; page++ {\n\t\turl := fmt.Sprintf(\"/data/v1/annotations?limit=%d&page=%d&timeframe[]=100:days\", limit, page)\n\t\tbody, statusCode, err := makeAPIRequest(client, key, secret, http.MethodGet, url)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif statusCode != http.StatusOK {\n\t\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t\t}\n\n\t\tresp := annotationListResponse{}\n\t\tif err := json.Unmarshal(body, &resp); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to unmarshal data: %w\", err)\n\t\t}\n\t\tif len(resp.Data) == 0 {\n\t\t\tbreak\n\t\t}\n\t\tinfo.Annotations = append(info.Annotations, resp.Data...)\n\t}\n\treturn nil\n}\n\nfunc populateSigningKeys(client *http.Client, info *secretInfo, key string, secret string) error {\n\tconst limit = 100\n\n\tfor page := 1; ; page++ {\n\t\turl := fmt.Sprintf(\"/system/v1/signing-keys?limit=%d&page=%d&timeframe[]=100:days\", limit, page)\n\t\tbody, statusCode, err := makeAPIRequest(client, key, secret, http.MethodGet, url)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif statusCode != http.StatusOK {\n\t\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t\t}\n\n\t\tresp := signingKeyListResponse{}\n\t\tif err := json.Unmarshal(body, &resp); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to unmarshal data: %w\", err)\n\t\t}\n\t\tif len(resp.Data) == 0 {\n\t\t\tbreak\n\t\t}\n\t\tinfo.SigningKeys = append(info.SigningKeys, resp.Data...)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mux/resources.go",
    "content": "package mux\n\nimport \"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\nvar muxResourcesMap map[ResourceType][]string\n\nfunc init() {\n\tmuxResourcesMap = map[ResourceType][]string{\n\t\tResourceTypeVideo: {\n\t\t\t\"Transcription Vocabularies\",\n\t\t\t\"Web Inputs\",\n\t\t\t\"Assets\",\n\t\t\t\"Live Streams\",\n\t\t\t\"Uploads\",\n\t\t\t\"Playback Restrictions\",\n\t\t\t\"DRM Configurations\",\n\t\t},\n\t\tResourceTypeData: {\n\t\t\t\"Video Views\",\n\t\t\t\"Filters\",\n\t\t\t\"Dimensions\",\n\t\t\t\"Export\",\n\t\t\t\"Metrics\",\n\t\t\t\"Monitoring\",\n\t\t\t\"Realtime\",\n\t\t\t\"Incidents\",\n\t\t\t\"Annotations\",\n\t\t},\n\t\tResourceTypeSystem: {\n\t\t\t\"Signing Keys\",\n\t\t},\n\t}\n}\n\nfunc createAssetResource(asset asset) analyzers.Resource {\n\treturn analyzers.Resource{\n\t\tName:               asset.ID,\n\t\tFullyQualifiedName: \"asset/\" + asset.ID,\n\t\tType:               \"asset\",\n\t\tMetadata: map[string]any{\n\t\t\t\"duration\":     asset.Duration,\n\t\t\t\"status\":       asset.Status,\n\t\t\t\"videoQuality\": asset.VideoQuality,\n\t\t\t\"mp4Support\":   asset.MP4Support,\n\t\t\t\"aspectRatio\":  asset.AspectRatio,\n\t\t\t\"createdAt\":    asset.CreatedAt,\n\t\t},\n\t}\n}\n\nfunc createAssetTrackResources(asset asset, parent *analyzers.Resource) []analyzers.Resource {\n\ttrackResources := []analyzers.Resource{}\n\tfor _, track := range asset.Tracks {\n\t\ttrackResources = append(trackResources, analyzers.Resource{\n\t\t\tName:               track.ID,\n\t\t\tFullyQualifiedName: \"asset/\" + asset.ID + \"/track/\" + track.ID,\n\t\t\tType:               \"track\",\n\t\t\tMetadata: map[string]any{\n\t\t\t\t\"name\":         track.Name,\n\t\t\t\t\"type\":         track.Type,\n\t\t\t\t\"duration\":     track.Duration,\n\t\t\t\t\"status\":       track.Status,\n\t\t\t\t\"primary\":      track.Primary,\n\t\t\t\t\"textType\":     track.TextType,\n\t\t\t\t\"textSource\":   track.TextSource,\n\t\t\t\t\"languageCode\": track.LanguageCode,\n\t\t\t\t\"maxWidth\":     track.MaxWidth,\n\t\t\t\t\"maxHeight\":    track.MaxHeight,\n\t\t\t\t\"maxFrameRate\": track.MaxFrameRate,\n\t\t\t\t\"maxChannels\":  track.MaxChannels,\n\t\t\t},\n\t\t\tParent: parent,\n\t\t})\n\t}\n\treturn trackResources\n}\n\nfunc createAssetPlaybackIDResources(asset asset, parent *analyzers.Resource) []analyzers.Resource {\n\tplaybackIDResources := []analyzers.Resource{}\n\tfor _, playbackID := range asset.PlaybackIDs {\n\t\tplaybackIDResources = append(playbackIDResources, analyzers.Resource{\n\t\t\tName:               playbackID.ID,\n\t\t\tFullyQualifiedName: \"asset/\" + asset.ID + \"/playback_id/\" + playbackID.ID,\n\t\t\tType:               \"playback_id\",\n\t\t\tMetadata: map[string]any{\n\t\t\t\t\"policy\": playbackID.Policy,\n\t\t\t},\n\t\t\tParent: parent,\n\t\t})\n\t}\n\treturn playbackIDResources\n}\n\nfunc createAnnotationResource(annotation annotation) analyzers.Resource {\n\treturn analyzers.Resource{\n\t\tName:               annotation.ID,\n\t\tFullyQualifiedName: \"annotation/\" + annotation.ID,\n\t\tType:               \"annotation\",\n\t\tMetadata: map[string]any{\n\t\t\t\"subPropertyID\": annotation.SubPropertyID,\n\t\t\t\"note\":          annotation.Note,\n\t\t\t\"date\":          annotation.Date,\n\t\t},\n\t}\n}\nfunc createSigningKeyResource(signingKey signingKey) analyzers.Resource {\n\treturn analyzers.Resource{\n\t\tName:               signingKey.ID,\n\t\tFullyQualifiedName: \"signing_key/\" + signingKey.ID,\n\t\tType:               \"signing_key\",\n\t\tMetadata: map[string]any{\n\t\t\t\"createdAt\": signingKey.CreatedAt,\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mux/tests.json",
    "content": "{\r\n    \"tests\": [\r\n        {\r\n            \"resource_type\": \"video\",\r\n            \"permission\": \"read\",\r\n            \"endpoint\": \"/video/v1/assets?limit=1\",\r\n            \"method\": \"GET\",\r\n            \"valid_status_code\": 200\r\n        },\r\n        {\r\n            \"resource_type\": \"video\",\r\n            \"permission\": \"write\",\r\n            \"endpoint\": \"/video/v1/assets\",\r\n            \"method\": \"POST\",\r\n            \"valid_status_code\": 400\r\n        },\r\n        {\r\n            \"resource_type\": \"data\",\r\n            \"permission\": \"read\",\r\n            \"endpoint\": \"/data/v1/annotations?limit=1\",\r\n            \"method\": \"GET\",\r\n            \"valid_status_code\": 200\r\n        },\r\n        {\r\n            \"resource_type\": \"data\",\r\n            \"permission\": \"write\",\r\n            \"endpoint\": \"/data/v1/annotations\",\r\n            \"method\": \"POST\",\r\n            \"valid_status_code\": 400\r\n        },\r\n        {\r\n            \"resource_type\": \"system\",\r\n            \"permission\": \"read\",\r\n            \"endpoint\": \"/system/v1/signing-keys?limit=1\",\r\n            \"method\": \"GET\",\r\n            \"valid_status_code\": 200\r\n        },\r\n        {\r\n            \"resource_type\": \"system\",\r\n            \"permission\": \"write\",\r\n            \"endpoint\": \"/system/v1/signing-keys\",\r\n            \"method\": \"DELETE\",\r\n            \"valid_status_code\": 400\r\n        }\r\n    ]\r\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/mysql/expected_output.json",
    "content": "{\"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}"
  },
  {
    "path": "pkg/analyzer/analyzers/mysql/mysql.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go mysql\n\npackage mysql\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dustin/go-humanize\"\n\t_ \"github.com/go-sql-driver/mysql\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/jedib0t/go-pretty/v6/text\"\n\t\"github.com/xo/dburl\"\n\n\t\"github.com/fatih/color\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeMySQL }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\turi, ok := credInfo[\"connection_string\"]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"missing connection string\")\n\t}\n\tinfo, err := AnalyzePermissions(a.Cfg, uri)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeMySQL,\n\t\tMetadata:     nil,\n\t\tBindings:     []analyzers.Binding{},\n\t}\n\n\t// add user privileges to bindings\n\tuserBindings, userResource := bakeUserBindings(info)\n\tresult.Bindings = append(result.Bindings, userBindings...)\n\n\t// add user's database privileges to bindings\n\tdatabaseBindings := bakeDatabaseBindings(userResource, info)\n\tresult.Bindings = append(result.Bindings, databaseBindings...)\n\n\treturn &result\n}\n\nfunc bakeUserBindings(info *SecretInfo) ([]analyzers.Binding, *analyzers.Resource) {\n\n\tvar userBindings []analyzers.Binding\n\n\t// add user and their privileges to bindings\n\tuserResource := analyzers.Resource{\n\t\tName:               info.User,\n\t\tFullyQualifiedName: info.Host + \"/\" + info.User,\n\t\tType:               \"user\",\n\t}\n\n\tfor _, priv := range info.GlobalPrivs.Privs {\n\t\tuserBindings = append(userBindings, analyzers.Binding{\n\t\t\tResource: userResource,\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: priv,\n\t\t\t},\n\t\t})\n\t}\n\n\treturn userBindings, &userResource\n}\n\nfunc bakeDatabaseBindings(userResource *analyzers.Resource, info *SecretInfo) []analyzers.Binding {\n\tvar databaseBindings []analyzers.Binding\n\n\tfor _, database := range info.Databases {\n\t\tdbResource := analyzers.Resource{\n\t\t\tName:               database.Name,\n\t\t\tFullyQualifiedName: info.Host + \"/\" + database.Name,\n\t\t\tType:               \"database\",\n\t\t\tMetadata: map[string]any{\n\t\t\t\t\"default\":      database.Default,\n\t\t\t\t\"non_existent\": database.Nonexistent,\n\t\t\t},\n\t\t\tParent: userResource,\n\t\t}\n\n\t\tfor _, priv := range database.Privs {\n\t\t\tdatabaseBindings = append(databaseBindings, analyzers.Binding{\n\t\t\t\tResource: dbResource,\n\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\tValue: priv,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\t// add this database's table privileges to bindings\n\t\ttableBindings := bakeTableBindings(&dbResource, database)\n\t\tdatabaseBindings = append(databaseBindings, tableBindings...)\n\n\t\t// add this database's routines privileges to bindings\n\t\troutineBindings := bakeRoutineBindings(&dbResource, database)\n\t\tdatabaseBindings = append(databaseBindings, routineBindings...)\n\t}\n\n\treturn databaseBindings\n}\n\nfunc bakeTableBindings(dbResource *analyzers.Resource, database *Database) []analyzers.Binding {\n\tif database.Tables == nil {\n\t\treturn nil\n\t}\n\tvar tableBindings []analyzers.Binding\n\tfor _, table := range *database.Tables {\n\t\ttableResource := analyzers.Resource{\n\t\t\tName:               table.Name,\n\t\t\tFullyQualifiedName: dbResource.FullyQualifiedName + \"/\" + table.Name,\n\t\t\tType:               \"table\",\n\t\t\tMetadata: map[string]any{\n\t\t\t\t\"bytes\":        table.Bytes,\n\t\t\t\t\"non_existent\": table.Nonexistent,\n\t\t\t},\n\t\t\tParent: dbResource,\n\t\t}\n\n\t\tfor _, priv := range table.Privs {\n\t\t\ttableBindings = append(tableBindings, analyzers.Binding{\n\t\t\t\tResource: tableResource,\n\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\tValue: priv,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\t// Add this table's column privileges to bindings\n\t\tfor _, column := range table.Columns {\n\t\t\tcolumnResource := analyzers.Resource{\n\t\t\t\tName:               column.Name,\n\t\t\t\tFullyQualifiedName: tableResource.FullyQualifiedName + \"/\" + column.Name,\n\t\t\t\tType:               \"column\",\n\t\t\t\tParent:             &tableResource,\n\t\t\t}\n\n\t\t\tfor _, priv := range column.Privs {\n\t\t\t\ttableBindings = append(tableBindings, analyzers.Binding{\n\t\t\t\t\tResource: columnResource,\n\t\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\t\tValue: priv,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\treturn tableBindings\n}\n\nfunc bakeRoutineBindings(dbResource *analyzers.Resource, database *Database) []analyzers.Binding {\n\tif database.Routines == nil {\n\t\treturn nil\n\t}\n\n\tvar routineBindings []analyzers.Binding\n\tfor _, routine := range *database.Routines {\n\t\troutineResource := analyzers.Resource{\n\t\t\tName:               routine.Name,\n\t\t\tFullyQualifiedName: dbResource.FullyQualifiedName + \"/\" + routine.Name,\n\t\t\tType:               \"routine\",\n\t\t\tMetadata: map[string]any{\n\t\t\t\t\"non_existent\": routine.Nonexistent,\n\t\t\t},\n\t\t\tParent: dbResource,\n\t\t}\n\n\t\tfor _, priv := range routine.Privs {\n\t\t\troutineBindings = append(routineBindings, analyzers.Binding{\n\t\t\t\tResource: routineResource,\n\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\tValue: priv,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t}\n\n\treturn routineBindings\n}\n\nconst (\n\t// MySQL SSL Modes\n\tmysql_sslmode                 = \"ssl-mode\"\n\tmysql_sslmode_disabled        = \"DISABLED\"\n\tmysql_sslmode_preferred       = \"PREFERRED\"\n\tmysql_sslmode_required        = \"REQUIRED\"\n\tmysql_sslmode_verify_ca       = \"VERIFY_CA\"\n\tmysql_sslmode_verify_identity = \"VERIFY_IDENTITY\"\n\t//https://github.com/go-sql-driver/mysql/issues/899#issuecomment-443493840\n\n\t// MySQL Built-in Databases\n\tmysql_db_sys      = \"sys\"\n\tmysql_db_perf_sch = \"performance_schema\"\n\tmysql_db_info_sch = \"information_schema\"\n\tmysql_db_mysql    = \"mysql\"\n\n\tmysql_all = \"*\"\n)\n\ntype GlobalPrivs struct {\n\tPrivs []string\n}\n\ntype Database struct {\n\tName        string\n\tDefault     bool\n\tTables      *[]Table\n\tPrivs       []string\n\tRoutines    *[]Routine\n\tNonexistent bool\n}\n\ntype Table struct {\n\tName        string\n\tColumns     []Column\n\tPrivs       []string\n\tNonexistent bool\n\tBytes       uint64\n}\n\ntype Column struct {\n\tName  string\n\tPrivs []string\n}\n\ntype Routine struct {\n\tName        string\n\tPrivs       []string\n\tNonexistent bool\n}\n\n// so CURRENT_USER returns `doadmin@%` and not `doadmin@localhost\n// USER() returns `doadmin@localhost`\n\ntype SecretInfo struct {\n\tHost        string\n\tUser        string\n\tDatabases   map[string]*Database\n\tGlobalPrivs GlobalPrivs\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\t// ToDo: Add in logging\n\tif cfg.LoggingEnabled {\n\t\tcolor.Red(\"[x] Logging is not supported for this analyzer.\")\n\t\treturn\n\t}\n\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error: %s\", err.Error())\n\t\treturn\n\t}\n\n\tcolor.Green(\"[+] Successfully connected as user: %s\", info.User)\n\n\t// Print the results\n\tprintResults(info.Databases, info.GlobalPrivs, cfg.ShowAll)\n}\n\nfunc AnalyzePermissions(cfg *config.Config, connectionStr string) (*SecretInfo, error) {\n\t// Parse the connection string\n\tu, err := parseConnectionStr(connectionStr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"parsing the connection string: %w\", err)\n\t}\n\n\tdb, err := createConnection(u)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"connecting to the MySQL database: %w\", err)\n\t}\n\tdefer db.Close()\n\n\t// Get the current user\n\tuser, err := getUser(db)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"getting the current user: %w\", err)\n\t}\n\n\t// Get all accessible databases\n\tvar databases = make(map[string]*Database, 0)\n\terr = getDatabases(db, databases)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"getting databases: %w\", err)\n\t}\n\t//Get all accessible tables\n\terr = getTables(db, databases)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"getting tables: %w\", err)\n\t}\n\t// Get user grants\n\tgrants, err := getGrants(db)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"getting user grants: %w\", err)\n\t}\n\t// Get all accessible routines\n\terr = getRoutines(db, databases)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"getting routines: %w\", err)\n\t}\n\n\tvar globalPrivs GlobalPrivs\n\t// Process user grants\n\tprocessGrants(grants, databases, &globalPrivs)\n\n\treturn &SecretInfo{\n\t\tHost:        u.Hostname(),\n\t\tUser:        user,\n\t\tDatabases:   databases,\n\t\tGlobalPrivs: globalPrivs,\n\t}, nil\n}\n\nfunc parseConnectionStr(connection string) (*dburl.URL, error) {\n\t// Check if the connection string starts with 'mysql://'\n\tif !strings.HasPrefix(connection, \"mysql://\") {\n\t\tcolor.Yellow(\"[i] The connection string should start with 'mysql://'. Adding it for you.\")\n\t\tconnection = \"mysql://\" + connection\n\t}\n\n\t// Adapt ssl-mode params to Go MySQL driver\n\tconnection, err := fixTLSQueryParam(connection)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Parse the connection string\n\tu, err := dburl.Parse(connection)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn u, nil\n}\n\nfunc createConnection(u *dburl.URL) (*sql.DB, error) {\n\t// Connect to the MySQL database\n\tdb, err := sql.Open(\"mysql\", u.DSN)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdb.SetConnMaxLifetime(time.Minute * 5)\n\tdb.SetMaxOpenConns(10)\n\tdb.SetMaxIdleConns(10)\n\n\t// Check the connection\n\terr = db.Ping()\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"certificate signed by unknown authority\") {\n\t\t\treturn nil, fmt.Errorf(\"%s. try adding 'ssl-mode=PREFERRED' to your connection string\", err.Error())\n\t\t}\n\t\treturn nil, err\n\t}\n\n\treturn db, nil\n}\n\nfunc fixTLSQueryParam(connection string) (string, error) {\n\t// Parse connection string on \"?\"\n\tparsed := strings.Split(connection, \"?\")\n\n\t// Check if has query parms\n\tif len(parsed) < 2 {\n\t\t// Add 10s timeout\n\t\tconnection += \"?timeout=10s\"\n\t\treturn connection, nil\n\t}\n\n\tvar error error\n\n\t// Split parms\n\tquerySlice := strings.Split(parsed[1], \"&\")\n\n\t// Check if ssl-mode is present\n\tfor i, part := range querySlice {\n\t\tif strings.HasPrefix(part, \"ssl-mode\") {\n\t\t\tmode := strings.Split(part, \"=\")[1]\n\t\t\tswitch mode {\n\t\t\tcase mysql_sslmode_disabled:\n\t\t\t\tquerySlice[i] = \"tls=false\"\n\t\t\tcase mysql_sslmode_preferred:\n\t\t\t\tquerySlice[i] = \"tls=preferred\"\n\t\t\tcase mysql_sslmode_required:\n\t\t\t\tquerySlice[i] = \"tls=true\"\n\t\t\tcase mysql_sslmode_verify_ca:\n\t\t\t\terror = fmt.Errorf(\"this implementation does not support VERIFY_CA. try removing it or using ssl-mode=REQUIRED\")\n\t\t\t\t// Need to implement --ssl-ca or --ssl-capath\n\t\t\tcase mysql_sslmode_verify_identity:\n\t\t\t\terror = fmt.Errorf(\"this implementation does not support VERIFY_IDENTITY. try removing it or using ssl-mode=REQUIRED\")\n\t\t\t\t// Need to implement --ssl-ca or --ssl-capath\n\t\t\t}\n\t\t}\n\t}\n\n\t// Join the parts back together\n\tnewQuerySlice := strings.Join(querySlice, \"&\")\n\treturn (parsed[0] + \"?\" + newQuerySlice + \"&timeout=10s\"), error\n}\n\nfunc getUser(db *sql.DB) (string, error) {\n\tvar user string\n\terr := db.QueryRow(\"SELECT CURRENT_USER()\").Scan(&user)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn user, nil\n}\n\nfunc getDatabases(db *sql.DB, databases map[string]*Database) error {\n\trows, err := db.Query(\"SHOW DATABASES\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\n\tfor rows.Next() {\n\t\tvar dbName string\n\t\terr = rows.Scan(&dbName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// check if the database is a built-in database\n\t\tbuilt_in_db := false\n\t\tswitch dbName {\n\t\tcase mysql_db_sys, mysql_db_perf_sch, mysql_db_info_sch, mysql_db_mysql:\n\t\t\tbuilt_in_db = true\n\t\t}\n\t\t// add the database to the databases map\n\t\tnewTables := make([]Table, 0)\n\t\tnewRoutines := make([]Routine, 0)\n\t\tdatabases[dbName] = &Database{Name: dbName, Default: built_in_db, Tables: &newTables, Routines: &newRoutines}\n\t}\n\n\treturn nil\n}\n\nfunc getTables(db *sql.DB, databases map[string]*Database) error {\n\trows, err := db.Query(\"SELECT table_schema, table_name, IFNULL(DATA_LENGTH,0) FROM information_schema.tables\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\n\tfor rows.Next() {\n\t\tvar dbName string\n\t\tvar tableName string\n\t\tvar tableSize uint64\n\t\terr = rows.Scan(&dbName, &tableName, &tableSize)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// find the database in the databases slice\n\t\td := databases[dbName]\n\t\t*d.Tables = append(*d.Tables, Table{Name: tableName, Bytes: tableSize})\n\t}\n\n\treturn nil\n}\n\nfunc getRoutines(db *sql.DB, databases map[string]*Database) error {\n\trows, err := db.Query(\"SELECT routine_schema, routine_name FROM information_schema.routines\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\n\tfor rows.Next() {\n\t\tvar dbName string\n\t\tvar routineName string\n\t\terr = rows.Scan(&dbName, &routineName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// find the database in the databases slice\n\t\td, ok := databases[dbName]\n\t\tif !ok {\n\t\t\tdatabases[dbName] = &Database{Name: dbName, Default: false, Tables: &[]Table{}, Routines: &[]Routine{}, Nonexistent: true}\n\t\t\td = databases[dbName]\n\t\t}\n\n\t\t*d.Routines = append(*d.Routines, Routine{Name: routineName})\n\t}\n\n\treturn nil\n}\n\nfunc getGrants(db *sql.DB) ([]string, error) {\n\trows, err := db.Query(\"SHOW GRANTS\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer rows.Close()\n\n\tvar grants []string\n\tfor rows.Next() {\n\t\tvar grant string\n\t\terr = rows.Scan(&grant)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgrants = append(grants, grant)\n\t}\n\n\treturn grants, nil\n}\n\n// ToDo: Deal with these GRANT/REVOKE statements\n// GRANT SELECT (col1), INSERT (col1, col2) ON mydb.mytbl TO 'someuser'@'somehost';\n// GRANT PROXY ON 'localuser'@'localhost' TO 'externaluser'@'somehost';\n// GRANT 'role1', 'role2' TO 'user1'@'localhost', 'user2'@'localhost';\n\n// What are the default privs on information_schema and performance_Schema?\n// Seems table by table...maybe just put \"Not Implemented\" and leave this to be a show_all option.\n\n// Note: Can't GRANT on a table that doesn't exist, but DB is fine.\n\n// processGrants processes the grants and adds them to the databases structs and globalPrivs\nfunc processGrants(grants []string, databases map[string]*Database, globalPrivs *GlobalPrivs) {\n\tfor _, grant := range grants {\n\t\t// GRANTs on non-existent databases are valid, but we need that object to exist in \"databases\" for processGrant().\n\t\tdb := parseDBFromGrant(grant)\n\t\tif db == mysql_all {\n\t\t\tcontinue\n\t\t}\n\t\t_, ok := databases[db]\n\t\tif !ok {\n\t\t\tdatabases[db] = &Database{Name: db, Default: false, Tables: &[]Table{}, Routines: &[]Routine{}, Nonexistent: true}\n\t\t}\n\t}\n\tfor _, grant := range grants {\n\t\t// TODO: How to deal with error here?\n\t\t_ = processGrant(grant, databases, globalPrivs)\n\t}\n}\n\nfunc processGrant(grant string, databases map[string]*Database, globalPrivs *GlobalPrivs) error {\n\tisGrant := strings.HasPrefix(grant, \"GRANT\")\n\t//hasGrantOption := strings.HasSuffix(grant, \"WITH GRANT OPTION\")\n\n\t// remove GRANT or REVOKE\n\tgrant = strings.TrimPrefix(grant, \"GRANT\")\n\tgrant = strings.TrimPrefix(grant, \"REVOKE\")\n\n\t// Split on \" ON \"\n\tparts := strings.Split(grant, \" ON \")\n\tif len(parts) < 2 {\n\t\treturn fmt.Errorf(\"Error processing grant: %s\", grant)\n\t}\n\n\t// Put privs in a slice\n\tprivs := strings.Split(parts[0], \",\")\n\tfor i, priv := range privs {\n\t\tprivs[i] = strings.Trim(priv, \" \")\n\t}\n\n\t// Get DB and Table\n\tdbName := strings.Trim(strings.Split(parts[1], \" TO \")[0], \" \")\n\tif dbName == parts[1] {\n\t\tdbName = strings.Trim(strings.Split(parts[1], \" FROM \")[0], \" \")\n\t}\n\n\t// Find the database in the databases slice\n\t// Note: table may not exist yet OR may be a routine\n\tdbTableParts := strings.Split(dbName, \".\")\n\tdb := strings.Trim(dbTableParts[0], \"\\\"`\")\n\ttable := strings.Trim(dbTableParts[1], \"\\\"`\")\n\n\t// dont' forget to deal with revoking db-level privs\n\n\tif db == mysql_all {\n\t\t// Deal with \"ALL\" and \"ALL PRIVILEGES\"\n\t\tswitch privs[0] {\n\t\tcase \"ALL\", \"ALL PRIVILEGES\":\n\t\t\taddRemoveAllPrivs(databases, globalPrivs, isGrant)\n\t\tdefault:\n\t\t\tfor _, priv := range privs {\n\t\t\t\taddRemoveOnePrivOnAll(databases, globalPrivs, priv, isGrant)\n\t\t\t}\n\t\t}\n\t} else {\n\n\t\t// Check if the privs are for a routine\n\t\tisRoutine := checkIsRoutine(privs)\n\t\tif isRoutine {\n\t\t\tdb = strings.TrimPrefix(db, \"PROCEDURE `\")\n\t\t\tdb = strings.TrimSuffix(db, \"`\")\n\t\t}\n\t\td := databases[db]\n\n\t\tswitch {\n\t\tcase table == mysql_all:\n\t\t\tfilteredDBPrivs := filterDBPrivs(privs)\n\t\t\tfilteredTablePrivs := filterTablePrivs(privs)\n\t\t\td.Privs = addRemovePrivs(d.Privs, filteredDBPrivs, isGrant)\n\t\t\tfor i, t := range *d.Tables {\n\t\t\t\t(*d.Tables)[i].Privs = addRemovePrivs(t.Privs, filteredTablePrivs, isGrant)\n\t\t\t}\n\t\tcase isRoutine:\n\t\t\tvar idx = getRoutineIndex(d, table)\n\t\t\tif idx == -1 {\n\t\t\t\t*d.Routines = append(*d.Routines, Routine{Name: table, Nonexistent: true})\n\t\t\t\tidx = len(*d.Routines) - 1\n\t\t\t}\n\t\t\t(*d.Routines)[idx].Privs = addRemovePrivs((*d.Routines)[idx].Privs, privs, isGrant)\n\t\tdefault:\n\t\t\tvar idx = getTableIndex(d, table)\n\t\t\tif idx == -1 {\n\t\t\t\t*d.Tables = append(*d.Tables, Table{Name: table, Nonexistent: true, Bytes: 0})\n\t\t\t\tidx = len(*d.Tables) - 1\n\t\t\t}\n\t\t\t(*d.Tables)[idx].Privs = addRemovePrivs((*d.Tables)[idx].Privs, privs, isGrant)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc parseDBFromGrant(grant string) string {\n\t// Split on \" ON \"\n\tparts := strings.Split(grant, \" ON \")\n\tif len(parts) < 2 {\n\t\tcolor.Red(\"[!] Error processing grant: %s\", grant)\n\t\treturn \"\"\n\t}\n\n\t// Get DB and Table\n\tdbName := strings.Trim(strings.Split(parts[1], \" TO \")[0], \" \")\n\tif dbName == parts[1] {\n\t\tdbName = strings.Trim(strings.Split(parts[1], \" FROM \")[0], \" \")\n\t}\n\tdbTableParts := strings.Split(dbName, \".\")\n\tdb := strings.Trim(dbTableParts[0], \"\\\"`\")\n\tdb = strings.TrimPrefix(db, \"PROCEDURE `\")\n\tdb = strings.TrimSuffix(db, \"`\")\n\treturn db\n}\n\nfunc filterDBPrivs(privs []string) []string {\n\tfiltered := make([]string, 0)\n\tfor _, priv := range privs {\n\t\tif SCOPES[priv].Database {\n\t\t\tfiltered = append(filtered, priv)\n\t\t}\n\t}\n\treturn filtered\n}\n\nfunc filterTablePrivs(privs []string) []string {\n\tfiltered := make([]string, 0)\n\tfor _, priv := range privs {\n\t\tif SCOPES[priv].Table {\n\t\t\tfiltered = append(filtered, priv)\n\t\t}\n\t}\n\treturn filtered\n}\n\nfunc addRemoveOnePrivOnAll(databases map[string]*Database, globalPrivs *GlobalPrivs, priv string, isGrant bool) {\n\tscope, ok := SCOPES[priv]\n\tif !ok {\n\t\tcolor.Red(\"[!] Error processing grant: privilege doesn't exist in our MySQL (%s)\", priv)\n\t\treturn\n\t}\n\n\tslicedPriv := []string{priv}\n\n\t// Add priv to globalPrivs\n\tif scope.Global {\n\t\tglobalPrivs.Privs = addRemovePrivs(globalPrivs.Privs, slicedPriv, isGrant)\n\t}\n\n\t// Add/Remove priv to all databases\n\tif scope.Database {\n\t\tfor _, d := range databases {\n\t\t\tif d.Name == \"information_schema\" || d.Name == \"performance_schema\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\td.Privs = addRemovePrivs(d.Privs, slicedPriv, isGrant)\n\t\t}\n\t}\n\n\t// Add/Remove priv to all tables\n\tif scope.Table {\n\t\tfor _, d := range databases {\n\t\t\tfor i, t := range *d.Tables {\n\t\t\t\t(*d.Tables)[i].Privs = addRemovePrivs(t.Privs, slicedPriv, isGrant)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Add/Remove priv to all routines\n\tif scope.Routine {\n\t\tfor _, d := range databases {\n\t\t\tfor i, r := range *d.Routines {\n\t\t\t\t(*d.Routines)[i].Privs = addRemovePrivs(r.Privs, slicedPriv, isGrant)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc addRemoveAllPrivs(databases map[string]*Database, globalPrivs *GlobalPrivs, isGrant bool) {\n\t// Add all privs to globalPrivs\n\tglobalAllPrivs := getGlobalAllPrivileges()\n\tglobalPrivs.Privs = addRemovePrivs(globalPrivs.Privs, globalAllPrivs, isGrant)\n\n\t// Get DB, Table and Routine Privs\n\tdbAllPrivs := getDBAllPrivs()\n\ttableAllPrivs := getTableAllPrivs()\n\troutineAllPrivs := getRoutineAllPrivs()\n\n\t// Add all privs to all databases and tables and routines\n\tfor _, d := range databases {\n\t\tif d.Name == \"information_schema\" || d.Name == \"performance_schema\" {\n\t\t\tcontinue\n\t\t}\n\t\t// Add DB-level privs\n\t\td.Privs = addRemovePrivs(d.Privs, dbAllPrivs, isGrant)\n\n\t\t// Add Table-level privs\n\t\tfor i, t := range *d.Tables {\n\t\t\t(*d.Tables)[i].Privs = addRemovePrivs(t.Privs, tableAllPrivs, isGrant)\n\t\t}\n\n\t\t// Add Routine-level privs\n\t\tfor i, r := range *d.Routines {\n\t\t\t(*d.Routines)[i].Privs = addRemovePrivs(r.Privs, routineAllPrivs, isGrant)\n\t\t}\n\t}\n}\n\nfunc getGlobalAllPrivileges() []string {\n\tprivs := make([]string, 0)\n\tfor priv, scope := range SCOPES {\n\t\tif scope.Global && !scope.Dynamic && priv != \"USAGE\" && priv != \"GRANT OPTION\" {\n\t\t\tprivs = append(privs, priv)\n\t\t}\n\t}\n\treturn privs\n}\n\nfunc getDBAllPrivs() []string {\n\tprivs := make([]string, 0)\n\tfor priv, scope := range SCOPES {\n\t\tif scope.Database && !scope.Dynamic && priv != \"USAGE\" && priv != \"GRANT OPTION\" {\n\t\t\tprivs = append(privs, priv)\n\t\t}\n\t}\n\treturn privs\n}\n\nfunc getTableAllPrivs() []string {\n\tprivs := make([]string, 0)\n\tfor priv, scope := range SCOPES {\n\t\tif scope.Table && !scope.Dynamic && priv != \"USAGE\" && priv != \"GRANT OPTION\" {\n\t\t\tprivs = append(privs, priv)\n\t\t}\n\t}\n\treturn privs\n}\n\nfunc getRoutineAllPrivs() []string {\n\tprivs := make([]string, 0)\n\tfor priv, scope := range SCOPES {\n\t\tif scope.Routine && !scope.Dynamic && priv != \"USAGE\" && priv != \"GRANT OPTION\" {\n\t\t\tprivs = append(privs, priv)\n\t\t}\n\t}\n\treturn privs\n}\n\nfunc checkIsRoutine(privs []string) bool {\n\tif len(privs) > 0 {\n\t\treturn SCOPES[privs[0]].Routine\n\t}\n\treturn false\n}\n\nfunc getTableIndex(d *Database, tableName string) int {\n\tfor i, t := range *d.Tables {\n\t\tif t.Name == tableName {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc getRoutineIndex(d *Database, routineName string) int {\n\tfor i, r := range *d.Routines {\n\t\tif r.Name == routineName {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc addRemovePrivs(currentPrivs []string, privsToAddRemove []string, add bool) []string {\n\tnewPrivs := make([]string, 0)\n\tif add {\n\t\tnewPrivs = append(currentPrivs, privsToAddRemove...)\n\t\treturn newPrivs\n\t}\n\tfor _, p := range currentPrivs {\n\t\tfound := false\n\t\tfor _, p2 := range privsToAddRemove {\n\t\t\tif p == p2 {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !found {\n\t\t\tnewPrivs = append(newPrivs, p)\n\t\t}\n\t}\n\treturn newPrivs\n}\n\nfunc printResults(databases map[string]*Database, globalPrivs GlobalPrivs, showAll bool) {\n\t// Print Global Privileges\n\tprintGlobalPrivs(globalPrivs)\n\t// Print Database and Table Privileges\n\tprintDBTablePrivs(databases, showAll)\n\t// Print Routine Privileges\n\tprintRoutinePrivs(databases, showAll)\n}\n\nfunc printGlobalPrivs(globalPrivs GlobalPrivs) {\n\t// Prep table writer\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Global Privileges\"})\n\n\t// Print global privs\n\tglobalPrivsStr := \"\"\n\tfor _, priv := range globalPrivs.Privs {\n\t\tglobalPrivsStr += priv + \", \"\n\t}\n\t// Clean up privs string\n\tglobalPrivsStr = cleanPrivStr(globalPrivsStr)\n\n\t// Add rows of priv string data\n\tt.AppendRow([]interface{}{analyzers.GreenWriter(text.WrapSoft(globalPrivsStr, 100))})\n\tt.Render()\n}\n\nfunc printDBTablePrivs(databases map[string]*Database, showAll bool) {\n\t// Prep table writer\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Database\", \"Table\", \"Privileges\", \"Est. Size\"})\n\n\t// Print database privs\n\tfor _, d := range databases {\n\t\tif isBuiltIn(d.Name) && !showAll {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Add privileges to db or table privs strings\n\t\tdbPrivsStr := \"\"\n\t\tdbTablesStr := \"\"\n\t\tfor _, priv := range d.Privs {\n\t\t\tscope := SCOPES[priv]\n\t\t\tif scope.Database && scope.Table {\n\t\t\t\tdbTablesStr += priv + \", \"\n\t\t\t} else {\n\t\t\t\tdbPrivsStr += priv + \", \"\n\t\t\t}\n\t\t}\n\n\t\t// Clean up privs strings\n\t\tdbPrivsStr = cleanPrivStr(dbPrivsStr)\n\t\tdbTablesStr = cleanPrivStr(dbTablesStr)\n\n\t\t// Prep String colors\n\t\tvar dbName string\n\t\tvar writer func(a ...interface{}) string\n\t\tif d.Default {\n\t\t\tdbName = d.Name + \" (built-in)\"\n\t\t\twriter = analyzers.YellowWriter\n\t\t} else if d.Nonexistent {\n\t\t\tdbName = d.Name + \" (nonexistent)\"\n\t\t\twriter = analyzers.RedWriter\n\t\t} else {\n\t\t\tdbName = d.Name\n\t\t\twriter = analyzers.GreenWriter\n\t\t}\n\n\t\t// Prep Priv Strings\n\n\t\t// Add rows of priv string data\n\t\tt.AppendRow([]interface{}{writer(dbName), writer(\"<DB-Level Privs>\"), writer(text.WrapSoft(dbPrivsStr, 80)), writer(\"-\")})\n\t\tt.AppendRow([]interface{}{\"\", writer(\"<All tables>\"), writer(text.WrapSoft(dbTablesStr, 80)), writer(\"-\")})\n\n\t\t// Print table privs\n\t\tfor _, t2 := range *d.Tables {\n\t\t\ttablePrivsStr := \"\"\n\t\t\tfor _, priv := range t2.Privs {\n\t\t\t\ttablePrivsStr += priv + \", \"\n\t\t\t}\n\t\t\ttablePrivsStr = cleanPrivStr(tablePrivsStr)\n\t\t\tt.AppendRow([]interface{}{\"\", writer(t2.Name), writer(text.WrapSoft(tablePrivsStr, 80)), writer(humanize.Bytes(t2.Bytes))})\n\t\t}\n\t\t// Add a separator between databases\n\t\tt.AppendSeparator()\n\t}\n\tt.Render()\n}\n\nfunc printRoutinePrivs(databases map[string]*Database, showAll bool) {\n\t// Print routine privs\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Database\", \"Routine\", \"Privileges\"})\n\n\t// Add rows of priv string data\n\tfor _, d := range databases {\n\t\tif isBuiltIn(d.Name) && !showAll {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, r := range *d.Routines {\n\t\t\troutinePrivsStr := \"\"\n\t\t\tfor _, priv := range r.Privs {\n\t\t\t\troutinePrivsStr += priv + \", \"\n\t\t\t}\n\t\t\troutinePrivsStr = cleanPrivStr(routinePrivsStr)\n\t\t\tvar writer func(a ...interface{}) string\n\t\t\tswitch d.Name {\n\t\t\tcase mysql_db_info_sch, mysql_db_perf_sch, mysql_db_sys, mysql_db_mysql:\n\t\t\t\twriter = analyzers.YellowWriter\n\t\t\tdefault:\n\t\t\t\twriter = analyzers.GreenWriter\n\t\t\t}\n\t\t\tt.AppendRow([]interface{}{writer(d.Name), writer(r.Name), writer(text.WrapSoft(routinePrivsStr, 80))})\n\t\t}\n\t}\n\tt.Render()\n}\n\nfunc cleanPrivStr(priv string) string {\n\tpriv = strings.TrimSuffix(priv, \", \")\n\tif priv == \"\" {\n\t\tpriv = \"-\"\n\t}\n\treturn priv\n}\n\nfunc isBuiltIn(dbName string) bool {\n\tswitch dbName {\n\tcase mysql_db_sys, mysql_db_perf_sch, mysql_db_info_sch, mysql_db_mysql:\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mysql/mysql_test.go",
    "content": "package mysql\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/brianvoe/gofakeit/v7\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/testcontainers/testcontainers-go/modules/mysql\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tmysqlUser := \"root\"\n\tmysqlPass := gofakeit.Password(true, true, true, false, false, 10)\n\tmysqlDatabase := \"mysql\"\n\n\tctx := context.Background()\n\n\tmysqlC, err := mysql.Run(ctx, \"mysql\",\n\t\tmysql.WithDatabase(mysqlDatabase),\n\t\tmysql.WithUsername(mysqlUser),\n\t\tmysql.WithPassword(mysqlPass),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdefer func() { _ = mysqlC.Terminate(ctx) }()\n\n\thost, err := mysqlC.Host(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tport, err := mysqlC.MappedPort(ctx, \"3306\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttests := []struct {\n\t\tname             string\n\t\tconnectionString string\n\t\twant             []byte // JSON string\n\t\twantErr          bool\n\t}{\n\t\t{\n\t\t\tname:             \"valid Mysql connection\",\n\t\t\tconnectionString: fmt.Sprintf(`root:%s@%s:%s/%s`, mysqlPass, host, port.Port(), mysqlDatabase),\n\t\t\twant:             expectedOutput,\n\t\t\twantErr:          false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(context.Background(), map[string]string{\"connection_string\": tt.connectionString})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal(tt.want, &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare bindings separately because they are not guaranteed to be in the same order\n\t\t\tif len(got.Bindings) != len(wantObj.Bindings) {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotJSON, wantJSON)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tgot.Bindings = nil\n\t\t\twantObj.Bindings = nil\n\n\t\t\t// Compare the rest of the Object\n\t\t\tif diff := cmp.Diff(&wantObj, got); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s: (-want +got)\\n%s\", tt.name, diff)\n\t\t\t\treturn\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mysql/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage mysql\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    Alter Permission = iota\n    AlterRoutine Permission = iota\n    AllowNonexistentDefiner Permission = iota\n    ApplicationPasswordAdmin Permission = iota\n    AuditAbortExempt Permission = iota\n    AuditAdmin Permission = iota\n    AuthenticationPolicyAdmin Permission = iota\n    BackupAdmin Permission = iota\n    BinlogAdmin Permission = iota\n    BinlogEncryptionAdmin Permission = iota\n    CloneAdmin Permission = iota\n    ConnectionAdmin Permission = iota\n    Create Permission = iota\n    CreateRole Permission = iota\n    CreateRoutine Permission = iota\n    CreateTablespace Permission = iota\n    CreateTemporaryTables Permission = iota\n    CreateUser Permission = iota\n    CreateView Permission = iota\n    Delete Permission = iota\n    Drop Permission = iota\n    DropRole Permission = iota\n    EncryptionKeyAdmin Permission = iota\n    Event Permission = iota\n    Execute Permission = iota\n    File Permission = iota\n    FirewallAdmin Permission = iota\n    FirewallExempt Permission = iota\n    FirewallUser Permission = iota\n    FlushOptimizerCosts Permission = iota\n    FlushStatus Permission = iota\n    FlushTables Permission = iota\n    FlushUserResources Permission = iota\n    GrantOption Permission = iota\n    GroupReplicationAdmin Permission = iota\n    GroupReplicationStream Permission = iota\n    Index Permission = iota\n    InnodbRedoLogArchive Permission = iota\n    InnodbRedoLogEnable Permission = iota\n    Insert Permission = iota\n    LockingTables Permission = iota\n    MaskingDictionariesAdmin Permission = iota\n    NdbStoredUser Permission = iota\n    PasswordlessUserAdmin Permission = iota\n    PersistRoVariablesAdmin Permission = iota\n    Process Permission = iota\n    Proxy Permission = iota\n    References Permission = iota\n    Reload Permission = iota\n    ReplicationApplier Permission = iota\n    ReplicationClient Permission = iota\n    ReplicationSlave Permission = iota\n    ReplicationSlaveAdmin Permission = iota\n    ResourceGroupAdmin Permission = iota\n    ResourceGroupUser Permission = iota\n    RoleAdmin Permission = iota\n    Select Permission = iota\n    SensitiveVariablesObserver Permission = iota\n    ServiceConnectionAdmin Permission = iota\n    SessionVariablesAdmin Permission = iota\n    SetAnyDefiner Permission = iota\n    SetUserId Permission = iota\n    ShowDatabases Permission = iota\n    ShowRoutine Permission = iota\n    ShowView Permission = iota\n    Shutdown Permission = iota\n    SkipQueryRewrite Permission = iota\n    Super Permission = iota\n    SystemUser Permission = iota\n    SystemVariablesAdmin Permission = iota\n    TableEncryptionAdmin Permission = iota\n    TelemetryLogAdmin Permission = iota\n    TpConnectionAdmin Permission = iota\n    TransactionGtidTag Permission = iota\n    Trigger Permission = iota\n    Update Permission = iota\n    Usage Permission = iota\n    VersionTokenAdmin Permission = iota\n    XaRecoverAdmin Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        Alter: \"alter\",\n        AlterRoutine: \"alter_routine\",\n        AllowNonexistentDefiner: \"allow_nonexistent_definer\",\n        ApplicationPasswordAdmin: \"application_password_admin\",\n        AuditAbortExempt: \"audit_abort_exempt\",\n        AuditAdmin: \"audit_admin\",\n        AuthenticationPolicyAdmin: \"authentication_policy_admin\",\n        BackupAdmin: \"backup_admin\",\n        BinlogAdmin: \"binlog_admin\",\n        BinlogEncryptionAdmin: \"binlog_encryption_admin\",\n        CloneAdmin: \"clone_admin\",\n        ConnectionAdmin: \"connection_admin\",\n        Create: \"create\",\n        CreateRole: \"create_role\",\n        CreateRoutine: \"create_routine\",\n        CreateTablespace: \"create_tablespace\",\n        CreateTemporaryTables: \"create_temporary_tables\",\n        CreateUser: \"create_user\",\n        CreateView: \"create_view\",\n        Delete: \"delete\",\n        Drop: \"drop\",\n        DropRole: \"drop_role\",\n        EncryptionKeyAdmin: \"encryption_key_admin\",\n        Event: \"event\",\n        Execute: \"execute\",\n        File: \"file\",\n        FirewallAdmin: \"firewall_admin\",\n        FirewallExempt: \"firewall_exempt\",\n        FirewallUser: \"firewall_user\",\n        FlushOptimizerCosts: \"flush_optimizer_costs\",\n        FlushStatus: \"flush_status\",\n        FlushTables: \"flush_tables\",\n        FlushUserResources: \"flush_user_resources\",\n        GrantOption: \"grant_option\",\n        GroupReplicationAdmin: \"group_replication_admin\",\n        GroupReplicationStream: \"group_replication_stream\",\n        Index: \"index\",\n        InnodbRedoLogArchive: \"innodb_redo_log_archive\",\n        InnodbRedoLogEnable: \"innodb_redo_log_enable\",\n        Insert: \"insert\",\n        LockingTables: \"locking_tables\",\n        MaskingDictionariesAdmin: \"masking_dictionaries_admin\",\n        NdbStoredUser: \"ndb_stored_user\",\n        PasswordlessUserAdmin: \"passwordless_user_admin\",\n        PersistRoVariablesAdmin: \"persist_ro_variables_admin\",\n        Process: \"process\",\n        Proxy: \"proxy\",\n        References: \"references\",\n        Reload: \"reload\",\n        ReplicationApplier: \"replication_applier\",\n        ReplicationClient: \"replication_client\",\n        ReplicationSlave: \"replication_slave\",\n        ReplicationSlaveAdmin: \"replication_slave_admin\",\n        ResourceGroupAdmin: \"resource_group_admin\",\n        ResourceGroupUser: \"resource_group_user\",\n        RoleAdmin: \"role_admin\",\n        Select: \"select\",\n        SensitiveVariablesObserver: \"sensitive_variables_observer\",\n        ServiceConnectionAdmin: \"service_connection_admin\",\n        SessionVariablesAdmin: \"session_variables_admin\",\n        SetAnyDefiner: \"set_any_definer\",\n        SetUserId: \"set_user_id\",\n        ShowDatabases: \"show_databases\",\n        ShowRoutine: \"show_routine\",\n        ShowView: \"show_view\",\n        Shutdown: \"shutdown\",\n        SkipQueryRewrite: \"skip_query_rewrite\",\n        Super: \"super\",\n        SystemUser: \"system_user\",\n        SystemVariablesAdmin: \"system_variables_admin\",\n        TableEncryptionAdmin: \"table_encryption_admin\",\n        TelemetryLogAdmin: \"telemetry_log_admin\",\n        TpConnectionAdmin: \"tp_connection_admin\",\n        TransactionGtidTag: \"transaction_gtid_tag\",\n        Trigger: \"trigger\",\n        Update: \"update\",\n        Usage: \"usage\",\n        VersionTokenAdmin: \"version_token_admin\",\n        XaRecoverAdmin: \"xa_recover_admin\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"alter\": Alter,\n        \"alter_routine\": AlterRoutine,\n        \"allow_nonexistent_definer\": AllowNonexistentDefiner,\n        \"application_password_admin\": ApplicationPasswordAdmin,\n        \"audit_abort_exempt\": AuditAbortExempt,\n        \"audit_admin\": AuditAdmin,\n        \"authentication_policy_admin\": AuthenticationPolicyAdmin,\n        \"backup_admin\": BackupAdmin,\n        \"binlog_admin\": BinlogAdmin,\n        \"binlog_encryption_admin\": BinlogEncryptionAdmin,\n        \"clone_admin\": CloneAdmin,\n        \"connection_admin\": ConnectionAdmin,\n        \"create\": Create,\n        \"create_role\": CreateRole,\n        \"create_routine\": CreateRoutine,\n        \"create_tablespace\": CreateTablespace,\n        \"create_temporary_tables\": CreateTemporaryTables,\n        \"create_user\": CreateUser,\n        \"create_view\": CreateView,\n        \"delete\": Delete,\n        \"drop\": Drop,\n        \"drop_role\": DropRole,\n        \"encryption_key_admin\": EncryptionKeyAdmin,\n        \"event\": Event,\n        \"execute\": Execute,\n        \"file\": File,\n        \"firewall_admin\": FirewallAdmin,\n        \"firewall_exempt\": FirewallExempt,\n        \"firewall_user\": FirewallUser,\n        \"flush_optimizer_costs\": FlushOptimizerCosts,\n        \"flush_status\": FlushStatus,\n        \"flush_tables\": FlushTables,\n        \"flush_user_resources\": FlushUserResources,\n        \"grant_option\": GrantOption,\n        \"group_replication_admin\": GroupReplicationAdmin,\n        \"group_replication_stream\": GroupReplicationStream,\n        \"index\": Index,\n        \"innodb_redo_log_archive\": InnodbRedoLogArchive,\n        \"innodb_redo_log_enable\": InnodbRedoLogEnable,\n        \"insert\": Insert,\n        \"locking_tables\": LockingTables,\n        \"masking_dictionaries_admin\": MaskingDictionariesAdmin,\n        \"ndb_stored_user\": NdbStoredUser,\n        \"passwordless_user_admin\": PasswordlessUserAdmin,\n        \"persist_ro_variables_admin\": PersistRoVariablesAdmin,\n        \"process\": Process,\n        \"proxy\": Proxy,\n        \"references\": References,\n        \"reload\": Reload,\n        \"replication_applier\": ReplicationApplier,\n        \"replication_client\": ReplicationClient,\n        \"replication_slave\": ReplicationSlave,\n        \"replication_slave_admin\": ReplicationSlaveAdmin,\n        \"resource_group_admin\": ResourceGroupAdmin,\n        \"resource_group_user\": ResourceGroupUser,\n        \"role_admin\": RoleAdmin,\n        \"select\": Select,\n        \"sensitive_variables_observer\": SensitiveVariablesObserver,\n        \"service_connection_admin\": ServiceConnectionAdmin,\n        \"session_variables_admin\": SessionVariablesAdmin,\n        \"set_any_definer\": SetAnyDefiner,\n        \"set_user_id\": SetUserId,\n        \"show_databases\": ShowDatabases,\n        \"show_routine\": ShowRoutine,\n        \"show_view\": ShowView,\n        \"shutdown\": Shutdown,\n        \"skip_query_rewrite\": SkipQueryRewrite,\n        \"super\": Super,\n        \"system_user\": SystemUser,\n        \"system_variables_admin\": SystemVariablesAdmin,\n        \"table_encryption_admin\": TableEncryptionAdmin,\n        \"telemetry_log_admin\": TelemetryLogAdmin,\n        \"tp_connection_admin\": TpConnectionAdmin,\n        \"transaction_gtid_tag\": TransactionGtidTag,\n        \"trigger\": Trigger,\n        \"update\": Update,\n        \"usage\": Usage,\n        \"version_token_admin\": VersionTokenAdmin,\n        \"xa_recover_admin\": XaRecoverAdmin,\n    }\n\n    PermissionIDs = map[Permission]int{\n        Alter: 1,\n        AlterRoutine: 2,\n        AllowNonexistentDefiner: 3,\n        ApplicationPasswordAdmin: 4,\n        AuditAbortExempt: 5,\n        AuditAdmin: 6,\n        AuthenticationPolicyAdmin: 7,\n        BackupAdmin: 8,\n        BinlogAdmin: 9,\n        BinlogEncryptionAdmin: 10,\n        CloneAdmin: 11,\n        ConnectionAdmin: 12,\n        Create: 13,\n        CreateRole: 14,\n        CreateRoutine: 15,\n        CreateTablespace: 16,\n        CreateTemporaryTables: 17,\n        CreateUser: 18,\n        CreateView: 19,\n        Delete: 20,\n        Drop: 21,\n        DropRole: 22,\n        EncryptionKeyAdmin: 23,\n        Event: 24,\n        Execute: 25,\n        File: 26,\n        FirewallAdmin: 27,\n        FirewallExempt: 28,\n        FirewallUser: 29,\n        FlushOptimizerCosts: 30,\n        FlushStatus: 31,\n        FlushTables: 32,\n        FlushUserResources: 33,\n        GrantOption: 34,\n        GroupReplicationAdmin: 35,\n        GroupReplicationStream: 36,\n        Index: 37,\n        InnodbRedoLogArchive: 38,\n        InnodbRedoLogEnable: 39,\n        Insert: 40,\n        LockingTables: 41,\n        MaskingDictionariesAdmin: 42,\n        NdbStoredUser: 43,\n        PasswordlessUserAdmin: 44,\n        PersistRoVariablesAdmin: 45,\n        Process: 46,\n        Proxy: 47,\n        References: 48,\n        Reload: 49,\n        ReplicationApplier: 50,\n        ReplicationClient: 51,\n        ReplicationSlave: 52,\n        ReplicationSlaveAdmin: 53,\n        ResourceGroupAdmin: 54,\n        ResourceGroupUser: 55,\n        RoleAdmin: 56,\n        Select: 57,\n        SensitiveVariablesObserver: 58,\n        ServiceConnectionAdmin: 59,\n        SessionVariablesAdmin: 60,\n        SetAnyDefiner: 61,\n        SetUserId: 62,\n        ShowDatabases: 63,\n        ShowRoutine: 64,\n        ShowView: 65,\n        Shutdown: 66,\n        SkipQueryRewrite: 67,\n        Super: 68,\n        SystemUser: 69,\n        SystemVariablesAdmin: 70,\n        TableEncryptionAdmin: 71,\n        TelemetryLogAdmin: 72,\n        TpConnectionAdmin: 73,\n        TransactionGtidTag: 74,\n        Trigger: 75,\n        Update: 76,\n        Usage: 77,\n        VersionTokenAdmin: 78,\n        XaRecoverAdmin: 79,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: Alter,\n        2: AlterRoutine,\n        3: AllowNonexistentDefiner,\n        4: ApplicationPasswordAdmin,\n        5: AuditAbortExempt,\n        6: AuditAdmin,\n        7: AuthenticationPolicyAdmin,\n        8: BackupAdmin,\n        9: BinlogAdmin,\n        10: BinlogEncryptionAdmin,\n        11: CloneAdmin,\n        12: ConnectionAdmin,\n        13: Create,\n        14: CreateRole,\n        15: CreateRoutine,\n        16: CreateTablespace,\n        17: CreateTemporaryTables,\n        18: CreateUser,\n        19: CreateView,\n        20: Delete,\n        21: Drop,\n        22: DropRole,\n        23: EncryptionKeyAdmin,\n        24: Event,\n        25: Execute,\n        26: File,\n        27: FirewallAdmin,\n        28: FirewallExempt,\n        29: FirewallUser,\n        30: FlushOptimizerCosts,\n        31: FlushStatus,\n        32: FlushTables,\n        33: FlushUserResources,\n        34: GrantOption,\n        35: GroupReplicationAdmin,\n        36: GroupReplicationStream,\n        37: Index,\n        38: InnodbRedoLogArchive,\n        39: InnodbRedoLogEnable,\n        40: Insert,\n        41: LockingTables,\n        42: MaskingDictionariesAdmin,\n        43: NdbStoredUser,\n        44: PasswordlessUserAdmin,\n        45: PersistRoVariablesAdmin,\n        46: Process,\n        47: Proxy,\n        48: References,\n        49: Reload,\n        50: ReplicationApplier,\n        51: ReplicationClient,\n        52: ReplicationSlave,\n        53: ReplicationSlaveAdmin,\n        54: ResourceGroupAdmin,\n        55: ResourceGroupUser,\n        56: RoleAdmin,\n        57: Select,\n        58: SensitiveVariablesObserver,\n        59: ServiceConnectionAdmin,\n        60: SessionVariablesAdmin,\n        61: SetAnyDefiner,\n        62: SetUserId,\n        63: ShowDatabases,\n        64: ShowRoutine,\n        65: ShowView,\n        66: Shutdown,\n        67: SkipQueryRewrite,\n        68: Super,\n        69: SystemUser,\n        70: SystemVariablesAdmin,\n        71: TableEncryptionAdmin,\n        72: TelemetryLogAdmin,\n        73: TpConnectionAdmin,\n        74: TransactionGtidTag,\n        75: Trigger,\n        76: Update,\n        77: Usage,\n        78: VersionTokenAdmin,\n        79: XaRecoverAdmin,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mysql/permissions.yaml",
    "content": "permissions:\n  - alter\n  - alter_routine\n  - allow_nonexistent_definer\n  - application_password_admin\n  - audit_abort_exempt\n  - audit_admin\n  - authentication_policy_admin\n  - backup_admin\n  - binlog_admin\n  - binlog_encryption_admin\n  - clone_admin\n  - connection_admin\n  - create\n  - create_role\n  - create_routine\n  - create_tablespace\n  - create_temporary_tables\n  - create_user\n  - create_view\n  - delete\n  - drop\n  - drop_role\n  - encryption_key_admin\n  - event\n  - execute\n  - file\n  - firewall_admin\n  - firewall_exempt\n  - firewall_user\n  - flush_optimizer_costs\n  - flush_status\n  - flush_tables\n  - flush_user_resources\n  - grant_option\n  - group_replication_admin\n  - group_replication_stream\n  - index\n  - innodb_redo_log_archive\n  - innodb_redo_log_enable\n  - insert\n  - locking_tables\n  - masking_dictionaries_admin\n  - ndb_stored_user\n  - passwordless_user_admin\n  - persist_ro_variables_admin\n  - process\n  - proxy\n  - references\n  - reload\n  - replication_applier\n  - replication_client\n  - replication_slave\n  - replication_slave_admin\n  - resource_group_admin\n  - resource_group_user\n  - role_admin\n  - select\n  - sensitive_variables_observer\n  - service_connection_admin\n  - session_variables_admin\n  - set_any_definer\n  - set_user_id\n  - show_databases\n  - show_routine\n  - show_view\n  - shutdown\n  - skip_query_rewrite\n  - super\n  - system_user\n  - system_variables_admin\n  - table_encryption_admin\n  - telemetry_log_admin\n  - tp_connection_admin\n  - transaction_gtid_tag\n  - trigger\n  - update\n  - usage\n  - version_token_admin\n  - xa_recover_admin\n"
  },
  {
    "path": "pkg/analyzer/analyzers/mysql/scopes.go",
    "content": "package mysql\n\ntype PrivTypes struct {\n\tGlobal   bool\n\tDatabase bool\n\tTable    bool\n\tColumn   bool\n\tRoutine  bool\n\tProxy    bool\n\tDynamic  bool\n}\n\n// 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\nvar SCOPES = map[string]PrivTypes{\n\t// Static privs\n\t\"ALTER\":                   {Global: true, Database: true, Table: true},\n\t\"ALTER ROUTINE\":           {Global: true, Database: true, Routine: true},\n\t\"CREATE\":                  {Global: true, Database: true, Table: true},\n\t\"CREATE ROLE\":             {Global: true},\n\t\"CREATE ROUTINE\":          {Global: true, Database: true},\n\t\"CREATE TABLESPACE\":       {Global: true},\n\t\"CREATE TEMPORARY TABLES\": {Global: true, Database: true},\n\t\"CREATE USER\":             {Global: true},\n\t\"CREATE VIEW\":             {Global: true, Database: true, Table: true},\n\t\"DELETE\":                  {Global: true, Database: true, Table: true},\n\t\"DROP\":                    {Global: true, Database: true, Table: true},\n\t\"DROP ROLE\":               {Global: true},\n\t\"EVENT\":                   {Global: true, Database: true},\n\t\"EXECUTE\":                 {Global: true, Database: true, Routine: true},\n\t\"FILE\":                    {Global: true},\n\t\"GRANT OPTION\":            {Global: true, Database: true, Table: true, Routine: true, Proxy: true}, // Not granted on ALL PRIVILEGES\n\t\"INDEX\":                   {Global: true, Database: true, Table: true},\n\t\"INSERT\":                  {Global: true, Database: true, Table: true, Column: true},\n\t\"LOCK TABLES\":             {Global: true, Database: true},\n\t\"PROCESS\":                 {Global: true},\n\t\"PROXY\":                   {Proxy: true}, // Not granted on ALL PRIVILEGES\n\t\"REFERENCES\":              {Global: true, Database: true, Table: true, Column: true},\n\t\"RELOAD\":                  {Global: true},\n\t\"REPLICATION CLIENT\":      {Global: true},\n\t\"REPLICATION SLAVE\":       {Global: true},\n\t\"SELECT\":                  {Global: true, Database: true, Table: true, Column: true},\n\t\"SHOW DATABASES\":          {Global: true},\n\t\"SHOW VIEW\":               {Global: true, Database: true, Table: true},\n\t\"SHUTDOWN\":                {Global: true},\n\t\"SUPER\":                   {Global: true},\n\t\"TRIGGER\":                 {Global: true, Database: true, Table: true},\n\t\"UPDATE\":                  {Global: true, Database: true, Table: true, Column: true},\n\n\t// This is a special case, it's not a real privilege\n\t\"USAGE\": {Global: true, Database: true, Table: true, Column: true, Routine: true},\n\n\t// Dynamic privs\n\t\"ALLOW_NONEXISTENT_DEFINER\":    {Global: true, Dynamic: true},\n\t\"APPLICATION_PASSWORD_ADMIN\":   {Global: true, Dynamic: true},\n\t\"AUDIT_ABORT_EXEMPT\":           {Global: true, Dynamic: true},\n\t\"AUDIT_ADMIN\":                  {Global: true, Dynamic: true},\n\t\"AUTHENTICATION_POLICY_ADMIN\":  {Global: true, Dynamic: true},\n\t\"BACKUP_ADMIN\":                 {Global: true, Dynamic: true},\n\t\"BINLOG_ADMIN\":                 {Global: true, Dynamic: true},\n\t\"BINLOG_ENCRYPTION_ADMIN\":      {Global: true, Dynamic: true},\n\t\"CLONE_ADMIN\":                  {Global: true, Dynamic: true},\n\t\"CONNECTION_ADMIN\":             {Global: true, Dynamic: true},\n\t\"ENCRYPTION_KEY_ADMIN\":         {Global: true, Dynamic: true},\n\t\"FIREWALL_ADMIN\":               {Global: true, Dynamic: true},\n\t\"FIREWALL_EXEMPT\":              {Global: true, Dynamic: true},\n\t\"FIREWALL_USER\":                {Global: true, Dynamic: true},\n\t\"FLUSH_OPTIMIZER_COSTS\":        {Global: true, Dynamic: true},\n\t\"FLUSH_STATUS\":                 {Global: true, Dynamic: true},\n\t\"FLUSH_TABLES\":                 {Global: true, Dynamic: true},\n\t\"FLUSH_USER_RESOURCES\":         {Global: true, Dynamic: true},\n\t\"GROUP_REPLICATION_ADMIN\":      {Global: true, Dynamic: true},\n\t\"GROUP_REPLICATION_STREAM\":     {Global: true, Dynamic: true},\n\t\"INNODB_REDO_LOG_ARCHIVE\":      {Global: true, Dynamic: true},\n\t\"INNODB_REDO_LOG_ENABLE\":       {Global: true, Dynamic: true},\n\t\"MASKING_DICTIONARIES_ADMIN\":   {Global: true, Dynamic: true},\n\t\"NDB_STORED_USER\":              {Global: true, Dynamic: true},\n\t\"PASSWORDLESS_USER_ADMIN\":      {Global: true, Dynamic: true},\n\t\"PERSIST_RO_VARIABLES_ADMIN\":   {Global: true, Dynamic: true},\n\t\"REPLICATION_APPLIER\":          {Global: true, Dynamic: true},\n\t\"REPLICATION_SLAVE_ADMIN\":      {Global: true, Dynamic: true},\n\t\"RESOURCE_GROUP_ADMIN\":         {Global: true, Dynamic: true},\n\t\"RESOURCE_GROUP_USER\":          {Global: true, Dynamic: true},\n\t\"ROLE_ADMIN\":                   {Global: true, Dynamic: true},\n\t\"SENSITIVE_VARIABLES_OBSERVER\": {Global: true, Dynamic: true},\n\t\"SERVICE_CONNECTION_ADMIN\":     {Global: true, Dynamic: true},\n\t\"SESSION_VARIABLES_ADMIN\":      {Global: true, Dynamic: true},\n\t\"SET_ANY_DEFINER\":              {Global: true, Dynamic: true},\n\t\"SET_USER_ID\":                  {Global: true, Dynamic: true},\n\t\"SHOW_ROUTINE\":                 {Global: true, Dynamic: true},\n\t\"SKIP_QUERY_REWRITE\":           {Global: true, Dynamic: true},\n\t\"SYSTEM_USER\":                  {Global: true, Dynamic: true},\n\t\"SYSTEM_VARIABLES_ADMIN\":       {Global: true, Dynamic: true},\n\t\"TABLE_ENCRYPTION_ADMIN\":       {Global: true, Dynamic: true},\n\t\"TELEMETRY_LOG_ADMIN\":          {Global: true, Dynamic: true},\n\t\"TP_CONNECTION_ADMIN\":          {Global: true, Dynamic: true},\n\t\"TRANSACTION_GTID_TAG\":         {Global: true, Dynamic: true},\n\t\"VERSION_TOKEN_ADMIN\":          {Global: true, Dynamic: true},\n\t\"XA_RECOVER_ADMIN\":             {Global: true, Dynamic: true},\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/netlify/models.go",
    "content": "package netlify\n\nimport \"sync\"\n\ntype ResourceType string\n\nfunc (r ResourceType) String() string {\n\treturn string(r)\n}\n\nconst (\n\tCurrentUser         ResourceType = \"User\"\n\tToken               ResourceType = \"Token\"\n\tSite                ResourceType = \"Site\"\n\tSiteFile            ResourceType = \"Site File\"\n\tSiteEnvVar          ResourceType = \"Site Env Variable\"\n\tSiteSnippet         ResourceType = \"Site Snippet\"\n\tSiteDeploy          ResourceType = \"Site Deploy\"\n\tSiteDeployedBranch  ResourceType = \"Site Deployed Branch\"\n\tSiteBuild           ResourceType = \"Site Build\"\n\tSiteDevServer       ResourceType = \"Site Dev Server\"\n\tSiteBuildHook       ResourceType = \"Site Build Hook\"\n\tSiteDevServerHook   ResourceType = \"Site Dev Server Hook\"\n\tSiteServiceInstance ResourceType = \"Site Service Instance\"\n\tSiteFunction        ResourceType = \"Site Function\"\n\tSiteForm            ResourceType = \"Site Form\"\n\tSiteSubmission      ResourceType = \"Site Submission\"\n\tSiteTrafficSplit    ResourceType = \"Site Traffic Split\"\n\tDNSZone             ResourceType = \"DNS Zone\"\n\tService             ResourceType = \"Service\"\n)\n\ntype SecretInfo struct {\n\tmu sync.RWMutex\n\n\tUserInfo  User\n\tResources []NetlifyResource\n}\n\nfunc (s *SecretInfo) appendResource(resource NetlifyResource) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\ts.Resources = append(s.Resources, resource)\n}\n\n// listResourceByType returns a list of resources matching the given type.\nfunc (s *SecretInfo) listResourceByType(resourceType ResourceType) []NetlifyResource {\n\ts.mu.RLock()\n\tdefer s.mu.RUnlock()\n\n\tresources := make([]NetlifyResource, 0, len(s.Resources))\n\tfor _, resource := range s.Resources {\n\t\tif resource.Type == resourceType.String() {\n\t\t\tresources = append(resources, resource)\n\t\t}\n\t}\n\n\treturn resources\n}\n\ntype User struct {\n\tID        string `json:\"id\"`\n\tName      string `json:\"full_name\"`\n\tEmail     string `json:\"email\"`\n\tAccountID string `json:\"account_id\"`\n\tLastLogin string `json:\"last_login\"`\n}\n\ntype NetlifyResource struct {\n\tID       string\n\tName     string\n\tType     string\n\tMetadata map[string]string\n\tParent   *NetlifyResource\n}\n\ntype token struct {\n\tID        string `json:\"id\"`\n\tName      string `json:\"name\"`\n\tPersonal  bool   `json:\"personal\"`\n\tExpiresAt string `json:\"expires_at\"`\n}\n\ntype site struct {\n\tSiteID   string `json:\"site_id\"`\n\tName     string `json:\"name\"`\n\tUrl      string `json:\"url\"`\n\tAdminUrl string `json:\"admin_url\"`\n\tRepoUrl  string `json:\"repo_url\"`\n}\n\ntype file struct {\n\tID       string `json:\"id\"`\n\tPath     string `json:\"path\"`\n\tMimeType string `json:\"mime_type\"`\n}\n\ntype envVariable struct {\n\tKey    string   `json:\"key\"`\n\tScopes []string `json:\"scopes\"`\n\tValues []struct {\n\t\tID    string `json:\"id\"`\n\t\tValue string `json:\"value\"`\n\t} `json:\"values\"`\n}\n\ntype snippet struct {\n\tID    string `json:\"id\"`\n\tTitle string `json:\"title\"`\n}\n\ntype deploy struct {\n\tID      string `json:\"id\"`\n\tName    string `json:\"name\"`\n\tBuildID string `json:\"build_id\"`\n\tState   string `json:\"state\"`\n\tUrl     string `json:\"url\"`\n}\n\ntype deployedBranch struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n\tSlug string `json:\"slug\"`\n}\n\ntype build struct {\n\tID          string `json:\"id\"`\n\tDeployState string `json:\"deploy_state\"`\n}\n\ntype devServer struct {\n\tID    string `json:\"id\"`\n\tTitle string `json:\"title\"`\n}\n\ntype buildHook struct {\n\tID     string `json:\"id\"`\n\tTitle  string `json:\"title\"`\n\tBranch string `json:\"branch\"`\n}\n\ntype serviceInstance struct {\n\tID          string `json:\"id\"`\n\tServiceName string `json:\"service_name\"`\n\tUrl         string `json:\"url\"`\n}\n\ntype function struct {\n\tID       string `json:\"id\"`\n\tProvider string `json:\"provider\"`\n}\n\n// this handle response of 3 API's\ntype formSubmissionSplitInfo struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\ntype dnsZone struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\ntype service struct {\n\tID          string `json:\"id\"`\n\tName        string `json:\"name\"`\n\tServicePath string `json:\"service_path\"`\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/netlify/netlify.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go netlify\npackage netlify\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (a Analyzer) Type() analyzers.AnalyzerType {\n\treturn analyzers.AnalyzerTypeNetlify\n}\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, exist := credInfo[\"key\"]\n\tif !exist {\n\t\treturn nil, fmt.Errorf(\"key not found in credential info\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\t// just print the error in cli and continue as a partial success\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t}\n\n\tif info == nil {\n\t\tcolor.Red(\"[x] Error : %s\", \"No information found\")\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid Netlify API key\\n\\n\")\n\n\tprintUserInfo(info.UserInfo)\n\tprintTokenInfo(info.listResourceByType(Token))\n\tprintResources(info.Resources)\n\n\tcolor.Yellow(\"\\n[i] Expires: %s\", \"N/A (Refer to Token Information Table)\")\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\tvar secretInfo = &SecretInfo{}\n\n\tif err := captureUserInfo(client, key, secretInfo); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := captureTokens(client, key, secretInfo); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := captureResources(client, key, secretInfo); err != nil {\n\t\treturn secretInfo, err\n\t}\n\n\treturn secretInfo, nil\n}\n\n// secretInfoToAnalyzerResult translate secret info to Analyzer Result\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeNetlify,\n\t\tMetadata:     map[string]any{},\n\t\tBindings:     make([]analyzers.Binding, 0),\n\t}\n\n\t// extract information from resource to create bindings and append to result bindings\n\tfor _, resource := range info.Resources {\n\t\tbinding := analyzers.Binding{\n\t\t\tResource: analyzers.Resource{\n\t\t\t\tName:               resource.Name,\n\t\t\t\tFullyQualifiedName: fmt.Sprintf(\"netlify/%s/%s\", resource.Type, resource.ID), // e.g: netlify/site/123\n\t\t\t\tType:               resource.Type,\n\t\t\t\tMetadata:           map[string]any{}, // to avoid panic\n\t\t\t},\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: PermissionStrings[FullAccess], // no fine grain access\n\t\t\t},\n\t\t}\n\n\t\tif resource.Parent != nil {\n\t\t\tbinding.Resource.Parent = &analyzers.Resource{\n\t\t\t\tName:               resource.Parent.Name,\n\t\t\t\tFullyQualifiedName: resource.Parent.ID,\n\t\t\t\tType:               resource.Parent.Type,\n\t\t\t\t// not copying parent metadata\n\t\t\t}\n\t\t}\n\n\t\tfor key, value := range resource.Metadata {\n\t\t\tbinding.Resource.Metadata[key] = value\n\t\t}\n\n\t\tresult.Bindings = append(result.Bindings, binding)\n\t}\n\n\treturn &result\n}\n\n// cli print functions\nfunc printUserInfo(user User) {\n\tcolor.Yellow(\"[i] User Information:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Name\", \"Email\", \"Account ID\", \"Last Login At\"})\n\tt.AppendRow(table.Row{color.GreenString(user.Name), color.GreenString(user.Email), color.GreenString(user.AccountID), color.GreenString(user.LastLogin)})\n\n\tt.Render()\n}\n\nfunc printTokenInfo(tokens []NetlifyResource) {\n\tcolor.Yellow(\"[i] Tokens Information:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"ID\", \"Name\", \"Personal\", \"Expires At\"})\n\tfor _, token := range tokens {\n\t\tt.AppendRow(table.Row{color.GreenString(token.ID), color.GreenString(token.Name), color.GreenString(token.Metadata[tokenPersonal]), color.GreenString(token.Metadata[tokenExpiresAt])})\n\t}\n\tt.Render()\n}\n\nfunc printResources(resources []NetlifyResource) {\n\tcolor.Yellow(\"[i] Resources:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Name\", \"Type\"})\n\tfor _, resource := range resources {\n\t\t// skip token type resource as we will print them separately\n\t\tif resource.Type == Token.String() {\n\t\t\tcontinue\n\t\t}\n\n\t\tt.AppendRow(table.Row{color.GreenString(resource.Name), color.GreenString(resource.Type)})\n\t}\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/netlify/netlify_test.go",
    "content": "package netlify\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed result_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"analyzers1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tkey := testSecrets.MustGetField(\"NETLIFY_PAT\")\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    []byte // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid netlify personal access token\",\n\t\t\tkey:     key,\n\t\t\twant:    expectedOutput,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\tfmt.Println(string(gotJSON))\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/netlify/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage netlify\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    FullAccess Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        FullAccess: \"full_access\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"full_access\": FullAccess,\n    }\n\n    PermissionIDs = map[Permission]int{\n        FullAccess: 1,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: FullAccess,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/netlify/permissions.yaml",
    "content": "permissions:\n  - full_access"
  },
  {
    "path": "pkg/analyzer/analyzers/netlify/requests.go",
    "content": "package netlify\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n)\n\nvar (\n\tapiEndpoints = map[ResourceType]string{\n\t\tCurrentUser:         \"https://api.netlify.com/api/v1/user\",\n\t\tToken:               \"https://app.netlify.com/access-control/bb-api/api/v1/oauth/applications\", // undocumented API - return personal tokens with metadata\n\t\tSite:                \"https://api.netlify.com/api/v1/sites\",\n\t\tSiteFile:            \"https://api.netlify.com/api/v1/sites/%s/files\",             // require site id\n\t\tSiteEnvVar:          \"https://api.netlify.com/api/v1/sites/%s/env\",               // require site id\n\t\tSiteSnippet:         \"https://api.netlify.com/api/v1/sites/%s/snippets\",          // require site id\n\t\tSiteDeploy:          \"https://api.netlify.com/api/v1/sites/%s/deploys\",           // require site id\n\t\tSiteDeployedBranch:  \"https://api.netlify.com/api/v1/sites/%s/deployed-branches\", // require site id\n\t\tSiteBuild:           \"https://api.netlify.com/api/v1/sites/%s/builds\",            // require site id\n\t\tSiteDevServer:       \"https://api.netlify.com/api/v1/sites/%s/dev_servers\",       // require site id\n\t\tSiteBuildHook:       \"https://api.netlify.com/api/v1/sites/%s/build_hooks\",       // require site id\n\t\tSiteDevServerHook:   \"https://api.netlify.com/api/v1/sites/%s/dev_server_hooks\",  // require site id\n\t\tSiteServiceInstance: \"https://api.netlify.com/api/v1/sites/%s/service-instances\", // require site id\n\t\tSiteFunction:        \"https://api.netlify.com/api/v1/sites/%s/functions\",         // require site id\n\t\tSiteForm:            \"https://api.netlify.com/api/v1/sites/%s/forms\",             // require site id\n\t\tSiteSubmission:      \"https://api.netlify.com/api/v1/sites/%s/submissions\",       // require site id\n\t\tSiteTrafficSplit:    \"https://api.netlify.com/api/v1/sites/%s/traffic_splits\",    // require site id\n\t\tDNSZone:             \"https://api.netlify.com/api/v1/dns_zones\",\n\t\tService:             \"https://api.netlify.com/api/v1/services\",\n\n\t\t/*\n\t\t\tTODO APIs:\n\t\t\t\t- https://api.netlify.com/api/v1/sites/{site_id}/metadata (Just return key and values added as metadata for a site)\n\t\t\t\t- https://api.netlify.com/api/v1/sites/{site_id}/assets/{asset_id} (Require asset id - No API to list assets)\n\t\t\t\t- https://api.netlify.com/api/v1/deploy_keys (Have id and a public key in response only)\n\t\t*/\n\t}\n\n\t// metadata keys - should always start with resource name\n\ttokenPersonal      = \"personal\"\n\ttokenExpiresAt     = \"expires_at\"\n\tsiteUrl            = \"site_url\"\n\tsiteAdminUrl       = \"site_admin_url\"\n\tsiteRepoUrl        = \"site_repo_url\"\n\tfileMimeType       = \"site_mime_type\"\n\tdeployBuildID      = \"deploy_build_id\"\n\tdeployState        = \"deploy_state\"\n\tdeployUrl          = \"deploy_url\"\n\tdeployedBranchSlug = \"deployed_branch_slug\"\n\tbuildHookBranch    = \"build_hook_branch\"\n\tserviceInstanceUrl = \"service_instance_url\"\n)\n\n// makeNetlifyRequest send the API request to passed url with passed key as personal access token and return response body and status code\nfunc makeNetlifyRequest(client *http.Client, endpoint, key string) ([]byte, int, error) {\n\t// create request\n\treq, err := http.NewRequest(http.MethodGet, endpoint, http.NoBody)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\t// add key in the header\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tresponseBodyByte, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\treturn responseBodyByte, resp.StatusCode, nil\n}\n\n// captureResources try to capture all the resource that the key can access\nfunc captureResources(client *http.Client, key string, secretInfo *SecretInfo) error {\n\tvar (\n\t\twg             sync.WaitGroup\n\t\terrAggWg       sync.WaitGroup\n\t\taggregatedErrs = make([]error, 0)\n\t\terrChan        = make(chan error, 1)\n\t)\n\n\terrAggWg.Add(1)\n\tgo func() {\n\t\tdefer errAggWg.Done()\n\t\tfor err := range errChan {\n\t\t\taggregatedErrs = append(aggregatedErrs, err)\n\t\t}\n\t}()\n\n\t// helper to launch tasks concurrently.\n\tlaunchTask := func(task func() error) {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tif err := task(); err != nil {\n\t\t\t\terrChan <- err\n\t\t\t}\n\t\t}()\n\t}\n\n\t// capture top level resources\n\tif err := captureSites(client, key, secretInfo); err != nil {\n\t\treturn err\n\t}\n\n\t// capture all sub resources of all sites\n\tsites := secretInfo.listResourceByType(Site)\n\tfor _, site := range sites {\n\t\tlaunchTask(func() error { return captureSiteFiles(client, key, site, secretInfo) })\n\t\tlaunchTask(func() error { return captureSiteEnvVar(client, key, site, secretInfo) })\n\t\tlaunchTask(func() error { return captureSiteSnippets(client, key, site, secretInfo) })\n\t\tlaunchTask(func() error { return captureSiteDeploys(client, key, site, secretInfo) })\n\t\tlaunchTask(func() error { return captureSiteDeployedBranches(client, key, site, secretInfo) })\n\t\tlaunchTask(func() error { return captureSiteBuilds(client, key, site, secretInfo) })\n\t\tlaunchTask(func() error { return captureSiteDevServers(client, key, site, secretInfo) })\n\t\tlaunchTask(func() error { return captureSiteBuildHooks(client, key, site, secretInfo) })\n\t\tlaunchTask(func() error { return captureSiteDevServerHooks(client, key, site, secretInfo) })\n\t\tlaunchTask(func() error { return captureSiteServiceInstances(client, key, site, secretInfo) })\n\t\tlaunchTask(func() error { return captureSiteFunctions(client, key, site, secretInfo) })\n\t\tlaunchTask(func() error { return captureSiteFormSubmissionSplitInfo(client, key, site, SiteForm, secretInfo) })\n\t\tlaunchTask(func() error { return captureSiteFormSubmissionSplitInfo(client, key, site, SiteSubmission, secretInfo) })\n\t\tlaunchTask(func() error {\n\t\t\treturn captureSiteFormSubmissionSplitInfo(client, key, site, SiteTrafficSplit, secretInfo)\n\t\t})\n\t}\n\n\tlaunchTask(func() error { return captureDNSZones(client, key, secretInfo) })\n\tlaunchTask(func() error { return captureServices(client, key, secretInfo) })\n\n\twg.Wait()\n\tclose(errChan)\n\terrAggWg.Wait()\n\n\tif len(aggregatedErrs) > 0 {\n\t\treturn errors.Join(aggregatedErrs...)\n\t}\n\n\treturn nil\n}\n\nfunc captureUserInfo(client *http.Client, key string, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeNetlifyRequest(client, apiEndpoints[CurrentUser], key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar user User\n\n\t\tif err := json.Unmarshal(respBody, &user); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tsecretInfo.UserInfo = user\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired personal access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d for API: %s\", statusCode, apiEndpoints[CurrentUser])\n\t}\n}\n\nfunc captureTokens(client *http.Client, key string, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeNetlifyRequest(client, apiEndpoints[Token], key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar tokens []token\n\n\t\tif err := json.Unmarshal(respBody, &tokens); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, token := range tokens {\n\t\t\tif token.ExpiresAt == \"\" {\n\t\t\t\ttoken.ExpiresAt = \"never\"\n\t\t\t}\n\n\t\t\tresource := NetlifyResource{\n\t\t\t\tID:   token.ID,\n\t\t\t\tName: token.Name,\n\t\t\t\tType: Token.String(),\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\ttokenExpiresAt: token.ExpiresAt,\n\t\t\t\t\ttokenPersonal:  strconv.FormatBool(token.Personal),\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tsecretInfo.Resources = append(secretInfo.Resources, resource)\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired personal access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d for API: %s\", statusCode, apiEndpoints[Token])\n\t}\n}\n\nfunc captureSites(client *http.Client, key string, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeNetlifyRequest(client, apiEndpoints[Site], key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar sites []site\n\n\t\tif err := json.Unmarshal(respBody, &sites); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, site := range sites {\n\t\t\tsecretInfo.appendResource(NetlifyResource{\n\t\t\t\tID:   site.SiteID,\n\t\t\t\tName: site.Name,\n\t\t\t\tType: Site.String(),\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\tsiteUrl:      site.Url,\n\t\t\t\t\tsiteAdminUrl: site.AdminUrl,\n\t\t\t\t\tsiteRepoUrl:  site.RepoUrl,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired personal access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\nfunc captureSiteFiles(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteFile], site.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar files []file\n\n\t\tif err := json.Unmarshal(respBody, &files); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, file := range files {\n\t\t\tsecretInfo.appendResource(NetlifyResource{\n\t\t\t\tID:   site.ID + \"/\" + file.ID, // combine site id with file id to make it unique\n\t\t\t\tName: file.Path,\n\t\t\t\tType: SiteFile.String(),\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\tfileMimeType: file.MimeType,\n\t\t\t\t},\n\t\t\t\tParent: &site,\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired personal access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\nfunc captureSiteEnvVar(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteEnvVar], site.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar envVariables []envVariable\n\n\t\tif err := json.Unmarshal(respBody, &envVariables); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, envVar := range envVariables {\n\t\t\t// multiple values exist for each env variable, so we append separate resource for each value\n\t\t\tfor _, value := range envVar.Values {\n\t\t\t\tsecretInfo.appendResource(NetlifyResource{\n\t\t\t\t\tID:   envVar.Key + \"/\" + value.ID,\n\t\t\t\t\tName: envVar.Key + \"/***\" + value.Value[len(value.Value)-4:], // append last 4 characters of value with key to make it unique\n\t\t\t\t\tType: SiteEnvVar.String(),\n\t\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\t\"value\":  value.Value,\n\t\t\t\t\t\t\"scopes\": strings.Join(envVar.Scopes, \";\"),\n\t\t\t\t\t},\n\t\t\t\t\tParent: &site,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired personal access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\nfunc captureSiteSnippets(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteSnippet], site.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar snippets []snippet\n\n\t\tif err := json.Unmarshal(respBody, &snippets); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, snippet := range snippets {\n\t\t\tsecretInfo.appendResource(NetlifyResource{\n\t\t\t\tID:     snippet.ID,\n\t\t\t\tName:   snippet.Title,\n\t\t\t\tType:   SiteSnippet.String(),\n\t\t\t\tParent: &site,\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired personal access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\nfunc captureSiteDeploys(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteDeploy], site.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar deploys []deploy\n\n\t\tif err := json.Unmarshal(respBody, &deploys); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, deploy := range deploys {\n\t\t\tsecretInfo.appendResource(NetlifyResource{\n\t\t\t\tID:   site.ID + \"/deploy/\" + deploy.ID,\n\t\t\t\tName: deploy.Name,\n\t\t\t\tType: SiteDeploy.String(),\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\tdeployBuildID: deploy.BuildID,\n\t\t\t\t\tdeployState:   deploy.State,\n\t\t\t\t\tdeployUrl:     deploy.Url,\n\t\t\t\t},\n\t\t\t\tParent: &site,\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired personal access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\nfunc captureSiteDeployedBranches(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteDeployedBranch], site.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar deployedBranches []deployedBranch\n\n\t\tif err := json.Unmarshal(respBody, &deployedBranches); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, deployedBranch := range deployedBranches {\n\t\t\tsecretInfo.appendResource(NetlifyResource{\n\t\t\t\tID:   deployedBranch.ID,\n\t\t\t\tName: deployedBranch.Name,\n\t\t\t\tType: SiteDeployedBranch.String(),\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\tdeployedBranchSlug: deployedBranch.Slug,\n\t\t\t\t},\n\t\t\t\tParent: &site,\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired personal access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\nfunc captureSiteBuilds(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteBuild], site.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar builds []build\n\n\t\tif err := json.Unmarshal(respBody, &builds); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, build := range builds {\n\t\t\tsecretInfo.appendResource(NetlifyResource{\n\t\t\t\tID:     build.ID,\n\t\t\t\tName:   build.ID + \"/state/\" + build.DeployState, // no specific name\n\t\t\t\tType:   SiteBuild.String(),\n\t\t\t\tParent: &site,\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired personal access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\nfunc captureSiteDevServers(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteDevServer], site.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar devServers []devServer\n\n\t\tif err := json.Unmarshal(respBody, &devServers); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, devServer := range devServers {\n\t\t\tsecretInfo.appendResource(NetlifyResource{\n\t\t\t\tID:     devServer.ID,\n\t\t\t\tName:   devServer.Title,\n\t\t\t\tType:   SiteDevServer.String(),\n\t\t\t\tParent: &site,\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired personal access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\nfunc captureSiteBuildHooks(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteBuildHook], site.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar hooks []buildHook\n\n\t\tif err := json.Unmarshal(respBody, &hooks); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, hook := range hooks {\n\t\t\tsecretInfo.appendResource(NetlifyResource{\n\t\t\t\tID:   hook.ID,\n\t\t\t\tName: hook.Title,\n\t\t\t\tType: SiteBuildHook.String(),\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\tbuildHookBranch: hook.Branch,\n\t\t\t\t},\n\t\t\t\tParent: &site,\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired personal access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\nfunc captureSiteDevServerHooks(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteDevServerHook], site.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar devServerHooks []buildHook\n\n\t\tif err := json.Unmarshal(respBody, &devServerHooks); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, hook := range devServerHooks {\n\t\t\tsecretInfo.appendResource(NetlifyResource{\n\t\t\t\tID:   hook.ID,\n\t\t\t\tName: hook.Title,\n\t\t\t\tType: SiteDevServerHook.String(),\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\tbuildHookBranch: hook.Branch,\n\t\t\t\t},\n\t\t\t\tParent: &site,\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired personal access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\nfunc captureSiteServiceInstances(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteServiceInstance], site.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar serviceInstances []serviceInstance\n\n\t\tif err := json.Unmarshal(respBody, &serviceInstances); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, instance := range serviceInstances {\n\t\t\tsecretInfo.appendResource(NetlifyResource{\n\t\t\t\tID:   instance.ID,\n\t\t\t\tName: instance.ServiceName + \"/instance/\" + instance.ID, // no specific name\n\t\t\t\tType: SiteServiceInstance.String(),\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\tserviceInstanceUrl: instance.Url,\n\t\t\t\t},\n\t\t\t\tParent: &site,\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired personal access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\nfunc captureSiteFunctions(client *http.Client, key string, site NetlifyResource, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[SiteFunction], site.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar data function\n\n\t\tif err := json.Unmarshal(respBody, &data); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tsecretInfo.appendResource(NetlifyResource{\n\t\t\tID:     data.ID,\n\t\t\tName:   \"function/\" + data.ID + \"/provider/\" + data.Provider, // no specific name\n\t\t\tType:   SiteFunction.String(),\n\t\t\tParent: &site,\n\t\t})\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired personal access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\nfunc captureSiteFormSubmissionSplitInfo(client *http.Client, key string, site NetlifyResource, resType ResourceType, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeNetlifyRequest(client, fmt.Sprintf(apiEndpoints[resType], site.ID), key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar formSubSplitInfos []formSubmissionSplitInfo\n\n\t\tif err := json.Unmarshal(respBody, &formSubSplitInfos); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, info := range formSubSplitInfos {\n\t\t\tsecretInfo.appendResource(NetlifyResource{\n\t\t\t\tID:     info.ID,\n\t\t\t\tName:   info.Name, // no specific name\n\t\t\t\tType:   resType.String(),\n\t\t\t\tParent: &site,\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired personal access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\nfunc captureDNSZones(client *http.Client, key string, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeNetlifyRequest(client, apiEndpoints[DNSZone], key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar dnsZones []dnsZone\n\n\t\tif err := json.Unmarshal(respBody, &dnsZones); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, dnsZone := range dnsZones {\n\t\t\tsecretInfo.appendResource(NetlifyResource{\n\t\t\t\tID:   dnsZone.ID,\n\t\t\t\tName: dnsZone.Name,\n\t\t\t\tType: DNSZone.String(),\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired personal access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n\nfunc captureServices(client *http.Client, key string, secretInfo *SecretInfo) error {\n\trespBody, statusCode, err := makeNetlifyRequest(client, apiEndpoints[Service], key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusOK:\n\t\tvar services []service\n\n\t\tif err := json.Unmarshal(respBody, &services); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, service := range services {\n\t\t\tsecretInfo.appendResource(NetlifyResource{\n\t\t\t\tID:   service.ID,\n\t\t\t\tName: service.Name,\n\t\t\t\tType: Service.String(),\n\t\t\t})\n\t\t}\n\n\t\treturn nil\n\tcase http.StatusUnauthorized:\n\t\treturn fmt.Errorf(\"invalid/expired personal access token\")\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d\", statusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/netlify/result_output.json",
    "content": "{\n    \"AnalyzerType\": 34,\n    \"Bindings\": [\n        {\n            \"Resource\": {\n                \"Name\": \"/assets/404-bp-rpyh2.js\",\n                \"FullyQualifiedName\": \"netlify/Site File/dda81214-b126-43bf-9508-ae94cf9d0506//assets/404-bp-rpyh2.js\",\n                \"Type\": \"Site File\",\n                \"Metadata\": {\n                    \"site_mime_type\": \"application/javascript\"\n                },\n                \"Parent\": {\n                    \"Name\": \"truffle-test-site\",\n                    \"FullyQualifiedName\": \"dda81214-b126-43bf-9508-ae94cf9d0506\",\n                    \"Type\": \"Site\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"/assets/about-c6ru7nfs.js\",\n                \"FullyQualifiedName\": \"netlify/Site File/dda81214-b126-43bf-9508-ae94cf9d0506//assets/about-c6ru7nfs.js\",\n                \"Type\": \"Site File\",\n                \"Metadata\": {\n                    \"site_mime_type\": \"application/javascript\"\n                },\n                \"Parent\": {\n                    \"Name\": \"truffle-test-site\",\n                    \"FullyQualifiedName\": \"dda81214-b126-43bf-9508-ae94cf9d0506\",\n                    \"Type\": \"Site\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"/assets/index-bjt0jjds.css\",\n                \"FullyQualifiedName\": \"netlify/Site File/dda81214-b126-43bf-9508-ae94cf9d0506//assets/index-bjt0jjds.css\",\n                \"Type\": \"Site File\",\n                \"Metadata\": {\n                    \"site_mime_type\": \"text/css\"\n                },\n                \"Parent\": {\n                    \"Name\": \"truffle-test-site\",\n                    \"FullyQualifiedName\": \"dda81214-b126-43bf-9508-ae94cf9d0506\",\n                    \"Type\": \"Site\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"/assets/index-csbqlcvs.js\",\n                \"FullyQualifiedName\": \"netlify/Site File/dda81214-b126-43bf-9508-ae94cf9d0506//assets/index-csbqlcvs.js\",\n                \"Type\": \"Site File\",\n                \"Metadata\": {\n                    \"site_mime_type\": \"application/javascript\"\n                },\n                \"Parent\": {\n                    \"Name\": \"truffle-test-site\",\n                    \"FullyQualifiedName\": \"dda81214-b126-43bf-9508-ae94cf9d0506\",\n                    \"Type\": \"Site\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"/index.html\",\n                \"FullyQualifiedName\": \"netlify/Site File/dda81214-b126-43bf-9508-ae94cf9d0506//index.html\",\n                \"Type\": \"Site File\",\n                \"Metadata\": {\n                    \"site_mime_type\": \"text/html\"\n                },\n                \"Parent\": {\n                    \"Name\": \"truffle-test-site\",\n                    \"FullyQualifiedName\": \"dda81214-b126-43bf-9508-ae94cf9d0506\",\n                    \"Type\": \"Site\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"/netlify.toml\",\n                \"FullyQualifiedName\": \"netlify/Site File/dda81214-b126-43bf-9508-ae94cf9d0506//netlify.toml\",\n                \"Type\": \"Site File\",\n                \"Metadata\": {\n                    \"site_mime_type\": \"application/octet-stream\"\n                },\n                \"Parent\": {\n                    \"Name\": \"truffle-test-site\",\n                    \"FullyQualifiedName\": \"dda81214-b126-43bf-9508-ae94cf9d0506\",\n                    \"Type\": \"Site\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"680a1332fb8af883c4da6666/state/ready\",\n                \"FullyQualifiedName\": \"netlify/Site Build/680a1332fb8af883c4da6666\",\n                \"Type\": \"Site Build\",\n                \"Metadata\": {},\n                \"Parent\": {\n                    \"Name\": \"truffle-test-site\",\n                    \"FullyQualifiedName\": \"dda81214-b126-43bf-9508-ae94cf9d0506\",\n                    \"Type\": \"Site\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Addon example\",\n                \"FullyQualifiedName\": \"netlify/Service/5ec5b30682bb8a00bad573ee\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"DataStax Astra\",\n                \"FullyQualifiedName\": \"netlify/Service/5fadc1941f0b1600909ffe94\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"DatoCMS Local\",\n                \"FullyQualifiedName\": \"netlify/Service/5bc77c2bac7ff24e6152b43c\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"DatoCMS Staging\",\n                \"FullyQualifiedName\": \"netlify/Service/5bc77adbac7ff24e6152b43b\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Demo add-on\",\n                \"FullyQualifiedName\": \"netlify/Service/5c1abf2cac7ff2374d58fce2\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"EXAMPLE_KEY/***ALUE\",\n                \"FullyQualifiedName\": \"netlify/Site Env Variable/EXAMPLE_KEY/0016840d-61c5-4e4a-aab4-8f9d73125846\",\n                \"Type\": \"Site Env Variable\",\n                \"Metadata\": {\n                    \"scopes\": \"builds;functions;post_processing;runtime\",\n                    \"value\": \"EXAMPLE_VALUE\"\n                },\n                \"Parent\": {\n                    \"Name\": \"truffle-test-site\",\n                    \"FullyQualifiedName\": \"dda81214-b126-43bf-9508-ae94cf9d0506\",\n                    \"Type\": \"Site\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"EXAMPLE_KEY_2/***anch\",\n                \"FullyQualifiedName\": \"netlify/Site Env Variable/EXAMPLE_KEY_2/c5d9d7f5-52f9-48a8-a218-a80d294f347e\",\n                \"Type\": \"Site Env Variable\",\n                \"Metadata\": {\n                    \"scopes\": \"builds;functions;runtime\",\n                    \"value\": \"****************anch\"\n                },\n                \"Parent\": {\n                    \"Name\": \"truffle-test-site\",\n                    \"FullyQualifiedName\": \"dda81214-b126-43bf-9508-ae94cf9d0506\",\n                    \"Type\": \"Site\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"EXAMPLE_KEY_2/***ocal\",\n                \"FullyQualifiedName\": \"netlify/Site Env Variable/EXAMPLE_KEY_2/b9df8f4a-7a18-4cc1-bf80-e0affa32569b\",\n                \"Type\": \"Site Env Variable\",\n                \"Metadata\": {\n                    \"scopes\": \"builds;functions;runtime\",\n                    \"value\": \"local\"\n                },\n                \"Parent\": {\n                    \"Name\": \"truffle-test-site\",\n                    \"FullyQualifiedName\": \"dda81214-b126-43bf-9508-ae94cf9d0506\",\n                    \"Type\": \"Site\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"EXAMPLE_KEY_2/***od_1\",\n                \"FullyQualifiedName\": \"netlify/Site Env Variable/EXAMPLE_KEY_2/1973b53f-60ab-40ce-ac23-20f6f2241fb3\",\n                \"Type\": \"Site Env Variable\",\n                \"Metadata\": {\n                    \"scopes\": \"builds;functions;runtime\",\n                    \"value\": \"****************od_1\"\n                },\n                \"Parent\": {\n                    \"Name\": \"truffle-test-site\",\n                    \"FullyQualifiedName\": \"dda81214-b126-43bf-9508-ae94cf9d0506\",\n                    \"Type\": \"Site\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"EXAMPLE_KEY_2/***ploy\",\n                \"FullyQualifiedName\": \"netlify/Site Env Variable/EXAMPLE_KEY_2/dab3426a-fb1e-405c-b89c-5143650e510e\",\n                \"Type\": \"Site Env Variable\",\n                \"Metadata\": {\n                    \"scopes\": \"builds;functions;runtime\",\n                    \"value\": \"****************ploy\"\n                },\n                \"Parent\": {\n                    \"Name\": \"truffle-test-site\",\n                    \"FullyQualifiedName\": \"dda81214-b126-43bf-9508-ae94cf9d0506\",\n                    \"Type\": \"Site\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"EXAMPLE_KEY_2/***thon\",\n                \"FullyQualifiedName\": \"netlify/Site Env Variable/EXAMPLE_KEY_2/658308b6-0cef-4987-a2a7-3235997af270\",\n                \"Type\": \"Site Env Variable\",\n                \"Metadata\": {\n                    \"scopes\": \"builds;functions;runtime\",\n                    \"value\": \"****************thon\"\n                },\n                \"Parent\": {\n                    \"Name\": \"truffle-test-site\",\n                    \"FullyQualifiedName\": \"dda81214-b126-43bf-9508-ae94cf9d0506\",\n                    \"Type\": \"Site\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"EXAMPLE_KEY_2/***view\",\n                \"FullyQualifiedName\": \"netlify/Site Env Variable/EXAMPLE_KEY_2/a210fa75-1194-42a2-8ff9-788bdf70d2b3\",\n                \"Type\": \"Site Env Variable\",\n                \"Metadata\": {\n                    \"scopes\": \"builds;functions;runtime\",\n                    \"value\": \"****************view\"\n                },\n                \"Parent\": {\n                    \"Name\": \"truffle-test-site\",\n                    \"FullyQualifiedName\": \"dda81214-b126-43bf-9508-ae94cf9d0506\",\n                    \"Type\": \"Site\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Expired Token\",\n                \"FullyQualifiedName\": \"netlify/Token/680b33106d9ae981575b4dec\",\n                \"Type\": \"Token\",\n                \"Metadata\": {\n                    \"expires_at\": \"2025-04-26T00:00:01.262Z\",\n                    \"personal\": \"true\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Express Example\",\n                \"FullyQualifiedName\": \"netlify/Service/5b96e429ac7ff24ff6916ae1\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Fauna DB staging\",\n                \"FullyQualifiedName\": \"netlify/Service/5bbbea43ac7ff23902cc2a64\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"FaunaDB Cloud\",\n                \"FullyQualifiedName\": \"netlify/Service/5bcf902fac7ff255bfc36233\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Get off my lawn\",\n                \"FullyQualifiedName\": \"netlify/Service/5ce6f8be82bb8a00b9940dfd\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Hasura GraphQL Engine\",\n                \"FullyQualifiedName\": \"netlify/Service/5c196638ac7ff255c853647e\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Joy Staging\",\n                \"FullyQualifiedName\": \"netlify/Service/5d23c4e682bb8a00ba311f23\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Netlify CMS Media Manager\",\n                \"FullyQualifiedName\": \"netlify/Service/5b9addcdac7ff27e11b0d4e4\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Nimbella\",\n                \"FullyQualifiedName\": \"netlify/Service/5f6d15de1f0b1600903dde32\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Nimbella Staging\",\n                \"FullyQualifiedName\": \"netlify/Service/5d9e587082bb8a00bb94943f\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"No Exp Token\",\n                \"FullyQualifiedName\": \"netlify/Token/680b32c6bccfc08cd7732add\",\n                \"Type\": \"Token\",\n                \"Metadata\": {\n                    \"expires_at\": \"never\",\n                    \"personal\": \"true\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Takeshape CMS\",\n                \"FullyQualifiedName\": \"netlify/Service/5c9934a882bb8a00bc657db7\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Takeshape CMS staging\",\n                \"FullyQualifiedName\": \"netlify/Service/5c798b4582bb8a00b7504197\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"VGS Staging\",\n                \"FullyQualifiedName\": \"netlify/Service/5be1c5bfac7ff267e9ba987b\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Very Good Security\",\n                \"FullyQualifiedName\": \"netlify/Service/5c6f1bbf82bb8a00bcdea659\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"View source banner addon\",\n                \"FullyQualifiedName\": \"netlify/Service/5b9aef73ac7ff23d0a3fecd4\",\n                \"Type\": \"Service\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"analyzer-test(do not delete)\",\n                \"FullyQualifiedName\": \"netlify/Token/6810b09ab80020167d7525fe\",\n                \"Type\": \"Token\",\n                \"Metadata\": {\n                    \"expires_at\": \"never\",\n                    \"personal\": \"true\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"function//provider/\",\n                \"FullyQualifiedName\": \"netlify/Site Function/\",\n                \"Type\": \"Site Function\",\n                \"Metadata\": {},\n                \"Parent\": {\n                    \"Name\": \"truffle-test-site\",\n                    \"FullyQualifiedName\": \"dda81214-b126-43bf-9508-ae94cf9d0506\",\n                    \"Type\": \"Site\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"hook1\",\n                \"FullyQualifiedName\": \"netlify/Site Build Hook/680a168ae30f218cd01bd4e8\",\n                \"Type\": \"Site Build Hook\",\n                \"Metadata\": {\n                    \"build_hook_branch\": \"main\"\n                },\n                \"Parent\": {\n                    \"Name\": \"truffle-test-site\",\n                    \"FullyQualifiedName\": \"dda81214-b126-43bf-9508-ae94cf9d0506\",\n                    \"Type\": \"Site\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"test-app\",\n                \"FullyQualifiedName\": \"netlify/Token/6809f1dbfb8af846a8da644f\",\n                \"Type\": \"Token\",\n                \"Metadata\": {\n                    \"expires_at\": \"never\",\n                    \"personal\": \"false\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"test01\",\n                \"FullyQualifiedName\": \"netlify/Token/6809f1b5830a5c43672123f8\",\n                \"Type\": \"Token\",\n                \"Metadata\": {\n                    \"expires_at\": \"2025-05-24T08:09:25.796Z\",\n                    \"personal\": \"true\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"truffle-test-site\",\n                \"FullyQualifiedName\": \"netlify/Site/dda81214-b126-43bf-9508-ae94cf9d0506\",\n                \"Type\": \"Site\",\n                \"Metadata\": {\n                    \"site_admin_url\": \"https://app.netlify.com/sites/truffle-test-site\",\n                    \"site_repo_url\": \"\",\n                    \"site_url\": \"http://truffle-test-site.netlify.app\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"truffle-test-site\",\n                \"FullyQualifiedName\": \"netlify/Site Deploy/dda81214-b126-43bf-9508-ae94cf9d0506/deploy/680a1332fb8af883c4da6668\",\n                \"Type\": \"Site Deploy\",\n                \"Metadata\": {\n                    \"deploy_build_id\": \"680a1332fb8af883c4da6666\",\n                    \"deploy_state\": \"ready\",\n                    \"deploy_url\": \"http://truffle-test-site.netlify.app\"\n                },\n                \"Parent\": {\n                    \"Name\": \"truffle-test-site\",\n                    \"FullyQualifiedName\": \"dda81214-b126-43bf-9508-ae94cf9d0506\",\n                    \"Type\": \"Site\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"trufflesecurity.com\",\n                \"FullyQualifiedName\": \"netlify/DNS Zone/6809f163830a5c42ca212432\",\n                \"Type\": \"DNS Zone\",\n                \"Metadata\": {},\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        }\n    ],\n    \"UnboundedResources\": null,\n    \"Metadata\": {}\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/ngrok/expected_output.json",
    "content": "{\"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}"
  },
  {
    "path": "pkg/analyzer/analyzers/ngrok/models.go",
    "content": "package ngrok\n\ntype apiKey struct {\n\tID          string `json:\"id\"`\n\tURI         string `json:\"uri\"`\n\tDescription string `json:\"description\"`\n\tMetadata    string `json:\"metadata\"`\n\tOwnerID     string `json:\"owner_id\"`\n\tCreatedAt   string `json:\"created_at\"`\n}\n\ntype authtoken struct {\n\tID          string   `json:\"id\"`\n\tURI         string   `json:\"uri\"`\n\tDescription string   `json:\"description\"`\n\tMetadata    string   `json:\"metadata\"`\n\tACL         []string `json:\"acl\"`\n\tOwnerID     string   `json:\"owner_id\"`\n\tCreatedAt   string   `json:\"created_at\"`\n}\n\ntype sshCredential struct {\n\tID          string   `json:\"id\"`\n\tURI         string   `json:\"uri\"`\n\tDescription string   `json:\"description\"`\n\tPublicKey   string   `json:\"public_key\"`\n\tMetadata    string   `json:\"metadata\"`\n\tACL         []string `json:\"acl\"`\n\tOwnerID     string   `json:\"owner_id\"`\n\tCreatedAt   string   `json:\"created_at\"`\n}\n\ntype domain struct {\n\tID        string `json:\"id\"`\n\tURI       string `json:\"uri\"`\n\tDomain    string `json:\"domain\"`\n\tMetadata  string `json:\"metadata\"`\n\tCreatedAt string `json:\"created_at\"`\n}\n\ntype endpoint struct {\n\tID        string   `json:\"id\"`\n\tRegion    string   `json:\"region\"`\n\tHost      string   `json:\"host\"`\n\tPort      int64    `json:\"port\"`\n\tPublicURL string   `json:\"public_url\"`\n\tProto     string   `json:\"proto\"`\n\tHostport  string   `json:\"hostport\"`\n\tType      string   `json:\"type\"`\n\tBindings  []string `json:\"bindings\"`\n\tURI       string   `json:\"uri\"`\n\tMetadata  string   `json:\"metadata\"`\n\tCreatedAt string   `json:\"created_at\"`\n\tUpdatedAt string   `json:\"updated_at\"`\n}\n\ntype botUser struct {\n\tID        string `json:\"id\"`\n\tURI       string `json:\"uri\"`\n\tName      string `json:\"name\"`\n\tActive    bool   `json:\"active\"`\n\tCreatedAt string `json:\"created_at\"`\n}\n\ntype user struct {\n\tID string `json:\"id\"`\n}\n\ntype paginatedResponse struct {\n\tNextPageURI    string          `json:\"next_page_uri\"`\n\tAPIKeys        []apiKey        `json:\"keys,omitempty\"`\n\tAuthtokens     []authtoken     `json:\"credentials,omitempty\"`\n\tSSHCredentials []sshCredential `json:\"ssh_credentials,omitempty\"`\n\tDomains        []domain        `json:\"reserved_domains,omitempty\"`\n\tEndpoints      []endpoint      `json:\"endpoints,omitempty\"`\n\tBotUsers       []botUser       `json:\"bot_users,omitempty\"`\n}\n\ntype secretInfo struct {\n\tUsers          []user\n\tBotUsers       []botUser\n\tAPIKeys        []apiKey\n\tAuthtokens     []authtoken\n\tSSHCredentials []sshCredential\n\tDomains        []domain\n\tEndpoints      []endpoint\n\tAccountType    AccountType\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/ngrok/ngrok.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go ngrok\npackage ngrok\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\n\t_ \"embed\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\ntype AccountType string\n\nconst (\n\tAccountFree AccountType = \"Free\"\n\tAccountPaid AccountType = \"Paid\"\n)\n\nfunc (a Analyzer) Type() analyzers.AnalyzerType {\n\treturn analyzers.AnalyzerTypeNgrok\n}\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, exist := credInfo[\"key\"]\n\tif !exist {\n\t\treturn nil, errors.New(\"key not found in credentials info\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Invalid Ngrok Key\\n\")\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t\treturn\n\t}\n\n\tif info == nil {\n\t\tcolor.Red(\"[x] Error : %s\", \"No information found\")\n\t\treturn\n\t}\n\n\tcolor.Green(\"[i] Valid Ngrok API Key\\n\")\n\tprintAccountAndPermissions(info)\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*secretInfo, error) {\n\t// Ngrok API keys provide full access to all resources depending on the account type\n\t// Free accounts have access to a limited set of resources.\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\tsecretInfo := &secretInfo{}\n\n\tif err := determineAccountType(client, secretInfo, key); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := populateAllResources(client, secretInfo, key); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfo, nil\n}\n\nfunc secretInfoToAnalyzerResult(info *secretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tbindings := []analyzers.Binding{}\n\tfullAccessPermission := analyzers.Permission{\n\t\tValue: PermissionStrings[FullAccess],\n\t}\n\n\tfor _, endpoint := range info.Endpoints {\n\t\tbindings = append(bindings, analyzers.Binding{\n\t\t\tResource:   createEndpointResource(endpoint),\n\t\t\tPermission: fullAccessPermission,\n\t\t})\n\t}\n\n\tfor _, domain := range info.Domains {\n\t\tbindings = append(bindings, analyzers.Binding{\n\t\t\tResource:   createDomainResource(domain),\n\t\t\tPermission: fullAccessPermission,\n\t\t})\n\t}\n\n\tfor _, apiKey := range info.APIKeys {\n\t\tbindings = append(bindings, analyzers.Binding{\n\t\t\tResource:   createAPIKeyResource(apiKey),\n\t\t\tPermission: fullAccessPermission,\n\t\t})\n\t}\n\n\tfor _, authtoken := range info.Authtokens {\n\t\tbindings = append(bindings, analyzers.Binding{\n\t\t\tResource:   createAuthtokenResource(authtoken),\n\t\t\tPermission: fullAccessPermission,\n\t\t})\n\t}\n\n\tfor _, sshCredential := range info.SSHCredentials {\n\t\tbindings = append(bindings, analyzers.Binding{\n\t\t\tResource:   createSSHKeyResource(sshCredential),\n\t\t\tPermission: fullAccessPermission,\n\t\t})\n\t}\n\n\tfor _, botUser := range info.BotUsers {\n\t\tbindings = append(bindings, analyzers.Binding{\n\t\t\tResource:   createBotUserResource(botUser),\n\t\t\tPermission: fullAccessPermission,\n\t\t})\n\t}\n\n\tfor _, user := range info.Users {\n\t\tbindings = append(bindings, analyzers.Binding{\n\t\t\tResource:   createUserResource(user),\n\t\t\tPermission: fullAccessPermission,\n\t\t})\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeNgrok,\n\t\tMetadata:     nil,\n\t\tBindings:     bindings,\n\t\tUnboundedResources: []analyzers.Resource{{\n\t\t\tName:               \"Account Plan\",\n\t\t\tFullyQualifiedName: \"account_plan/\" + string(info.AccountType),\n\t\t\tType:               \"account_plan\",\n\t\t}},\n\t}\n\treturn &result\n}\n\nfunc printAccountAndPermissions(info *secretInfo) {\n\taccountIsFree := info.AccountType != AccountPaid\n\tcolor.Yellow(\"[i] Account Type: %s\", info.AccountType)\n\n\tcolor.Yellow(\"\\n[i] Permissions:\")\n\tt1 := table.NewWriter()\n\tt1.AppendHeader(table.Row{\"Resource\", \"Access Level\"})\n\n\t// Printing the access level to Ngrok resources\n\tfor _, resource := range ngrokResources {\n\t\taccessLevel := \"Full Access\"\n\t\tif resource.IsPaidFeature && accountIsFree {\n\t\t\taccessLevel = \"None\"\n\t\t}\n\t\tt1.AppendRow(table.Row{\n\t\t\tcolor.GreenString(resource.Name),\n\t\t\tcolor.GreenString(accessLevel),\n\t\t})\n\t\tt1.AppendSeparator()\n\t}\n\n\tt1.SetOutputMirror(os.Stdout)\n\tt1.Render()\n\n\tcolor.Yellow(\"\\n[i] Resources:\")\n\n\tt2 := table.NewWriter()\n\tt2.SetTitle(\"User IDs\")\n\tt2.AppendHeader(table.Row{\"ID\"})\n\n\tfor _, user := range info.Users {\n\t\tt2.AppendRow(table.Row{\n\t\t\tcolor.GreenString(user.ID),\n\t\t})\n\t}\n\n\tt2.SetOutputMirror(os.Stdout)\n\tt2.Render()\n\n\tt3 := table.NewWriter()\n\tt3.SetTitle(\"Endpoints\")\n\tt3.AppendHeader(table.Row{\"ID\", \"Region\", \"Public URL\", \"Type\", \"Created At\", \"Updated At\"})\n\tfor _, endpoint := range info.Endpoints {\n\t\tt3.AppendRow(table.Row{\n\t\t\tcolor.GreenString(endpoint.ID),\n\t\t\tcolor.GreenString(endpoint.Region),\n\t\t\tcolor.GreenString(endpoint.PublicURL),\n\t\t\tcolor.GreenString(endpoint.Type),\n\t\t\tcolor.GreenString(endpoint.CreatedAt),\n\t\t\tcolor.GreenString(endpoint.UpdatedAt),\n\t\t})\n\t}\n\n\tt3.SetOutputMirror(os.Stdout)\n\tt3.Render()\n\n\tt4 := table.NewWriter()\n\tt4.SetTitle(\"Domains\")\n\tt4.AppendHeader(table.Row{\"ID\", \"Domain\", \"URI\", \"Created At\"})\n\tfor _, domain := range info.Domains {\n\t\tt4.AppendRow(table.Row{\n\t\t\tcolor.GreenString(domain.ID),\n\t\t\tcolor.GreenString(domain.Domain),\n\t\t\tcolor.GreenString(domain.URI),\n\t\t\tcolor.GreenString(domain.CreatedAt),\n\t\t})\n\t}\n\n\tt4.SetOutputMirror(os.Stdout)\n\tt4.Render()\n\n\tt5 := table.NewWriter()\n\tt5.SetTitle(\"API Keys\")\n\tt5.AppendHeader(table.Row{\"ID\", \"Description\", \"Owner ID\", \"Created At\"})\n\tfor _, key := range info.APIKeys {\n\t\tt5.AppendRow(table.Row{\n\t\t\tcolor.GreenString(key.ID),\n\t\t\tcolor.GreenString(key.Description),\n\t\t\tcolor.GreenString(key.OwnerID),\n\t\t\tcolor.GreenString(key.CreatedAt),\n\t\t})\n\t}\n\n\tt5.SetOutputMirror(os.Stdout)\n\tt5.Render()\n\n\tt6 := table.NewWriter()\n\tt6.SetTitle(\"Authtokens\")\n\tt6.AppendHeader(table.Row{\"ID\", \"Description\", \"Owner ID\", \"Created At\"})\n\tfor _, token := range info.Authtokens {\n\t\tt6.AppendRow(table.Row{\n\t\t\tcolor.GreenString(token.ID),\n\t\t\tcolor.GreenString(token.Description),\n\t\t\tcolor.GreenString(token.OwnerID),\n\t\t\tcolor.GreenString(token.CreatedAt),\n\t\t})\n\t}\n\n\tt6.SetOutputMirror(os.Stdout)\n\tt6.Render()\n\n\tt7 := table.NewWriter()\n\tt7.SetTitle(\"SSH Credentials\")\n\tt7.AppendHeader(table.Row{\"ID\", \"Description\", \"Owner ID\", \"Created At\"})\n\tfor _, key := range info.SSHCredentials {\n\t\tt7.AppendRow(table.Row{\n\t\t\tcolor.GreenString(key.ID),\n\t\t\tcolor.GreenString(key.Description),\n\t\t\tcolor.GreenString(key.OwnerID),\n\t\t\tcolor.GreenString(key.CreatedAt),\n\t\t})\n\t}\n\n\tt7.SetOutputMirror(os.Stdout)\n\tt7.Render()\n\n\tt8 := table.NewWriter()\n\tt8.SetTitle(\"Bot Users\")\n\tt8.AppendHeader(table.Row{\"ID\", \"Name\", \"Is Active\", \"Created At\"})\n\tfor _, endpoint := range info.BotUsers {\n\t\tisActive := \"No\"\n\t\tif endpoint.Active {\n\t\t\tisActive = \"Yes\"\n\t\t}\n\t\tt8.AppendRow(table.Row{\n\t\t\tcolor.GreenString(endpoint.ID),\n\t\t\tcolor.GreenString(endpoint.Name),\n\t\t\tcolor.GreenString(isActive),\n\t\t\tcolor.GreenString(endpoint.CreatedAt),\n\t\t})\n\t}\n\n\tt8.SetOutputMirror(os.Stdout)\n\tt8.Render()\n\n\tfmt.Printf(\"%s: https://www.ngrok.com/developers/documentation\\n\\n\", color.GreenString(\"Ref\"))\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/ngrok/ngrok_test.go",
    "content": "package ngrok\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"analyzers1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tkey := testSecrets.MustGetField(\"NGROK\")\n\n\ttests := []struct {\n\t\tname    string\n\t\tsecret  string\n\t\twant    string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid ngrok credentials\",\n\t\t\tsecret:  key,\n\t\t\twant:    string(expectedOutput),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\n\t\t\t\t\"key\": tt.secret,\n\t\t\t})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Type == bindings[j].Resource.Type {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Type < bindings[j].Resource.Type\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/ngrok/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage ngrok\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    FullAccess Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        FullAccess: \"full_access\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"full_access\": FullAccess,\n    }\n\n    PermissionIDs = map[Permission]int{\n        FullAccess: 1,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: FullAccess,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/ngrok/permissions.yaml",
    "content": "permissions:\r\n  - full_access\r\n"
  },
  {
    "path": "pkg/analyzer/analyzers/ngrok/requests.go",
    "content": "package ngrok\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n)\n\nconst (\n\tngrokAPIBaseURL           = \"https://api.ngrok.com\"\n\treservedAddressesEndpoint = \"/reserved_addrs\"\n\tdomainsEndpoint           = \"/reserved_domains\"\n\tendpointsEndpoint         = \"/endpoints\"\n\tapiKeysEndpoint           = \"/api_keys\"\n\tsshCredentialsEndpoint    = \"/ssh_credentials\"\n\tauthtokensEndpoint        = \"/credentials\"\n\tbotUsersEndpoint          = \"/bot_users\"\n)\n\nfunc determineAccountType(client *http.Client, info *secretInfo, key string) error {\n\t// To determine if the account is free or paid, we can attempt to create a reserved address\n\t// Reserved Addresses are only available to paid accounts, so if the response contains the\n\t// error \"ERR_NGROK_501\", we can assume the account is on a free plan.\n\t// Ref: https://ngrok.com/docs/errors/err_ngrok_501\n\n\tconst errorCodeFreeAccount = \"ERR_NGROK_501\"\n\n\turl := fmt.Sprintf(\"%s%s\", ngrokAPIBaseURL, reservedAddressesEndpoint)\n\tbody, statusCode, err := makeAPIRequest(client, http.MethodPost, url, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// The response should be a 400 Bad Request based on our request. Any other status code indicates an error.\n\tif statusCode != http.StatusBadRequest {\n\t\treturn fmt.Errorf(\"unexpected status code: %d while determining account type\", statusCode)\n\t}\n\n\tswitch statusCode {\n\tcase http.StatusBadRequest:\n\t\tif strings.Contains(string(body), errorCodeFreeAccount) {\n\t\t\tinfo.AccountType = AccountFree\n\t\t} else {\n\t\t\tinfo.AccountType = AccountPaid\n\t\t}\n\tcase http.StatusForbidden:\n\t\treturn fmt.Errorf(\"invalid API key or access forbidden: %s\", body)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code: %d while determining account type\", statusCode)\n\t}\n\n\treturn nil\n}\n\nfunc populateAllResources(client *http.Client, info *secretInfo, key string) error {\n\t// Fetch all resources and populate the secretInfo struct with the data\n\t// This is a placeholder function. The actual implementation will depend on the API endpoints and response formats.\n\t// For example, you might want to call different endpoints to fetch API keys, SSH keys, etc.\n\n\t// Example of populating API keys\n\tif err := populateEndpoints(client, info, key); err != nil {\n\t\treturn err\n\t}\n\tif err := populateDomains(client, info, key); err != nil {\n\t\treturn err\n\t}\n\tif err := populateAPIKeys(client, info, key); err != nil {\n\t\treturn err\n\t}\n\tif err := populateAuthtokens(client, info, key); err != nil {\n\t\treturn err\n\t}\n\tif err := populateSSHCredentials(client, info, key); err != nil {\n\t\treturn err\n\t}\n\tif err := populateBotUsers(client, info, key); err != nil {\n\t\treturn err\n\t}\n\tpopulateUsers(info)\n\n\treturn nil\n}\n\nfunc populateEndpoints(client *http.Client, info *secretInfo, key string) error {\n\turl := fmt.Sprintf(\"%s%s\", ngrokAPIBaseURL, endpointsEndpoint)\n\tinfo.Endpoints = []endpoint{}\n\tfor {\n\t\tres, err := fetchResources(client, url, key)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tinfo.Endpoints = append(info.Endpoints, res.Endpoints...)\n\t\turl = res.NextPageURI\n\t\tif url == \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc populateAPIKeys(client *http.Client, info *secretInfo, key string) error {\n\turl := fmt.Sprintf(\"%s%s\", ngrokAPIBaseURL, apiKeysEndpoint)\n\tinfo.APIKeys = []apiKey{}\n\tfor {\n\t\tres, err := fetchResources(client, url, key)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tinfo.APIKeys = append(info.APIKeys, res.APIKeys...)\n\t\turl = res.NextPageURI\n\t\tif url == \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc populateSSHCredentials(client *http.Client, info *secretInfo, key string) error {\n\turl := fmt.Sprintf(\"%s%s\", ngrokAPIBaseURL, sshCredentialsEndpoint)\n\tinfo.SSHCredentials = []sshCredential{}\n\tfor {\n\t\tres, err := fetchResources(client, url, key)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tinfo.SSHCredentials = append(info.SSHCredentials, res.SSHCredentials...)\n\t\turl = res.NextPageURI\n\t\tif url == \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc populateAuthtokens(client *http.Client, info *secretInfo, key string) error {\n\turl := fmt.Sprintf(\"%s%s\", ngrokAPIBaseURL, authtokensEndpoint)\n\tinfo.Authtokens = []authtoken{}\n\tfor {\n\t\tres, err := fetchResources(client, url, key)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tinfo.Authtokens = append(info.Authtokens, res.Authtokens...)\n\t\turl = res.NextPageURI\n\t\tif url == \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc populateDomains(client *http.Client, info *secretInfo, key string) error {\n\turl := fmt.Sprintf(\"%s%s\", ngrokAPIBaseURL, domainsEndpoint)\n\tinfo.Domains = []domain{}\n\tfor {\n\t\tres, err := fetchResources(client, url, key)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tinfo.Domains = append(info.Domains, res.Domains...)\n\t\turl = res.NextPageURI\n\t\tif url == \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc populateBotUsers(client *http.Client, info *secretInfo, key string) error {\n\turl := fmt.Sprintf(\"%s%s\", ngrokAPIBaseURL, botUsersEndpoint)\n\tinfo.BotUsers = []botUser{}\n\tfor {\n\t\tres, err := fetchResources(client, url, key)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tinfo.BotUsers = append(info.BotUsers, res.BotUsers...)\n\t\turl = res.NextPageURI\n\t\tif url == \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc fetchResources(client *http.Client, url string, key string) (*paginatedResponse, error) {\n\tfor {\n\t\tbody, status, err := makeAPIRequest(client, http.MethodGet, url, key)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tswitch status {\n\t\tcase http.StatusOK:\n\t\t\tvar resource paginatedResponse\n\t\t\tif err := json.Unmarshal(body, &resource); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn &resource, nil\n\t\tcase http.StatusForbidden:\n\t\t\treturn nil, fmt.Errorf(\"invalid API key or access forbidden: %s\", body)\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unexpected status code: %d\", status)\n\t\t}\n\t}\n}\n\nfunc populateUsers(info *secretInfo) {\n\t// Creating a map to track unique user IDs to help in avoiding\n\t// duplicates when adding users to the info.Users slice\n\tuniqueUserIDs := map[string]bool{}\n\n\tprocessOwnerID := func(ownerID string) {\n\t\tif strings.HasPrefix(ownerID, \"usr_\") {\n\t\t\tif uniqueUserIDs[ownerID] {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tuniqueUserIDs[ownerID] = true\n\t\t\tinfo.Users = append(info.Users, user{ID: ownerID})\n\t\t}\n\t}\n\n\tfor _, token := range info.Authtokens {\n\t\tprocessOwnerID(token.OwnerID)\n\t}\n\n\tfor _, sshKey := range info.SSHCredentials {\n\t\tprocessOwnerID(sshKey.OwnerID)\n\t}\n\n\tfor _, apiKey := range info.APIKeys {\n\t\tprocessOwnerID(apiKey.OwnerID)\n\t}\n}\n\nfunc makeAPIRequest(client *http.Client, method string, url string, key string) ([]byte, int, error) {\n\tvar reqBody io.Reader = nil\n\tif method == http.MethodPost {\n\t\treqBody = strings.NewReader(\"{}\")\n\t}\n\treq, err := http.NewRequest(method, url, reqBody)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+key)\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Ngrok-Version\", \"2\")\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tbodyBytes, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, 0, fmt.Errorf(\"failed to read response body: %w\", err)\n\t}\n\n\treturn bodyBytes, res.StatusCode, nil\n}\n\n// Functions to create analyzers.Resource objects for different resource types\n\nfunc createEndpointResource(endpoint endpoint) analyzers.Resource {\n\treturn analyzers.Resource{\n\t\tName:               endpoint.ID,\n\t\tFullyQualifiedName: \"endpoint/\" + endpoint.ID,\n\t\tType:               \"endpoint\",\n\t\tMetadata: map[string]any{\n\t\t\t\"region\":    endpoint.Region,\n\t\t\t\"host\":      endpoint.Host,\n\t\t\t\"port\":      endpoint.Port,\n\t\t\t\"publicURL\": endpoint.PublicURL,\n\t\t\t\"proto\":     endpoint.Proto,\n\t\t\t\"hostport\":  endpoint.Hostport,\n\t\t\t\"type\":      endpoint.Type,\n\t\t\t\"uri\":       endpoint.URI,\n\t\t\t\"bindings\":  endpoint.Bindings,\n\t\t\t\"metadata\":  endpoint.Metadata,\n\t\t\t\"createdAt\": endpoint.CreatedAt,\n\t\t\t\"updatedAt\": endpoint.UpdatedAt,\n\t\t},\n\t}\n}\n\nfunc createDomainResource(domain domain) analyzers.Resource {\n\treturn analyzers.Resource{\n\t\tName:               domain.ID,\n\t\tFullyQualifiedName: \"domain/\" + domain.ID,\n\t\tType:               \"domain\",\n\t\tMetadata: map[string]any{\n\t\t\t\"uri\":       domain.URI,\n\t\t\t\"domain\":    domain.Domain,\n\t\t\t\"metadata\":  domain.Metadata,\n\t\t\t\"createdAt\": domain.CreatedAt,\n\t\t},\n\t}\n}\n\nfunc createAPIKeyResource(apiKey apiKey) analyzers.Resource {\n\treturn analyzers.Resource{\n\t\tName:               apiKey.ID,\n\t\tFullyQualifiedName: \"api_key/\" + apiKey.ID,\n\t\tType:               \"api_key\",\n\t\tMetadata: map[string]any{\n\t\t\t\"uri\":         apiKey.URI,\n\t\t\t\"description\": apiKey.Description,\n\t\t\t\"metadata\":    apiKey.Metadata,\n\t\t\t\"ownerID\":     apiKey.OwnerID,\n\t\t\t\"createdAt\":   apiKey.CreatedAt,\n\t\t},\n\t}\n}\nfunc createSSHKeyResource(sshCredential sshCredential) analyzers.Resource {\n\treturn analyzers.Resource{\n\t\tName:               sshCredential.ID,\n\t\tFullyQualifiedName: \"ssh_credential/\" + sshCredential.ID,\n\t\tType:               \"ssh_credential\",\n\t\tMetadata: map[string]any{\n\t\t\t\"uri\":         sshCredential.URI,\n\t\t\t\"description\": sshCredential.Description,\n\t\t\t\"publicKey\":   sshCredential.PublicKey,\n\t\t\t\"metadata\":    sshCredential.Metadata,\n\t\t\t\"acl\":         sshCredential.ACL,\n\t\t\t\"ownerID\":     sshCredential.OwnerID,\n\t\t\t\"createdAt\":   sshCredential.CreatedAt,\n\t\t},\n\t}\n}\n\nfunc createAuthtokenResource(authtoken authtoken) analyzers.Resource {\n\treturn analyzers.Resource{\n\t\tName:               authtoken.ID,\n\t\tFullyQualifiedName: \"authtoken/\" + authtoken.ID,\n\t\tType:               \"authtoken\",\n\t\tMetadata: map[string]any{\n\t\t\t\"uri\":         authtoken.URI,\n\t\t\t\"description\": authtoken.Description,\n\t\t\t\"metadata\":    authtoken.Metadata,\n\t\t\t\"acl\":         authtoken.ACL,\n\t\t\t\"ownerID\":     authtoken.OwnerID,\n\t\t\t\"createdAt\":   authtoken.CreatedAt,\n\t\t},\n\t}\n}\n\nfunc createBotUserResource(botUser botUser) analyzers.Resource {\n\treturn analyzers.Resource{\n\t\tName:               botUser.ID,\n\t\tFullyQualifiedName: \"bot_user/\" + botUser.ID,\n\t\tType:               \"bot_user\",\n\t\tMetadata: map[string]any{\n\t\t\t\"uri\":       botUser.URI,\n\t\t\t\"name\":      botUser.Name,\n\t\t\t\"active\":    botUser.Active,\n\t\t\t\"createdAt\": botUser.CreatedAt,\n\t\t},\n\t}\n}\n\nfunc createUserResource(user user) analyzers.Resource {\n\treturn analyzers.Resource{\n\t\tName:               user.ID,\n\t\tFullyQualifiedName: \"user/\" + user.ID,\n\t\tType:               \"user\",\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/ngrok/resources.go",
    "content": "package ngrok\n\ntype ngrokResource struct {\n\tName          string\n\tIsPaidFeature bool\n}\n\nvar ngrokResources = []ngrokResource{\n\t{\n\t\tName:          \"Endpoints\",\n\t\tIsPaidFeature: false,\n\t},\n\t{\n\t\tName:          \"Domains\",\n\t\tIsPaidFeature: false,\n\t},\n\t{\n\t\tName:          \"Reserved Addresses\",\n\t\tIsPaidFeature: true,\n\t},\n\t{\n\t\tName:          \"TLS Certificates\",\n\t\tIsPaidFeature: true,\n\t},\n\t{\n\t\tName:          \"Kubernetes Operators\",\n\t\tIsPaidFeature: true,\n\t},\n\t{\n\t\tName:          \"Certificate Authorities\",\n\t\tIsPaidFeature: true,\n\t},\n\t{\n\t\tName:          \"IP Policies\",\n\t\tIsPaidFeature: true,\n\t},\n\t{\n\t\tName:          \"Policy Rules\",\n\t\tIsPaidFeature: true,\n\t},\n\t{\n\t\tName:          \"Application Users\",\n\t\tIsPaidFeature: true,\n\t},\n\t{\n\t\tName:          \"Application Sessions\",\n\t\tIsPaidFeature: true,\n\t},\n\t{\n\t\tName:          \"Agent Ingress\",\n\t\tIsPaidFeature: true,\n\t},\n\t{\n\t\tName:          \"Tunnels\",\n\t\tIsPaidFeature: false,\n\t},\n\t{\n\t\tName:          \"Tunnel Sessions\",\n\t\tIsPaidFeature: false,\n\t},\n\t{\n\t\tName:          \"Event Destinations\",\n\t\tIsPaidFeature: false,\n\t},\n\t{\n\t\tName:          \"Event Sources\",\n\t\tIsPaidFeature: false,\n\t},\n\t{\n\t\tName:          \"Event Subscriptions\",\n\t\tIsPaidFeature: false,\n\t},\n\t{\n\t\tName:          \"IP Restrictions\",\n\t\tIsPaidFeature: true,\n\t},\n\t{\n\t\tName:          \"API Keys\",\n\t\tIsPaidFeature: false,\n\t},\n\t{\n\t\tName:          \"SSH Credentials\",\n\t\tIsPaidFeature: false,\n\t},\n\t{\n\t\tName:          \"Authtokens\",\n\t\tIsPaidFeature: false,\n\t},\n\t{\n\t\tName:          \"Bot Users\",\n\t\tIsPaidFeature: false,\n\t},\n\t{\n\t\tName:          \"SSH Certificate Authorities\",\n\t\tIsPaidFeature: true,\n\t},\n\t{\n\t\tName:          \"SSH Host Certificates\",\n\t\tIsPaidFeature: true,\n\t},\n\t{\n\t\tName:          \"SSH User Certificates\",\n\t\tIsPaidFeature: true,\n\t},\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/notion/expected_output.json",
    "content": "{\"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}"
  },
  {
    "path": "pkg/analyzer/analyzers/notion/notion.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go notion\n\npackage notion\n\nimport (\n\t\"bytes\"\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeNotion }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credInfo[\"key\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"missing key in credInfo\")\n\t}\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType:       analyzers.AnalyzerTypeNotion,\n\t\tMetadata:           nil,\n\t\tBindings:           make([]analyzers.Binding, len(info.Permissions)),\n\t\tUnboundedResources: make([]analyzers.Resource, 0, len(info.WorkspaceUsers)),\n\t}\n\n\tresource := analyzers.Resource{\n\t\tName:               info.Bot.Name,\n\t\tFullyQualifiedName: \"notion.so/bot/\" + info.Bot.Id,\n\t\tType:               info.Bot.Type,\n\t\tMetadata: map[string]interface{}{\n\t\t\t\"workspace\": info.Bot.GetWorkspaceName(),\n\t\t},\n\t}\n\n\tfor idx, permission := range info.Permissions {\n\t\tresult.Bindings[idx] = analyzers.Binding{\n\t\t\tResource: resource,\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: permission,\n\t\t\t},\n\t\t}\n\t}\n\n\t// We can find list of users in the current workspace\n\t// if the API key has read_user permission, so these can be\n\t// unbounded resources\n\tfor _, user := range info.WorkspaceUsers {\n\t\tif info.Bot.Id == user.Id {\n\t\t\t// Skip the bot itself\n\t\t\tcontinue\n\t\t}\n\t\tunboundresource := analyzers.Resource{\n\t\t\tName:               user.Name,\n\t\t\tFullyQualifiedName: fmt.Sprintf(\"notion.so/%s/%s\", user.Type, user.Id),\n\t\t\tType:               user.Type, // person or bot\n\t\t}\n\t\tif user.Person.Email != \"\" {\n\t\t\tunboundresource.Metadata = map[string]interface{}{\n\t\t\t\t\"email\": user.Person.Email,\n\t\t\t}\n\t\t}\n\n\t\tresult.UnboundedResources = append(result.UnboundedResources, unboundresource)\n\t}\n\n\treturn &result\n}\n\n//go:embed scopes.json\nvar scopesConfig []byte\n\ntype HttpStatusTest struct {\n\tEndpoint        string      `json:\"endpoint\"`\n\tMethod          string      `json:\"method\"`\n\tPayload         interface{} `json:\"payload\"`\n\tValidStatuses   []int       `json:\"valid_status_code\"`\n\tInvalidStatuses []int       `json:\"invalid_status_code\"`\n}\n\nfunc StatusContains(status int, vals []int) bool {\n\tfor _, v := range vals {\n\t\tif status == v {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (h *HttpStatusTest) RunTest(cfg *config.Config, headers map[string]string) (bool, error) {\n\t// If body data, marshal to JSON\n\tvar data io.Reader\n\tif h.Payload != nil {\n\t\tjsonData, err := json.Marshal(h.Payload)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tdata = bytes.NewBuffer(jsonData)\n\t}\n\n\t// Create new HTTP request\n\tclient := analyzers.NewAnalyzeClientUnrestricted(cfg)\n\treq, err := http.NewRequest(h.Method, h.Endpoint, data)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Add custom headers if provided\n\tfor key, value := range headers {\n\t\treq.Header.Set(key, value)\n\t}\n\n\t// Execute HTTP Request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer resp.Body.Close()\n\n\t// Check response status code\n\tswitch {\n\tcase StatusContains(resp.StatusCode, h.ValidStatuses):\n\t\treturn true, nil\n\tcase StatusContains(resp.StatusCode, h.InvalidStatuses):\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, errors.New(\"error checking response status code\")\n\t}\n}\n\ntype Scope struct {\n\tName     string         `json:\"name\"`\n\tHttpTest HttpStatusTest `json:\"test\"`\n}\n\nfunc readInScopes() ([]Scope, error) {\n\tvar scopes []Scope\n\tif err := json.Unmarshal(scopesConfig, &scopes); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn scopes, nil\n}\n\nfunc getPermissions(cfg *config.Config, key string) ([]string, error) {\n\tscopes, err := readInScopes()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"reading in scopes: %w\", err)\n\t}\n\n\tpermissions := make([]string, 0, len(scopes))\n\tfor _, scope := range scopes {\n\t\tstatus, err := scope.HttpTest.RunTest(cfg, map[string]string{\"Authorization\": \"Bearer \" + key, \"Notion-Version\": \"2022-06-28\"})\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"running test: %w\", err)\n\t\t}\n\t\tif status {\n\t\t\tpermissions = append(permissions, scope.Name)\n\t\t}\n\t}\n\n\treturn permissions, nil\n}\n\ntype SecretInfo struct {\n\tBot            *bot\n\tWorkspaceUsers []user\n\tPermissions    []string\n}\n\ntype user struct {\n\tId     string `json:\"id\"`\n\tName   string `json:\"name\"`\n\tType   string `json:\"type\"`\n\tPerson struct {\n\t\tEmail string `json:\"email\"`\n\t}\n}\n\ntype bot struct {\n\tId   string `json:\"id\"`\n\tName string `json:\"name\"`\n\tType string `json:\"type\"`\n\tBot  struct {\n\t\tOwner *struct {\n\t\t\tType string `json:\"type\"`\n\t\t}\n\t\tWorkspaceName string `json:\"workspace_name\"`\n\t} `json:\"bot\"`\n}\n\nfunc (b *bot) GetWorkspaceName() string {\n\treturn b.Bot.WorkspaceName\n}\n\nfunc (b *bot) OwnedBy() string {\n\tif b.Bot.Owner != nil {\n\t\treturn b.Bot.Owner.Type\n\t}\n\treturn \"N/A\"\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid Notion API key\\n\\n\")\n\n\tcolor.Green(\"[i] Bot: %s (%s)\\n\", info.Bot.Name, info.Bot.Id)\n\tcolor.Green(\"[i] Bot Owned By: %s\\n\", info.Bot.OwnedBy())\n\n\tif info.Bot.GetWorkspaceName() != \"\" {\n\t\tcolor.Green(\"[i] Workspace: %s\\n\\n\", info.Bot.GetWorkspaceName())\n\t}\n\n\tprintPermissions(info.Permissions)\n\tif len(info.WorkspaceUsers) > 0 {\n\t\tprintUsers(info.WorkspaceUsers)\n\t}\n\tcolor.Yellow(\"\\n[i] Expires: Never\")\n\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\tpermissions := make([]string, 0)\n\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\tbot, err := getBotInfo(client, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcredPermissions, err := getPermissions(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpermissions = append(permissions, credPermissions...)\n\n\tusers, err := getWorkspaceUsers(client, key)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error getting user permission: %s\", err.Error())\n\t}\n\n\t// check if email is returned in users to determine permission\n\tfor _, user := range users {\n\t\tif user.Type == \"person\" {\n\t\t\tif user.Person.Email == \"\" {\n\t\t\t\tpermissions = append(permissions, PermissionStrings[ReadUsersWithoutEmail])\n\t\t\t} else {\n\t\t\t\tpermissions = append(permissions, PermissionStrings[ReadUsersWithEmail])\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\treturn &SecretInfo{\n\t\tBot:            bot,\n\t\tPermissions:    permissions,\n\t\tWorkspaceUsers: users,\n\t}, nil\n}\n\nfunc printPermissions(permissions []string) {\n\tcolor.Yellow(\"[i] Permissions:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Permission\"})\n\tfor _, permission := range permissions {\n\t\tt.AppendRow(table.Row{color.GreenString(permission)})\n\t}\n\tt.Render()\n}\n\nfunc printUsers(users []user) {\n\tcolor.Yellow(\"\\n[i] Workspace Users:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"ID\", \"Name\", \"Type\", \"Email\"})\n\tfor _, user := range users {\n\t\tt.AppendRow(table.Row{color.GreenString(user.Id), color.GreenString(user.Name), color.GreenString(user.Type), color.GreenString(user.Person.Email)})\n\t}\n\tt.Render()\n}\n\nfunc getBotInfo(client *http.Client, key string) (*bot, error) {\n\t// Create new HTTP request\n\treq, err := http.NewRequest(http.MethodGet, \"https://api.notion.com/v1/users/me\", http.NoBody)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Add custom headers if provided\n\treq.Header.Set(\"Authorization\", \"Bearer \"+key)\n\treq.Header.Set(\"Notion-Version\", \"2022-06-28\")\n\n\t// Execute HTTP Request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tme := &bot{}\n\t\terr = json.NewDecoder(resp.Body).Decode(me)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn me, nil\n\tcase http.StatusUnauthorized:\n\t\treturn nil, errors.New(\"invalid API key\")\n\tdefault:\n\t\treturn nil, errors.New(\"error getting bot info\")\n\t}\n}\n\n// Decode response body\ntype usersResponse struct {\n\tResults []user `json:\"results\"`\n}\n\nfunc getWorkspaceUsers(client *http.Client, key string) ([]user, error) {\n\t// Create new HTTP request\n\treq, err := http.NewRequest(http.MethodGet, \"https://api.notion.com/v1/users\", http.NoBody)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Add custom headers if provided\n\treq.Header.Set(\"Authorization\", \"Bearer \"+key)\n\treq.Header.Set(\"Notion-Version\", \"2022-06-28\")\n\n\t// Execute HTTP Request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tresponse := &usersResponse{}\n\t\terr = json.NewDecoder(resp.Body).Decode(response)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn response.Results, nil\n\tcase http.StatusUnauthorized:\n\t\treturn nil, errors.New(\"invalid API key\")\n\tcase http.StatusForbidden:\n\t\treturn nil, nil // no permission\n\tcase http.StatusNotFound:\n\t\treturn nil, errors.New(\"workspace not found\")\n\tdefault:\n\t\treturn nil, errors.New(\"error checking user permissions\")\n\t}\n\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/notion/notion_test.go",
    "content": "package notion\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*15)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid notion key\",\n\t\t\tkey:     testSecrets.MustGetField(\"NOTION_TOKEN\"),\n\t\t\twant:    string(expectedOutput),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/notion/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage notion\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    ReadContent Permission = iota\n    UpdateContent Permission = iota\n    InsertContent Permission = iota\n    ReadComments Permission = iota\n    InsertComments Permission = iota\n    ReadUsersWithEmail Permission = iota\n    ReadUsersWithoutEmail Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        ReadContent: \"read_content\",\n        UpdateContent: \"update_content\",\n        InsertContent: \"insert_content\",\n        ReadComments: \"read_comments\",\n        InsertComments: \"insert_comments\",\n        ReadUsersWithEmail: \"read_users_with_email\",\n        ReadUsersWithoutEmail: \"read_users_without_email\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"read_content\": ReadContent,\n        \"update_content\": UpdateContent,\n        \"insert_content\": InsertContent,\n        \"read_comments\": ReadComments,\n        \"insert_comments\": InsertComments,\n        \"read_users_with_email\": ReadUsersWithEmail,\n        \"read_users_without_email\": ReadUsersWithoutEmail,\n    }\n\n    PermissionIDs = map[Permission]int{\n        ReadContent: 1,\n        UpdateContent: 2,\n        InsertContent: 3,\n        ReadComments: 4,\n        InsertComments: 5,\n        ReadUsersWithEmail: 6,\n        ReadUsersWithoutEmail: 7,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: ReadContent,\n        2: UpdateContent,\n        3: InsertContent,\n        4: ReadComments,\n        5: InsertComments,\n        6: ReadUsersWithEmail,\n        7: ReadUsersWithoutEmail,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/notion/permissions.yaml",
    "content": "permissions:\n  - read_content\n  - update_content\n  - insert_content\n  - read_comments\n  - insert_comments\n  - read_users_with_email\n  - read_users_without_email"
  },
  {
    "path": "pkg/analyzer/analyzers/notion/scopes.json",
    "content": "[\n    {\n        \"name\": \"read_content\",\n        \"test\": {\n            \"endpoint\": \"https://api.notion.com/v1/pages/`nowaythiscanexist\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [400],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"update_content\",\n        \"test\": {\n            \"endpoint\": \"https://api.notion.com/v1/pages/`nowaythiscanexist\",\n            \"method\": \"PATCH\",\n            \"valid_status_code\": [400],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"insert_content\",\n        \"test\": {\n            \"endpoint\": \"https://api.notion.com/v1/pages\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [400],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"read_comments\",\n        \"test\": {\n            \"endpoint\": \"https://api.notion.com/v1/comments\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [400],\n            \"invalid_status_code\": [403]\n        }\n    },\n\t{\n        \"name\": \"insert_comments\",\n        \"test\": {\n            \"endpoint\": \"https://api.notion.com/v1/comments\",\n            \"method\": \"POST\",\n            \"valid_status_code\": [400],\n            \"invalid_status_code\": [403]\n        }\n    }\n]"
  },
  {
    "path": "pkg/analyzer/analyzers/openai/openai.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go openai\n\npackage openai\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeOpenAI }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tinfo, err := AnalyzePermissions(a.Cfg, credInfo[\"key\"])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *AnalyzerJSON) *analyzers.AnalyzerResult {\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeOpenAI,\n\t\tMetadata: map[string]any{\n\t\t\t\"user\":          info.me.Name,\n\t\t\t\"email\":         info.me.Email,\n\t\t\t\"mfa\":           strconv.FormatBool(info.me.MfaEnabled),\n\t\t\t\"is_admin\":      strconv.FormatBool(info.isAdmin),\n\t\t\t\"is_restricted\": strconv.FormatBool(info.isRestricted),\n\t\t},\n\t}\n\n\tperms := convertPermissions(info.isAdmin, info.perms)\n\tfor _, org := range info.me.Orgs.Data {\n\t\tresource := analyzers.Resource{\n\t\t\tName:               org.Title,\n\t\t\tFullyQualifiedName: org.ID,\n\t\t\tType:               \"organization\",\n\t\t\tMetadata: map[string]any{\n\t\t\t\t\"description\": org.Description,\n\t\t\t\t\"user\":        org.User,\n\t\t\t},\n\t\t}\n\t\t// Copy each permission into this resource.\n\t\tresult.Bindings = append(result.Bindings, analyzers.BindAllPermissions(resource, perms...)...)\n\t}\n\n\treturn &result\n}\n\nfunc convertPermissions(isAdmin bool, perms []permissionData) []analyzers.Permission {\n\tvar permissions []analyzers.Permission\n\n\tif isAdmin {\n\t\tpermissions = append(permissions, analyzers.Permission{Value: analyzers.FullAccess})\n\t} else {\n\t\tfor _, perm := range flattenPerms(perms...) {\n\t\t\tpermName := PermissionStrings[perm]\n\t\t\tpermissions = append(permissions, analyzers.Permission{Value: permName})\n\t\t}\n\t}\n\n\treturn permissions\n}\n\n// flattenPerms takes a slice of permissionData and returns all of the\n// individual Permission values in a single slice.\nfunc flattenPerms(perms ...permissionData) []Permission {\n\tvar output []Permission\n\tfor _, perm := range perms {\n\t\toutput = append(output, perm.permissions...)\n\t}\n\treturn output\n}\n\nconst (\n\tBASE_URL      = \"https://api.openai.com\"\n\tORGS_ENDPOINT = \"/v1/organizations\"\n\tME_ENDPOINT   = \"/v1/me\"\n)\n\ntype MeJSON struct {\n\tID         string `json:\"id\"`\n\tName       string `json:\"name\"`\n\tEmail      string `json:\"email\"`\n\tPhone      string `json:\"phone_number\"`\n\tMfaEnabled bool   `json:\"mfa_flag_enabled\"`\n\tOrgs       struct {\n\t\tData []struct {\n\t\t\tID          string `json:\"id\"`\n\t\t\tTitle       string `json:\"title\"`\n\t\t\tUser        string `json:\"name\"`\n\t\t\tDescription string `json:\"description\"`\n\t\t\tPersonal    bool   `json:\"personal\"`\n\t\t\tDefault     bool   `json:\"is_default\"`\n\t\t\tRole        string `json:\"role\"`\n\t\t} `json:\"data\"`\n\t} `json:\"orgs\"`\n}\n\ntype permissionData struct {\n\tname        string\n\tendpoints   []string\n\tstatus      analyzers.PermissionType\n\tpermissions []Permission\n}\n\ntype AnalyzerJSON struct {\n\tme           MeJSON\n\tisAdmin      bool\n\tisRestricted bool\n\tperms        []permissionData\n}\n\nvar POST_PAYLOAD = map[string]interface{}{\"speed\": 1}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, apiKey string) {\n\tdata, err := AnalyzePermissions(cfg, apiKey)\n\tif err != nil {\n\t\tcolor.Red(\"[x] %s\", err.Error())\n\t\treturn\n\t}\n\tcolor.Green(\"[!] Valid OpenAI Token\\n\\n\")\n\n\tprintAPIKeyType(apiKey)\n\tprintData(data.me)\n\n\tif data.isAdmin {\n\t\tcolor.Green(\"[!] Admin API Key. All permissions available.\")\n\t} else if data.isRestricted {\n\t\tcolor.Yellow(\"[!] Restricted API Key. Limited permissions available.\")\n\t\tprintPermissions(data.perms, cfg.ShowAll)\n\t}\n}\n\n// AnalyzePermissions will analyze the permissions of an OpenAI API key\nfunc AnalyzePermissions(cfg *config.Config, key string) (*AnalyzerJSON, error) {\n\tdata := AnalyzerJSON{\n\t\tisAdmin:      false,\n\t\tisRestricted: false,\n\t}\n\n\tmeJSON, err := getUserData(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdata.me = meJSON\n\n\tisAdmin, err := checkAdminKey(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif isAdmin {\n\t\tdata.isAdmin = true\n\t} else {\n\t\tdata.isRestricted = true\n\t\tif err := analyzeScopes(key); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdata.perms = getPermissions()\n\t}\n\n\treturn &data, nil\n}\n\nfunc analyzeScopes(key string) error {\n\tfor _, scope := range SCOPES {\n\t\tif err := scope.RunTests(key); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc openAIRequest(cfg *config.Config, method string, url string, key string, data map[string]interface{}) ([]byte, *http.Response, error) {\n\tvar inBody io.Reader\n\tif data != nil {\n\t\tjsonData, err := json.Marshal(data)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tinBody = bytes.NewBuffer(jsonData)\n\t}\n\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(method, url, inBody)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treq.Header.Add(\"Authorization\", \"Bearer \"+key)\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tdefer resp.Body.Close()\n\n\toutBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn outBody, resp, nil\n}\n\nfunc checkAdminKey(cfg *config.Config, key string) (bool, error) {\n\t// Check for all permissions\n\t//nolint:bodyclose\n\t_, resp, err := openAIRequest(cfg, \"GET\", BASE_URL+ORGS_ENDPOINT, key, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tswitch resp.StatusCode {\n\tcase 200:\n\t\treturn true, nil\n\tcase 403:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, err\n\t}\n}\n\nfunc getUserData(cfg *config.Config, key string) (MeJSON, error) {\n\tvar meJSON MeJSON\n\t//nolint:bodyclose\n\tme, resp, err := openAIRequest(cfg, \"GET\", BASE_URL+ME_ENDPOINT, key, nil)\n\tif err != nil {\n\t\treturn meJSON, err\n\t}\n\n\tif resp.StatusCode != 200 {\n\t\treturn meJSON, fmt.Errorf(\"invalid OpenAI token\")\n\t}\n\n\t// Marshall me into meJSON struct\n\tif err := json.Unmarshal(me, &meJSON); err != nil {\n\t\treturn meJSON, err\n\t}\n\treturn meJSON, nil\n}\n\nfunc printAPIKeyType(apiKey string) {\n\tif strings.Contains(apiKey, \"-svcacct-\") {\n\t\tcolor.Yellow(\"[i] Service Account API Key\\n\")\n\t} else if strings.Contains(apiKey, \"-admin-\") {\n\t\tcolor.Yellow(\"[i] Admin API Key\\n\")\n\t} else {\n\t\tcolor.Yellow(\"[i] Project/Org API Key\\n\")\n\t}\n}\nfunc printData(meJSON MeJSON) {\n\tif meJSON.Name != \"\" && meJSON.Email != \"\" {\n\t\tuserTable := table.NewWriter()\n\t\tuserTable.SetOutputMirror(os.Stdout)\n\t\tcolor.Green(\"[i] User Information\")\n\t\tuserTable.AppendHeader(table.Row{\"UserID\", \"User\", \"Email\", \"Phone\", \"MFA Enabled\"})\n\t\tuserTable.AppendRow(table.Row{meJSON.ID, meJSON.Name, meJSON.Email, meJSON.Phone, meJSON.MfaEnabled})\n\t\tuserTable.Render()\n\t} else {\n\t\tcolor.Yellow(\"[!] No User Information Available\")\n\t}\n\n\tif len(meJSON.Orgs.Data) > 0 {\n\t\torgTable := table.NewWriter()\n\t\torgTable.SetOutputMirror(os.Stdout)\n\t\tcolor.Green(\"[i] Organizations Information\")\n\t\torgTable.AppendHeader(table.Row{\"Org ID\", \"Title\", \"User\", \"Default\", \"Role\"})\n\t\tfor _, org := range meJSON.Orgs.Data {\n\t\t\torgTable.AppendRow(table.Row{org.ID, fmt.Sprintf(\"%s (%s)\", org.Title, org.Description), org.User, org.Default, org.Role})\n\t\t}\n\t\torgTable.Render()\n\t} else {\n\t\tcolor.Yellow(\"[!] No Organizations Information Available\")\n\t}\n}\n\nfunc stringifyPermissionStatus(scope OpenAIScope) ([]Permission, analyzers.PermissionType) {\n\treadStatus := false\n\twriteStatus := false\n\terrors := false\n\tfor _, test := range scope.ReadTests {\n\t\tif test.Type == analyzers.READ {\n\t\t\treadStatus = test.Status.Value\n\t\t}\n\t\tif test.Status.IsError {\n\t\t\terrors = true\n\t\t}\n\t}\n\tfor _, test := range scope.WriteTests {\n\t\tif test.Type == analyzers.WRITE {\n\t\t\twriteStatus = test.Status.Value\n\t\t}\n\t\tif test.Status.IsError {\n\t\t\terrors = true\n\t\t}\n\t}\n\tif errors {\n\t\treturn nil, analyzers.ERROR\n\t}\n\tif readStatus && writeStatus {\n\t\treturn []Permission{scope.ReadPermission, scope.WritePermission}, analyzers.READ_WRITE\n\t} else if readStatus {\n\t\treturn []Permission{scope.ReadPermission}, analyzers.READ\n\t} else if writeStatus {\n\t\treturn []Permission{scope.WritePermission}, analyzers.WRITE\n\t} else {\n\t\treturn nil, analyzers.NONE\n\t}\n}\n\nfunc getPermissions() []permissionData {\n\tvar perms []permissionData\n\n\tfor _, scope := range SCOPES {\n\t\tpermissions, status := stringifyPermissionStatus(scope)\n\t\tperms = append(perms, permissionData{\n\t\t\tname:        scope.Endpoints[0], // Using the first endpoint as the name for simplicity\n\t\t\tendpoints:   scope.Endpoints,\n\t\t\tstatus:      status,\n\t\t\tpermissions: permissions,\n\t\t})\n\t}\n\n\treturn perms\n}\n\nfunc printPermissions(perms []permissionData, showAll bool) {\n\tfmt.Print(\"\\n\\n\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Scope\", \"Endpoints\", \"Permission\"})\n\n\tfor _, perm := range perms {\n\t\tif showAll || perm.status != analyzers.NONE {\n\t\t\tt.AppendRow([]any{perm.name, perm.endpoints[0], perm.status})\n\n\t\t\tfor i := 1; i < len(perm.endpoints); i++ {\n\t\t\t\tt.AppendRow([]any{\"\", perm.endpoints[i], perm.status})\n\t\t\t}\n\t\t}\n\t}\n\n\tt.Render()\n\tfmt.Print(\"\\n\\n\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/openai/openai_test.go",
    "content": "package openai\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed result_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    []byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid OpenAI key\",\n\t\t\tkey:     testSecrets.MustGetField(\"OPENAI_VERIFIED\"),\n\t\t\twant:    expectedOutput,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/openai/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage openai\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    ModelsRead Permission = iota\n    ModelCapabilitiesWrite Permission = iota\n    AssistantsRead Permission = iota\n    AssistantsWrite Permission = iota\n    ThreadsRead Permission = iota\n    ThreadsWrite Permission = iota\n    FineTuningRead Permission = iota\n    FineTuningWrite Permission = iota\n    FilesRead Permission = iota\n    FilesWrite Permission = iota\n    EvalsRead Permission = iota\n    EvalsWrite Permission = iota\n    ResponsesRead Permission = iota\n    ResponsesWrite Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        ModelsRead: \"models:read\",\n        ModelCapabilitiesWrite: \"model_capabilities:write\",\n        AssistantsRead: \"assistants:read\",\n        AssistantsWrite: \"assistants:write\",\n        ThreadsRead: \"threads:read\",\n        ThreadsWrite: \"threads:write\",\n        FineTuningRead: \"fine_tuning:read\",\n        FineTuningWrite: \"fine_tuning:write\",\n        FilesRead: \"files:read\",\n        FilesWrite: \"files:write\",\n        EvalsRead: \"evals:read\",\n        EvalsWrite: \"evals:write\",\n        ResponsesRead: \"responses:read\",\n        ResponsesWrite: \"responses:write\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"models:read\": ModelsRead,\n        \"model_capabilities:write\": ModelCapabilitiesWrite,\n        \"assistants:read\": AssistantsRead,\n        \"assistants:write\": AssistantsWrite,\n        \"threads:read\": ThreadsRead,\n        \"threads:write\": ThreadsWrite,\n        \"fine_tuning:read\": FineTuningRead,\n        \"fine_tuning:write\": FineTuningWrite,\n        \"files:read\": FilesRead,\n        \"files:write\": FilesWrite,\n        \"evals:read\": EvalsRead,\n        \"evals:write\": EvalsWrite,\n        \"responses:read\": ResponsesRead,\n        \"responses:write\": ResponsesWrite,\n    }\n\n    PermissionIDs = map[Permission]int{\n        ModelsRead: 1,\n        ModelCapabilitiesWrite: 2,\n        AssistantsRead: 3,\n        AssistantsWrite: 4,\n        ThreadsRead: 5,\n        ThreadsWrite: 6,\n        FineTuningRead: 7,\n        FineTuningWrite: 8,\n        FilesRead: 9,\n        FilesWrite: 10,\n        EvalsRead: 11,\n        EvalsWrite: 12,\n        ResponsesRead: 13,\n        ResponsesWrite: 14,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: ModelsRead,\n        2: ModelCapabilitiesWrite,\n        3: AssistantsRead,\n        4: AssistantsWrite,\n        5: ThreadsRead,\n        6: ThreadsWrite,\n        7: FineTuningRead,\n        8: FineTuningWrite,\n        9: FilesRead,\n        10: FilesWrite,\n        11: EvalsRead,\n        12: EvalsWrite,\n        13: ResponsesRead,\n        14: ResponsesWrite,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/openai/permissions.yaml",
    "content": "permissions:\n- models:read\n- model_capabilities:write\n- assistants:read\n- assistants:write\n- threads:read\n- threads:write\n- fine_tuning:read\n- fine_tuning:write\n- files:read\n- files:write\n- evals:read\n- evals:write\n- responses:read\n- responses:write\n"
  },
  {
    "path": "pkg/analyzer/analyzers/openai/result_output.json",
    "content": "{\n    \"AnalyzerType\": 13,\n    \"Bindings\": [\n        {\n            \"Resource\": {\n                \"Name\": \"Truffle Security Co\",\n                \"FullyQualifiedName\": \"org-n56tuYdSewh06PEGJZC0xWHf\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"truffle-security-co\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"models:read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Truffle Security Co\",\n                \"FullyQualifiedName\": \"org-n56tuYdSewh06PEGJZC0xWHf\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"truffle-security-co\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"model_capabilities:write\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Truffle Security Co\",\n                \"FullyQualifiedName\": \"org-n56tuYdSewh06PEGJZC0xWHf\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"truffle-security-co\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"assistants:read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Truffle Security Co\",\n                \"FullyQualifiedName\": \"org-n56tuYdSewh06PEGJZC0xWHf\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"truffle-security-co\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"assistants:write\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Truffle Security Co\",\n                \"FullyQualifiedName\": \"org-n56tuYdSewh06PEGJZC0xWHf\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"truffle-security-co\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"threads:read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Truffle Security Co\",\n                \"FullyQualifiedName\": \"org-n56tuYdSewh06PEGJZC0xWHf\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"truffle-security-co\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"threads:write\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Truffle Security Co\",\n                \"FullyQualifiedName\": \"org-n56tuYdSewh06PEGJZC0xWHf\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"truffle-security-co\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"fine_tuning:read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Truffle Security Co\",\n                \"FullyQualifiedName\": \"org-n56tuYdSewh06PEGJZC0xWHf\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"truffle-security-co\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"fine_tuning:write\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Truffle Security Co\",\n                \"FullyQualifiedName\": \"org-n56tuYdSewh06PEGJZC0xWHf\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"truffle-security-co\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"files:read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Truffle Security Co\",\n                \"FullyQualifiedName\": \"org-n56tuYdSewh06PEGJZC0xWHf\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"truffle-security-co\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"files:write\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Truffle Security Co\",\n                \"FullyQualifiedName\": \"org-n56tuYdSewh06PEGJZC0xWHf\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"truffle-security-co\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"evals:read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Truffle Security Co\",\n                \"FullyQualifiedName\": \"org-n56tuYdSewh06PEGJZC0xWHf\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"truffle-security-co\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"evals:write\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Truffle Security Co\",\n                \"FullyQualifiedName\": \"org-n56tuYdSewh06PEGJZC0xWHf\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"truffle-security-co\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"responses:read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Truffle Security Co\",\n                \"FullyQualifiedName\": \"org-n56tuYdSewh06PEGJZC0xWHf\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"truffle-security-co\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"responses:write\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Personal\",\n                \"FullyQualifiedName\": \"org-S2T2qOGM1KofMLUxb9rt7eV0\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"user-ohfap0ky8lkatw97iskuhghv\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"models:read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Personal\",\n                \"FullyQualifiedName\": \"org-S2T2qOGM1KofMLUxb9rt7eV0\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"user-ohfap0ky8lkatw97iskuhghv\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"model_capabilities:write\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Personal\",\n                \"FullyQualifiedName\": \"org-S2T2qOGM1KofMLUxb9rt7eV0\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"user-ohfap0ky8lkatw97iskuhghv\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"assistants:read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Personal\",\n                \"FullyQualifiedName\": \"org-S2T2qOGM1KofMLUxb9rt7eV0\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"user-ohfap0ky8lkatw97iskuhghv\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"assistants:write\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Personal\",\n                \"FullyQualifiedName\": \"org-S2T2qOGM1KofMLUxb9rt7eV0\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"user-ohfap0ky8lkatw97iskuhghv\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"threads:read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Personal\",\n                \"FullyQualifiedName\": \"org-S2T2qOGM1KofMLUxb9rt7eV0\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"user-ohfap0ky8lkatw97iskuhghv\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"threads:write\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Personal\",\n                \"FullyQualifiedName\": \"org-S2T2qOGM1KofMLUxb9rt7eV0\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"user-ohfap0ky8lkatw97iskuhghv\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"fine_tuning:read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Personal\",\n                \"FullyQualifiedName\": \"org-S2T2qOGM1KofMLUxb9rt7eV0\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"user-ohfap0ky8lkatw97iskuhghv\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"fine_tuning:write\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Personal\",\n                \"FullyQualifiedName\": \"org-S2T2qOGM1KofMLUxb9rt7eV0\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"user-ohfap0ky8lkatw97iskuhghv\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"files:read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Personal\",\n                \"FullyQualifiedName\": \"org-S2T2qOGM1KofMLUxb9rt7eV0\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"user-ohfap0ky8lkatw97iskuhghv\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"files:write\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Personal\",\n                \"FullyQualifiedName\": \"org-S2T2qOGM1KofMLUxb9rt7eV0\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"user-ohfap0ky8lkatw97iskuhghv\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"evals:read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Personal\",\n                \"FullyQualifiedName\": \"org-S2T2qOGM1KofMLUxb9rt7eV0\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"user-ohfap0ky8lkatw97iskuhghv\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"evals:write\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Personal\",\n                \"FullyQualifiedName\": \"org-S2T2qOGM1KofMLUxb9rt7eV0\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"user-ohfap0ky8lkatw97iskuhghv\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"responses:read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Personal\",\n                \"FullyQualifiedName\": \"org-S2T2qOGM1KofMLUxb9rt7eV0\",\n                \"Type\": \"organization\",\n                \"Metadata\": {\n                    \"description\": \"Personal org for dustin@trufflesec.com\",\n                    \"user\": \"user-ohfap0ky8lkatw97iskuhghv\"\n                },\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"responses:write\",\n                \"Parent\": null\n            }\n        }\n    ],\n    \"UnboundedResources\": null,\n    \"Metadata\": {\n        \"email\": \"dustin@trufflesec.com\",\n        \"is_admin\": \"false\",\n        \"is_restricted\": \"true\",\n        \"mfa\": \"true\",\n        \"user\": \"Dustin Decker\"\n    }\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/openai/scopes.go",
    "content": "package openai\n\nimport (\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n)\n\ntype OpenAIScope struct {\n\tReadTests       []analyzers.HttpStatusTest\n\tWriteTests      []analyzers.HttpStatusTest\n\tEndpoints       []string\n\tReadPermission  Permission\n\tWritePermission Permission\n}\n\nfunc (s *OpenAIScope) RunTests(key string) error {\n\theaders := map[string]string{\n\t\t\"Authorization\": \"Bearer \" + key,\n\t\t\"Content-Type\":  \"application/json\",\n\t}\n\tfor i := range s.ReadTests {\n\t\ttest := &s.ReadTests[i]\n\t\tif err := test.RunTest(headers); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tfor i := range s.WriteTests {\n\t\ttest := &s.WriteTests[i]\n\t\tif err := test.RunTest(headers); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nvar SCOPES = []OpenAIScope{\n\t{\n\t\tReadTests: []analyzers.HttpStatusTest{\n\t\t\t{URL: BASE_URL + \"/v1/models\", Method: \"GET\", Valid: []int{200}, Invalid: []int{403}, Type: analyzers.READ, Status: analyzers.PermissionStatus{}},\n\t\t},\n\t\tEndpoints:      []string{\"/v1/models\"},\n\t\tReadPermission: ModelsRead,\n\t},\n\t{\n\t\tWriteTests: []analyzers.HttpStatusTest{\n\t\t\t{URL: BASE_URL + \"/v1/images/generations\", Method: \"POST\", Payload: POST_PAYLOAD, Valid: []int{400}, Invalid: []int{401}, Type: analyzers.WRITE, Status: analyzers.PermissionStatus{}},\n\t\t},\n\t\tEndpoints:       []string{\"/v1/audio\", \"/v1/chat/completions\", \"/v1/embeddings\", \"/v1/images\", \"/v1/moderations\"},\n\t\tWritePermission: ModelCapabilitiesWrite,\n\t},\n\t{\n\t\tReadTests: []analyzers.HttpStatusTest{\n\t\t\t{URL: BASE_URL + \"/v1/assistants\", Method: \"GET\", Valid: []int{400}, Invalid: []int{401}, Type: analyzers.READ, Status: analyzers.PermissionStatus{}},\n\t\t},\n\t\tWriteTests: []analyzers.HttpStatusTest{\n\t\t\t{URL: BASE_URL + \"/v1/assistants\", Method: \"POST\", Payload: POST_PAYLOAD, Valid: []int{400}, Invalid: []int{401}, Type: analyzers.WRITE, Status: analyzers.PermissionStatus{}},\n\t\t},\n\t\tEndpoints:       []string{\"/v1/assistants\"},\n\t\tReadPermission:  AssistantsRead,\n\t\tWritePermission: AssistantsWrite,\n\t},\n\t{\n\t\tReadTests: []analyzers.HttpStatusTest{\n\t\t\t{URL: BASE_URL + \"/v1/threads/1\", Method: \"GET\", Valid: []int{400}, Invalid: []int{401}, Type: analyzers.READ, Status: analyzers.PermissionStatus{}},\n\t\t},\n\t\tWriteTests: []analyzers.HttpStatusTest{\n\t\t\t{URL: BASE_URL + \"/v1/threads\", Method: \"POST\", Payload: POST_PAYLOAD, Valid: []int{400}, Invalid: []int{401}, Type: analyzers.WRITE, Status: analyzers.PermissionStatus{}},\n\t\t},\n\t\tEndpoints:       []string{\"/v1/threads\"},\n\t\tReadPermission:  ThreadsRead,\n\t\tWritePermission: ThreadsWrite,\n\t},\n\t{\n\t\tReadTests: []analyzers.HttpStatusTest{\n\t\t\t{URL: BASE_URL + \"/v1/fine_tuning/jobs\", Method: \"GET\", Valid: []int{200}, Invalid: []int{401}, Type: analyzers.READ, Status: analyzers.PermissionStatus{}},\n\t\t},\n\t\tWriteTests: []analyzers.HttpStatusTest{\n\t\t\t{URL: BASE_URL + \"/v1/fine_tuning/jobs\", Method: \"POST\", Payload: POST_PAYLOAD, Valid: []int{400}, Invalid: []int{401}, Type: analyzers.WRITE, Status: analyzers.PermissionStatus{}},\n\t\t},\n\t\tEndpoints:       []string{\"/v1/fine_tuning\"},\n\t\tReadPermission:  FineTuningRead,\n\t\tWritePermission: FineTuningWrite,\n\t},\n\t{\n\t\tReadTests: []analyzers.HttpStatusTest{\n\t\t\t{URL: BASE_URL + \"/v1/files\", Method: \"GET\", Valid: []int{200}, Invalid: []int{401}, Type: analyzers.READ, Status: analyzers.PermissionStatus{}},\n\t\t},\n\t\tWriteTests: []analyzers.HttpStatusTest{\n\t\t\t{URL: BASE_URL + \"/v1/files\", Method: \"POST\", Payload: POST_PAYLOAD, Valid: []int{415}, Invalid: []int{401}, Type: analyzers.WRITE, Status: analyzers.PermissionStatus{}},\n\t\t},\n\t\tEndpoints:       []string{\"/v1/files\"},\n\t\tReadPermission:  FilesRead,\n\t\tWritePermission: FilesWrite,\n\t},\n\t{\n\t\tReadTests: []analyzers.HttpStatusTest{\n\t\t\t{URL: BASE_URL + \"/v1/evals\", Method: \"GET\", Valid: []int{200}, Invalid: []int{401}, Type: analyzers.READ, Status: analyzers.PermissionStatus{}},\n\t\t},\n\t\tWriteTests: []analyzers.HttpStatusTest{\n\t\t\t{URL: BASE_URL + \"/v1/evals\", Method: \"POST\", Payload: POST_PAYLOAD, Valid: []int{400}, Invalid: []int{401}, Type: analyzers.WRITE, Status: analyzers.PermissionStatus{}},\n\t\t},\n\t\tEndpoints:       []string{\"/v1/evals\"},\n\t\tReadPermission:  EvalsRead,\n\t\tWritePermission: EvalsWrite,\n\t},\n\t{\n\t\tReadTests: []analyzers.HttpStatusTest{\n\t\t\t{URL: BASE_URL + \"/v1/responses/1\", Method: \"GET\", Valid: []int{400}, Invalid: []int{401}, Type: analyzers.READ, Status: analyzers.PermissionStatus{}},\n\t\t},\n\t\tWriteTests: []analyzers.HttpStatusTest{\n\t\t\t{URL: BASE_URL + \"/v1/responses\", Method: \"POST\", Payload: POST_PAYLOAD, Valid: []int{400}, Invalid: []int{401}, Type: analyzers.WRITE, Status: analyzers.PermissionStatus{}},\n\t\t},\n\t\tEndpoints:       []string{\"/v1/responses\"},\n\t\tReadPermission:  ResponsesRead,\n\t\tWritePermission: ResponsesWrite,\n\t},\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/opsgenie/opsgenie.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go opsgenie\n\npackage opsgenie\n\nimport (\n\t\"bytes\"\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeOpsgenie }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credInfo[\"key\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"missing key in credInfo\")\n\t}\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType:       analyzers.AnalyzerTypeOpsgenie,\n\t\tMetadata:           nil,\n\t\tBindings:           make([]analyzers.Binding, len(info.Permissions)),\n\t\tUnboundedResources: make([]analyzers.Resource, len(info.Users)),\n\t}\n\n\t// Opsgenie has API integrations, so the key does not belong\n\t// to a particular user or account, it itself is a resource\n\tresource := analyzers.Resource{\n\t\tName:               \"Opsgenie API Integration Key\",\n\t\tFullyQualifiedName: \"Opsgenie API Integration Key\",\n\t\tType:               \"API Key\",\n\t\tMetadata: map[string]any{\n\t\t\t\"expires\": \"never\",\n\t\t},\n\t}\n\n\tfor idx, permission := range info.Permissions {\n\t\tresult.Bindings[idx] = analyzers.Binding{\n\t\t\tResource: resource,\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: permission,\n\t\t\t},\n\t\t}\n\t}\n\n\t// We can find list of users in the current account\n\t// if the API key has Configuration Access, so these can be\n\t// unbounded resources\n\tfor idx, user := range info.Users {\n\t\tresult.UnboundedResources[idx] = analyzers.Resource{\n\t\t\tName:               user.FullName,\n\t\t\tFullyQualifiedName: user.Username,\n\t\t\tType:               \"user\",\n\t\t\tMetadata: map[string]any{\n\t\t\t\t\"username\": user.Username,\n\t\t\t\t\"role\":     user.Role.Name,\n\t\t\t},\n\t\t}\n\t}\n\n\treturn &result\n}\n\n//go:embed scopes.json\nvar scopesConfig []byte\n\ntype User struct {\n\tFullName string `json:\"fullName\"`\n\tUsername string `json:\"username\"`\n\tRole     struct {\n\t\tName string `json:\"name\"`\n\t} `json:\"role\"`\n}\n\ntype UsersJSON struct {\n\tUsers []User `json:\"data\"`\n}\n\ntype HttpStatusTest struct {\n\tEndpoint        string      `json:\"endpoint\"`\n\tMethod          string      `json:\"method\"`\n\tPayload         interface{} `json:\"payload\"`\n\tValidStatuses   []int       `json:\"valid_status_code\"`\n\tInvalidStatuses []int       `json:\"invalid_status_code\"`\n}\n\nfunc StatusContains(status int, vals []int) bool {\n\tfor _, v := range vals {\n\t\tif status == v {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (h *HttpStatusTest) RunTest(cfg *config.Config, headers map[string]string) (bool, error) {\n\t// If body data, marshal to JSON\n\tvar data io.Reader\n\tif h.Payload != nil {\n\t\tjsonData, err := json.Marshal(h.Payload)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tdata = bytes.NewBuffer(jsonData)\n\t}\n\n\t// Create new HTTP request\n\tvar client *http.Client\n\n\t// Non-safe Opsgenie APIs are asynchronous and always return 202 if credential has the permission.\n\t// For Safe API Methods, use the restricted client\n\tif analyzers.IsMethodSafe(h.Method) {\n\t\tclient = analyzers.NewAnalyzeClient(cfg)\n\t} else {\n\t\tclient = analyzers.NewAnalyzeClientUnrestricted(cfg)\n\t}\n\n\treq, err := http.NewRequest(h.Method, h.Endpoint, data)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Add custom headers if provided\n\tfor key, value := range headers {\n\t\treq.Header.Set(key, value)\n\t}\n\n\t// Execute HTTP Request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer resp.Body.Close()\n\n\t// Check response status code\n\tswitch {\n\tcase StatusContains(resp.StatusCode, h.ValidStatuses):\n\t\treturn true, nil\n\tcase StatusContains(resp.StatusCode, h.InvalidStatuses):\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, errors.New(\"error checking response status code\")\n\t}\n}\n\ntype Scope struct {\n\tName     string         `json:\"name\"`\n\tHttpTest HttpStatusTest `json:\"test\"`\n}\n\nfunc readInScopes() ([]Scope, error) {\n\tvar scopes []Scope\n\tif err := json.Unmarshal(scopesConfig, &scopes); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn scopes, nil\n}\n\nfunc checkPermissions(cfg *config.Config, key string) ([]string, error) {\n\tscopes, err := readInScopes()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"reading in scopes: %w\", err)\n\t}\n\n\tpermissions := make([]string, 0)\n\tfor _, scope := range scopes {\n\t\tstatus, err := scope.HttpTest.RunTest(cfg, map[string]string{\"Authorization\": \"GenieKey \" + key})\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"running test: %w\", err)\n\t\t}\n\t\tif status {\n\t\t\tpermissions = append(permissions, scope.Name)\n\t\t}\n\t}\n\n\treturn permissions, nil\n}\n\nfunc contains(s []string, e string) bool {\n\tfor _, a := range s {\n\t\tif a == e {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc getUserList(cfg *config.Config, key string) ([]User, error) {\n\t// Create new HTTP request\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(\"GET\", \"https://api.opsgenie.com/v2/users\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Add custom headers if provided\n\treq.Header.Set(\"Authorization\", \"GenieKey \"+key)\n\n\t// Execute HTTP Request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\t// Decode response body\n\tvar userList UsersJSON\n\terr = json.NewDecoder(resp.Body).Decode(&userList)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn userList.Users, nil\n}\n\ntype SecretInfo struct {\n\tUsers       []User\n\tPermissions []string\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid OpsGenie API key\\n\\n\")\n\tprintPermissions(info.Permissions)\n\tif len(info.Users) > 0 {\n\t\tprintUsers(info.Users)\n\t}\n\tcolor.Yellow(\"\\n[i] Expires: Never\")\n\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\tvar info = &SecretInfo{}\n\n\tpermissions, err := checkPermissions(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(permissions) == 0 {\n\t\treturn nil, fmt.Errorf(\"invalid OpsGenie API key\")\n\t}\n\n\tinfo.Permissions = permissions\n\n\tif contains(permissions, \"configuration_access\") {\n\t\tusers, err := getUserList(cfg, key)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"getting user list: %w\", err)\n\t\t}\n\t\tinfo.Users = users\n\t}\n\n\treturn info, nil\n}\n\nfunc printPermissions(permissions []string) {\n\tcolor.Yellow(\"[i] Permissions:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Permission\"})\n\tfor _, permission := range permissions {\n\t\tt.AppendRow(table.Row{color.GreenString(permission)})\n\t}\n\tt.Render()\n}\n\nfunc printUsers(users []User) {\n\tcolor.Green(\"\\n[i] Users:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Name\", \"Username\", \"Role\"})\n\tfor _, user := range users {\n\t\tt.AppendRow(table.Row{color.GreenString(user.FullName), color.GreenString(user.Username), color.GreenString(user.Role.Name)})\n\t}\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/opsgenie/opsgenie_test.go",
    "content": "package opsgenie\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tkey := testSecrets.MustGetField(\"OPSGENIE\")\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"valid Opsgenie API key\",\n\t\t\tkey:  key,\n\t\t\twant: `{\n\t\t\t\t\t\"AnalyzerType\": 11,\n\t\t\t\t\t\"Bindings\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\"Resource\": {\n\t\t\t\t\t\t\t\"Name\": \"Opsgenie API Integration Key\",\n\t\t\t\t\t\t\t\"FullyQualifiedName\": \"Opsgenie API Integration Key\",\n\t\t\t\t\t\t\t\"Type\": \"API Key\",\n\t\t\t\t\t\t\t\"Metadata\": {\n\t\t\t\t\t\t\t\"expires\": \"never\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"Parent\": null\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"Permission\": {\n\t\t\t\t\t\t\t\"Value\": \"configuration_access\",\n\t\t\t\t\t\t\t\"Parent\": null\n\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\"Resource\": {\n\t\t\t\t\t\t\t\"Name\": \"Opsgenie API Integration Key\",\n\t\t\t\t\t\t\t\"FullyQualifiedName\": \"Opsgenie API Integration Key\",\n\t\t\t\t\t\t\t\"Type\": \"API Key\",\n\t\t\t\t\t\t\t\"Metadata\": {\n\t\t\t\t\t\t\t\"expires\": \"never\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"Parent\": null\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"Permission\": {\n\t\t\t\t\t\t\t\"Value\": \"read\",\n\t\t\t\t\t\t\t\"Parent\": null\n\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\"Resource\": {\n\t\t\t\t\t\t\t\"Name\": \"Opsgenie API Integration Key\",\n\t\t\t\t\t\t\t\"FullyQualifiedName\": \"Opsgenie API Integration Key\",\n\t\t\t\t\t\t\t\"Type\": \"API Key\",\n\t\t\t\t\t\t\t\"Metadata\": {\n\t\t\t\t\t\t\t\"expires\": \"never\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"Parent\": null\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"Permission\": {\n\t\t\t\t\t\t\t\"Value\": \"delete\",\n\t\t\t\t\t\t\t\"Parent\": null\n\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\"Resource\": {\n\t\t\t\t\t\t\t\"Name\": \"Opsgenie API Integration Key\",\n\t\t\t\t\t\t\t\"FullyQualifiedName\": \"Opsgenie API Integration Key\",\n\t\t\t\t\t\t\t\"Type\": \"API Key\",\n\t\t\t\t\t\t\t\"Metadata\": {\n\t\t\t\t\t\t\t\"expires\": \"never\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"Parent\": null\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"Permission\": {\n\t\t\t\t\t\t\t\"Value\": \"create_and_update\",\n\t\t\t\t\t\t\t\"Parent\": null\n\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t],\n\t\t\t\t\t\"UnboundedResources\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\"Name\": \"John Scanner\",\n\t\t\t\t\t\t\"FullyQualifiedName\": \"secretscanner02@zohomail.com\",\n\t\t\t\t\t\t\"Type\": \"user\",\n\t\t\t\t\t\t\"Metadata\": {\n\t\t\t\t\t\t\t\"role\": \"Owner\",\n\t\t\t\t\t\t\t\"username\": \"secretscanner02@zohomail.com\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"Parent\": null\n\t\t\t\t\t\t}\n\t\t\t\t\t],\n\t\t\t\t\t\"Metadata\": null\n\t\t\t\t\t}`,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/opsgenie/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage opsgenie\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    ConfigurationAccess Permission = iota\n    Read Permission = iota\n    Delete Permission = iota\n    CreateAndUpdate Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        ConfigurationAccess: \"configuration_access\",\n        Read: \"read\",\n        Delete: \"delete\",\n        CreateAndUpdate: \"create_and_update\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"configuration_access\": ConfigurationAccess,\n        \"read\": Read,\n        \"delete\": Delete,\n        \"create_and_update\": CreateAndUpdate,\n    }\n\n    PermissionIDs = map[Permission]int{\n        ConfigurationAccess: 1,\n        Read: 2,\n        Delete: 3,\n        CreateAndUpdate: 4,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: ConfigurationAccess,\n        2: Read,\n        3: Delete,\n        4: CreateAndUpdate,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/opsgenie/permissions.yaml",
    "content": "permissions:\n - configuration_access\n - read\n - delete\n - create_and_update"
  },
  {
    "path": "pkg/analyzer/analyzers/opsgenie/scopes.json",
    "content": "[\n    {\n        \"name\": \"configuration_access\",\n        \"test\": {\n            \"endpoint\": \"https://api.opsgenie.com/v2/account\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"read\",\n        \"test\": {\n            \"endpoint\": \"https://api.opsgenie.com/v2/alerts\",\n            \"method\": \"GET\",\n            \"valid_status_code\": [200],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"delete\",\n        \"test\": {\n            \"endpoint\": \"https://api.opsgenie.com/v2/alerts/`nowaythiscanexist\",\n            \"method\": \"DELETE\",\n            \"valid_status_code\": [202],\n            \"invalid_status_code\": [403]\n        }\n    },\n    {\n        \"name\": \"create_and_update\",\n        \"test\": {\n            \"endpoint\": \"https://api.opsgenie.com/v2/alerts/`nowaycanthisexist/message\",\n            \"method\": \"PUT\",\n            \"valid_status_code\": [400],\n            \"invalid_status_code\": [403]\n        }\n    }\n]"
  },
  {
    "path": "pkg/analyzer/analyzers/plaid/expected_output.json",
    "content": "{\"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}"
  },
  {
    "path": "pkg/analyzer/analyzers/plaid/models.go",
    "content": "package plaid\n\ntype account struct {\n\tAccountID    string `json:\"account_id\"`\n\tName         string `json:\"name\"`\n\tOfficialName string `json:\"official_name\"`\n\tSubtype      string `json:\"subtype\"`\n\tType         string `json:\"type\"`\n}\n\ntype item struct {\n\tProducts []string `json:\"products\"`\n\tItemID   string   `json:\"item_id\"`\n}\n\ntype accountsResponse struct {\n\tAccounts []account `json:\"accounts\"`\n\tItem     item      `json:\"item\"`\n}\n\ntype secretInfo struct {\n\tItem        item\n\tAccounts    []account\n\tEnvironment string\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/plaid/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage plaid\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    Read Permission = iota\n    Write Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        Read: \"read\",\n        Write: \"write\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"read\": Read,\n        \"write\": Write,\n    }\n\n    PermissionIDs = map[Permission]int{\n        Read: 1,\n        Write: 2,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: Read,\n        2: Write,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/plaid/permissions.yaml",
    "content": "permissions:\n  - read\n  - write\n"
  },
  {
    "path": "pkg/analyzer/analyzers/plaid/plaid.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go plaid\npackage plaid\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (a Analyzer) Type() analyzers.AnalyzerType {\n\treturn analyzers.AnalyzerTypePlaid\n}\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tsecret, exist := credInfo[\"secret\"]\n\tif !exist {\n\t\treturn nil, errors.New(\"secret not found in credentials info\")\n\t}\n\tclientID, exist := credInfo[\"id\"]\n\tif !exist {\n\t\treturn nil, errors.New(\"id not found in credentials info\")\n\t}\n\taccessToken, exist := credInfo[\"token\"]\n\tif !exist {\n\t\treturn nil, errors.New(\"token not found in credentials info\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, secret, clientID, accessToken)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, secret string, clientID string, accessToken string) {\n\tinfo, err := AnalyzePermissions(cfg, secret, clientID, accessToken)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Invalid Plaid API key\\n\")\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t\treturn\n\t}\n\n\tif info == nil {\n\t\tcolor.Red(\"[x] Error : %s\", \"No information found\")\n\t\treturn\n\t}\n\n\tcolor.Green(\"[i] Valid Plaid API Credentials\\n\")\n\tcolor.Yellow(\"\\n[i] Environment: %s\", info.Environment)\n\tif info.Environment == \"sandbox\" {\n\t\tcolor.Cyan(\"Credentials are for Sandbox environment. All resources found are simulated and not real data.\\n\")\n\t}\n\tprintAccountsAndProducts(info)\n}\n\nfunc AnalyzePermissions(cfg *config.Config, secret string, clientId string, accessToken string) (*secretInfo, error) {\n\tenvironment := \"sandbox\"\n\tif strings.Contains(accessToken, \"production\") {\n\t\tenvironment = \"production\"\n\t}\n\n\t// Plaid API uses POST requests for all requests, so we need to use an unrestricted client\n\tclient := analyzers.NewAnalyzeClientUnrestricted(cfg)\n\tvar secretInfo = &secretInfo{}\n\tsecretInfo.Environment = environment\n\tresp, err := getPlaidAccounts(client, clientId, secret, accessToken, environment)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsecretInfo.Item = resp.Item\n\tsecretInfo.Accounts = resp.Accounts\n\treturn secretInfo, nil\n}\n\nfunc getPlaidAccounts(client *http.Client, clientID string, secret string, accessToken string, environment string) (*accountsResponse, error) {\n\tbody := map[string]interface{}{\n\t\t\"client_id\":    clientID,\n\t\t\"secret\":       secret,\n\t\t\"access_token\": accessToken,\n\t}\n\turl := \"https://\" + environment + \".plaid.com/accounts/get\"\n\tjsonBody, _ := json.Marshal(body)\n\treq, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonBody))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"received non-OK HTTP status: %d\", resp.StatusCode)\n\t}\n\n\tvar accounts accountsResponse\n\tif err := json.NewDecoder(resp.Body).Decode(&accounts); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &accounts, nil\n}\n\nfunc secretInfoToAnalyzerResult(info *secretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\titemID := info.Item.ItemID\n\tuserProducts := info.Item.Products\n\tuserAccounts := info.Accounts\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType:       analyzers.AnalyzerTypePlaid,\n\t\tMetadata:           nil,\n\t\tBindings:           make([]analyzers.Binding, len(userProducts)),\n\t\tUnboundedResources: make([]analyzers.Resource, len(userAccounts)),\n\t}\n\n\tfor idx, productName := range userProducts {\n\t\tproduct, ok := GetProductByName(productName)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tresult.Bindings[idx] = analyzers.Binding{\n\t\t\tResource: analyzers.Resource{\n\t\t\t\tName:               product.DisplayName,\n\t\t\t\tFullyQualifiedName: itemID + \"/product/\" + product.Name,\n\t\t\t\tType:               \"product\",\n\t\t\t\tMetadata: map[string]any{\n\t\t\t\t\t\"productDesc\": product.Description,\n\t\t\t\t},\n\t\t\t},\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: PermissionStrings[product.PermissionLevel],\n\t\t\t},\n\t\t}\n\t}\n\n\tfor idx, account := range info.Accounts {\n\t\tresult.UnboundedResources[idx] = analyzers.Resource{\n\t\t\tName:               account.Name,\n\t\t\tFullyQualifiedName: account.AccountID,\n\t\t\tType:               \"account\",\n\t\t\tMetadata: map[string]any{\n\t\t\t\t\"officialName\": account.OfficialName,\n\t\t\t},\n\t\t}\n\t}\n\n\treturn &result\n}\n\nfunc printAccountsAndProducts(info *secretInfo) {\n\tuserProducts := info.Item.Products\n\tuserAccounts := info.Accounts\n\n\tcolor.Yellow(\"\\n[i] Item ID: %s\", info.Item.ItemID)\n\n\tcolor.Yellow(\"\\n[i] Accounts Info:\")\n\tt1 := table.NewWriter()\n\tt1.SetOutputMirror(os.Stdout)\n\tt1.AppendHeader(table.Row{\"ID\", \"Name\", \"Official Name\", \"Type\", \"Subtype\"})\n\tfor _, account := range userAccounts {\n\t\tt1.AppendRow(table.Row{\n\t\t\tcolor.GreenString(account.AccountID),\n\t\t\tcolor.GreenString(account.Name),\n\t\t\tcolor.GreenString(account.OfficialName),\n\t\t\tcolor.GreenString(account.Type),\n\t\t\tcolor.GreenString(account.Subtype),\n\t\t})\n\t\tt1.AppendSeparator()\n\t}\n\tt1.SetOutputMirror(os.Stdout)\n\tt1.Render()\n\n\tcolor.Yellow(\"\\n[i] Products:\")\n\tt2 := table.NewWriter()\n\tt2.AppendHeader(table.Row{\"Product Name\", \"Access Level\", \"Capabilities\"})\n\n\tfor _, product := range plaidProducts {\n\t\tproductCell := color.GreenString(product.DisplayName)\n\t\tproductDescCell := color.GreenString(product.Description)\n\t\tproductPermissionCell := color.GreenString(\"Denied\")\n\n\t\tfor _, productName := range userProducts {\n\t\t\tif productName == product.Name {\n\t\t\t\tpermissionLevel := PermissionStrings[product.PermissionLevel]\n\t\t\t\tproductPermissionCell = \"Granted\" // If permission level is not defined, default to \"Granted\"\n\t\t\t\tif len(permissionLevel) > 0 {\n\t\t\t\t\t// Capitalize the perssion level string\n\t\t\t\t\tcapitalizedLevel := strings.ToUpper(string(permissionLevel[0])) + strings.ToLower(permissionLevel[1:])\n\t\t\t\t\tproductPermissionCell = color.GreenString(capitalizedLevel)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tt2.AppendRow(table.Row{productCell, productPermissionCell, productDescCell})\n\t\tt2.AppendSeparator()\n\t}\n\n\tt2.SetOutputMirror(os.Stdout)\n\tt2.Render()\n\tfmt.Printf(\"%s: https://plaid.com/docs/api/\\n\\n\", color.GreenString(\"Ref\"))\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/plaid/plaid_test.go",
    "content": "package plaid\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tsecret := testSecrets.MustGetField(\"PLAIDKEY_SECRET\")\n\tclientID := testSecrets.MustGetField(\"PLAIDKEY_CLIENTID\")\n\taccessToken := testSecrets.MustGetField(\"PLAIDKEY_ACCESS_TOKEN\")\n\n\ttests := []struct {\n\t\tname        string\n\t\tclientID    string\n\t\tsecret      string\n\t\taccessToken string\n\t\twant        string\n\t\twantErr     bool\n\t}{\n\t\t{\n\t\t\tname:        \"valid plaid credentials\",\n\t\t\tclientID:    clientID,\n\t\t\tsecret:      secret,\n\t\t\taccessToken: accessToken,\n\t\t\twant:        string(expectedOutput),\n\t\t\twantErr:     false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\n\t\t\t\t\"secret\": tt.secret,\n\t\t\t\t\"id\":     tt.clientID,\n\t\t\t\t\"token\":  tt.accessToken,\n\t\t\t})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\tfmt.Println(string(gotJSON))\n\n\t\t\t// compare the JSON strings\n\t\t\tif string(gotJSON) != string(tt.want) {\n\t\t\t\t// pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(tt.want, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/plaid/products.go",
    "content": "package plaid\n\ntype plaidProduct struct {\n\tName            string\n\tDisplayName     string\n\tDescription     string\n\tPermissionLevel Permission\n}\n\ntype Product int\n\nconst (\n\tAssets Product = iota\n\tAuth\n\tBalance\n\tBalancePlus\n\tBeacon\n\tCraBaseReport\n\tCraIncomeInsights\n\tCraPartnerInsights\n\tCraNetworkInsights\n\tCraCashflowInsights\n\tCreditDetails\n\tEmployment\n\tIdentity\n\tIdentityMatch\n\tIdentityVerification\n\tIncome\n\tIncomeVerification\n\tInvestments\n\tInvestmentsAuth\n\tLayer\n\tLiabilities\n\tPayByBank\n\tPaymentInitiation\n\tProcessorPayments\n\tProcessorIdentity\n\tProfile\n\tRecurringTransactions\n\tSignal\n\tStandingOrders\n\tStatements\n\tTransactions\n\tTransactionsRefresh\n\tTransfer\n)\n\nvar plaidProducts = map[Product]plaidProduct{\n\tAssets: {\n\t\tName:            \"assets\",\n\t\tDisplayName:     \"Assets\",\n\t\tDescription:     \"Request, retrieve and share detailed reports of financial assets and account history\",\n\t\tPermissionLevel: Write,\n\t},\n\tAuth: {\n\t\tName:            \"auth\",\n\t\tDisplayName:     \"Auth\",\n\t\tDescription:     \"Retrieve account and routing numbers\",\n\t\tPermissionLevel: Read,\n\t},\n\tBalance: {\n\t\tName:            \"balance\",\n\t\tDisplayName:     \"Balance\",\n\t\tDescription:     \"Check current and available account balance in real time\",\n\t\tPermissionLevel: Read,\n\t},\n\tBalancePlus: {\n\t\tName:            \"balance_plus\",\n\t\tDisplayName:     \"Balance Plus\",\n\t\tDescription:     \"Estimate projected balances and financial runway\",\n\t\tPermissionLevel: Read,\n\t},\n\tBeacon: {\n\t\tName:            \"beacon\",\n\t\tDisplayName:     \"Beacon\",\n\t\tDescription:     \"Generate risk insights and fraud signals based on user account behavior\",\n\t\tPermissionLevel: Write,\n\t},\n\tCraBaseReport: {\n\t\tName:            \"cra_base_report\",\n\t\tDisplayName:     \"CRA Base Report\",\n\t\tDescription:     \"Generate a standardized financial report\",\n\t\tPermissionLevel: Write,\n\t},\n\tCraIncomeInsights: {\n\t\tName:            \"cra_income_insights\",\n\t\tDisplayName:     \"CRA Income Insights\",\n\t\tDescription:     \"Analyze income trends and consistency\",\n\t\tPermissionLevel: Write,\n\t},\n\tCraPartnerInsights: {\n\t\tName:            \"cra_partner_insights\",\n\t\tDisplayName:     \"CRA Partner Insights\",\n\t\tDescription:     \"Access custom insights\",\n\t\tPermissionLevel: Write,\n\t},\n\tCraNetworkInsights: {\n\t\tName:            \"cra_network_insights\",\n\t\tDisplayName:     \"CRA Network Insights\",\n\t\tDescription:     \"View analytics and performance benchmarks\",\n\t\tPermissionLevel: Write,\n\t},\n\tCraCashflowInsights: {\n\t\tName:            \"cra_cashflow_insights\",\n\t\tDisplayName:     \"CRA Cashflow Insights\",\n\t\tDescription:     \"Evaluate cash flow behavior including recurring income and expenses\",\n\t\tPermissionLevel: Write,\n\t},\n\tCreditDetails: {\n\t\tName:            \"credit_details\",\n\t\tDisplayName:     \"Credit Details\",\n\t\tDescription:     \"Access credit account usage, limits, and repayment history\",\n\t\tPermissionLevel: Read,\n\t},\n\tEmployment: {\n\t\tName:            \"employment\",\n\t\tDisplayName:     \"Employment\",\n\t\tDescription:     \"Retrieve current employment status and employer details\",\n\t\tPermissionLevel: Read,\n\t},\n\tIdentity: {\n\t\tName:            \"identity\",\n\t\tDisplayName:     \"Identity\",\n\t\tDescription:     \"Access personal identity information like name, phone, address, and email\",\n\t\tPermissionLevel: Read,\n\t},\n\tIdentityMatch: {\n\t\tName:            \"identity_match\",\n\t\tDisplayName:     \"Identity Match\",\n\t\tDescription:     \"Match user-provided identity details against institution records\",\n\t\tPermissionLevel: Read,\n\t},\n\tIdentityVerification: {\n\t\tName:            \"identity_verification\",\n\t\tDisplayName:     \"Identity Verification\",\n\t\tDescription:     \"Verify user identity through government documents and identity data sources\",\n\t\tPermissionLevel: Write,\n\t},\n\tIncome: {\n\t\tName:            \"income\",\n\t\tDisplayName:     \"Income\",\n\t\tDescription:     \"Analyze income patterns based on transaction history\",\n\t\tPermissionLevel: Write,\n\t},\n\tIncomeVerification: {\n\t\tName:            \"income_verification\",\n\t\tDisplayName:     \"Income Verification\",\n\t\tDescription:     \"Verify income through paystubs, payroll data, or bank information\",\n\t\tPermissionLevel: Write,\n\t},\n\tInvestments: {\n\t\tName:            \"investments\",\n\t\tDisplayName:     \"Investments\",\n\t\tDescription:     \"Retrieve holdings, balances, and historical investment transactions\",\n\t\tPermissionLevel: Read,\n\t},\n\tInvestmentsAuth: {\n\t\tName:            \"investments_auth\",\n\t\tDisplayName:     \"Investments Auth\",\n\t\tDescription:     \"Retrieve account and routing numbers for investment accounts\",\n\t\tPermissionLevel: Read,\n\t},\n\tLayer: {\n\t\tName:            \"layer\",\n\t\tDisplayName:     \"Layer\",\n\t\tDescription:     \"Use a simplified onboarding experience for linking financial accounts\",\n\t\tPermissionLevel: Read,\n\t},\n\tLiabilities: {\n\t\tName:            \"liabilities\",\n\t\tDisplayName:     \"Liabilities\",\n\t\tDescription:     \"Access detailed information about loans, credit cards, and other liabilities\",\n\t\tPermissionLevel: Write,\n\t},\n\tPayByBank: {\n\t\tName:            \"pay_by_bank\",\n\t\tDisplayName:     \"Pay By Bank\",\n\t\tDescription:     \"Initiate payments directly from the user's bank account\",\n\t\tPermissionLevel: Write,\n\t},\n\tPaymentInitiation: {\n\t\tName:            \"payment_initiation\",\n\t\tDisplayName:     \"Payment Initiation\",\n\t\tDescription:     \"Create and manage payment requests and track their status\",\n\t\tPermissionLevel: Write,\n\t},\n\tProcessorPayments: {\n\t\tName:            \"processor_payments\",\n\t\tDisplayName:     \"Processor Payments\",\n\t\tDescription:     \"Send payment details securely to third-party processors\",\n\t\tPermissionLevel: Write,\n\t},\n\tProcessorIdentity: {\n\t\tName:            \"processor_identity\",\n\t\tDisplayName:     \"Processor Identity\",\n\t\tDescription:     \"Share identity data with payment processors for verification\",\n\t\tPermissionLevel: Read,\n\t},\n\tProfile: {\n\t\tName:            \"profile\",\n\t\tDisplayName:     \"Profile\",\n\t\tDescription:     \"Access user profile data\",\n\t\tPermissionLevel: Read,\n\t},\n\tRecurringTransactions: {\n\t\tName:            \"recurring_transactions\",\n\t\tDisplayName:     \"Recurring Transactions\",\n\t\tDescription:     \"Identify and analyze recurring payments and subscriptions\",\n\t\tPermissionLevel: Write,\n\t},\n\tSignal: {\n\t\tName:            \"signal\",\n\t\tDisplayName:     \"Signal\",\n\t\tDescription:     \"Assess the likelihood of ACH returns\",\n\t\tPermissionLevel: Read,\n\t},\n\tStandingOrders: {\n\t\tName:            \"standing_orders\",\n\t\tDisplayName:     \"Standing Orders\",\n\t\tDescription:     \"View and manage recurring scheduled bank transfers\",\n\t\tPermissionLevel: Write,\n\t},\n\tStatements: {\n\t\tName:            \"statements\",\n\t\tDisplayName:     \"Statements\",\n\t\tDescription:     \"List and download historical bank statements in PDF format\",\n\t\tPermissionLevel: Read,\n\t},\n\tTransactions: {\n\t\tName:            \"transactions\",\n\t\tDisplayName:     \"Transactions\",\n\t\tDescription:     \"Retrieve, filter, and analyze categorized transaction history\",\n\t\tPermissionLevel: Read,\n\t},\n\tTransactionsRefresh: {\n\t\tName:            \"transactions_refresh\",\n\t\tDisplayName:     \"Transactions Refresh\",\n\t\tDescription:     \"Trigger a manual refresh to retrieve the latest transactions\",\n\t\tPermissionLevel: Read,\n\t},\n\tTransfer: {\n\t\tName:            \"transfer\",\n\t\tDisplayName:     \"Transfer\",\n\t\tDescription:     \"Initiate, manage, and track bank transfers\",\n\t\tPermissionLevel: Write,\n\t},\n}\n\nfunc GetProductByName(name string) (plaidProduct, bool) {\n\tfor _, product := range plaidProducts {\n\t\tif product.Name == name {\n\t\t\treturn product, true\n\t\t}\n\t}\n\treturn plaidProduct{}, false\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/planetscale/expected_output.json",
    "content": "{\"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}"
  },
  {
    "path": "pkg/analyzer/analyzers/planetscale/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage planetscale\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    ReadOrganization Permission = iota\n    ReadInvoices Permission = iota\n    ReadDatabases Permission = iota\n    ReadAuditLogs Permission = iota\n    CreateDatabases Permission = iota\n    DeleteDatabases Permission = iota\n    ReadOauthApplications Permission = iota\n    WriteOauthTokens Permission = iota\n    ReadOauthTokens Permission = iota\n    DeleteOauthTokens Permission = iota\n    ReadDatabase Permission = iota\n    WriteDatabase Permission = iota\n    DeleteDatabase Permission = iota\n    ReadBranch Permission = iota\n    CreateBranch Permission = iota\n    DeleteBranch Permission = iota\n    DeleteBranchPassword Permission = iota\n    DeleteProductionBranch Permission = iota\n    DeleteProductionBranchPassword Permission = iota\n    ReadDeployRequest Permission = iota\n    CreateDeployRequest Permission = iota\n    ApproveDeployRequest Permission = iota\n    ConnectBranch Permission = iota\n    ConnectProductionBranch Permission = iota\n    ReadComment Permission = iota\n    CreateComment Permission = iota\n    RestoreBackup Permission = iota\n    WriteBackups Permission = iota\n    ReadBackups Permission = iota\n    DeleteBackups Permission = iota\n    RestoreProductionBranchBackup Permission = iota\n    DeleteProductionBranchBackups Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        ReadOrganization: \"read_organization\",\n        ReadInvoices: \"read_invoices\",\n        ReadDatabases: \"read_databases\",\n        ReadAuditLogs: \"read_audit_logs\",\n        CreateDatabases: \"create_databases\",\n        DeleteDatabases: \"delete_databases\",\n        ReadOauthApplications: \"read_oauth_applications\",\n        WriteOauthTokens: \"write_oauth_tokens\",\n        ReadOauthTokens: \"read_oauth_tokens\",\n        DeleteOauthTokens: \"delete_oauth_tokens\",\n        ReadDatabase: \"read_database\",\n        WriteDatabase: \"write_database\",\n        DeleteDatabase: \"delete_database\",\n        ReadBranch: \"read_branch\",\n        CreateBranch: \"create_branch\",\n        DeleteBranch: \"delete_branch\",\n        DeleteBranchPassword: \"delete_branch_password\",\n        DeleteProductionBranch: \"delete_production_branch\",\n        DeleteProductionBranchPassword: \"delete_production_branch_password\",\n        ReadDeployRequest: \"read_deploy_request\",\n        CreateDeployRequest: \"create_deploy_request\",\n        ApproveDeployRequest: \"approve_deploy_request\",\n        ConnectBranch: \"connect_branch\",\n        ConnectProductionBranch: \"connect_production_branch\",\n        ReadComment: \"read_comment\",\n        CreateComment: \"create_comment\",\n        RestoreBackup: \"restore_backup\",\n        WriteBackups: \"write_backups\",\n        ReadBackups: \"read_backups\",\n        DeleteBackups: \"delete_backups\",\n        RestoreProductionBranchBackup: \"restore_production_branch_backup\",\n        DeleteProductionBranchBackups: \"delete_production_branch_backups\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"read_organization\": ReadOrganization,\n        \"read_invoices\": ReadInvoices,\n        \"read_databases\": ReadDatabases,\n        \"read_audit_logs\": ReadAuditLogs,\n        \"create_databases\": CreateDatabases,\n        \"delete_databases\": DeleteDatabases,\n        \"read_oauth_applications\": ReadOauthApplications,\n        \"write_oauth_tokens\": WriteOauthTokens,\n        \"read_oauth_tokens\": ReadOauthTokens,\n        \"delete_oauth_tokens\": DeleteOauthTokens,\n        \"read_database\": ReadDatabase,\n        \"write_database\": WriteDatabase,\n        \"delete_database\": DeleteDatabase,\n        \"read_branch\": ReadBranch,\n        \"create_branch\": CreateBranch,\n        \"delete_branch\": DeleteBranch,\n        \"delete_branch_password\": DeleteBranchPassword,\n        \"delete_production_branch\": DeleteProductionBranch,\n        \"delete_production_branch_password\": DeleteProductionBranchPassword,\n        \"read_deploy_request\": ReadDeployRequest,\n        \"create_deploy_request\": CreateDeployRequest,\n        \"approve_deploy_request\": ApproveDeployRequest,\n        \"connect_branch\": ConnectBranch,\n        \"connect_production_branch\": ConnectProductionBranch,\n        \"read_comment\": ReadComment,\n        \"create_comment\": CreateComment,\n        \"restore_backup\": RestoreBackup,\n        \"write_backups\": WriteBackups,\n        \"read_backups\": ReadBackups,\n        \"delete_backups\": DeleteBackups,\n        \"restore_production_branch_backup\": RestoreProductionBranchBackup,\n        \"delete_production_branch_backups\": DeleteProductionBranchBackups,\n    }\n\n    PermissionIDs = map[Permission]int{\n        ReadOrganization: 1,\n        ReadInvoices: 2,\n        ReadDatabases: 3,\n        ReadAuditLogs: 4,\n        CreateDatabases: 5,\n        DeleteDatabases: 6,\n        ReadOauthApplications: 7,\n        WriteOauthTokens: 8,\n        ReadOauthTokens: 9,\n        DeleteOauthTokens: 10,\n        ReadDatabase: 11,\n        WriteDatabase: 12,\n        DeleteDatabase: 13,\n        ReadBranch: 14,\n        CreateBranch: 15,\n        DeleteBranch: 16,\n        DeleteBranchPassword: 17,\n        DeleteProductionBranch: 18,\n        DeleteProductionBranchPassword: 19,\n        ReadDeployRequest: 20,\n        CreateDeployRequest: 21,\n        ApproveDeployRequest: 22,\n        ConnectBranch: 23,\n        ConnectProductionBranch: 24,\n        ReadComment: 25,\n        CreateComment: 26,\n        RestoreBackup: 27,\n        WriteBackups: 28,\n        ReadBackups: 29,\n        DeleteBackups: 30,\n        RestoreProductionBranchBackup: 31,\n        DeleteProductionBranchBackups: 32,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: ReadOrganization,\n        2: ReadInvoices,\n        3: ReadDatabases,\n        4: ReadAuditLogs,\n        5: CreateDatabases,\n        6: DeleteDatabases,\n        7: ReadOauthApplications,\n        8: WriteOauthTokens,\n        9: ReadOauthTokens,\n        10: DeleteOauthTokens,\n        11: ReadDatabase,\n        12: WriteDatabase,\n        13: DeleteDatabase,\n        14: ReadBranch,\n        15: CreateBranch,\n        16: DeleteBranch,\n        17: DeleteBranchPassword,\n        18: DeleteProductionBranch,\n        19: DeleteProductionBranchPassword,\n        20: ReadDeployRequest,\n        21: CreateDeployRequest,\n        22: ApproveDeployRequest,\n        23: ConnectBranch,\n        24: ConnectProductionBranch,\n        25: ReadComment,\n        26: CreateComment,\n        27: RestoreBackup,\n        28: WriteBackups,\n        29: ReadBackups,\n        30: DeleteBackups,\n        31: RestoreProductionBranchBackup,\n        32: DeleteProductionBranchBackups,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/planetscale/permissions.yaml",
    "content": "permissions:\n - read_organization\n - read_invoices\n - read_databases\n - read_audit_logs\n - create_databases\n - delete_databases\n - read_oauth_applications\n - write_oauth_tokens\n - read_oauth_tokens\n - delete_oauth_tokens\n - read_database\n - write_database\n - delete_database\n - read_branch\n - create_branch\n - delete_branch\n - delete_branch_password\n - delete_production_branch\n - delete_production_branch_password\n - read_deploy_request\n - create_deploy_request\n - approve_deploy_request\n - connect_branch\n - connect_production_branch\n - read_comment\n - create_comment\n - restore_backup\n - write_backups\n - read_backups\n - delete_backups\n - restore_production_branch_backup\n - delete_production_branch_backups"
  },
  {
    "path": "pkg/analyzer/analyzers/planetscale/planetscale.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go planetscale\n\npackage planetscale\n\nimport (\n\t\"bytes\"\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypePlanetScale }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tid, ok := credInfo[\"id\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"missing id in credInfo\")\n\t}\n\tkey, ok := credInfo[\"token\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"missing key in credInfo\")\n\t}\n\tinfo, err := AnalyzePermissions(a.Cfg, id, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypePlanetScale,\n\t\tMetadata:     nil,\n\t\tBindings:     make([]analyzers.Binding, 0),\n\t}\n\n\tresource := analyzers.Resource{\n\t\tName:               info.Organization.Name,\n\t\tFullyQualifiedName: \"planetscale.com/organization/\" + info.Organization.Id,\n\t\tType:               \"Organization\",\n\t}\n\n\tfor _, permission := range info.OrgPermissions {\n\t\tresult.Bindings = append(result.Bindings, analyzers.Binding{\n\t\t\tResource: resource,\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: permission,\n\t\t\t},\n\t\t})\n\t}\n\n\tfor db, permissions := range info.DBPermissions {\n\t\tdbResource := analyzers.Resource{\n\t\t\tName:               db.Name,\n\t\t\tFullyQualifiedName: \"planetscale.com/database/\" + db.Id,\n\t\t\tType:               \"Database\",\n\t\t\tParent:             &resource,\n\t\t}\n\t\tfor _, permission := range permissions {\n\t\t\tresult.Bindings = append(result.Bindings, analyzers.Binding{\n\t\t\t\tResource: dbResource,\n\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\tValue: permission,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t}\n\treturn &result\n}\n\n//go:embed scopes.json\nvar scopesConfig []byte\n\ntype HttpStatusTest struct {\n\tEndpoint        string      `json:\"endpoint\"`\n\tMethod          string      `json:\"method\"`\n\tPayload         interface{} `json:\"payload\"`\n\tValidStatuses   []int       `json:\"valid_status_code\"`\n\tInvalidStatuses []int       `json:\"invalid_status_code\"`\n}\n\nfunc StatusContains(status int, vals []int) bool {\n\tfor _, v := range vals {\n\t\tif status == v {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (h *HttpStatusTest) RunTest(cfg *config.Config, headers map[string]string, args ...any) (bool, error) {\n\t// If body data, marshal to JSON\n\tvar data io.Reader\n\tif h.Payload != nil {\n\t\tjsonData, err := json.Marshal(h.Payload)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tdata = bytes.NewBuffer(jsonData)\n\t}\n\n\t// Create new HTTP request\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(h.Method, fmt.Sprintf(h.Endpoint, args...), data)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Add custom headers if provided\n\tfor key, value := range headers {\n\t\treq.Header.Set(key, value)\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\t// Execute HTTP Request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer resp.Body.Close()\n\n\t// Check response status code\n\tswitch {\n\tcase StatusContains(resp.StatusCode, h.ValidStatuses):\n\t\treturn true, nil\n\tcase StatusContains(resp.StatusCode, h.InvalidStatuses):\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, errors.New(\"error checking response status code\")\n\t}\n}\n\ntype Scopes struct {\n\tOrganizationScopes     []Scope       `json:\"organization_scopes\"`\n\tOAuthApplicationScopes []Scope       `json:\"oauth_application_scopes\"`\n\tDatabaseScopes         []Scope       `json:\"database_scopes\"`\n\tDeployRequestScopes    []Scope       `json:\"deploy_request_scopes\"`\n\tBranchScopes           []BranchScope `json:\"branch_scopes\"`\n\tBackupScopes           []BranchScope `json:\"backup_scopes\"`\n}\n\ntype Scope struct {\n\tName     string         `json:\"name\"`\n\tHttpTest HttpStatusTest `json:\"test\"`\n}\n\ntype BranchScope struct {\n\tScope\n\tProduction bool `json:\"production\"`\n}\n\nfunc readInScopes() (*Scopes, error) {\n\tvar scopes Scopes\n\tif err := json.Unmarshal(scopesConfig, &scopes); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &scopes, nil\n}\n\nfunc checkPermissions(cfg *config.Config, scopes []Scope, id, key string, args ...any) ([]string, error) {\n\n\tpermissions := make([]string, 0)\n\tfor _, scope := range scopes {\n\t\tstatus, err := scope.HttpTest.RunTest(cfg, map[string]string{\"Authorization\": fmt.Sprintf(\"%s:%s\", id, key)}, args...)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"running test: %w\", err)\n\t\t}\n\t\tif status {\n\t\t\tpermissions = append(permissions, scope.Name)\n\t\t}\n\t}\n\n\treturn permissions, nil\n}\n\nfunc checkBranchPermissions(cfg *config.Config, scopes []BranchScope, id, key, organization, db, branch string, production bool) ([]string, error) {\n\tpermissions := make([]string, 0)\n\tfor _, scope := range scopes {\n\t\t// check if scope is for production or non production branch\n\t\tif production != scope.Production {\n\t\t\tcontinue\n\t\t}\n\t\tstatus, err := scope.HttpTest.RunTest(cfg, map[string]string{\"Authorization\": fmt.Sprintf(\"%s:%s\", id, key)}, organization, db, branch)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"running test: %w\", err)\n\t\t}\n\t\tif status {\n\t\t\tpermissions = append(permissions, scope.Name)\n\t\t}\n\t}\n\n\treturn permissions, nil\n}\n\nfunc checkBackupPermissions(cfg *config.Config, scopes []BranchScope, id, key, organization, db, backupId string, production bool) ([]string, error) {\n\tpermissions := make([]string, 0)\n\tfor _, scope := range scopes {\n\t\t// check if scope is for production or non production branch\n\t\tif production != scope.Production {\n\t\t\tcontinue\n\t\t}\n\t\tscope.HttpTest.Payload = map[string]string{\"backup_id\": backupId}\n\t\tstatus, err := scope.HttpTest.RunTest(cfg, map[string]string{\"Authorization\": fmt.Sprintf(\"%s:%s\", id, key)}, organization, db)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"running test: %w\", err)\n\t\t}\n\t\tif status {\n\t\t\tpermissions = append(permissions, scope.Name)\n\t\t}\n\t}\n\n\treturn permissions, nil\n}\n\ntype SecretInfo struct {\n\tOrganization          organization\n\tOrgPermissions        []string\n\tDBPermissions         map[Database][]string\n\tUnverifiedPermissions []string\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, id, token string) {\n\tinfo, err := AnalyzePermissions(cfg, id, token)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid PlanetScale credentials\\n\\n\")\n\tcolor.Green(\"[i] Organization: %s\\n\\n\", info.Organization.Name)\n\tprintOrganizationPermissions(info.OrgPermissions)\n\n\tif len(info.DBPermissions) > 0 {\n\t\tprintDatabasePermissions(info.DBPermissions)\n\t}\n\n\tprintUnverifiedPermissions(info.UnverifiedPermissions)\n}\n\nfunc AnalyzePermissions(cfg *config.Config, id, token string) (*SecretInfo, error) {\n\tvar info = &SecretInfo{}\n\n\torg, err := getOrganization(cfg, id, token)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinfo.Organization = *org\n\n\tscopes, err := readInScopes()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"reading in scopes: %w\", err)\n\t}\n\n\t// organization permissions\n\torgPermissions, err := getOrganizationPermissions(cfg, scopes, id, token, org.Name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinfo.OrgPermissions = orgPermissions\n\n\t// database permissions\n\tdbPermissions, err := getDatabasePermissions(cfg, scopes, id, token, org.Name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinfo.DBPermissions = dbPermissions\n\n\t// These are permissions that can not be verified,\n\t// either due to no endpoint available that specifically requires the permission\n\t// or there does not exist a way to verify these permissions without changing the state of the system (mostly DELETE permissions)\n\tinfo.UnverifiedPermissions = []string{\n\t\tPermissionStrings[ReadComment],\n\t\tPermissionStrings[CreateComment],\n\t\tPermissionStrings[ApproveDeployRequest],\n\t\tPermissionStrings[DeleteDatabases],\n\t\tPermissionStrings[DeleteDatabase],\n\t\tPermissionStrings[DeleteOauthTokens],\n\t\tPermissionStrings[DeleteBranch],\n\t\tPermissionStrings[DeleteBranchPassword],\n\t\tPermissionStrings[DeleteProductionBranch],\n\t\tPermissionStrings[DeleteProductionBranchPassword],\n\t\tPermissionStrings[DeleteBackups],\n\t\tPermissionStrings[DeleteProductionBranchBackups],\n\t\tPermissionStrings[WriteBackups],\n\t}\n\n\treturn info, nil\n}\n\ntype organization struct {\n\tId   string `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\ntype organizationJSON struct {\n\tData []organization `json:\"data\"`\n}\n\nfunc getOrganization(cfg *config.Config, id, key string) (*organization, error) {\n\turl := \"https://api.planetscale.com/v1/organizations\"\n\n\tvar organizationJSON organizationJSON\n\terr := sendGetRequest(cfg, id, key, url, &organizationJSON)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(organizationJSON.Data) == 0 {\n\t\treturn nil, errors.New(\"invalid api credentials\")\n\t}\n\n\treturn &organizationJSON.Data[0], nil\n}\n\nfunc getOrganizationPermissions(cfg *config.Config, scopes *Scopes, id, token, orgName string) ([]string, error) {\n\torganizationPermissions, err := checkPermissions(cfg, scopes.OrganizationScopes, id, token, orgName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\toauthPermissions, err := getOAuthApplicationPermissions(cfg, scopes.OAuthApplicationScopes, id, token, orgName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\torganizationPermissions = append(organizationPermissions, oauthPermissions...)\n\n\treturn organizationPermissions, nil\n}\n\nfunc getOAuthApplicationPermissions(cfg *config.Config, scopes []Scope, id, key, organization string) ([]string, error) {\n\toauthApplicationId, err := getOAuthApplicationId(cfg, id, key, organization)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif oauthApplicationId != \"\" {\n\t\toauthPermissions, err := checkPermissions(cfg, scopes, id, key, organization, oauthApplicationId)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn oauthPermissions, nil\n\t}\n\treturn nil, nil\n}\n\ntype oauthApplicationJSON struct {\n\tData []struct {\n\t\tId string `json:\"id\"`\n\t}\n}\n\nfunc getOAuthApplicationId(cfg *config.Config, id, key, organization string) (string, error) {\n\turl := fmt.Sprintf(\"https://api.planetscale.com/v1/organizations/%s/oauth-applications\", organization)\n\n\tvar oauthApplicationJSON oauthApplicationJSON\n\terr := sendGetRequest(cfg, id, key, url, &oauthApplicationJSON)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif len(oauthApplicationJSON.Data) > 0 {\n\t\treturn oauthApplicationJSON.Data[0].Id, nil\n\t}\n\treturn \"\", nil // no oauth application found\n}\n\nfunc getDatabasePermissions(cfg *config.Config, scopes *Scopes, id, token, orgName string) (map[Database][]string, error) {\n\tdatabases, err := getDatabases(cfg, id, token, orgName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdbPermissionsMap := make(map[Database][]string)\n\tfor _, database := range databases {\n\t\tdbPermissions, err := checkPermissions(cfg, scopes.DatabaseScopes, id, token, orgName, database.Name)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdbPermissionsMap[database] = dbPermissions\n\n\t\tbranchPermissions, err := getBranchPermissions(cfg, scopes, id, token, orgName, database.Name)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdbPermissionsMap[database] = append(dbPermissionsMap[database], branchPermissions...)\n\t}\n\n\treturn dbPermissionsMap, nil\n}\n\nfunc getBranchPermissions(cfg *config.Config, scopes *Scopes, id, token, orgName, dbName string) ([]string, error) {\n\tbranches, err := getDbBranches(cfg, id, token, orgName, dbName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// get permissions for prod and non prod branches\n\tprodDone, nonProdDone := false, false\n\tallBranchPermissions := make([]string, 0)\n\tfor _, branch := range branches {\n\t\t// check if we have already checked permissions for prod or non prod branches\n\t\tif (prodDone && branch.Production) || (nonProdDone && !branch.Production) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif branch.Production {\n\t\t\tprodDone = true\n\t\t} else {\n\t\t\tnonProdDone = true\n\t\t}\n\n\t\tbranchPermissions, err := checkBranchPermissions(cfg, scopes.BranchScopes, id, token, orgName, dbName, branch.Name, branch.Production)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tallBranchPermissions = append(allBranchPermissions, branchPermissions...)\n\n\t\tbackupId, err := getBackupId(cfg, id, token, orgName, dbName, branch.Name)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif backupId != \"\" {\n\t\t\tbackupPermissions, err := checkBackupPermissions(cfg, scopes.BackupScopes, id, token, orgName, dbName, backupId, branch.Production)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tallBranchPermissions = append(allBranchPermissions, backupPermissions...)\n\t\t}\n\n\t\tif prodDone && nonProdDone {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn allBranchPermissions, err\n}\n\ntype Database struct {\n\tId   string `json:\"id\"`\n\tName string `json:\"name\"`\n}\ntype databasesJSON struct {\n\tData        []Database `json:\"data\"`\n\tNextPageUrl string     `json:\"next_page_url\"`\n}\n\nfunc getDatabases(cfg *config.Config, id, key, organization string) ([]Database, error) {\n\turl := fmt.Sprintf(\"https://api.planetscale.com/v1/organizations/%s/databases\", organization)\n\tdatabases := make([]Database, 0)\n\n\t// loop for pagination\n\tfor url != \"\" {\n\t\tvar databasesResponse databasesJSON\n\t\terr := sendGetRequest(cfg, id, key, url, &databasesResponse)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tdatabases = append(databases, databasesResponse.Data...)\n\t\turl = databasesResponse.NextPageUrl\n\t}\n\n\treturn databases, nil\n}\n\ntype Branch struct {\n\tId         string `json:\"id\"`\n\tName       string `json:\"name\"`\n\tProduction bool   `json:\"production\"`\n}\n\ntype branchesJSON struct {\n\tData []Branch `json:\"data\"`\n}\n\nfunc getDbBranches(cfg *config.Config, id, key, organization, db string) ([]Branch, error) {\n\turl := fmt.Sprintf(\"https://api.planetscale.com/v1/organizations/%s/databases/%s/branches\", organization, db)\n\tvar branchesResponse branchesJSON\n\terr := sendGetRequest(cfg, id, key, url, &branchesResponse)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn branchesResponse.Data, nil\n}\n\ntype backupsJson struct {\n\tData []struct {\n\t\tId string `json:\"id\"`\n\t}\n}\n\nfunc getBackupId(cfg *config.Config, id, key, organization, db, branch string) (string, error) {\n\turl := fmt.Sprintf(\"https://api.planetscale.com/v1/organizations/%s/databases/%s/branches/%s/backups\", organization, db, branch)\n\tvar backupsResponse backupsJson\n\terr := sendGetRequest(cfg, id, key, url, &backupsResponse)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif len(backupsResponse.Data) > 0 {\n\t\treturn backupsResponse.Data[0].Id, nil\n\t}\n\treturn \"\", nil // no backups found\n}\n\nfunc sendGetRequest(cfg *config.Config, id, key, url string, responseObj interface{}) error {\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"%s:%s\", id, key))\n\n\t// Execute HTTP Request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\n\t// Check response status code\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\t// Decode response body\n\t\terr = json.NewDecoder(resp.Body).Decode(&responseObj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil // response successfully decoded\n\tcase http.StatusForbidden:\n\t\treturn nil // no permission\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected status code %d\", resp.StatusCode)\n\t}\n}\n\nfunc printOrganizationPermissions(permissions []string) {\n\tcolor.Yellow(\"[i] Organization Permissions:\")\n\n\tif len(permissions) == 0 {\n\t\tcolor.Yellow(\"No permissions found\")\n\t} else {\n\t\tt := table.NewWriter()\n\t\tt.SetOutputMirror(os.Stdout)\n\t\tt.AppendHeader(table.Row{\"Permission\"})\n\t\tfor _, permission := range permissions {\n\t\t\tt.AppendRow(table.Row{color.GreenString(permission)})\n\t\t}\n\t\tt.Render()\n\t}\n}\n\nfunc printDatabasePermissions(permissions map[Database][]string) {\n\tcolor.Yellow(\"[i] Database Permissions:\")\n\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Database\", \"Permission\"})\n\tfor database, dbPermissions := range permissions {\n\t\tt.AppendRow(table.Row{database.Name, color.GreenString(strings.Join(dbPermissions, \", \"))})\n\t}\n\tt.Render()\n}\n\nfunc printUnverifiedPermissions(permissions []string) {\n\tcolor.Yellow(\"[i] Unverified Permissions:\")\n\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Permission\"})\n\tfor _, permission := range permissions {\n\t\tt.AppendRow(table.Row{color.YellowString(permission)})\n\t}\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/planetscale/planetscale_test.go",
    "content": "package planetscale\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*15)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tid      string\n\t\ttoken   string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid planetscale id and key\",\n\t\t\tid:      testSecrets.MustGetField(\"PLANET_SCALE_ID\"),\n\t\t\ttoken:   testSecrets.MustGetField(\"PLANET_SCALE_TOKEN\"),\n\t\t\twant:    string(expectedOutput),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"id\": tt.id, \"token\": tt.token})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/planetscale/scopes.json",
    "content": "{\n    \"organization_scopes\": [\n        {\n            \"name\": \"read_organization\",\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s\",\n                \"method\": \"GET\",\n                \"valid_status_code\": [200],\n                \"invalid_status_code\": [403]\n            }\n        },\n        {\n            \"name\": \"read_invoices\",\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/invoices\",\n                \"method\": \"GET\",\n                \"valid_status_code\": [200],\n                \"invalid_status_code\": [403]\n            }\n        },\n        {\n            \"name\": \"read_databases\",\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/databases\",\n                \"method\": \"GET\",\n                \"valid_status_code\": [200],\n                \"invalid_status_code\": [403]\n            }\n        },\n        {\n            \"name\": \"read_audit_logs\",\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/audit-log\",\n                \"method\": \"GET\",\n                \"valid_status_code\": [200],\n                \"invalid_status_code\": [403]\n            }\n        },\n        {\n            \"name\": \"create_databases\",\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/databases\",\n                \"method\": \"POST\",\n                \"valid_status_code\": [422],\n                \"invalid_status_code\": [403]\n            }\n        },\n        {\n            \"name\": \"read_oauth_applications\",\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/oauth-applications\",\n                \"method\": \"GET\",\n                \"valid_status_code\": [200],\n                \"invalid_status_code\": [403]\n            }\n        }\n    ],\n    \"oauth_application_scopes\": [\n        {\n            \"name\": \"write_oauth_tokens\",\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/oauth-applications/%s/token\",\n                \"method\": \"POST\",\n                \"valid_status_code\": [422],\n                \"invalid_status_code\": [403]\n            }\n        },\n        {\n            \"name\": \"read_oauth_tokens\",\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/oauth-applications/%s/tokens\",\n                \"method\": \"GET\",\n                \"valid_status_code\": [200],\n                \"invalid_status_code\": [403]\n            }\n        }\n    ],\n    \"database_scopes\": [\n        {\n            \"name\": \"read_database\",\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/databases/%s\",\n                \"method\": \"GET\",\n                \"valid_status_code\": [200],\n                \"invalid_status_code\": [403]\n            }\n        },\n        {\n            \"name\": \"write_database\",\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/databases/%s\",\n                \"method\": \"PATCH\",\n                \"valid_status_code\": [400],\n                \"invalid_status_code\": [403],\n                \"payload\": {\n                    \"default_branch\": \"`nowaythisbranchcanexist\"\n                }\n            }\n        },\n        {\n            \"name\": \"read_branch\",\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/databases/%s/branches\",\n                \"method\": \"GET\",\n                \"valid_status_code\": [200],\n                \"invalid_status_code\": [403]\n            }\n        },\n        {\n            \"name\": \"create_branch\",\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/databases/%s/branches\",\n                \"method\": \"POST\",\n                \"valid_status_code\": [422],\n                \"invalid_status_code\": [403]\n            }\n        },\n        {\n            \"name\": \"read_deploy_request\",\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/databases/%s/deploy-requests\",\n                \"method\": \"GET\",\n                \"valid_status_code\": [200],\n                \"invalid_status_code\": [403]\n            }\n        },\n        {\n            \"name\": \"create_deploy_request\",\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/databases/%s/deploy-requests\",\n                \"method\": \"POST\",\n                \"valid_status_code\": [422],\n                \"invalid_status_code\": [403]\n            }\n        }\n    ],\n    \"branch_scopes\": [\n        {\n            \"name\": \"connect_branch\",\n            \"production\": false,\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/databases/%s/branches/%s/passwords\",\n                \"method\": \"POST\",\n                \"valid_status_code\": [422],\n                \"invalid_status_code\": [403],\n                \"payload\": {\n                    \"role\": \"`nowaythisrolecanexist\"\n                  }\n            }\n        },\n        {\n            \"name\": \"connect_production_branch\",\n            \"production\": true,\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/databases/%s/branches/%s/passwords\",\n                \"method\": \"POST\",\n                \"valid_status_code\": [422],\n                \"invalid_status_code\": [403],\n                \"payload\": {\n                    \"role\": \"`nowaythisrolecanexist\"\n                  }\n            }\n        },\n        {\n            \"name\": \"read_backups\",\n            \"production\": true,\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/databases/%s/branches/%s/backups\",\n                \"method\": \"GET\",\n                \"valid_status_code\": [200],\n                \"invalid_status_code\": [403]\n            }\n        }\n    ],\n    \"backup_scopes\": [\n        {\n            \"name\": \"restore_backup\",\n            \"production\": false,\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/databases/%s/branches\",\n                \"method\": \"POST\",\n                \"valid_status_code\": [422],\n                \"invalid_status_code\": [403],\n                \"payload\": {\n                    \"backup_id\": \"%s\"\n                  }\n            }\n        },\n        {\n            \"name\": \"restore_production_branch_backup\",\n            \"production\": true,\n            \"test\": {\n                \"endpoint\": \"https://api.planetscale.com/v1/organizations/%s/databases/%s/branches\",\n                \"method\": \"POST\",\n                \"valid_status_code\": [422],\n                \"invalid_status_code\": [403],\n                \"payload\": {\n                    \"backup_id\": \"%s\"\n                  }\n            }\n        }\n    ]\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/postgres/expected_output.json",
    "content": "{\"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}"
  },
  {
    "path": "pkg/analyzer/analyzers/postgres/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage postgres\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    BypassRls Permission = iota\n    Connect Permission = iota\n    Create Permission = iota\n    CreateDb Permission = iota\n    CreateRole Permission = iota\n    Delete Permission = iota\n    InheritanceOfPrivs Permission = iota\n    Insert Permission = iota\n    Login Permission = iota\n    References Permission = iota\n    Replication Permission = iota\n    Select Permission = iota\n    Superuser Permission = iota\n    Temp Permission = iota\n    Trigger Permission = iota\n    Truncate Permission = iota\n    Update Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        BypassRls: \"bypass_rls\",\n        Connect: \"connect\",\n        Create: \"create\",\n        CreateDb: \"create_db\",\n        CreateRole: \"create_role\",\n        Delete: \"delete\",\n        InheritanceOfPrivs: \"inheritance_of_privs\",\n        Insert: \"insert\",\n        Login: \"login\",\n        References: \"references\",\n        Replication: \"replication\",\n        Select: \"select\",\n        Superuser: \"superuser\",\n        Temp: \"temp\",\n        Trigger: \"trigger\",\n        Truncate: \"truncate\",\n        Update: \"update\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"bypass_rls\": BypassRls,\n        \"connect\": Connect,\n        \"create\": Create,\n        \"create_db\": CreateDb,\n        \"create_role\": CreateRole,\n        \"delete\": Delete,\n        \"inheritance_of_privs\": InheritanceOfPrivs,\n        \"insert\": Insert,\n        \"login\": Login,\n        \"references\": References,\n        \"replication\": Replication,\n        \"select\": Select,\n        \"superuser\": Superuser,\n        \"temp\": Temp,\n        \"trigger\": Trigger,\n        \"truncate\": Truncate,\n        \"update\": Update,\n    }\n\n    PermissionIDs = map[Permission]int{\n        BypassRls: 1,\n        Connect: 2,\n        Create: 3,\n        CreateDb: 4,\n        CreateRole: 5,\n        Delete: 6,\n        InheritanceOfPrivs: 7,\n        Insert: 8,\n        Login: 9,\n        References: 10,\n        Replication: 11,\n        Select: 12,\n        Superuser: 13,\n        Temp: 14,\n        Trigger: 15,\n        Truncate: 16,\n        Update: 17,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: BypassRls,\n        2: Connect,\n        3: Create,\n        4: CreateDb,\n        5: CreateRole,\n        6: Delete,\n        7: InheritanceOfPrivs,\n        8: Insert,\n        9: Login,\n        10: References,\n        11: Replication,\n        12: Select,\n        13: Superuser,\n        14: Temp,\n        15: Trigger,\n        16: Truncate,\n        17: Update,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/postgres/permissions.yaml",
    "content": "permissions:\n  - bypass_rls\n  - connect\n  - create\n  - create_db\n  - create_role\n  - delete\n  - inheritance_of_privs\n  - insert\n  - login\n  - references\n  - replication\n  - select\n  - superuser\n  - temp\n  - trigger\n  - truncate\n  - update\n"
  },
  {
    "path": "pkg/analyzer/analyzers/postgres/postgres.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go postgres\n\npackage postgres\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/lib/pq\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypePostgres }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\turi, ok := credInfo[\"connection_string\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"connection string not found in credInfo\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, uri)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypePostgres,\n\t\tMetadata:     nil,\n\t\tBindings:     []analyzers.Binding{},\n\t}\n\n\t// set user related bindings in result\n\tuserResource, userBindings := bakeUserBindings(info)\n\tresult.Bindings = append(result.Bindings, userBindings...)\n\n\t// add user's database privileges to bindings\n\tdbNameToResourceMap, dbBindings := bakeDatabaseBindings(userResource, info)\n\tresult.Bindings = append(result.Bindings, dbBindings...)\n\n\t// add user's table privileges to bindings\n\ttableBindings := bakeTableBindings(dbNameToResourceMap, info)\n\tresult.Bindings = append(result.Bindings, tableBindings...)\n\n\treturn &result\n}\n\nfunc bakeUserBindings(info *SecretInfo) (analyzers.Resource, []analyzers.Binding) {\n\tuserResource := analyzers.Resource{\n\t\tName:               info.User,\n\t\tFullyQualifiedName: info.Host + \"/\" + info.User,\n\t\tType:               \"user\",\n\t\tMetadata: map[string]any{\n\t\t\t\"role\": info.Role,\n\t\t},\n\t}\n\n\tvar bindings []analyzers.Binding\n\n\tfor rolePriv, exists := range info.RolePrivs {\n\t\tif exists {\n\t\t\tbindings = append(bindings, analyzers.Binding{\n\t\t\t\tResource: userResource,\n\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\tValue: rolePriv,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t}\n\n\treturn userResource, bindings\n}\n\nfunc bakeDatabaseBindings(userResource analyzers.Resource, info *SecretInfo) (map[string]*analyzers.Resource, []analyzers.Binding) {\n\tdbNameToResourceMap := map[string]*analyzers.Resource{}\n\tdbBindings := []analyzers.Binding{}\n\n\tfor _, db := range info.DBs {\n\t\tdbResource := analyzers.Resource{\n\t\t\tName:               db.DatabaseName,\n\t\t\tFullyQualifiedName: info.Host + \"/\" + db.DatabaseName,\n\t\t\tType:               \"database\",\n\t\t\tMetadata: map[string]any{\n\t\t\t\t\"owner\": db.Owner,\n\t\t\t},\n\t\t\tParent: &userResource,\n\t\t}\n\n\t\t// populate map to reference later for tables\n\t\tdbNameToResourceMap[db.DatabaseName] = &dbResource\n\n\t\tdbPriviliges := map[string]bool{\n\t\t\t\"connect\": db.Connect,\n\t\t\t\"create\":  db.Create,\n\t\t\t\"temp\":    db.CreateTemp,\n\t\t}\n\n\t\tfor priv, exists := range dbPriviliges {\n\t\t\tif exists {\n\t\t\t\tdbBindings = append(dbBindings, analyzers.Binding{\n\t\t\t\t\tResource: dbResource,\n\t\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\t\tValue: priv,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\treturn dbNameToResourceMap, dbBindings\n}\n\nfunc bakeTableBindings(dbNameToResourceMap map[string]*analyzers.Resource, info *SecretInfo) []analyzers.Binding {\n\tvar tableBindings []analyzers.Binding\n\n\tfor dbName, tableMap := range info.TablePrivs {\n\t\tdbResource, ok := dbNameToResourceMap[dbName]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor tableName, tableData := range tableMap {\n\t\t\ttableResource := analyzers.Resource{\n\t\t\t\tName:               tableName,\n\t\t\t\tFullyQualifiedName: info.Host + \"/\" + dbResource.Name + \"/\" + tableName,\n\t\t\t\tType:               \"table\",\n\t\t\t\tMetadata: map[string]any{\n\t\t\t\t\t\"size\": tableData.Size,\n\t\t\t\t\t\"rows\": tableData.Rows,\n\t\t\t\t},\n\t\t\t\tParent: dbResource,\n\t\t\t}\n\n\t\t\ttablePrivsMap := map[string]bool{\n\t\t\t\t\"select\":     tableData.Privs.Select,\n\t\t\t\t\"insert\":     tableData.Privs.Insert,\n\t\t\t\t\"update\":     tableData.Privs.Update,\n\t\t\t\t\"delete\":     tableData.Privs.Delete,\n\t\t\t\t\"truncate\":   tableData.Privs.Truncate,\n\t\t\t\t\"references\": tableData.Privs.References,\n\t\t\t\t\"trigger\":    tableData.Privs.Trigger,\n\t\t\t}\n\n\t\t\tfor priv, exists := range tablePrivsMap {\n\t\t\t\tif exists {\n\t\t\t\t\ttableBindings = append(tableBindings, analyzers.Binding{\n\t\t\t\t\t\tResource: tableResource,\n\t\t\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\t\t\tValue: priv,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn tableBindings\n}\n\ntype DBPrivs struct {\n\tConnect    bool\n\tCreate     bool\n\tCreateTemp bool\n}\n\ntype DB struct {\n\tDatabaseName string\n\tOwner        string\n\tDBPrivs\n}\n\ntype TablePrivs struct {\n\tSelect     bool\n\tInsert     bool\n\tUpdate     bool\n\tDelete     bool\n\tTruncate   bool\n\tReferences bool\n\tTrigger    bool\n}\n\ntype TableData struct {\n\tSize  string\n\tRows  string\n\tPrivs TablePrivs\n}\n\nconst (\n\tpg_connect_timeout = \"connect_timeout\"\n\tpg_dbname          = \"dbname\"\n\tpg_host            = \"host\"\n\tpg_password        = \"password\"\n\tpg_port            = \"port\"\n\tpg_requiressl      = \"requiressl\"\n\tpg_sslmode         = \"sslmode\"\n\tpg_sslmode_allow   = \"allow\"\n\tpg_sslmode_disable = \"disable\"\n\tpg_sslmode_prefer  = \"prefer\"\n\tpg_sslmode_require = \"require\"\n\tpg_user            = \"user\"\n)\n\nvar connStrPartPattern = regexp.MustCompile(`([[:alpha:]]+)='(.+?)' ?`)\n\ntype SecretInfo struct {\n\tHost       string\n\tUser       string\n\tRole       string\n\tRolePrivs  map[string]bool\n\tDBs        []DB\n\tTablePrivs map[string]map[string]*TableData\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, connectionStr string) {\n\n\t// ToDo: Add in logging\n\tif cfg.LoggingEnabled {\n\t\tcolor.Red(\"[x] Logging is not supported for this analyzer.\")\n\t\treturn\n\t}\n\n\tinfo, err := AnalyzePermissions(cfg, connectionStr)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error: %s\", err.Error())\n\t\treturn\n\t}\n\n\tcolor.Yellow(\"[!] Successfully connected to Postgres database.\")\n\tprintUserRoleAndPriv(info.Role, info.RolePrivs)\n\n\t// Print db privs\n\tif len(info.DBs) > 0 {\n\t\tfmt.Print(\"\\n\\n\")\n\t\tcolor.Green(\"[i] User has the following database privileges:\")\n\t\tprintDBPrivs(info.DBs, info.User)\n\t}\n\n\t// Print table privs\n\tif len(info.TablePrivs) > 0 {\n\t\tfmt.Print(\"\\n\\n\")\n\t\tcolor.Green(\"[i] User has the following table privileges:\")\n\t\tprintTablePrivs(info.TablePrivs)\n\t}\n}\n\nfunc AnalyzePermissions(cfg *config.Config, connectionStr string) (*SecretInfo, error) {\n\n\tconnStr, err := pq.ParseURL(string(connectionStr))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse Postgres connection string: %w\", err)\n\t}\n\tparts := connStrPartPattern.FindAllStringSubmatch(connStr, -1)\n\tparams := make(map[string]string, len(parts))\n\tfor _, part := range parts {\n\t\tparams[part[1]] = part[2]\n\t}\n\tdb, err := createConnection(params, \"\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to connect to Postgres database: %w\", err)\n\t}\n\tdefer db.Close()\n\n\trole, privs, err := getUserPrivs(db)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to retrieve user privileges: %w\", err)\n\t}\n\tcurrentUser, dbs, err := getDBPrivs(db)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to retrieve database privileges: %w\", err)\n\t}\n\ttablePrivs, err := getTablePrivs(params, buildSliceDBNames(dbs))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to retrieve table privileges: %w\", err)\n\t}\n\n\treturn &SecretInfo{\n\t\tHost:       params[pg_host],\n\t\tUser:       currentUser,\n\t\tRole:       role,\n\t\tRolePrivs:  privs,\n\t\tDBs:        dbs,\n\t\tTablePrivs: tablePrivs,\n\t}, nil\n}\n\nfunc isErrorDatabaseNotFound(err error, dbName string, user string) bool {\n\toptions := []string{dbName, user, \"postgres\"}\n\tfor _, option := range options {\n\t\tif strings.Contains(err.Error(), fmt.Sprintf(\"database \\\"%s\\\" does not exist\", option)) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc createConnection(params map[string]string, database string) (*sql.DB, error) {\n\tif sslmode := params[pg_sslmode]; sslmode == pg_sslmode_allow || sslmode == pg_sslmode_prefer {\n\t\t// pq doesn't support 'allow' or 'prefer'. If we find either of them, we'll just ignore it. This will trigger\n\t\t// the same logic that is run if no sslmode is set at all (which mimics 'prefer', which is the default).\n\t\tdelete(params, pg_sslmode)\n\t}\n\n\tvar connStr string\n\tfor key, value := range params {\n\t\tif database != \"\" && key == \"dbname\" {\n\t\t\tconnStr += fmt.Sprintf(\"%s='%s'\", key, database)\n\t\t} else {\n\t\t\tconnStr += fmt.Sprintf(\"%s='%s'\", key, value)\n\t\t}\n\t}\n\n\tdb, err := sql.Open(\"postgres\", connStr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = db.Ping()\n\tswitch {\n\tcase err == nil:\n\t\treturn db, nil\n\tcase strings.Contains(err.Error(), \"password authentication failed\"):\n\t\treturn nil, errors.New(\"password authentication failed\")\n\tcase errors.Is(err, pq.ErrSSLNotSupported) && params[pg_sslmode] == \"\":\n\t\t// If the sslmode is unset, then either it was unset in the candidate secret, or we've intentionally unset it\n\t\t// because it was specified as 'allow' or 'prefer', neither of which pq supports. In all of these cases, non-SSL\n\t\t// connections are acceptable, so now we try a connection without SSL.\n\t\tparams[pg_sslmode] = pg_sslmode_disable\n\t\tdefer delete(params, pg_sslmode) // We want to return with the original params map intact (for ExtraData)\n\t\treturn createConnection(params, database)\n\tcase isErrorDatabaseNotFound(err, params[pg_dbname], params[pg_user]):\n\t\tcolor.Green(\"[!] Successfully connected to Postgres database.\")\n\t\treturn nil, err\n\tdefault:\n\t\treturn nil, err\n\t}\n}\n\nfunc getUserPrivs(db *sql.DB) (string, map[string]bool, error) {\n\t// Prepare the SQL statement\n\tquery := `SELECT rolname AS role_name,\n\t\t\t\trolsuper AS is_superuser,\n\t\t\t\trolinherit AS can_inherit,\n\t\t\t\trolcreaterole AS can_create_role,\n\t\t\t\trolcreatedb AS can_create_db,\n\t\t\t\trolcanlogin AS can_login,\n\t\t\t\trolreplication AS is_replication_role,\n\t\t\t\trolbypassrls AS bypasses_rls\n\t\t\tFROM pg_roles WHERE rolname = current_user;`\n\n\t// Execute the SQL query\n\trows, err := db.Query(query)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\tdefer rows.Close()\n\n\tvar roleName string\n\tvar isSuperuser, canInherit, canCreateRole, canCreateDB, canLogin, isReplicationRole, bypassesRLS bool\n\t// Iterate over the rows\n\tfor rows.Next() {\n\t\tif err := rows.Scan(&roleName, &isSuperuser, &canInherit, &canCreateRole, &canCreateDB, &canLogin, &isReplicationRole, &bypassesRLS); err != nil {\n\t\t\treturn \"\", nil, err\n\t\t}\n\t}\n\n\t// Check for errors during iteration\n\tif err := rows.Err(); err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\t// Map roles to privileges\n\tvar mapRoles map[string]bool = map[string]bool{\n\t\t\"Superuser\":            isSuperuser,\n\t\t\"Inheritance of Privs\": canInherit,\n\t\t\"Create Role\":          canCreateRole,\n\t\t\"Create DB\":            canCreateDB,\n\t\t\"Login\":                canLogin,\n\t\t\"Replication\":          isReplicationRole,\n\t\t\"Bypass RLS\":           bypassesRLS,\n\t}\n\n\treturn roleName, mapRoles, nil\n}\n\nfunc getDBPrivs(db *sql.DB) (string, []DB, error) {\n\tquery := `\n        SELECT \n            d.datname AS database_name,\n            u.usename AS owner,\n            current_user AS current_user,\n            has_database_privilege(current_user, d.datname, 'CONNECT') AS can_connect,\n            has_database_privilege(current_user, d.datname, 'CREATE') AS can_create,\n            has_database_privilege(current_user, d.datname, 'TEMP') AS can_create_temporary_tables\n        FROM \n            pg_database d\n        JOIN \n            pg_user u ON d.datdba = u.usesysid\n        WHERE \n            NOT d.datistemplate\n        ORDER BY \n            d.datname;\n    `\n\t// Originally had WHERE NOT d.datistemplate  AND d.datallowconn\n\n\t// Execute the query\n\trows, err := db.Query(query)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\tdefer rows.Close()\n\n\tdbs := make([]DB, 0)\n\n\tvar currentUser string\n\t// Iterate through the result set\n\tfor rows.Next() {\n\t\tvar dbName, owner string\n\t\tvar canConnect, canCreate, canCreateTemp bool\n\t\terr := rows.Scan(&dbName, &owner, &currentUser, &canConnect, &canCreate, &canCreateTemp)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, err\n\t\t}\n\n\t\tdb := DB{\n\t\t\tDatabaseName: dbName,\n\t\t\tOwner:        owner,\n\t\t\tDBPrivs: DBPrivs{\n\t\t\t\tConnect:    canConnect,\n\t\t\t\tCreate:     canCreate,\n\t\t\t\tCreateTemp: canCreateTemp,\n\t\t\t},\n\t\t}\n\t\tdbs = append(dbs, db)\n\t}\n\tif err = rows.Err(); err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\treturn currentUser, dbs, nil\n}\n\nfunc printDBPrivs(dbs []DB, current_user string) {\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Database\", \"Owner\", \"Access Privileges\"})\n\tfor _, db := range dbs {\n\t\tprivs := buildDBPrivsStr(db)\n\t\twriter := getDBWriter(db, current_user)\n\t\tt.AppendRow([]interface{}{writer(db.DatabaseName), writer(db.Owner), writer(privs)})\n\t}\n\tt.Render()\n}\n\nfunc buildDBPrivsStr(db DB) string {\n\tprivs := \"\"\n\tif db.Connect {\n\t\tprivs += \"CONNECT\"\n\t}\n\tif db.Create {\n\t\tprivs += \", CREATE\"\n\t}\n\tif db.CreateTemp {\n\t\tprivs += \", TEMP\"\n\t}\n\tprivs = strings.TrimPrefix(privs, \", \")\n\treturn privs\n}\n\nfunc getDBWriter(db DB, current_user string) func(a ...interface{}) string {\n\tif db.Owner == current_user {\n\t\treturn analyzers.GreenWriter\n\t} else if db.Connect && db.Create && db.CreateTemp {\n\t\treturn analyzers.GreenWriter\n\t} else if db.Connect || db.Create || db.CreateTemp {\n\t\treturn analyzers.YellowWriter\n\t} else {\n\t\treturn analyzers.DefaultWriter\n\t}\n}\n\nfunc buildSliceDBNames(dbs []DB) []string {\n\tvar dbNames []string\n\tfor _, db := range dbs {\n\t\tif db.DBPrivs.Connect {\n\t\t\tdbNames = append(dbNames, db.DatabaseName)\n\t\t}\n\t}\n\treturn dbNames\n}\n\nfunc getTablePrivs(params map[string]string, databases []string) (map[string]map[string]*TableData, error) {\n\n\ttablePrivileges := make(map[string]map[string]*TableData, 0)\n\n\tfor _, dbase := range databases {\n\t\t// Connect to db\n\t\tdb, err := createConnection(params, dbase)\n\t\tif err != nil {\n\t\t\t// color.Red(\"[x] Failed to connect to Postgres database: %s\", dbase)\n\t\t\tcontinue\n\t\t}\n\t\tdefer db.Close()\n\n\t\t// Get table privs\n\t\tquery := `\n\t\tSELECT\n\t\t\trtg.table_catalog,\n\t\t\trtg.table_name,\n\t\t\trtg.privilege_type,\n\t\t\tpg_size_pretty(pg_total_relation_size(pc.oid)) AS table_size,\n\t\t\tpc.reltuples AS estimate\n\t\tFROM\n\t\t\tinformation_schema.role_table_grants rtg\n\t\tJOIN\n\t\t\tpg_catalog.pg_class pc ON rtg.table_name = pc.relname\n\t\tWHERE\n\t\t\trtg.grantee = current_user;\n\n\t\t`\n\n\t\t// Execute the query\n\t\trows, err := db.Query(query)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer rows.Close()\n\n\t\t// Iterate through the result set\n\t\tfor rows.Next() {\n\t\t\tvar database, table, priv, size, row_count string\n\t\t\terr := rows.Scan(&database, &table, &priv, &size, &row_count)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif _, ok := tablePrivileges[database]; !ok {\n\t\t\t\ttablePrivileges[database] = map[string]*TableData{\n\t\t\t\t\ttable: {},\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif _, ok := tablePrivileges[database][table]; !ok {\n\t\t\t\ttablePrivileges[database][table] = &TableData{}\n\t\t\t}\n\n\t\t\tswitch priv {\n\t\t\tcase \"SELECT\":\n\t\t\t\ttablePrivileges[database][table].Privs.Select = true\n\t\t\tcase \"INSERT\":\n\t\t\t\ttablePrivileges[database][table].Privs.Insert = true\n\t\t\tcase \"UPDATE\":\n\t\t\t\ttablePrivileges[database][table].Privs.Update = true\n\t\t\tcase \"DELETE\":\n\t\t\t\ttablePrivileges[database][table].Privs.Delete = true\n\t\t\tcase \"TRUNCATE\":\n\t\t\t\ttablePrivileges[database][table].Privs.Truncate = true\n\t\t\tcase \"REFERENCES\":\n\t\t\t\ttablePrivileges[database][table].Privs.References = true\n\t\t\tcase \"TRIGGER\":\n\t\t\t\ttablePrivileges[database][table].Privs.Trigger = true\n\t\t\t}\n\t\t\ttablePrivileges[database][table].Size = size\n\t\t\tif row_count != \"-1\" {\n\t\t\t\ttablePrivileges[database][table].Rows = row_count\n\t\t\t} else {\n\t\t\t\ttablePrivileges[database][table].Rows = \"Unknown\"\n\t\t\t}\n\t\t}\n\t\tif err = rows.Err(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdb.Close()\n\t}\n\n\treturn tablePrivileges, nil\n}\n\nfunc printTablePrivs(tables map[string]map[string]*TableData) {\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Database\", \"Table\", \"Access Privileges\", \"Est. Size\", \"Est. Rows\"})\n\tvar writer func(a ...interface{}) string\n\tfor db, table := range tables {\n\t\tfor table_name, tableData := range table {\n\t\t\tprivs := tableData.Privs\n\t\t\tprivsStr := buildTablePrivsStr(privs)\n\t\t\tif privsStr == \"\" {\n\t\t\t\twriter = color.New().SprintFunc()\n\t\t\t} else {\n\t\t\t\twriter = color.New(color.FgGreen).SprintFunc()\n\t\t\t}\n\t\t\tt.AppendRow([]interface{}{writer(db), writer(table_name), writer(privsStr), writer(\"< \" + tableData.Size), writer(tableData.Rows)})\n\t\t}\n\t}\n\tt.Render()\n}\n\nfunc printUserRoleAndPriv(role string, privs map[string]bool) {\n\tcolor.Yellow(\"[i] User: %s\", role)\n\tcolor.Yellow(\"[i] Privileges: \")\n\tfor role, priv := range privs {\n\t\tif role == \"Superuser\" && priv {\n\t\t\tcolor.Green(\"  - %s\", role)\n\t\t} else if priv {\n\t\t\tcolor.Yellow(\"  - %s\", role)\n\t\t}\n\t}\n}\n\nfunc buildTablePrivsStr(privs TablePrivs) string {\n\tvar privsStr string\n\tif privs.Select {\n\t\tprivsStr += \"SELECT\"\n\t}\n\tif privs.Insert {\n\t\tprivsStr += \", INSERT\"\n\t}\n\tif privs.Update {\n\t\tprivsStr += \", UPDATE\"\n\t}\n\tif privs.Delete {\n\t\tprivsStr += \", DELETE\"\n\t}\n\tif privs.Truncate {\n\t\tprivsStr += \", TRUNCATE\"\n\t}\n\tif privs.References {\n\t\tprivsStr += \", REFERENCES\"\n\t}\n\tif privs.Trigger {\n\t\tprivsStr += \", TRIGGER\"\n\t}\n\tprivsStr = strings.TrimPrefix(privsStr, \", \")\n\treturn privsStr\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/postgres/postgres_test.go",
    "content": "package postgres\n\nimport (\n\t\"bytes\"\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os/exec\"\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nconst (\n\tpostgresUser = \"postgres\"\n\tpostgresPass = \"23201da=b56ca236f3dc6736c0f9afad\"\n\tpostgresHost = \"localhost\"\n\tpostgresPort = \"5434\" // Do not use 5433, as local dev environments can use it for other things\n\tdefaultPort  = \"5432\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tif err := startPostgres(); err != nil {\n\t\tif exitErr, ok := err.(*exec.ExitError); ok {\n\t\t\tt.Fatalf(\"could not start local postgres: %v w/stderr:\\n%s\", err, string(exitErr.Stderr))\n\t\t} else {\n\t\t\tt.Fatalf(\"could not start local postgres: %v\", err)\n\t\t}\n\t}\n\tdefer stopPostgres()\n\n\ttests := []struct {\n\t\tname             string\n\t\tconnectionString string\n\t\twant             []byte // JSON string\n\t\twantErr          bool\n\t}{\n\t\t{\n\t\t\tname:             \"valid Postgres connection\",\n\t\t\tconnectionString: fmt.Sprintf(`postgresql://%s:%s@%s:%s/postgres`, postgresUser, postgresPass, postgresHost, postgresPort),\n\t\t\twant:             expectedOutput,\n\t\t\twantErr:          false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(context.Background(), map[string]string{\"connection_string\": tt.connectionString})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal(tt.want, &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n\nvar postgresDockerHash string\n\nfunc dockerLogLine(hash string, needle string) chan struct{} {\n\tch := make(chan struct{}, 1)\n\tgo func() {\n\t\tfor {\n\t\t\tout, err := exec.Command(\"docker\", \"logs\", hash).CombinedOutput()\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tif strings.Contains(string(out), needle) {\n\t\t\t\tch <- struct{}{}\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t}\n\t}()\n\treturn ch\n}\n\nfunc startPostgres() error {\n\tcmd := exec.Command(\n\t\t\"docker\", \"run\", \"--rm\", \"-p\", postgresPort+\":\"+defaultPort,\n\t\t\"-e\", \"POSTGRES_PASSWORD=\"+postgresPass,\n\t\t\"-e\", \"POSTGRES_USER=\"+postgresUser,\n\t\t\"-d\", \"postgres\",\n\t)\n\tfmt.Println(cmd.String())\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn err\n\t}\n\tpostgresDockerHash = string(bytes.TrimSpace(out))\n\tselect {\n\tcase <-dockerLogLine(postgresDockerHash, \"PostgreSQL init process complete; ready for start up.\"):\n\t\treturn nil\n\tcase <-time.After(30 * time.Second):\n\t\tstopPostgres()\n\t\treturn errors.New(\"timeout waiting for postgres database to be ready\")\n\t}\n}\n\nfunc stopPostgres() {\n\terr := exec.Command(\"docker\", \"kill\", postgresDockerHash).Run()\n\tif err != nil {\n\t\tfmt.Println(\"could not stop postgres container:\", err)\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/posthog/expected_output.json",
    "content": "{\"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}"
  },
  {
    "path": "pkg/analyzer/analyzers/posthog/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage posthog\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    ActionRead Permission = iota\n    ActionWrite Permission = iota\n    ActivityLogRead Permission = iota\n    ActivityLogWrite Permission = iota\n    AnnotationRead Permission = iota\n    AnnotationWrite Permission = iota\n    BatchExportRead Permission = iota\n    BatchExportWrite Permission = iota\n    CohortRead Permission = iota\n    CohortWrite Permission = iota\n    DashboardRead Permission = iota\n    DashboardWrite Permission = iota\n    DashboardTemplateRead Permission = iota\n    DashboardTemplateWrite Permission = iota\n    EarlyAccessFeatureRead Permission = iota\n    EarlyAccessFeatureWrite Permission = iota\n    EventDefinitionRead Permission = iota\n    EventDefinitionWrite Permission = iota\n    ErrorTrackingRead Permission = iota\n    ErrorTrackingWrite Permission = iota\n    ExperimentRead Permission = iota\n    ExperimentWrite Permission = iota\n    ExportRead Permission = iota\n    ExportWrite Permission = iota\n    FeatureFlagRead Permission = iota\n    FeatureFlagWrite Permission = iota\n    GroupRead Permission = iota\n    GroupWrite Permission = iota\n    HogFunctionRead Permission = iota\n    HogFunctionWrite Permission = iota\n    InsightRead Permission = iota\n    InsightWrite Permission = iota\n    NotebookRead Permission = iota\n    NotebookWrite Permission = iota\n    OrganizationRead Permission = iota\n    OrganizationWrite Permission = iota\n    OrganizationMemberRead Permission = iota\n    OrganizationMemberWrite Permission = iota\n    PersonRead Permission = iota\n    PersonWrite Permission = iota\n    PluginRead Permission = iota\n    PluginWrite Permission = iota\n    ProjectRead Permission = iota\n    ProjectWrite Permission = iota\n    PropertyDefinitionRead Permission = iota\n    PropertyDefinitionWrite Permission = iota\n    QueryRead Permission = iota\n    SessionRecordingRead Permission = iota\n    SessionRecordingWrite Permission = iota\n    SessionRecordingPlaylistRead Permission = iota\n    SessionRecordingPlaylistWrite Permission = iota\n    SharingConfigurationRead Permission = iota\n    SharingConfigurationWrite Permission = iota\n    SubscriptionRead Permission = iota\n    SubscriptionWrite Permission = iota\n    SurveyRead Permission = iota\n    SurveyWrite Permission = iota\n    UserRead Permission = iota\n    WebhookRead Permission = iota\n    WebhookWrite Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        ActionRead: \"action:read\",\n        ActionWrite: \"action:write\",\n        ActivityLogRead: \"activity_log:read\",\n        ActivityLogWrite: \"activity_log:write\",\n        AnnotationRead: \"annotation:read\",\n        AnnotationWrite: \"annotation:write\",\n        BatchExportRead: \"batch_export:read\",\n        BatchExportWrite: \"batch_export:write\",\n        CohortRead: \"cohort:read\",\n        CohortWrite: \"cohort:write\",\n        DashboardRead: \"dashboard:read\",\n        DashboardWrite: \"dashboard:write\",\n        DashboardTemplateRead: \"dashboard_template:read\",\n        DashboardTemplateWrite: \"dashboard_template:write\",\n        EarlyAccessFeatureRead: \"early_access_feature:read\",\n        EarlyAccessFeatureWrite: \"early_access_feature:write\",\n        EventDefinitionRead: \"event_definition:read\",\n        EventDefinitionWrite: \"event_definition:write\",\n        ErrorTrackingRead: \"error_tracking:read\",\n        ErrorTrackingWrite: \"error_tracking:write\",\n        ExperimentRead: \"experiment:read\",\n        ExperimentWrite: \"experiment:write\",\n        ExportRead: \"export:read\",\n        ExportWrite: \"export:write\",\n        FeatureFlagRead: \"feature_flag:read\",\n        FeatureFlagWrite: \"feature_flag:write\",\n        GroupRead: \"group:read\",\n        GroupWrite: \"group:write\",\n        HogFunctionRead: \"hog_function:read\",\n        HogFunctionWrite: \"hog_function:write\",\n        InsightRead: \"insight:read\",\n        InsightWrite: \"insight:write\",\n        NotebookRead: \"notebook:read\",\n        NotebookWrite: \"notebook:write\",\n        OrganizationRead: \"organization:read\",\n        OrganizationWrite: \"organization:write\",\n        OrganizationMemberRead: \"organization_member:read\",\n        OrganizationMemberWrite: \"organization_member:write\",\n        PersonRead: \"person:read\",\n        PersonWrite: \"person:write\",\n        PluginRead: \"plugin:read\",\n        PluginWrite: \"plugin:write\",\n        ProjectRead: \"project:read\",\n        ProjectWrite: \"project:write\",\n        PropertyDefinitionRead: \"property_definition:read\",\n        PropertyDefinitionWrite: \"property_definition:write\",\n        QueryRead: \"query:read\",\n        SessionRecordingRead: \"session_recording:read\",\n        SessionRecordingWrite: \"session_recording:write\",\n        SessionRecordingPlaylistRead: \"session_recording_playlist:read\",\n        SessionRecordingPlaylistWrite: \"session_recording_playlist:write\",\n        SharingConfigurationRead: \"sharing_configuration:read\",\n        SharingConfigurationWrite: \"sharing_configuration:write\",\n        SubscriptionRead: \"subscription:read\",\n        SubscriptionWrite: \"subscription:write\",\n        SurveyRead: \"survey:read\",\n        SurveyWrite: \"survey:write\",\n        UserRead: \"user:read\",\n        WebhookRead: \"webhook:read\",\n        WebhookWrite: \"webhook:write\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"action:read\": ActionRead,\n        \"action:write\": ActionWrite,\n        \"activity_log:read\": ActivityLogRead,\n        \"activity_log:write\": ActivityLogWrite,\n        \"annotation:read\": AnnotationRead,\n        \"annotation:write\": AnnotationWrite,\n        \"batch_export:read\": BatchExportRead,\n        \"batch_export:write\": BatchExportWrite,\n        \"cohort:read\": CohortRead,\n        \"cohort:write\": CohortWrite,\n        \"dashboard:read\": DashboardRead,\n        \"dashboard:write\": DashboardWrite,\n        \"dashboard_template:read\": DashboardTemplateRead,\n        \"dashboard_template:write\": DashboardTemplateWrite,\n        \"early_access_feature:read\": EarlyAccessFeatureRead,\n        \"early_access_feature:write\": EarlyAccessFeatureWrite,\n        \"event_definition:read\": EventDefinitionRead,\n        \"event_definition:write\": EventDefinitionWrite,\n        \"error_tracking:read\": ErrorTrackingRead,\n        \"error_tracking:write\": ErrorTrackingWrite,\n        \"experiment:read\": ExperimentRead,\n        \"experiment:write\": ExperimentWrite,\n        \"export:read\": ExportRead,\n        \"export:write\": ExportWrite,\n        \"feature_flag:read\": FeatureFlagRead,\n        \"feature_flag:write\": FeatureFlagWrite,\n        \"group:read\": GroupRead,\n        \"group:write\": GroupWrite,\n        \"hog_function:read\": HogFunctionRead,\n        \"hog_function:write\": HogFunctionWrite,\n        \"insight:read\": InsightRead,\n        \"insight:write\": InsightWrite,\n        \"notebook:read\": NotebookRead,\n        \"notebook:write\": NotebookWrite,\n        \"organization:read\": OrganizationRead,\n        \"organization:write\": OrganizationWrite,\n        \"organization_member:read\": OrganizationMemberRead,\n        \"organization_member:write\": OrganizationMemberWrite,\n        \"person:read\": PersonRead,\n        \"person:write\": PersonWrite,\n        \"plugin:read\": PluginRead,\n        \"plugin:write\": PluginWrite,\n        \"project:read\": ProjectRead,\n        \"project:write\": ProjectWrite,\n        \"property_definition:read\": PropertyDefinitionRead,\n        \"property_definition:write\": PropertyDefinitionWrite,\n        \"query:read\": QueryRead,\n        \"session_recording:read\": SessionRecordingRead,\n        \"session_recording:write\": SessionRecordingWrite,\n        \"session_recording_playlist:read\": SessionRecordingPlaylistRead,\n        \"session_recording_playlist:write\": SessionRecordingPlaylistWrite,\n        \"sharing_configuration:read\": SharingConfigurationRead,\n        \"sharing_configuration:write\": SharingConfigurationWrite,\n        \"subscription:read\": SubscriptionRead,\n        \"subscription:write\": SubscriptionWrite,\n        \"survey:read\": SurveyRead,\n        \"survey:write\": SurveyWrite,\n        \"user:read\": UserRead,\n        \"webhook:read\": WebhookRead,\n        \"webhook:write\": WebhookWrite,\n    }\n\n    PermissionIDs = map[Permission]int{\n        ActionRead: 1,\n        ActionWrite: 2,\n        ActivityLogRead: 3,\n        ActivityLogWrite: 4,\n        AnnotationRead: 5,\n        AnnotationWrite: 6,\n        BatchExportRead: 7,\n        BatchExportWrite: 8,\n        CohortRead: 9,\n        CohortWrite: 10,\n        DashboardRead: 11,\n        DashboardWrite: 12,\n        DashboardTemplateRead: 13,\n        DashboardTemplateWrite: 14,\n        EarlyAccessFeatureRead: 15,\n        EarlyAccessFeatureWrite: 16,\n        EventDefinitionRead: 17,\n        EventDefinitionWrite: 18,\n        ErrorTrackingRead: 19,\n        ErrorTrackingWrite: 20,\n        ExperimentRead: 21,\n        ExperimentWrite: 22,\n        ExportRead: 23,\n        ExportWrite: 24,\n        FeatureFlagRead: 25,\n        FeatureFlagWrite: 26,\n        GroupRead: 27,\n        GroupWrite: 28,\n        HogFunctionRead: 29,\n        HogFunctionWrite: 30,\n        InsightRead: 31,\n        InsightWrite: 32,\n        NotebookRead: 33,\n        NotebookWrite: 34,\n        OrganizationRead: 35,\n        OrganizationWrite: 36,\n        OrganizationMemberRead: 37,\n        OrganizationMemberWrite: 38,\n        PersonRead: 39,\n        PersonWrite: 40,\n        PluginRead: 41,\n        PluginWrite: 42,\n        ProjectRead: 43,\n        ProjectWrite: 44,\n        PropertyDefinitionRead: 45,\n        PropertyDefinitionWrite: 46,\n        QueryRead: 47,\n        SessionRecordingRead: 48,\n        SessionRecordingWrite: 49,\n        SessionRecordingPlaylistRead: 50,\n        SessionRecordingPlaylistWrite: 51,\n        SharingConfigurationRead: 52,\n        SharingConfigurationWrite: 53,\n        SubscriptionRead: 54,\n        SubscriptionWrite: 55,\n        SurveyRead: 56,\n        SurveyWrite: 57,\n        UserRead: 58,\n        WebhookRead: 59,\n        WebhookWrite: 60,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: ActionRead,\n        2: ActionWrite,\n        3: ActivityLogRead,\n        4: ActivityLogWrite,\n        5: AnnotationRead,\n        6: AnnotationWrite,\n        7: BatchExportRead,\n        8: BatchExportWrite,\n        9: CohortRead,\n        10: CohortWrite,\n        11: DashboardRead,\n        12: DashboardWrite,\n        13: DashboardTemplateRead,\n        14: DashboardTemplateWrite,\n        15: EarlyAccessFeatureRead,\n        16: EarlyAccessFeatureWrite,\n        17: EventDefinitionRead,\n        18: EventDefinitionWrite,\n        19: ErrorTrackingRead,\n        20: ErrorTrackingWrite,\n        21: ExperimentRead,\n        22: ExperimentWrite,\n        23: ExportRead,\n        24: ExportWrite,\n        25: FeatureFlagRead,\n        26: FeatureFlagWrite,\n        27: GroupRead,\n        28: GroupWrite,\n        29: HogFunctionRead,\n        30: HogFunctionWrite,\n        31: InsightRead,\n        32: InsightWrite,\n        33: NotebookRead,\n        34: NotebookWrite,\n        35: OrganizationRead,\n        36: OrganizationWrite,\n        37: OrganizationMemberRead,\n        38: OrganizationMemberWrite,\n        39: PersonRead,\n        40: PersonWrite,\n        41: PluginRead,\n        42: PluginWrite,\n        43: ProjectRead,\n        44: ProjectWrite,\n        45: PropertyDefinitionRead,\n        46: PropertyDefinitionWrite,\n        47: QueryRead,\n        48: SessionRecordingRead,\n        49: SessionRecordingWrite,\n        50: SessionRecordingPlaylistRead,\n        51: SessionRecordingPlaylistWrite,\n        52: SharingConfigurationRead,\n        53: SharingConfigurationWrite,\n        54: SubscriptionRead,\n        55: SubscriptionWrite,\n        56: SurveyRead,\n        57: SurveyWrite,\n        58: UserRead,\n        59: WebhookRead,\n        60: WebhookWrite,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/posthog/permissions.yaml",
    "content": "permissions:\n  - action:read\n  - action:write\n  - activity_log:read\n  - activity_log:write\n  - annotation:read\n  - annotation:write\n  - batch_export:read\n  - batch_export:write\n  - cohort:read\n  - cohort:write\n  - dashboard:read\n  - dashboard:write\n  - dashboard_template:read\n  - dashboard_template:write\n  - early_access_feature:read\n  - early_access_feature:write\n  - event_definition:read\n  - event_definition:write\n  - error_tracking:read\n  - error_tracking:write\n  - experiment:read\n  - experiment:write\n  - export:read\n  - export:write\n  - feature_flag:read\n  - feature_flag:write\n  - group:read\n  - group:write\n  - hog_function:read\n  - hog_function:write\n  - insight:read\n  - insight:write\n  - notebook:read\n  - notebook:write\n  - organization:read\n  - organization:write\n  - organization_member:read\n  - organization_member:write\n  - person:read\n  - person:write\n  - plugin:read\n  - plugin:write\n  - project:read\n  - project:write\n  - property_definition:read\n  - property_definition:write\n  - query:read\n  - session_recording:read\n  - session_recording:write\n  - session_recording_playlist:read\n  - session_recording_playlist:write\n  - sharing_configuration:read\n  - sharing_configuration:write\n  - subscription:read\n  - subscription:write\n  - survey:read\n  - survey:write\n  - user:read\n  - webhook:read\n  - webhook:write\n"
  },
  {
    "path": "pkg/analyzer/analyzers/posthog/posthog.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go posthog\n\npackage posthog\n\nimport (\n\t\"bytes\"\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\nconst (\n\tUSDomain = \"https://us.posthog.com\"\n\tEUDomain = \"https://eu.posthog.com\"\n)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypePosthog }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credInfo[\"key\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"missing key in credInfo\")\n\t}\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypePosthog,\n\t\tMetadata:     nil,\n\t\tBindings:     make([]analyzers.Binding, 0),\n\t}\n\n\tif info.orgPermissions == nil {\n\t\t// no permissions to check\n\t\treturn &result\n\t}\n\n\tif info.user != nil {\n\t\t// for user resource\n\t\tuserResource := analyzers.Resource{\n\t\t\tName:               info.user.FirstName + \" \" + info.user.LastName,\n\t\t\tFullyQualifiedName: info.user.UUID,\n\t\t\tType:               \"user\",\n\t\t}\n\t\tanalyzerPermission := analyzers.Permission{\n\t\t\tValue: PermissionStrings[UserRead],\n\t\t}\n\t\tresult.Bindings = append(result.Bindings, analyzers.Binding{\n\t\t\tResource:   userResource,\n\t\t\tPermission: analyzerPermission,\n\t\t})\n\t}\n\n\t// for organization permissions, we need to bind the permissions to the organization resource\n\torganizationResource := analyzers.Resource{\n\t\tName:               info.organization.Name,\n\t\tFullyQualifiedName: info.organization.ID,\n\t\tType:               \"organization\",\n\t}\n\tfor _, permission := range info.orgPermissions {\n\t\tif value, ok := PermissionStrings[permission]; ok {\n\t\t\tanalyzerPermission := analyzers.Permission{\n\t\t\t\tValue: value,\n\t\t\t}\n\t\t\tresult.Bindings = append(result.Bindings, analyzers.Binding{\n\t\t\t\tResource:   organizationResource,\n\t\t\t\tPermission: analyzerPermission,\n\t\t\t})\n\t\t}\n\t}\n\n\t// for project permissions, we need to bind the permissions to the project resource and organization as the parent resource\n\tfor _, projectPermission := range info.projectPermissions {\n\t\tprojectResource := analyzers.Resource{\n\t\t\tName:               projectPermission.Project.Name,\n\t\t\tFullyQualifiedName: strconv.FormatInt(projectPermission.Project.ID, 10),\n\t\t\tType:               \"project\",\n\t\t\tParent:             &organizationResource,\n\t\t}\n\t\tfor _, permission := range projectPermission.Permissions {\n\t\t\tpermissionStr, _ := permission.ToString()\n\t\t\tanalyzerPermission := analyzers.Permission{\n\t\t\t\tValue: permissionStr,\n\t\t\t}\n\t\t\tresult.Bindings = append(result.Bindings, analyzers.Binding{\n\t\t\t\tResource:   projectResource,\n\t\t\t\tPermission: analyzerPermission,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn &result\n}\n\n//go:embed scopes.json\nvar scopesConfigBytes []byte\n\ntype HttpStatusTest struct {\n\tEndpoint        string      `json:\"endpoint\"`\n\tMethod          string      `json:\"method\"`\n\tPayload         interface{} `json:\"payload\"`\n\tValidStatuses   []int       `json:\"valid_status_code\"`\n\tInvalidStatuses []int       `json:\"invalid_status_code\"`\n}\n\nfunc StatusContains(status int, vals []int) bool {\n\tfor _, v := range vals {\n\t\tif status == v {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (h *HttpStatusTest) RunTest(cfg *config.Config, client *http.Client, domain string, headers map[string]string, args ...any) (bool, error) {\n\t// If body data, marshal to JSON\n\tvar data io.Reader\n\tif h.Payload != nil {\n\t\tjsonData, err := json.Marshal(h.Payload)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tdata = bytes.NewBuffer(jsonData)\n\t}\n\n\treq, err := http.NewRequest(h.Method, fmt.Sprintf(domain+h.Endpoint, args...), data)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Add custom headers if provided\n\tfor key, value := range headers {\n\t\treq.Header.Set(key, value)\n\t}\n\n\t// Execute HTTP Request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer resp.Body.Close()\n\n\t// Check response status code\n\tswitch {\n\tcase StatusContains(resp.StatusCode, h.ValidStatuses):\n\t\treturn true, nil\n\tcase StatusContains(resp.StatusCode, h.InvalidStatuses):\n\t\treturn false, nil\n\tdefault:\n\t\tfmt.Println(h.Method, h.Endpoint)\n\t\treturn false, errors.New(\"error checking response status code\")\n\t}\n}\n\ntype ScopesConfig struct {\n\tGeneralScopes      []Scope `json:\"general_scopes\"`\n\tOrganizationScopes []Scope `json:\"organization_scopes\"`\n\tProjectScopes      []Scope `json:\"project_scopes\"`\n}\n\ntype Scope struct {\n\tName string    `json:\"name\"`\n\tTest ScopeTest `json:\"test\"`\n}\n\ntype ScopeTest struct {\n\tRead  *HttpStatusTest `json:\"read\"`\n\tWrite *HttpStatusTest `json:\"write\"`\n}\n\nfunc readInScopesConfig() (*ScopesConfig, error) {\n\tvar scopesConfig ScopesConfig\n\tif err := json.Unmarshal(scopesConfigBytes, &scopesConfig); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &scopesConfig, nil\n}\n\nfunc checkPermissions(cfg *config.Config, client *http.Client, domain string, key string, scopes []Scope, args ...any) ([]Permission, error) {\n\n\tpermissions := make([]Permission, 0)\n\theaders := map[string]string{\"Authorization\": \"Bearer \" + key}\n\tfor _, scope := range scopes {\n\t\tvar status bool\n\t\tvar err error\n\t\tif scope.Test.Write != nil {\n\t\t\tstatus, err = scope.Test.Write.RunTest(cfg, client, domain, headers, args...)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"running test: %w\", err)\n\t\t\t}\n\t\t}\n\t\tif status {\n\t\t\tif permission, ok := StringToPermission[scope.Name+\":write\"]; ok {\n\t\t\t\tpermissions = append(permissions, permission)\n\t\t\t}\n\t\t\t// if write exists, read also exists\n\t\t\tif permission, ok := StringToPermission[scope.Name+\":read\"]; ok {\n\t\t\t\tpermissions = append(permissions, permission)\n\t\t\t}\n\t\t} else {\n\t\t\tstatus, err = scope.Test.Read.RunTest(cfg, client, domain, headers, args...)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"running test: %w\", err)\n\t\t\t}\n\t\t\tif status {\n\t\t\t\tif permission, ok := StringToPermission[scope.Name+\":read\"]; ok {\n\t\t\t\t\tpermissions = append(permissions, permission)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\n\treturn permissions, nil\n}\n\ntype ProjectPermissions struct {\n\tProject     *Project\n\tPermissions []Permission\n}\n\ntype SecretInfo struct {\n\tuser               *User\n\torganization       *Organization\n\torgPermissions     []Permission\n\tprojectPermissions []ProjectPermissions\n\t// generalPermissions      []Permission\n\tunverifiedPermissions map[Permission]struct{}\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error : %s\", err.Error())\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid Posthog API key\")\n\tcolor.Yellow(\"[i] Expires: Never\")\n\tif info.user != nil {\n\t\tprintUser(*info.user)\n\t}\n\n\tif info.organization == nil {\n\t\tcolor.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.\")\n\t}\n\n\tif info.orgPermissions != nil {\n\t\tprintOrganizationPermissions(*info.organization, info.orgPermissions)\n\t}\n\tif len(info.projectPermissions) > 0 {\n\t\tprintProjectPermissions(info.projectPermissions)\n\t}\n\tprintUnverifiedPermissions(info.unverifiedPermissions)\n\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\tvar info = &SecretInfo{}\n\n\t// These are permissions that cannot be verified due to no endpoint available\n\tinfo.unverifiedPermissions = map[Permission]struct{}{\n\t\tErrorTrackingRead:         {},\n\t\tErrorTrackingWrite:        {},\n\t\tSharingConfigurationRead:  {},\n\t\tSharingConfigurationWrite: {},\n\t\tWebhookRead:               {},\n\t\tWebhookWrite:              {},\n\t}\n\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\t// we need to determine if the key is for US or EU domain\n\tdomain, user, err := resolveDomainAndUser(cfg, client, key)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Invalid API Key: %w\", err)\n\t}\n\n\tinfo.user = user\n\n\t// Most posthog API scopes are bound to projects and organization, so to determine the scopes we need to first get the organization and projects.\n\t// If the key has user:read scope, we will get the user above which contains the organizations and projects.\n\t// If the key does not have user:read scope, we can call the /organizations/@current endpoint to get the\n\t// organization and projects. If the key does not have organization:read scope as well, we cannot determine any scope.\n\tvar org *Organization\n\tif user == nil {\n\t\torg, err = getOrganization(cfg, client, domain, key)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif org == nil {\n\t\t\t// can't determine any scopes\n\t\t\tfor permission := range PermissionStrings {\n\t\t\t\tinfo.unverifiedPermissions[permission] = struct{}{}\n\t\t\t}\n\t\t\treturn info, nil\n\t\t}\n\t} else {\n\t\torg = &user.Organization\n\t}\n\n\t// set the organization in the info struct\n\tinfo.organization = org\n\n\t// read in scopes\n\tscopesConfig, err := readInScopesConfig()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// check organization permissions\n\torganizationPermissions, err := checkOrganizationPermissions(cfg, client, domain, key, scopesConfig, org)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// check general permissions\n\tgeneralOrganizationPermissions, err := checkGeneralPermissions(cfg, client, domain, key, scopesConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// merge general permissions with organization permissions\n\tinfo.orgPermissions = organizationPermissions\n\tinfo.orgPermissions = append(info.orgPermissions, generalOrganizationPermissions...)\n\n\t// check project permissions\n\tprojectPermissions, err := checkProjectPermissions(cfg, client, domain, key, scopesConfig, org)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinfo.projectPermissions = projectPermissions\n\n\treturn info, nil\n}\n\nfunc checkGeneralPermissions(cfg *config.Config, client *http.Client, domain, key string, scopesConfig *ScopesConfig) ([]Permission, error) {\n\treturn checkPermissions(cfg, client, domain, key, scopesConfig.GeneralScopes)\n}\n\nfunc checkOrganizationPermissions(\n\tcfg *config.Config,\n\tclient *http.Client,\n\tdomain,\n\tkey string,\n\tscopesConfig *ScopesConfig,\n\torg *Organization,\n) ([]Permission, error) {\n\treturn checkPermissions(cfg, client, domain, key, scopesConfig.OrganizationScopes, org.ID)\n}\n\nfunc checkProjectPermissions(\n\tcfg *config.Config,\n\tclient *http.Client,\n\tdomain,\n\tkey string,\n\tscopesConfig *ScopesConfig,\n\torg *Organization,\n) ([]ProjectPermissions, error) {\n\tprojectPermissions := make([]ProjectPermissions, 0)\n\tfor _, project := range org.Projects {\n\t\tprojectPermission := ProjectPermissions{\n\t\t\tProject: &project,\n\t\t}\n\t\tpermissions, err := checkPermissions(cfg, client, domain, key, scopesConfig.ProjectScopes, project.ID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tprojectPermission.Permissions = permissions\n\t\tprojectPermissions = append(projectPermissions, projectPermission)\n\t}\n\treturn projectPermissions, nil\n}\n\ntype User struct {\n\tUUID         string       `json:\"uuid\"`\n\tFirstName    string       `json:\"first_name\"`\n\tLastName     string       `json:\"last_name\"`\n\tEmail        string       `json:\"email\"`\n\tOrganization Organization `json:\"organization\"`\n}\n\ntype Organization struct {\n\tID       string    `json:\"id\"`\n\tName     string    `json:\"name\"`\n\tProjects []Project `json:\"projects\"`\n}\n\ntype Project struct {\n\tID   int64  `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\n// resolves the domain and user (if permission exists) by calling the /users/@me method for both US and EU domains\n// if the response is 200 OK, it means the domain is valid and user:read permission is also there\n// if the response is 403 Forbidden, it means the domain is valid but user:read permission is not there\n// if the response is 401 Unauthorized, it means the domain is invalid\nfunc resolveDomainAndUser(cfg *config.Config, client *http.Client, key string) (string, *User, error) {\n\n\tdomains := []string{USDomain, EUDomain}\n\tfor _, domain := range domains {\n\t\treq, err := http.NewRequest(http.MethodGet, domain+\"/api/users/@me/\", nil)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, err\n\t\t}\n\t\treq.Header.Set(\"Authorization\", \"Bearer \"+key)\n\n\t\t// Execute HTTP Request\n\t\tresp, err := client.Do(req)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, err\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tswitch resp.StatusCode {\n\t\tcase http.StatusOK:\n\t\t\t// domain is valid and user permission also exists\n\t\t\tvar userInfo User\n\t\t\tif err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {\n\t\t\t\treturn \"\", nil, err\n\t\t\t}\n\t\t\treturn domain, &userInfo, nil\n\t\tcase http.StatusForbidden:\n\t\t\t// domain is valid but user permission does not exist\n\t\t\treturn domain, nil, nil\n\t\tcase http.StatusUnauthorized:\n\t\t\t// Key might not be valid of this domain\n\t\t\t// Try the other domain\n\t\t\tcontinue\n\t\tdefault:\n\t\t\t// unexpected status code\n\t\t\treturn \"\", nil, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t\t}\n\t}\n\treturn \"\", nil, fmt.Errorf(\"invalid Posthog API key\")\n}\n\nfunc getOrganization(cfg *config.Config, client *http.Client, domain string, key string) (*Organization, error) {\n\treq, err := http.NewRequest(http.MethodGet, domain+\"/api/organizations/@current/\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+key)\n\n\t// Execute HTTP Request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tvar org Organization\n\t\tif err := json.NewDecoder(resp.Body).Decode(&org); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &org, nil\n\tcase http.StatusForbidden:\n\t\treturn nil, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\nfunc printUser(user User) {\n\tcolor.Yellow(\"\\n[i] User Info:\")\n\tcolor.Green(\"[i] Name: %s %s\", user.FirstName, user.LastName)\n\tcolor.Green(\"[i] Email: %s\", user.Email)\n\tcolor.Green(\"[i] ID: %s\", user.UUID)\n}\n\nfunc printOrganizationPermissions(organization Organization, permissions []Permission) {\n\tcolor.Yellow(\"\\n[i] Organization Permissions:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Organization\", \"Permission\"})\n\tpermissionsString := make([]string, len(permissions))\n\tfor i, permission := range permissions {\n\t\tpermissionsString[i], _ = permission.ToString()\n\t}\n\tt.AppendRow(table.Row{\n\t\tcolor.GreenString(organization.Name),\n\t\tcolor.GreenString(strings.Join(permissionsString, \"\\n\")),\n\t})\n\tt.Render()\n}\n\nfunc printProjectPermissions(projectPermissions []ProjectPermissions) {\n\tcolor.Yellow(\"\\n[i] Project Permissions:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Project\", \"Permission\"})\n\tfor _, projectPermission := range projectPermissions {\n\t\tpermissionsString := make([]string, len(projectPermission.Permissions))\n\t\tfor i, permission := range projectPermission.Permissions {\n\t\t\tpermissionsString[i], _ = permission.ToString()\n\n\t\t}\n\t\tt.AppendRow(table.Row{\n\t\t\tcolor.GreenString(projectPermission.Project.Name),\n\t\t\tcolor.GreenString(strings.Join(permissionsString, \"\\n\")),\n\t\t})\n\t}\n\tt.Render()\n}\n\nfunc printUnverifiedPermissions(permissions map[Permission]struct{}) {\n\tcolor.Yellow(\"\\n[i] Unverified Permissions:\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Permission\"})\n\tfor permission := range permissions {\n\t\tpermissionStr, _ := permission.ToString()\n\t\tt.AppendRow(table.Row{color.YellowString(permissionStr)})\n\t}\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/posthog/posthog_test.go",
    "content": "package posthog\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*15)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid posthog api key\",\n\t\t\tkey:     testSecrets.MustGetField(\"POSTHOG_API_KEY\"),\n\t\t\twant:    string(expectedOutput),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/posthog/scopes.json",
    "content": "{\n    \"general_scopes\": [\n        {\n            \"name\": \"organization\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/organizations\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/organizations\",\n                    \"method\": \"POST\",\n                    \"valid_status_code\": [\n                        400\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        }\n    ],\n    \"organization_scopes\": [\n        {\n            \"name\": \"batch_export\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/organizations/%s/batch_exports\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/organizations/%s/batch_exports\",\n                    \"method\": \"POST\",\n                    \"valid_status_code\": [\n                        400\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"organization_member\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/organizations/%s/members\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/organizations/%s/members/`nowaythiscanexist\",\n                    \"method\": \"PATCH\",\n                    \"valid_status_code\": [\n                        500\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"project\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/organizations/%s/projects\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/organizations/%s/projects/`nowaythiscanexist\",\n                    \"method\": \"DELETE\",\n                    \"valid_status_code\": [\n                        400\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        }\n    ],\n    \"project_scopes\": [\n        {\n            \"name\": \"action\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/actions\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [200],\n                    \"invalid_status_code\": [403]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/actions\",\n                    \"method\": \"POST\",\n                    \"valid_status_code\": [500],\n                    \"invalid_status_code\": [403]\n                }\n            }\n        },\n        {\n            \"name\": \"activity_log\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/activity_log\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/activity_log\",\n                    \"method\": \"POST\",\n                    \"valid_status_code\": [\n                        500\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"annotation\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/annotations\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/annotations/`nowaythiscanexist\",\n                    \"method\": \"PATCH\",\n                    \"valid_status_code\": [\n                        404\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"cohort\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/cohorts\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/cohorts/`nowaythiscanexist\",\n                    \"method\": \"PATCH\",\n                    \"valid_status_code\": [\n                        404\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"dashboard\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/dashboards\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/dashboards/`nowaythiscanexist\",\n                    \"method\": \"PATCH\",\n                    \"valid_status_code\": [\n                        500\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"dashboard_template\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/dashboard_templates\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/dashboard_templates/`nowaythiscanexist\",\n                    \"method\": \"PATCH\",\n                    \"valid_status_code\": [\n                        404\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"early_access_feature\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/early_access_feature\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/early_access_feature\",\n                    \"method\": \"POST\",\n                    \"valid_status_code\": [\n                        400\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"event_definition\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/event_definitions\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/event_definitions/`nowaythiscanexist\",\n                    \"method\": \"PATCH\",\n                    \"valid_status_code\": [\n                        500\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"experiment\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/experiments\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/experiments\",\n                    \"method\": \"POST\",\n                    \"valid_status_code\": [\n                        400\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"export\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/exports\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/exports\",\n                    \"method\": \"POST\",\n                    \"valid_status_code\": [\n                        400\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"feature_flag\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/feature_flags\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/feature_flags\",\n                    \"method\": \"POST\",\n                    \"valid_status_code\": [\n                        400\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"group\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/groups\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        400\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/groups/update_property\",\n                    \"method\": \"POST\",\n                    \"valid_status_code\": [\n                        500\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"hog_function\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/hog_functions\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/hog_functions/`nowaythiscanexist\",\n                    \"method\": \"PATCH\",\n                    \"valid_status_code\": [\n                        404\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"insight\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/insights\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/insights/`nowaythiscanexist\",\n                    \"method\": \"PATCH\",\n                    \"valid_status_code\": [\n                        404\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"notebook\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/notebooks\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/notebooks/`nowaythiscanexist\",\n                    \"method\": \"PATCH\",\n                    \"valid_status_code\": [\n                        404\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"person\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/persons\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/persons/`nowaythiscanexist\",\n                    \"method\": \"PATCH\",\n                    \"valid_status_code\": [\n                        400\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"plugin\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/plugin_configs\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/plugin_configs\",\n                    \"method\": \"POST\",\n                    \"valid_status_code\": [\n                        400\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"property_definition\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/property_definitions\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/property_definitions/`nowaythiscanexist\",\n                    \"method\": \"PATCH\",\n                    \"valid_status_code\": [\n                        500\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"query\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/query/`nowaythiscanexist\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        404\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"session_recording\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/session_recordings\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/session_recordings/`nowaythisexists\",\n                    \"method\": \"PATCH\",\n                    \"valid_status_code\": [\n                        404\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"session_recording_playlist\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/session_recording_playlists\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/session_recording_playlists/`nowaythiscanexist\",\n                    \"method\": \"PATCH\",\n                    \"valid_status_code\": [\n                        404\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"subscription\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/subscriptions\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200,\n                        402\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/subscriptions/`nowaythiscanexist\",\n                    \"method\": \"PATCH\",\n                    \"valid_status_code\": [\n                        402,\n                        404\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        },\n        {\n            \"name\": \"survey\",\n            \"test\": {\n                \"read\": {\n                    \"endpoint\": \"/api/projects/%d/surveys\",\n                    \"method\": \"GET\",\n                    \"valid_status_code\": [\n                        200\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                },\n                \"write\": {\n                    \"endpoint\": \"/api/projects/%d/surveys\",\n                    \"method\": \"POST\",\n                    \"valid_status_code\": [\n                        400\n                    ],\n                    \"invalid_status_code\": [\n                        403\n                    ]\n                }\n            }\n        }\n    ]\n    \n}"
  },
  {
    "path": "pkg/analyzer/analyzers/postman/expected_output.json",
    "content": "{\n    \"AnalyzerType\": 13,\n    \"Bindings\": [\n     {\n      \"Resource\": {\n       \"Name\": \"rendy\",\n       \"FullyQualifiedName\": \"rendyplayground@gmail.com\",\n       \"Type\": \"user\",\n       \"Metadata\": {\n        \"email\": \"rendyplayground@gmail.com\",\n        \"role\": \"user\",\n        \"team_domain\": \"\",\n        \"team_name\": \"\",\n        \"username\": \"rendyplayground\"\n       },\n       \"Parent\": null\n      },\n      \"Permission\": {\n       \"Value\": \"usage_data:view\",\n       \"Parent\": null\n      }\n     },\n     {\n      \"Resource\": {\n       \"Name\": \"rendy\",\n       \"FullyQualifiedName\": \"rendyplayground@gmail.com\",\n       \"Type\": \"user\",\n       \"Metadata\": {\n        \"email\": \"rendyplayground@gmail.com\",\n        \"role\": \"user\",\n        \"team_domain\": \"\",\n        \"team_name\": \"\",\n        \"username\": \"rendyplayground\"\n       },\n       \"Parent\": null\n      },\n      \"Permission\": {\n       \"Value\": \"team_workspaces:create\",\n       \"Parent\": null\n      }\n     },\n     {\n      \"Resource\": {\n       \"Name\": \"rendy\",\n       \"FullyQualifiedName\": \"rendyplayground@gmail.com\",\n       \"Type\": \"user\",\n       \"Metadata\": {\n        \"email\": \"rendyplayground@gmail.com\",\n        \"role\": \"user\",\n        \"team_domain\": \"\",\n        \"team_name\": \"\",\n        \"username\": \"rendyplayground\"\n       },\n       \"Parent\": null\n      },\n      \"Permission\": {\n       \"Value\": \"team_workspaces:view\",\n       \"Parent\": null\n      }\n     }\n    ],\n    \"UnboundedResources\": [\n     {\n      \"Name\": \"My Workspace\",\n      \"FullyQualifiedName\": \"4d06fc0c-6402-4a26-857d-80787b10eabf\",\n      \"Type\": \"workspace\",\n      \"Metadata\": {\n       \"id\": \"4d06fc0c-6402-4a26-857d-80787b10eabf\",\n       \"type\": \"personal\",\n       \"visibility\": \"personal\"\n      },\n      \"Parent\": null\n     }\n    ],\n    \"Metadata\": null\n   }"
  },
  {
    "path": "pkg/analyzer/analyzers/postman/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage postman\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    NoAccess Permission = iota\n    UserAdd Permission = iota\n    UserRemove Permission = iota\n    TeamAdminManage Permission = iota\n    TeamDevelopersManage Permission = iota\n    SsoManage Permission = iota\n    CustomDomainAdd Permission = iota\n    CustomDomainEdit Permission = iota\n    CustomDomainRemove Permission = iota\n    AuditLogsView Permission = iota\n    UsageDataView Permission = iota\n    BillingMembersManage Permission = iota\n    PaymentManage Permission = iota\n    PlanUpdate Permission = iota\n    TeamWorkspacesView Permission = iota\n    TeamWorkspacesCreate Permission = iota\n    TeamPublicProfileEnable Permission = iota\n    TeamPrivateApiNetworkManage Permission = iota\n    ParternerWorkspaceView Permission = iota\n    ParternerWorkspaceManage Permission = iota\n    ParternerWorkspaceVisibilityManage Permission = iota\n    PartnersManage Permission = iota\n    FlowAdd Permission = iota\n    FlowEdit Permission = iota\n    FlowRun Permission = iota\n    FlowPublish Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        UserAdd: \"user:add\",\n        UserRemove: \"user:remove\",\n        TeamAdminManage: \"team_admin:manage\",\n        TeamDevelopersManage: \"team_developers:manage\",\n        SsoManage: \"sso:manage\",\n        CustomDomainAdd: \"custom_domain:add\",\n        CustomDomainEdit: \"custom_domain:edit\",\n        CustomDomainRemove: \"custom_domain:remove\",\n        AuditLogsView: \"audit_logs:view\",\n        UsageDataView: \"usage_data:view\",\n        BillingMembersManage: \"billing_members:manage\",\n        PaymentManage: \"payment:manage\",\n        PlanUpdate: \"plan:update\",\n        TeamWorkspacesView: \"team_workspaces:view\",\n        TeamWorkspacesCreate: \"team_workspaces:create\",\n        TeamPublicProfileEnable: \"team_public_profile:enable\",\n        TeamPrivateApiNetworkManage: \"team_private_api_network:manage\",\n        ParternerWorkspaceView: \"parterner_workspace:view\",\n        ParternerWorkspaceManage: \"parterner_workspace:manage\",\n        ParternerWorkspaceVisibilityManage: \"parterner_workspace_visibility:manage\",\n        PartnersManage: \"partners:manage\",\n        FlowAdd: \"flow:add\",\n        FlowEdit: \"flow:edit\",\n        FlowRun: \"flow:run\",\n        FlowPublish: \"flow:publish\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"user:add\": UserAdd,\n        \"user:remove\": UserRemove,\n        \"team_admin:manage\": TeamAdminManage,\n        \"team_developers:manage\": TeamDevelopersManage,\n        \"sso:manage\": SsoManage,\n        \"custom_domain:add\": CustomDomainAdd,\n        \"custom_domain:edit\": CustomDomainEdit,\n        \"custom_domain:remove\": CustomDomainRemove,\n        \"audit_logs:view\": AuditLogsView,\n        \"usage_data:view\": UsageDataView,\n        \"billing_members:manage\": BillingMembersManage,\n        \"payment:manage\": PaymentManage,\n        \"plan:update\": PlanUpdate,\n        \"team_workspaces:view\": TeamWorkspacesView,\n        \"team_workspaces:create\": TeamWorkspacesCreate,\n        \"team_public_profile:enable\": TeamPublicProfileEnable,\n        \"team_private_api_network:manage\": TeamPrivateApiNetworkManage,\n        \"parterner_workspace:view\": ParternerWorkspaceView,\n        \"parterner_workspace:manage\": ParternerWorkspaceManage,\n        \"parterner_workspace_visibility:manage\": ParternerWorkspaceVisibilityManage,\n        \"partners:manage\": PartnersManage,\n        \"flow:add\": FlowAdd,\n        \"flow:edit\": FlowEdit,\n        \"flow:run\": FlowRun,\n        \"flow:publish\": FlowPublish,\n    }\n\n    PermissionIDs = map[Permission]int{\n        UserAdd: 0,\n        UserRemove: 1,\n        TeamAdminManage: 2,\n        TeamDevelopersManage: 3,\n        SsoManage: 4,\n        CustomDomainAdd: 5,\n        CustomDomainEdit: 6,\n        CustomDomainRemove: 7,\n        AuditLogsView: 8,\n        UsageDataView: 9,\n        BillingMembersManage: 10,\n        PaymentManage: 11,\n        PlanUpdate: 12,\n        TeamWorkspacesView: 13,\n        TeamWorkspacesCreate: 14,\n        TeamPublicProfileEnable: 15,\n        TeamPrivateApiNetworkManage: 16,\n        ParternerWorkspaceView: 17,\n        ParternerWorkspaceManage: 18,\n        ParternerWorkspaceVisibilityManage: 19,\n        PartnersManage: 20,\n        FlowAdd: 21,\n        FlowEdit: 22,\n        FlowRun: 23,\n        FlowPublish: 24,\n    }\n\n    IdToPermission = map[int]Permission{\n        0: UserAdd,\n        1: UserRemove,\n        2: TeamAdminManage,\n        3: TeamDevelopersManage,\n        4: SsoManage,\n        5: CustomDomainAdd,\n        6: CustomDomainEdit,\n        7: CustomDomainRemove,\n        8: AuditLogsView,\n        9: UsageDataView,\n        10: BillingMembersManage,\n        11: PaymentManage,\n        12: PlanUpdate,\n        13: TeamWorkspacesView,\n        14: TeamWorkspacesCreate,\n        15: TeamPublicProfileEnable,\n        16: TeamPrivateApiNetworkManage,\n        17: ParternerWorkspaceView,\n        18: ParternerWorkspaceManage,\n        19: ParternerWorkspaceVisibilityManage,\n        20: PartnersManage,\n        21: FlowAdd,\n        22: FlowEdit,\n        23: FlowRun,\n        24: FlowPublish,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/postman/permissions.yaml",
    "content": "permissions:\n- user:add\n- user:remove\n- team_admin:manage\n- team_developers:manage\n- sso:manage\n- custom_domain:add\n- custom_domain:edit\n- custom_domain:remove\n- audit_logs:view\n- usage_data:view\n- billing_members:manage\n- payment:manage\n- plan:update\n- team_workspaces:view\n- team_workspaces:create\n- team_public_profile:enable\n- team_private_api_network:manage\n- parterner_workspace:view\n- parterner_workspace:manage\n- parterner_workspace_visibility:manage\n- partners:manage\n- flow:add\n- flow:edit\n- flow:run\n- flow:publish\n\n"
  },
  {
    "path": "pkg/analyzer/analyzers/postman/postman.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go postman\npackage postman\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypePostman }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credInfo[\"key\"]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"missing key in credInfo\")\n\t}\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType:       analyzers.AnalyzerTypePostman,\n\t\tMetadata:           nil,\n\t\tUnboundedResources: []analyzers.Resource{},\n\t}\n\n\tresource := analyzers.Resource{\n\t\tName:               info.User.User.FullName,\n\t\tFullyQualifiedName: info.User.User.Email,\n\t\tType:               \"user\",\n\t\tMetadata: map[string]any{\n\t\t\t\"role\":        strings.Join(info.User.User.Roles, \",\"),\n\t\t\t\"username\":    info.User.User.Username,\n\t\t\t\"email\":       info.User.User.Email,\n\t\t\t\"team_name\":   info.User.User.TeamName,\n\t\t\t\"team_domain\": info.User.User.TeamDomain,\n\t\t},\n\t}\n\n\tpermissions := bakePermissions(info.User.User.Roles)\n\n\t// bind all permissions with resources\n\tresult.Bindings = analyzers.BindAllPermissions(resource, permissions...)\n\n\tfor _, workspace := range info.Workspace.Workspaces {\n\t\tresult.UnboundedResources = append(result.UnboundedResources, analyzers.Resource{\n\t\t\tName:               workspace.Name,\n\t\t\tFullyQualifiedName: workspace.ID,\n\t\t\tType:               \"workspace\",\n\t\t\tMetadata: map[string]any{\n\t\t\t\t\"id\":         workspace.ID,\n\t\t\t\t\"type\":       workspace.Type,\n\t\t\t\t\"visibility\": workspace.Visibility,\n\t\t\t},\n\t\t})\n\t}\n\n\treturn &result\n}\n\nfunc bakePermissions(roles []string) []analyzers.Permission {\n\tpermissionMap := map[Permission]struct{}{}\n\n\tfor _, role := range roles {\n\t\tpermissions, ok := rolePermission[role]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, permission := range permissions {\n\t\t\tpermissionMap[permission] = struct{}{}\n\t\t}\n\t}\n\n\tpermissions := make([]analyzers.Permission, 0, len(permissionMap))\n\tfor perm := range permissionMap {\n\t\tpermStr, err := perm.ToString()\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tpermissions = append(permissions, analyzers.Permission{\n\t\t\tValue:  permStr,\n\t\t\tParent: nil,\n\t\t})\n\t}\n\n\treturn permissions\n}\n\ntype UserInfoJSON struct {\n\tUser struct {\n\t\tUsername   string   `json:\"username\"`\n\t\tEmail      string   `json:\"email\"`\n\t\tFullName   string   `json:\"fullName\"`\n\t\tRoles      []string `json:\"roles\"`\n\t\tTeamName   string   `json:\"teamName\"`\n\t\tTeamDomain string   `json:\"teamDomain\"`\n\t} `json:\"user\"`\n}\n\ntype WorkspaceJSON struct {\n\tWorkspaces []struct {\n\t\tID         string `json:\"id\"`\n\t\tName       string `json:\"name\"`\n\t\tType       string `json:\"type\"`\n\t\tVisibility string `json:\"visibility\"`\n\t} `json:\"workspaces\"`\n}\n\nfunc getUserInfo(cfg *config.Config, key string) (UserInfoJSON, error) {\n\tvar me UserInfoJSON\n\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(\"GET\", \"https://api.getpostman.com/me\", nil)\n\tif err != nil {\n\t\treturn me, err\n\t}\n\n\treq.Header.Add(\"X-API-Key\", key)\n\n\t// send request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn me, err\n\t}\n\n\t// read response\n\tdefer resp.Body.Close()\n\n\t// if status code is 200, decode response\n\tif resp.StatusCode == 200 {\n\t\terr = json.NewDecoder(resp.Body).Decode(&me)\n\t}\n\treturn me, err\n}\n\nfunc getWorkspaces(cfg *config.Config, key string) (WorkspaceJSON, error) {\n\tvar workspaces WorkspaceJSON\n\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(\"GET\", \"https://api.getpostman.com/workspaces\", nil)\n\tif err != nil {\n\t\treturn workspaces, err\n\t}\n\n\treq.Header.Add(\"X-API-Key\", key)\n\n\t// send request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn workspaces, err\n\t}\n\n\t// read response\n\tdefer resp.Body.Close()\n\n\t// if status code is 200, decode response\n\tif resp.StatusCode == 200 {\n\t\terr = json.NewDecoder(resp.Body).Decode(&workspaces)\n\t}\n\treturn workspaces, err\n}\n\ntype SecretInfo struct {\n\tUser           UserInfoJSON\n\tWorkspace      WorkspaceJSON\n\tWorkspaceError error\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\t// ToDo: Add in logging\n\tif cfg.LoggingEnabled {\n\t\tcolor.Red(\"[x] Logging is not supported for this analyzer.\")\n\t\treturn\n\t}\n\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error: %s\", err.Error())\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid Postman API Key\")\n\tprintUserInfo(info.User)\n\n\tif info.WorkspaceError != nil {\n\t\tcolor.Red(\"[x] Error Fetching Workspaces: %s\", info.WorkspaceError.Error())\n\t} else if len(info.Workspace.Workspaces) == 0 {\n\t\tcolor.Red(\"[x] No Workspaces Found\")\n\t} else {\n\t\tprintWorkspaces(info.Workspace)\n\t}\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\t// validate key & get user info\n\n\tme, err := getUserInfo(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif me.User.Username == \"\" {\n\t\treturn nil, fmt.Errorf(\"Invalid Postman API Key\")\n\t}\n\n\t// get workspaces, if there is error user with empty workspaces will be returned\n\tworkspaces, err := getWorkspaces(cfg, key)\n\n\treturn &SecretInfo{\n\t\tUser:           me,\n\t\tWorkspace:      workspaces,\n\t\tWorkspaceError: err,\n\t}, nil\n}\n\nfunc printUserInfo(me UserInfoJSON) {\n\n\tcolor.Yellow(\"\\n[i] User Information\")\n\tcolor.Green(\"Username: \" + me.User.Username)\n\tcolor.Green(\"Email: \" + me.User.Email)\n\tcolor.Green(\"Full Name: \" + me.User.FullName)\n\n\tcolor.Yellow(\"\\n[i] Team Information\")\n\tcolor.Green(\"Name: \" + me.User.TeamName)\n\tcolor.Green(\"Domain: https://\" + me.User.TeamDomain + \".postman.co\")\n\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Scope\", \"Permissions\"})\n\n\tfor _, role := range me.User.Roles {\n\t\tt.AppendRow([]interface{}{color.GreenString(role), color.GreenString(roleDescriptions[role])})\n\t}\n\tt.Render()\n\tfmt.Println(\"Reference: https://learning.postman.com/docs/collaborating-in-postman/roles-and-permissions/#team-roles\")\n}\n\nfunc printWorkspaces(workspaces WorkspaceJSON) {\n\tcolor.Yellow(\"[i] Accessible Workspaces\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Workspace Name\", \"Type\", \"Visibility\", \"Link\"})\n\tfor _, workspace := range workspaces.Workspaces {\n\t\tt.AppendRow([]interface{}{color.GreenString(workspace.Name), color.GreenString(workspace.Type), color.GreenString(workspace.Visibility), color.GreenString(\"https://go.postman.co/workspaces/\" + workspace.ID)})\n\t}\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/postman/postman_test.go",
    "content": "package postman\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*15)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid Postman key\",\n\t\t\tkey:     testSecrets.MustGetField(\"POSTMAN_TOKEN\"),\n\t\t\twant:    string(expectedOutput),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/postman/scopes.go",
    "content": "package postman\n\nvar roleDescriptions = map[string]string{\n\t\"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.\",\n\t\"admin\":             \"Manages team members and team settings. Can also view monitor metadata and run, pause, and resume monitors.\",\n\t\"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.\",\n\t\"user\":              \"Has access to all team resources and workspaces.\",\n\t\"community-manager\": \"(Pro & Enterprise Only) Manages the public visibility of workspaces and team profile.\",\n\t\"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.\",\n\t\"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.\",\n\t\"guest\":             \"Views collections and sends requests in collections that have been shared with them. This role can't be directly assigned to a user.\",\n\t\"flow-editor\":       \"(Basic and Professional plans only) - Can create, edit, run, and publish Postman Flows.\",\n}\n\nvar rolePermission = map[string][]Permission{\n\t\"super-admin\": {\n\t\tUserAdd,\n\t\tUserRemove,\n\t\tTeamAdminManage,\n\t\tTeamDevelopersManage,\n\t\tSsoManage,\n\t\tCustomDomainAdd,\n\t\tCustomDomainEdit,\n\t\tCustomDomainRemove,\n\t\tAuditLogsView,\n\t\tUsageDataView,\n\t\tBillingMembersManage,\n\t\tPaymentManage,\n\t\tPlanUpdate,\n\t\tTeamWorkspacesView,\n\t\tTeamWorkspacesCreate,\n\t\tTeamPublicProfileEnable,\n\t\tTeamPrivateApiNetworkManage,\n\t\tPartnersManage,\n\t\tParternerWorkspaceManage,\n\t\tParternerWorkspaceView,\n\t\tParternerWorkspaceVisibilityManage,\n\t\tFlowAdd,\n\t\tFlowEdit,\n\t\tFlowRun,\n\t\tFlowPublish,\n\t},\n\t\"admin\": {\n\t\tUserAdd,\n\t\tUserRemove,\n\t\tTeamAdminManage,\n\t\tTeamDevelopersManage,\n\t\tSsoManage,\n\t\tCustomDomainAdd,\n\t\tCustomDomainEdit,\n\t\tCustomDomainRemove,\n\t\tAuditLogsView,\n\t\tUsageDataView,\n\t\tBillingMembersManage,\n\t\tTeamPublicProfileEnable,\n\t\tPartnersManage,\n\t\tParternerWorkspaceManage,\n\t\tParternerWorkspaceView,\n\t\tParternerWorkspaceVisibilityManage,\n\t\tFlowAdd,\n\t\tFlowEdit,\n\t\tFlowRun,\n\t\tFlowPublish,\n\t},\n\t\"billing\": {\n\t\tUsageDataView,\n\t\tBillingMembersManage,\n\t\tPaymentManage,\n\t\tPlanUpdate,\n\t},\n\t\"user\": {\n\t\tUsageDataView,\n\t\tTeamWorkspacesCreate,\n\t\tTeamWorkspacesView,\n\t},\n\t\"community-manager\": {\n\t\tCustomDomainAdd,\n\t\tCustomDomainEdit,\n\t\tAuditLogsView,\n\t\tUsageDataView,\n\t\tTeamWorkspacesView,\n\t\tTeamWorkspacesCreate,\n\t\tTeamPublicProfileEnable,\n\t},\n\t\"partner-manager\": {\n\t\tPartnersManage,\n\t\tParternerWorkspaceManage,\n\t\tParternerWorkspaceView,\n\t\tParternerWorkspaceVisibilityManage,\n\t},\n\t\"partner\": {\n\t\tParternerWorkspaceView,\n\t},\n\t\"guest\": {\n\t\tTeamWorkspacesView,\n\t},\n\t\"flow-editor\": {\n\t\tFlowAdd,\n\t\tFlowEdit,\n\t\tFlowRun,\n\t\tFlowPublish,\n\t},\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/privatekey/expected_output.json",
    "content": "{\"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}"
  },
  {
    "path": "pkg/analyzer/analyzers/privatekey/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage privatekey\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    Digitalsignature Permission = iota\n    Nonrepudiation Permission = iota\n    Keyencipherment Permission = iota\n    Dataencipherment Permission = iota\n    Keyagreement Permission = iota\n    Certificatesigning Permission = iota\n    Crlsigning Permission = iota\n    Encipheronly Permission = iota\n    Decipheronly Permission = iota\n    Serverauth Permission = iota\n    Clientauth Permission = iota\n    Codesigning Permission = iota\n    Emailprotection Permission = iota\n    Timestamping Permission = iota\n    Ocspsigning Permission = iota\n    Clone Permission = iota\n    Push Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        Digitalsignature: \"DigitalSignature\",\n        Nonrepudiation: \"NonRepudiation\",\n        Keyencipherment: \"KeyEncipherment\",\n        Dataencipherment: \"DataEncipherment\",\n        Keyagreement: \"KeyAgreement\",\n        Certificatesigning: \"CertificateSigning\",\n        Crlsigning: \"CRLSigning\",\n        Encipheronly: \"EncipherOnly\",\n        Decipheronly: \"DecipherOnly\",\n        Serverauth: \"ServerAuth\",\n        Clientauth: \"ClientAuth\",\n        Codesigning: \"CodeSigning\",\n        Emailprotection: \"EmailProtection\",\n        Timestamping: \"TimeStamping\",\n        Ocspsigning: \"OCSPSigning\",\n        Clone: \"Clone\",\n        Push: \"Push\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"DigitalSignature\": Digitalsignature,\n        \"NonRepudiation\": Nonrepudiation,\n        \"KeyEncipherment\": Keyencipherment,\n        \"DataEncipherment\": Dataencipherment,\n        \"KeyAgreement\": Keyagreement,\n        \"CertificateSigning\": Certificatesigning,\n        \"CRLSigning\": Crlsigning,\n        \"EncipherOnly\": Encipheronly,\n        \"DecipherOnly\": Decipheronly,\n        \"ServerAuth\": Serverauth,\n        \"ClientAuth\": Clientauth,\n        \"CodeSigning\": Codesigning,\n        \"EmailProtection\": Emailprotection,\n        \"TimeStamping\": Timestamping,\n        \"OCSPSigning\": Ocspsigning,\n        \"Clone\": Clone,\n        \"Push\": Push,\n    }\n\n    PermissionIDs = map[Permission]int{\n        Digitalsignature: 1,\n        Nonrepudiation: 2,\n        Keyencipherment: 3,\n        Dataencipherment: 4,\n        Keyagreement: 5,\n        Certificatesigning: 6,\n        Crlsigning: 7,\n        Encipheronly: 8,\n        Decipheronly: 9,\n        Serverauth: 10,\n        Clientauth: 11,\n        Codesigning: 12,\n        Emailprotection: 13,\n        Timestamping: 14,\n        Ocspsigning: 15,\n        Clone: 16,\n        Push: 17,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: Digitalsignature,\n        2: Nonrepudiation,\n        3: Keyencipherment,\n        4: Dataencipherment,\n        5: Keyagreement,\n        6: Certificatesigning,\n        7: Crlsigning,\n        8: Encipheronly,\n        9: Decipheronly,\n        10: Serverauth,\n        11: Clientauth,\n        12: Codesigning,\n        13: Emailprotection,\n        14: Timestamping,\n        15: Ocspsigning,\n        16: Clone,\n        17: Push,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/privatekey/permissions.yaml",
    "content": "permissions:\n# TLS: \n# KeyUsuage: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3\n# ExtendedKeyUsage: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.12\n- DigitalSignature\n- NonRepudiation\n- KeyEncipherment\n- DataEncipherment\n- KeyAgreement\n- CertificateSigning\n- CRLSigning\n- EncipherOnly\n- DecipherOnly\n- ServerAuth\n- ClientAuth\n- CodeSigning\n- EmailProtection\n- TimeStamping\n- OCSPSigning\n\n# Github/Gitlab\n- Clone\n- Push"
  },
  {
    "path": "pkg/analyzer/analyzers/privatekey/privatekey.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go privatekey\n\npackage privatekey\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/privatekey\"\n\t\"golang.org/x/crypto/ssh\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypePrivateKey }\n\nfunc (a Analyzer) Analyze(ctx context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\t// token will be already normalized by the time it reaches here\n\ttoken, ok := credInfo[\"token\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"token not found in credInfo\")\n\t}\n\n\tinfo, err := AnalyzePermissions(ctx, a.Cfg, token)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\ntype SecretInfo struct {\n\tTLSCertificateResult *privatekey.DriftwoodResult\n\tGithubUsername       *string\n\tGitlabUsername       *string\n}\n\nfunc AnalyzePermissions(ctx context.Context, cfg *config.Config, token string) (*SecretInfo, error) {\n\n\tvar (\n\t\twg             sync.WaitGroup\n\t\tparsedKey      any\n\t\terr            error\n\t\tanalyzerErrors = privatekey.NewVerificationErrors(3)\n\t\tinfo           = &SecretInfo{}\n\t)\n\n\tparsedKey, err = ssh.ParseRawPrivateKey([]byte(token))\n\tif err != nil && strings.Contains(err.Error(), \"private key is passphrase protected\") {\n\t\t// key is password protected\n\t\tparsedKey, _, err = privatekey.Crack([]byte(token))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else if err != nil {\n\t\treturn nil, err\n\t}\n\n\tfingerprint, err := privatekey.FingerprintPEMKey(parsedKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Look up certificate information.\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tdata, err := analyzeFingerprint(ctx, fingerprint)\n\t\tif err != nil {\n\t\t\tanalyzerErrors.Add(err)\n\t\t} else {\n\t\t\tinfo.TLSCertificateResult = data\n\t\t}\n\t}()\n\n\t// Test SSH key against github.com\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tuser, err := analyzeGithubUser(ctx, parsedKey)\n\t\tif err != nil {\n\t\t\tanalyzerErrors.Add(err)\n\t\t} else if user != nil {\n\t\t\tinfo.GithubUsername = user\n\t\t}\n\t}()\n\n\t// Test SSH key against gitlab.com\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tuser, err := analyzeGitlabUser(ctx, parsedKey)\n\t\tif err != nil {\n\t\t\tanalyzerErrors.Add(err)\n\t\t} else if user != nil {\n\t\t\tinfo.GitlabUsername = user\n\t\t}\n\t}()\n\twg.Wait()\n\n\tif len(analyzerErrors.Errors) == 3 {\n\t\treturn nil, fmt.Errorf(\"analyzer failures: %s\", strings.Join(analyzerErrors.Errors, \", \"))\n\t}\n\n\treturn info, nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tif cfg.LoggingEnabled {\n\t\tcolor.Red(\"[x] Logging is not supported for this analyzer.\")\n\t\treturn\n\t}\n\n\ttoken := privatekey.Normalize(key)\n\tif len(token) < 64 {\n\t\tcolor.Red(\"[x] Error: Invalid Private Key\")\n\t\treturn\n\t}\n\n\t// key entered through command line may have spaces instead of newlines, replace them\n\ttoken = replaceSpacesWithNewlines(token)\n\n\tinfo, err := AnalyzePermissions(context.Background(), cfg, token)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error: %s\", err.Error())\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid Private Key\\n\\n\")\n\n\tif info.GithubUsername == nil && info.GitlabUsername == nil && info.TLSCertificateResult == nil {\n\t\tcolor.Yellow(\"[i] Insufficient information returned from fingerprint analysis. No permissions found.\")\n\t\treturn\n\t}\n\n\tif info.GithubUsername != nil {\n\t\tcolor.Yellow(\"[i] GitHub Details:\")\n\t\tprintUserInfo(*info.GithubUsername)\n\t}\n\tif info.GitlabUsername != nil {\n\t\tcolor.Yellow(\"[i] GitLab Details:\")\n\t\tprintUserInfo(*info.GitlabUsername)\n\t}\n\tif info.TLSCertificateResult != nil {\n\t\tprintTLSCertificateResult(info.TLSCertificateResult)\n\t}\n\n}\n\nfunc printUserInfo(username string) {\n\tcolor.Yellow(\"[i] Username: %s\", username)\n\tcolor.Yellow(\"[i] Permissions: %s\\n\\n\", color.GreenString(\"Clone/Push\"))\n}\n\nfunc printTLSCertificateResult(result *privatekey.DriftwoodResult) {\n\tcolor.Yellow(\"[i] TLS Certificate Details:\")\n\tfmt.Print(\"\\n\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(\n\t\ttable.Row{\"Subject Key ID\", \"Subject Name\", \"Subject Organization\", \"Permissions\", \"Expiration Date\", \"Domains\"})\n\tgreen := color.New(color.FgGreen).SprintFunc()\n\tfor _, certificateResult := range result.CertificateResults {\n\t\tt.AppendRow([]interface{}{\n\t\t\tgreen(certificateResult.SubjectKeyID),\n\t\t\tgreen(certificateResult.SubjectName),\n\t\t\tgreen(strings.Join(certificateResult.SubjectOrganization, \", \")),\n\t\t\tgreen(strings.Join(append(certificateResult.KeyUsages, certificateResult.ExtendedKeyUsages...), \", \")),\n\t\t\tgreen(certificateResult.ExpirationTimestamp.Format(time.RFC3339)),\n\t\t\tgreen(strings.Join(certificateResult.Domains, \", \")),\n\t\t})\n\t}\n\tt.Render()\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType:       analyzers.AnalyzerTypePrivateKey,\n\t\tMetadata:           nil,\n\t\tBindings:           []analyzers.Binding{},\n\t\tUnboundedResources: []analyzers.Resource{},\n\t}\n\n\tif info.TLSCertificateResult != nil {\n\t\tbounded, unbounded := bakeTLSResources(info.TLSCertificateResult)\n\t\tresult.Bindings = append(result.Bindings, bounded...)\n\t\tresult.UnboundedResources = append(result.UnboundedResources, unbounded...)\n\t}\n\n\tif info.GithubUsername != nil {\n\t\tresult.Bindings = append(result.Bindings, bakeGithubResources(info.GithubUsername)...)\n\t}\n\n\tif info.GitlabUsername != nil {\n\t\tresult.Bindings = append(result.Bindings, bakeGitlabResources(info.GitlabUsername)...)\n\t}\n\n\treturn &result\n}\n\nfunc bakeGithubResources(username *string) []analyzers.Binding {\n\tresource := &analyzers.Resource{\n\t\tName:               *username,\n\t\tFullyQualifiedName: fmt.Sprintf(\"github.com/user/%s\", *username),\n\t\tType:               \"user\", // always user ???\n\t}\n\n\tpermissions := []analyzers.Permission{\n\t\t{Value: PermissionStrings[Clone], Parent: nil},\n\t\t{Value: PermissionStrings[Push], Parent: nil},\n\t}\n\n\treturn analyzers.BindAllPermissions(*resource, permissions...)\n}\n\nfunc bakeGitlabResources(username *string) []analyzers.Binding {\n\tresource := &analyzers.Resource{\n\t\tName:               *username,\n\t\tFullyQualifiedName: fmt.Sprintf(\"gitlab.com/user/%s\", *username),\n\t\tType:               \"user\", // always user ???\n\t}\n\n\tpermissions := []analyzers.Permission{\n\t\t{Value: PermissionStrings[Clone], Parent: nil},\n\t\t{Value: PermissionStrings[Push], Parent: nil},\n\t}\n\n\treturn analyzers.BindAllPermissions(*resource, permissions...)\n}\n\nfunc bakeTLSResources(result *privatekey.DriftwoodResult) ([]analyzers.Binding, []analyzers.Resource) {\n\n\tunboundedResources := make([]analyzers.Resource, 0, len(result.CertificateResults))\n\tboundedResources := make([]analyzers.Binding, 0, len(result.CertificateResults))\n\n\t// iterate result.CertificateResults\n\tfor _, cert := range result.CertificateResults {\n\t\tif cert.SubjectName == \"\" && cert.SubjectKeyID == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tresource := &analyzers.Resource{\n\t\t\tName:               cert.SubjectName,\n\t\t\tFullyQualifiedName: fmt.Sprintf(\"%s/%s\", cert.SubjectKeyID, cert.SubjectName),\n\t\t\tType:               \"certificate\",\n\t\t}\n\t\tcertPermissions := append(cert.KeyUsages, cert.ExtendedKeyUsages...)\n\t\tpermissions := make([]analyzers.Permission, 0, len(certPermissions))\n\t\tfor _, perm := range certPermissions {\n\t\t\tperm, ok := StringToPermission[perm]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tpermissions = append(permissions, analyzers.Permission{\n\t\t\t\tValue:  PermissionStrings[perm],\n\t\t\t\tParent: nil,\n\t\t\t})\n\t\t}\n\n\t\tif len(permissions) > 0 {\n\t\t\t// bind all permissions with resources\n\t\t\tboundedResources = append(boundedResources, analyzers.BindAllPermissions(*resource, permissions...)...)\n\t\t} else {\n\t\t\tunboundedResources = append(unboundedResources, *resource)\n\t\t}\n\n\t}\n\n\treturn boundedResources, unboundedResources\n}\n\nfunc analyzeFingerprint(ctx context.Context, fingerprint string) (*privatekey.DriftwoodResult, error) {\n\n\tresult, err := privatekey.LookupFingerprint(ctx, fingerprint)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(result.CertificateResults) == 0 {\n\t\treturn nil, nil\n\t}\n\treturn result, nil\n}\n\nfunc analyzeGithubUser(ctx context.Context, parsedKey any) (*string, error) {\n\treturn privatekey.VerifyGitHubUser(ctx, parsedKey)\n}\n\nfunc analyzeGitlabUser(ctx context.Context, parsedKey any) (*string, error) {\n\treturn privatekey.VerifyGitLabUser(ctx, parsedKey)\n}\n\n// replaceSpacesWithNewlines extracts the base64 part, replaces spaces with newlines if needed, and reconstructs the key.\nfunc replaceSpacesWithNewlines(privateKey string) string {\n\t// Regex pattern to extract the key content\n\tre := regexp.MustCompile(`(?i)(-----\\s*BEGIN[ A-Z0-9_-]*PRIVATE KEY\\s*-----)\\s*([\\s\\S]*?)\\s*(-----\\s*END[ A-Z0-9_-]*PRIVATE KEY\\s*-----)`)\n\n\t// Find matches\n\tmatches := re.FindStringSubmatch(privateKey)\n\tif len(matches) != 4 {\n\t\t// no need to process\n\t\treturn privateKey\n\t}\n\n\theader := matches[1]     // BEGIN line\n\tbase64Part := matches[2] // Base64 content\n\tfooter := matches[3]     // END line\n\n\t// Replace spaces with newlines\n\tformattedBase64 := strings.ReplaceAll(base64Part, \" \", \"\\n\")\n\n\t// Reconstruct the private key\n\treturn fmt.Sprintf(\"%s\\n%s\\n%s\", header, formattedBase64, footer)\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/privatekey/privatekey_test.go",
    "content": "package privatekey\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*10)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tprivateKey := testSecrets.MustGetField(\"PRIVATEKEY_TLS\")\n\n\ttests := []struct {\n\t\tname     string\n\t\tkey      string\n\t\tstoreUrl string\n\t\twant     string\n\t\twantErr  bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid TLS key\",\n\t\t\tkey:     privateKey,\n\t\t\twant:    string(expectedOutput),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/sendgrid/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage sendgrid\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    AccessSettingsActivityRead Permission = iota\n    AccessSettingsWhitelistCreate Permission = iota\n    AccessSettingsWhitelistDelete Permission = iota\n    AccessSettingsWhitelistRead Permission = iota\n    AccessSettingsWhitelistUpdate Permission = iota\n    AlertsCreate Permission = iota\n    AlertsDelete Permission = iota\n    AlertsRead Permission = iota\n    AlertsUpdate Permission = iota\n    ApiKeysCreate Permission = iota\n    ApiKeysDelete Permission = iota\n    ApiKeysRead Permission = iota\n    ApiKeysUpdate Permission = iota\n    AsmGroupsCreate Permission = iota\n    AsmGroupsDelete Permission = iota\n    AsmGroupsRead Permission = iota\n    AsmGroupsUpdate Permission = iota\n    BillingCreate Permission = iota\n    BillingDelete Permission = iota\n    BillingRead Permission = iota\n    BillingUpdate Permission = iota\n    BrowsersStatsRead Permission = iota\n    CategoriesCreate Permission = iota\n    CategoriesDelete Permission = iota\n    CategoriesRead Permission = iota\n    CategoriesStatsRead Permission = iota\n    CategoriesStatsSumsRead Permission = iota\n    CategoriesUpdate Permission = iota\n    ClientsDesktopStatsRead Permission = iota\n    ClientsPhoneStatsRead Permission = iota\n    ClientsStatsRead Permission = iota\n    ClientsTabletStatsRead Permission = iota\n    ClientsWebmailStatsRead Permission = iota\n    DevicesStatsRead Permission = iota\n    EmailActivityRead Permission = iota\n    GeoStatsRead Permission = iota\n    IpsAssignedRead Permission = iota\n    IpsPoolsCreate Permission = iota\n    IpsPoolsDelete Permission = iota\n    IpsPoolsIpsCreate Permission = iota\n    IpsPoolsIpsDelete Permission = iota\n    IpsPoolsIpsRead Permission = iota\n    IpsPoolsIpsUpdate Permission = iota\n    IpsPoolsRead Permission = iota\n    IpsPoolsUpdate Permission = iota\n    IpsRead Permission = iota\n    IpsWarmupCreate Permission = iota\n    IpsWarmupDelete Permission = iota\n    IpsWarmupRead Permission = iota\n    IpsWarmupUpdate Permission = iota\n    MailSettingsAddressWhitelistRead Permission = iota\n    MailSettingsAddressWhitelistUpdate Permission = iota\n    MailSettingsBouncePurgeRead Permission = iota\n    MailSettingsBouncePurgeUpdate Permission = iota\n    MailSettingsFooterRead Permission = iota\n    MailSettingsFooterUpdate Permission = iota\n    MailSettingsForwardBounceRead Permission = iota\n    MailSettingsForwardBounceUpdate Permission = iota\n    MailSettingsForwardSpamRead Permission = iota\n    MailSettingsForwardSpamUpdate Permission = iota\n    MailSettingsPlainContentRead Permission = iota\n    MailSettingsPlainContentUpdate Permission = iota\n    MailSettingsRead Permission = iota\n    MailSettingsTemplateRead Permission = iota\n    MailSettingsTemplateUpdate Permission = iota\n    MailBatchCreate Permission = iota\n    MailBatchDelete Permission = iota\n    MailBatchRead Permission = iota\n    MailBatchUpdate Permission = iota\n    MailSend Permission = iota\n    MailboxProvidersStatsRead Permission = iota\n    MarketingCampaignsCreate Permission = iota\n    MarketingCampaignsDelete Permission = iota\n    MarketingCampaignsRead Permission = iota\n    MarketingCampaignsUpdate Permission = iota\n    PartnerSettingsNewRelicRead Permission = iota\n    PartnerSettingsNewRelicUpdate Permission = iota\n    PartnerSettingsRead Permission = iota\n    StatsGlobalRead Permission = iota\n    StatsRead Permission = iota\n    SubusersCreate Permission = iota\n    SubusersCreditsCreate Permission = iota\n    SubusersCreditsDelete Permission = iota\n    SubusersCreditsRead Permission = iota\n    SubusersCreditsRemainingCreate Permission = iota\n    SubusersCreditsRemainingDelete Permission = iota\n    SubusersCreditsRemainingRead Permission = iota\n    SubusersCreditsRemainingUpdate Permission = iota\n    SubusersCreditsUpdate Permission = iota\n    SubusersDelete Permission = iota\n    SubusersMonitorCreate Permission = iota\n    SubusersMonitorDelete Permission = iota\n    SubusersMonitorRead Permission = iota\n    SubusersMonitorUpdate Permission = iota\n    SubusersRead Permission = iota\n    SubusersReputationsRead Permission = iota\n    SubusersStatsMonthlyRead Permission = iota\n    SubusersStatsRead Permission = iota\n    SubusersStatsSumsRead Permission = iota\n    SubusersSummaryRead Permission = iota\n    SubusersUpdate Permission = iota\n    SuppressionBlocksCreate Permission = iota\n    SuppressionBlocksDelete Permission = iota\n    SuppressionBlocksRead Permission = iota\n    SuppressionBlocksUpdate Permission = iota\n    SuppressionBouncesCreate Permission = iota\n    SuppressionBouncesDelete Permission = iota\n    SuppressionBouncesRead Permission = iota\n    SuppressionBouncesUpdate Permission = iota\n    SuppressionCreate Permission = iota\n    SuppressionDelete Permission = iota\n    SuppressionInvalidEmailsCreate Permission = iota\n    SuppressionInvalidEmailsDelete Permission = iota\n    SuppressionInvalidEmailsRead Permission = iota\n    SuppressionInvalidEmailsUpdate Permission = iota\n    SuppressionRead Permission = iota\n    SuppressionSpamReportsCreate Permission = iota\n    SuppressionSpamReportsDelete Permission = iota\n    SuppressionSpamReportsRead Permission = iota\n    SuppressionSpamReportsUpdate Permission = iota\n    SuppressionUnsubscribesCreate Permission = iota\n    SuppressionUnsubscribesDelete Permission = iota\n    SuppressionUnsubscribesRead Permission = iota\n    SuppressionUnsubscribesUpdate Permission = iota\n    SuppressionUpdate Permission = iota\n    TeammatesCreate Permission = iota\n    TeammatesRead Permission = iota\n    TeammatesUpdate Permission = iota\n    TeammatesDelete Permission = iota\n    TemplatesCreate Permission = iota\n    TemplatesDelete Permission = iota\n    TemplatesRead Permission = iota\n    TemplatesUpdate Permission = iota\n    TemplatesVersionsActivateCreate Permission = iota\n    TemplatesVersionsActivateDelete Permission = iota\n    TemplatesVersionsActivateRead Permission = iota\n    TemplatesVersionsActivateUpdate Permission = iota\n    TemplatesVersionsCreate Permission = iota\n    TemplatesVersionsDelete Permission = iota\n    TemplatesVersionsRead Permission = iota\n    TemplatesVersionsUpdate Permission = iota\n    TrackingSettingsClickRead Permission = iota\n    TrackingSettingsClickUpdate Permission = iota\n    TrackingSettingsGoogleAnalyticsRead Permission = iota\n    TrackingSettingsGoogleAnalyticsUpdate Permission = iota\n    TrackingSettingsOpenRead Permission = iota\n    TrackingSettingsOpenUpdate Permission = iota\n    TrackingSettingsRead Permission = iota\n    TrackingSettingsSubscriptionRead Permission = iota\n    TrackingSettingsSubscriptionUpdate Permission = iota\n    UserAccountRead Permission = iota\n    UserCreditsRead Permission = iota\n    UserEmailCreate Permission = iota\n    UserEmailDelete Permission = iota\n    UserEmailRead Permission = iota\n    UserEmailUpdate Permission = iota\n    UserMultifactorAuthenticationCreate Permission = iota\n    UserMultifactorAuthenticationDelete Permission = iota\n    UserMultifactorAuthenticationRead Permission = iota\n    UserMultifactorAuthenticationUpdate Permission = iota\n    UserPasswordRead Permission = iota\n    UserPasswordUpdate Permission = iota\n    UserProfileRead Permission = iota\n    UserProfileUpdate Permission = iota\n    UserScheduledSendsCreate Permission = iota\n    UserScheduledSendsDelete Permission = iota\n    UserScheduledSendsRead Permission = iota\n    UserScheduledSendsUpdate Permission = iota\n    UserSettingsEnforcedTlsRead Permission = iota\n    UserSettingsEnforcedTlsUpdate Permission = iota\n    UserTimezoneRead Permission = iota\n    UserUsernameRead Permission = iota\n    UserUsernameUpdate Permission = iota\n    UserWebhooksEventSettingsRead Permission = iota\n    UserWebhooksEventSettingsUpdate Permission = iota\n    UserWebhooksEventTestCreate Permission = iota\n    UserWebhooksEventTestRead Permission = iota\n    UserWebhooksEventTestUpdate Permission = iota\n    UserWebhooksParseSettingsCreate Permission = iota\n    UserWebhooksParseSettingsDelete Permission = iota\n    UserWebhooksParseSettingsRead Permission = iota\n    UserWebhooksParseSettingsUpdate Permission = iota\n    UserWebhooksParseStatsRead Permission = iota\n    WhitelabelCreate Permission = iota\n    WhitelabelDelete Permission = iota\n    WhitelabelRead Permission = iota\n    WhitelabelUpdate Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        AccessSettingsActivityRead: \"access_settings.activity.read\",\n        AccessSettingsWhitelistCreate: \"access_settings.whitelist.create\",\n        AccessSettingsWhitelistDelete: \"access_settings.whitelist.delete\",\n        AccessSettingsWhitelistRead: \"access_settings.whitelist.read\",\n        AccessSettingsWhitelistUpdate: \"access_settings.whitelist.update\",\n        AlertsCreate: \"alerts.create\",\n        AlertsDelete: \"alerts.delete\",\n        AlertsRead: \"alerts.read\",\n        AlertsUpdate: \"alerts.update\",\n        ApiKeysCreate: \"api_keys.create\",\n        ApiKeysDelete: \"api_keys.delete\",\n        ApiKeysRead: \"api_keys.read\",\n        ApiKeysUpdate: \"api_keys.update\",\n        AsmGroupsCreate: \"asm.groups.create\",\n        AsmGroupsDelete: \"asm.groups.delete\",\n        AsmGroupsRead: \"asm.groups.read\",\n        AsmGroupsUpdate: \"asm.groups.update\",\n        BillingCreate: \"billing.create\",\n        BillingDelete: \"billing.delete\",\n        BillingRead: \"billing.read\",\n        BillingUpdate: \"billing.update\",\n        BrowsersStatsRead: \"browsers.stats.read\",\n        CategoriesCreate: \"categories.create\",\n        CategoriesDelete: \"categories.delete\",\n        CategoriesRead: \"categories.read\",\n        CategoriesStatsRead: \"categories.stats.read\",\n        CategoriesStatsSumsRead: \"categories.stats.sums.read\",\n        CategoriesUpdate: \"categories.update\",\n        ClientsDesktopStatsRead: \"clients.desktop.stats.read\",\n        ClientsPhoneStatsRead: \"clients.phone.stats.read\",\n        ClientsStatsRead: \"clients.stats.read\",\n        ClientsTabletStatsRead: \"clients.tablet.stats.read\",\n        ClientsWebmailStatsRead: \"clients.webmail.stats.read\",\n        DevicesStatsRead: \"devices.stats.read\",\n        EmailActivityRead: \"email_activity.read\",\n        GeoStatsRead: \"geo.stats.read\",\n        IpsAssignedRead: \"ips.assigned.read\",\n        IpsPoolsCreate: \"ips.pools.create\",\n        IpsPoolsDelete: \"ips.pools.delete\",\n        IpsPoolsIpsCreate: \"ips.pools.ips.create\",\n        IpsPoolsIpsDelete: \"ips.pools.ips.delete\",\n        IpsPoolsIpsRead: \"ips.pools.ips.read\",\n        IpsPoolsIpsUpdate: \"ips.pools.ips.update\",\n        IpsPoolsRead: \"ips.pools.read\",\n        IpsPoolsUpdate: \"ips.pools.update\",\n        IpsRead: \"ips.read\",\n        IpsWarmupCreate: \"ips.warmup.create\",\n        IpsWarmupDelete: \"ips.warmup.delete\",\n        IpsWarmupRead: \"ips.warmup.read\",\n        IpsWarmupUpdate: \"ips.warmup.update\",\n        MailSettingsAddressWhitelistRead: \"mail_settings.address_whitelist.read\",\n        MailSettingsAddressWhitelistUpdate: \"mail_settings.address_whitelist.update\",\n        MailSettingsBouncePurgeRead: \"mail_settings.bounce_purge.read\",\n        MailSettingsBouncePurgeUpdate: \"mail_settings.bounce_purge.update\",\n        MailSettingsFooterRead: \"mail_settings.footer.read\",\n        MailSettingsFooterUpdate: \"mail_settings.footer.update\",\n        MailSettingsForwardBounceRead: \"mail_settings.forward_bounce.read\",\n        MailSettingsForwardBounceUpdate: \"mail_settings.forward_bounce.update\",\n        MailSettingsForwardSpamRead: \"mail_settings.forward_spam.read\",\n        MailSettingsForwardSpamUpdate: \"mail_settings.forward_spam.update\",\n        MailSettingsPlainContentRead: \"mail_settings.plain_content.read\",\n        MailSettingsPlainContentUpdate: \"mail_settings.plain_content.update\",\n        MailSettingsRead: \"mail_settings.read\",\n        MailSettingsTemplateRead: \"mail_settings.template.read\",\n        MailSettingsTemplateUpdate: \"mail_settings.template.update\",\n        MailBatchCreate: \"mail.batch.create\",\n        MailBatchDelete: \"mail.batch.delete\",\n        MailBatchRead: \"mail.batch.read\",\n        MailBatchUpdate: \"mail.batch.update\",\n        MailSend: \"mail.send\",\n        MailboxProvidersStatsRead: \"mailbox_providers.stats.read\",\n        MarketingCampaignsCreate: \"marketing_campaigns.create\",\n        MarketingCampaignsDelete: \"marketing_campaigns.delete\",\n        MarketingCampaignsRead: \"marketing_campaigns.read\",\n        MarketingCampaignsUpdate: \"marketing_campaigns.update\",\n        PartnerSettingsNewRelicRead: \"partner_settings.new_relic.read\",\n        PartnerSettingsNewRelicUpdate: \"partner_settings.new_relic.update\",\n        PartnerSettingsRead: \"partner_settings.read\",\n        StatsGlobalRead: \"stats.global.read\",\n        StatsRead: \"stats.read\",\n        SubusersCreate: \"subusers.create\",\n        SubusersCreditsCreate: \"subusers.credits.create\",\n        SubusersCreditsDelete: \"subusers.credits.delete\",\n        SubusersCreditsRead: \"subusers.credits.read\",\n        SubusersCreditsRemainingCreate: \"subusers.credits.remaining.create\",\n        SubusersCreditsRemainingDelete: \"subusers.credits.remaining.delete\",\n        SubusersCreditsRemainingRead: \"subusers.credits.remaining.read\",\n        SubusersCreditsRemainingUpdate: \"subusers.credits.remaining.update\",\n        SubusersCreditsUpdate: \"subusers.credits.update\",\n        SubusersDelete: \"subusers.delete\",\n        SubusersMonitorCreate: \"subusers.monitor.create\",\n        SubusersMonitorDelete: \"subusers.monitor.delete\",\n        SubusersMonitorRead: \"subusers.monitor.read\",\n        SubusersMonitorUpdate: \"subusers.monitor.update\",\n        SubusersRead: \"subusers.read\",\n        SubusersReputationsRead: \"subusers.reputations.read\",\n        SubusersStatsMonthlyRead: \"subusers.stats.monthly.read\",\n        SubusersStatsRead: \"subusers.stats.read\",\n        SubusersStatsSumsRead: \"subusers.stats.sums.read\",\n        SubusersSummaryRead: \"subusers.summary.read\",\n        SubusersUpdate: \"subusers.update\",\n        SuppressionBlocksCreate: \"suppression.blocks.create\",\n        SuppressionBlocksDelete: \"suppression.blocks.delete\",\n        SuppressionBlocksRead: \"suppression.blocks.read\",\n        SuppressionBlocksUpdate: \"suppression.blocks.update\",\n        SuppressionBouncesCreate: \"suppression.bounces.create\",\n        SuppressionBouncesDelete: \"suppression.bounces.delete\",\n        SuppressionBouncesRead: \"suppression.bounces.read\",\n        SuppressionBouncesUpdate: \"suppression.bounces.update\",\n        SuppressionCreate: \"suppression.create\",\n        SuppressionDelete: \"suppression.delete\",\n        SuppressionInvalidEmailsCreate: \"suppression.invalid_emails.create\",\n        SuppressionInvalidEmailsDelete: \"suppression.invalid_emails.delete\",\n        SuppressionInvalidEmailsRead: \"suppression.invalid_emails.read\",\n        SuppressionInvalidEmailsUpdate: \"suppression.invalid_emails.update\",\n        SuppressionRead: \"suppression.read\",\n        SuppressionSpamReportsCreate: \"suppression.spam_reports.create\",\n        SuppressionSpamReportsDelete: \"suppression.spam_reports.delete\",\n        SuppressionSpamReportsRead: \"suppression.spam_reports.read\",\n        SuppressionSpamReportsUpdate: \"suppression.spam_reports.update\",\n        SuppressionUnsubscribesCreate: \"suppression.unsubscribes.create\",\n        SuppressionUnsubscribesDelete: \"suppression.unsubscribes.delete\",\n        SuppressionUnsubscribesRead: \"suppression.unsubscribes.read\",\n        SuppressionUnsubscribesUpdate: \"suppression.unsubscribes.update\",\n        SuppressionUpdate: \"suppression.update\",\n        TeammatesCreate: \"teammates.create\",\n        TeammatesRead: \"teammates.read\",\n        TeammatesUpdate: \"teammates.update\",\n        TeammatesDelete: \"teammates.delete\",\n        TemplatesCreate: \"templates.create\",\n        TemplatesDelete: \"templates.delete\",\n        TemplatesRead: \"templates.read\",\n        TemplatesUpdate: \"templates.update\",\n        TemplatesVersionsActivateCreate: \"templates.versions.activate.create\",\n        TemplatesVersionsActivateDelete: \"templates.versions.activate.delete\",\n        TemplatesVersionsActivateRead: \"templates.versions.activate.read\",\n        TemplatesVersionsActivateUpdate: \"templates.versions.activate.update\",\n        TemplatesVersionsCreate: \"templates.versions.create\",\n        TemplatesVersionsDelete: \"templates.versions.delete\",\n        TemplatesVersionsRead: \"templates.versions.read\",\n        TemplatesVersionsUpdate: \"templates.versions.update\",\n        TrackingSettingsClickRead: \"tracking_settings.click.read\",\n        TrackingSettingsClickUpdate: \"tracking_settings.click.update\",\n        TrackingSettingsGoogleAnalyticsRead: \"tracking_settings.google_analytics.read\",\n        TrackingSettingsGoogleAnalyticsUpdate: \"tracking_settings.google_analytics.update\",\n        TrackingSettingsOpenRead: \"tracking_settings.open.read\",\n        TrackingSettingsOpenUpdate: \"tracking_settings.open.update\",\n        TrackingSettingsRead: \"tracking_settings.read\",\n        TrackingSettingsSubscriptionRead: \"tracking_settings.subscription.read\",\n        TrackingSettingsSubscriptionUpdate: \"tracking_settings.subscription.update\",\n        UserAccountRead: \"user.account.read\",\n        UserCreditsRead: \"user.credits.read\",\n        UserEmailCreate: \"user.email.create\",\n        UserEmailDelete: \"user.email.delete\",\n        UserEmailRead: \"user.email.read\",\n        UserEmailUpdate: \"user.email.update\",\n        UserMultifactorAuthenticationCreate: \"user.multifactor_authentication.create\",\n        UserMultifactorAuthenticationDelete: \"user.multifactor_authentication.delete\",\n        UserMultifactorAuthenticationRead: \"user.multifactor_authentication.read\",\n        UserMultifactorAuthenticationUpdate: \"user.multifactor_authentication.update\",\n        UserPasswordRead: \"user.password.read\",\n        UserPasswordUpdate: \"user.password.update\",\n        UserProfileRead: \"user.profile.read\",\n        UserProfileUpdate: \"user.profile.update\",\n        UserScheduledSendsCreate: \"user.scheduled_sends.create\",\n        UserScheduledSendsDelete: \"user.scheduled_sends.delete\",\n        UserScheduledSendsRead: \"user.scheduled_sends.read\",\n        UserScheduledSendsUpdate: \"user.scheduled_sends.update\",\n        UserSettingsEnforcedTlsRead: \"user.settings.enforced_tls.read\",\n        UserSettingsEnforcedTlsUpdate: \"user.settings.enforced_tls.update\",\n        UserTimezoneRead: \"user.timezone.read\",\n        UserUsernameRead: \"user.username.read\",\n        UserUsernameUpdate: \"user.username.update\",\n        UserWebhooksEventSettingsRead: \"user.webhooks.event.settings.read\",\n        UserWebhooksEventSettingsUpdate: \"user.webhooks.event.settings.update\",\n        UserWebhooksEventTestCreate: \"user.webhooks.event.test.create\",\n        UserWebhooksEventTestRead: \"user.webhooks.event.test.read\",\n        UserWebhooksEventTestUpdate: \"user.webhooks.event.test.update\",\n        UserWebhooksParseSettingsCreate: \"user.webhooks.parse.settings.create\",\n        UserWebhooksParseSettingsDelete: \"user.webhooks.parse.settings.delete\",\n        UserWebhooksParseSettingsRead: \"user.webhooks.parse.settings.read\",\n        UserWebhooksParseSettingsUpdate: \"user.webhooks.parse.settings.update\",\n        UserWebhooksParseStatsRead: \"user.webhooks.parse.stats.read\",\n        WhitelabelCreate: \"whitelabel.create\",\n        WhitelabelDelete: \"whitelabel.delete\",\n        WhitelabelRead: \"whitelabel.read\",\n        WhitelabelUpdate: \"whitelabel.update\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"access_settings.activity.read\": AccessSettingsActivityRead,\n        \"access_settings.whitelist.create\": AccessSettingsWhitelistCreate,\n        \"access_settings.whitelist.delete\": AccessSettingsWhitelistDelete,\n        \"access_settings.whitelist.read\": AccessSettingsWhitelistRead,\n        \"access_settings.whitelist.update\": AccessSettingsWhitelistUpdate,\n        \"alerts.create\": AlertsCreate,\n        \"alerts.delete\": AlertsDelete,\n        \"alerts.read\": AlertsRead,\n        \"alerts.update\": AlertsUpdate,\n        \"api_keys.create\": ApiKeysCreate,\n        \"api_keys.delete\": ApiKeysDelete,\n        \"api_keys.read\": ApiKeysRead,\n        \"api_keys.update\": ApiKeysUpdate,\n        \"asm.groups.create\": AsmGroupsCreate,\n        \"asm.groups.delete\": AsmGroupsDelete,\n        \"asm.groups.read\": AsmGroupsRead,\n        \"asm.groups.update\": AsmGroupsUpdate,\n        \"billing.create\": BillingCreate,\n        \"billing.delete\": BillingDelete,\n        \"billing.read\": BillingRead,\n        \"billing.update\": BillingUpdate,\n        \"browsers.stats.read\": BrowsersStatsRead,\n        \"categories.create\": CategoriesCreate,\n        \"categories.delete\": CategoriesDelete,\n        \"categories.read\": CategoriesRead,\n        \"categories.stats.read\": CategoriesStatsRead,\n        \"categories.stats.sums.read\": CategoriesStatsSumsRead,\n        \"categories.update\": CategoriesUpdate,\n        \"clients.desktop.stats.read\": ClientsDesktopStatsRead,\n        \"clients.phone.stats.read\": ClientsPhoneStatsRead,\n        \"clients.stats.read\": ClientsStatsRead,\n        \"clients.tablet.stats.read\": ClientsTabletStatsRead,\n        \"clients.webmail.stats.read\": ClientsWebmailStatsRead,\n        \"devices.stats.read\": DevicesStatsRead,\n        \"email_activity.read\": EmailActivityRead,\n        \"geo.stats.read\": GeoStatsRead,\n        \"ips.assigned.read\": IpsAssignedRead,\n        \"ips.pools.create\": IpsPoolsCreate,\n        \"ips.pools.delete\": IpsPoolsDelete,\n        \"ips.pools.ips.create\": IpsPoolsIpsCreate,\n        \"ips.pools.ips.delete\": IpsPoolsIpsDelete,\n        \"ips.pools.ips.read\": IpsPoolsIpsRead,\n        \"ips.pools.ips.update\": IpsPoolsIpsUpdate,\n        \"ips.pools.read\": IpsPoolsRead,\n        \"ips.pools.update\": IpsPoolsUpdate,\n        \"ips.read\": IpsRead,\n        \"ips.warmup.create\": IpsWarmupCreate,\n        \"ips.warmup.delete\": IpsWarmupDelete,\n        \"ips.warmup.read\": IpsWarmupRead,\n        \"ips.warmup.update\": IpsWarmupUpdate,\n        \"mail_settings.address_whitelist.read\": MailSettingsAddressWhitelistRead,\n        \"mail_settings.address_whitelist.update\": MailSettingsAddressWhitelistUpdate,\n        \"mail_settings.bounce_purge.read\": MailSettingsBouncePurgeRead,\n        \"mail_settings.bounce_purge.update\": MailSettingsBouncePurgeUpdate,\n        \"mail_settings.footer.read\": MailSettingsFooterRead,\n        \"mail_settings.footer.update\": MailSettingsFooterUpdate,\n        \"mail_settings.forward_bounce.read\": MailSettingsForwardBounceRead,\n        \"mail_settings.forward_bounce.update\": MailSettingsForwardBounceUpdate,\n        \"mail_settings.forward_spam.read\": MailSettingsForwardSpamRead,\n        \"mail_settings.forward_spam.update\": MailSettingsForwardSpamUpdate,\n        \"mail_settings.plain_content.read\": MailSettingsPlainContentRead,\n        \"mail_settings.plain_content.update\": MailSettingsPlainContentUpdate,\n        \"mail_settings.read\": MailSettingsRead,\n        \"mail_settings.template.read\": MailSettingsTemplateRead,\n        \"mail_settings.template.update\": MailSettingsTemplateUpdate,\n        \"mail.batch.create\": MailBatchCreate,\n        \"mail.batch.delete\": MailBatchDelete,\n        \"mail.batch.read\": MailBatchRead,\n        \"mail.batch.update\": MailBatchUpdate,\n        \"mail.send\": MailSend,\n        \"mailbox_providers.stats.read\": MailboxProvidersStatsRead,\n        \"marketing_campaigns.create\": MarketingCampaignsCreate,\n        \"marketing_campaigns.delete\": MarketingCampaignsDelete,\n        \"marketing_campaigns.read\": MarketingCampaignsRead,\n        \"marketing_campaigns.update\": MarketingCampaignsUpdate,\n        \"partner_settings.new_relic.read\": PartnerSettingsNewRelicRead,\n        \"partner_settings.new_relic.update\": PartnerSettingsNewRelicUpdate,\n        \"partner_settings.read\": PartnerSettingsRead,\n        \"stats.global.read\": StatsGlobalRead,\n        \"stats.read\": StatsRead,\n        \"subusers.create\": SubusersCreate,\n        \"subusers.credits.create\": SubusersCreditsCreate,\n        \"subusers.credits.delete\": SubusersCreditsDelete,\n        \"subusers.credits.read\": SubusersCreditsRead,\n        \"subusers.credits.remaining.create\": SubusersCreditsRemainingCreate,\n        \"subusers.credits.remaining.delete\": SubusersCreditsRemainingDelete,\n        \"subusers.credits.remaining.read\": SubusersCreditsRemainingRead,\n        \"subusers.credits.remaining.update\": SubusersCreditsRemainingUpdate,\n        \"subusers.credits.update\": SubusersCreditsUpdate,\n        \"subusers.delete\": SubusersDelete,\n        \"subusers.monitor.create\": SubusersMonitorCreate,\n        \"subusers.monitor.delete\": SubusersMonitorDelete,\n        \"subusers.monitor.read\": SubusersMonitorRead,\n        \"subusers.monitor.update\": SubusersMonitorUpdate,\n        \"subusers.read\": SubusersRead,\n        \"subusers.reputations.read\": SubusersReputationsRead,\n        \"subusers.stats.monthly.read\": SubusersStatsMonthlyRead,\n        \"subusers.stats.read\": SubusersStatsRead,\n        \"subusers.stats.sums.read\": SubusersStatsSumsRead,\n        \"subusers.summary.read\": SubusersSummaryRead,\n        \"subusers.update\": SubusersUpdate,\n        \"suppression.blocks.create\": SuppressionBlocksCreate,\n        \"suppression.blocks.delete\": SuppressionBlocksDelete,\n        \"suppression.blocks.read\": SuppressionBlocksRead,\n        \"suppression.blocks.update\": SuppressionBlocksUpdate,\n        \"suppression.bounces.create\": SuppressionBouncesCreate,\n        \"suppression.bounces.delete\": SuppressionBouncesDelete,\n        \"suppression.bounces.read\": SuppressionBouncesRead,\n        \"suppression.bounces.update\": SuppressionBouncesUpdate,\n        \"suppression.create\": SuppressionCreate,\n        \"suppression.delete\": SuppressionDelete,\n        \"suppression.invalid_emails.create\": SuppressionInvalidEmailsCreate,\n        \"suppression.invalid_emails.delete\": SuppressionInvalidEmailsDelete,\n        \"suppression.invalid_emails.read\": SuppressionInvalidEmailsRead,\n        \"suppression.invalid_emails.update\": SuppressionInvalidEmailsUpdate,\n        \"suppression.read\": SuppressionRead,\n        \"suppression.spam_reports.create\": SuppressionSpamReportsCreate,\n        \"suppression.spam_reports.delete\": SuppressionSpamReportsDelete,\n        \"suppression.spam_reports.read\": SuppressionSpamReportsRead,\n        \"suppression.spam_reports.update\": SuppressionSpamReportsUpdate,\n        \"suppression.unsubscribes.create\": SuppressionUnsubscribesCreate,\n        \"suppression.unsubscribes.delete\": SuppressionUnsubscribesDelete,\n        \"suppression.unsubscribes.read\": SuppressionUnsubscribesRead,\n        \"suppression.unsubscribes.update\": SuppressionUnsubscribesUpdate,\n        \"suppression.update\": SuppressionUpdate,\n        \"teammates.create\": TeammatesCreate,\n        \"teammates.read\": TeammatesRead,\n        \"teammates.update\": TeammatesUpdate,\n        \"teammates.delete\": TeammatesDelete,\n        \"templates.create\": TemplatesCreate,\n        \"templates.delete\": TemplatesDelete,\n        \"templates.read\": TemplatesRead,\n        \"templates.update\": TemplatesUpdate,\n        \"templates.versions.activate.create\": TemplatesVersionsActivateCreate,\n        \"templates.versions.activate.delete\": TemplatesVersionsActivateDelete,\n        \"templates.versions.activate.read\": TemplatesVersionsActivateRead,\n        \"templates.versions.activate.update\": TemplatesVersionsActivateUpdate,\n        \"templates.versions.create\": TemplatesVersionsCreate,\n        \"templates.versions.delete\": TemplatesVersionsDelete,\n        \"templates.versions.read\": TemplatesVersionsRead,\n        \"templates.versions.update\": TemplatesVersionsUpdate,\n        \"tracking_settings.click.read\": TrackingSettingsClickRead,\n        \"tracking_settings.click.update\": TrackingSettingsClickUpdate,\n        \"tracking_settings.google_analytics.read\": TrackingSettingsGoogleAnalyticsRead,\n        \"tracking_settings.google_analytics.update\": TrackingSettingsGoogleAnalyticsUpdate,\n        \"tracking_settings.open.read\": TrackingSettingsOpenRead,\n        \"tracking_settings.open.update\": TrackingSettingsOpenUpdate,\n        \"tracking_settings.read\": TrackingSettingsRead,\n        \"tracking_settings.subscription.read\": TrackingSettingsSubscriptionRead,\n        \"tracking_settings.subscription.update\": TrackingSettingsSubscriptionUpdate,\n        \"user.account.read\": UserAccountRead,\n        \"user.credits.read\": UserCreditsRead,\n        \"user.email.create\": UserEmailCreate,\n        \"user.email.delete\": UserEmailDelete,\n        \"user.email.read\": UserEmailRead,\n        \"user.email.update\": UserEmailUpdate,\n        \"user.multifactor_authentication.create\": UserMultifactorAuthenticationCreate,\n        \"user.multifactor_authentication.delete\": UserMultifactorAuthenticationDelete,\n        \"user.multifactor_authentication.read\": UserMultifactorAuthenticationRead,\n        \"user.multifactor_authentication.update\": UserMultifactorAuthenticationUpdate,\n        \"user.password.read\": UserPasswordRead,\n        \"user.password.update\": UserPasswordUpdate,\n        \"user.profile.read\": UserProfileRead,\n        \"user.profile.update\": UserProfileUpdate,\n        \"user.scheduled_sends.create\": UserScheduledSendsCreate,\n        \"user.scheduled_sends.delete\": UserScheduledSendsDelete,\n        \"user.scheduled_sends.read\": UserScheduledSendsRead,\n        \"user.scheduled_sends.update\": UserScheduledSendsUpdate,\n        \"user.settings.enforced_tls.read\": UserSettingsEnforcedTlsRead,\n        \"user.settings.enforced_tls.update\": UserSettingsEnforcedTlsUpdate,\n        \"user.timezone.read\": UserTimezoneRead,\n        \"user.username.read\": UserUsernameRead,\n        \"user.username.update\": UserUsernameUpdate,\n        \"user.webhooks.event.settings.read\": UserWebhooksEventSettingsRead,\n        \"user.webhooks.event.settings.update\": UserWebhooksEventSettingsUpdate,\n        \"user.webhooks.event.test.create\": UserWebhooksEventTestCreate,\n        \"user.webhooks.event.test.read\": UserWebhooksEventTestRead,\n        \"user.webhooks.event.test.update\": UserWebhooksEventTestUpdate,\n        \"user.webhooks.parse.settings.create\": UserWebhooksParseSettingsCreate,\n        \"user.webhooks.parse.settings.delete\": UserWebhooksParseSettingsDelete,\n        \"user.webhooks.parse.settings.read\": UserWebhooksParseSettingsRead,\n        \"user.webhooks.parse.settings.update\": UserWebhooksParseSettingsUpdate,\n        \"user.webhooks.parse.stats.read\": UserWebhooksParseStatsRead,\n        \"whitelabel.create\": WhitelabelCreate,\n        \"whitelabel.delete\": WhitelabelDelete,\n        \"whitelabel.read\": WhitelabelRead,\n        \"whitelabel.update\": WhitelabelUpdate,\n    }\n\n    PermissionIDs = map[Permission]int{\n        AccessSettingsActivityRead: 1,\n        AccessSettingsWhitelistCreate: 2,\n        AccessSettingsWhitelistDelete: 3,\n        AccessSettingsWhitelistRead: 4,\n        AccessSettingsWhitelistUpdate: 5,\n        AlertsCreate: 6,\n        AlertsDelete: 7,\n        AlertsRead: 8,\n        AlertsUpdate: 9,\n        ApiKeysCreate: 10,\n        ApiKeysDelete: 11,\n        ApiKeysRead: 12,\n        ApiKeysUpdate: 13,\n        AsmGroupsCreate: 14,\n        AsmGroupsDelete: 15,\n        AsmGroupsRead: 16,\n        AsmGroupsUpdate: 17,\n        BillingCreate: 18,\n        BillingDelete: 19,\n        BillingRead: 20,\n        BillingUpdate: 21,\n        BrowsersStatsRead: 22,\n        CategoriesCreate: 23,\n        CategoriesDelete: 24,\n        CategoriesRead: 25,\n        CategoriesStatsRead: 26,\n        CategoriesStatsSumsRead: 27,\n        CategoriesUpdate: 28,\n        ClientsDesktopStatsRead: 29,\n        ClientsPhoneStatsRead: 30,\n        ClientsStatsRead: 31,\n        ClientsTabletStatsRead: 32,\n        ClientsWebmailStatsRead: 33,\n        DevicesStatsRead: 34,\n        EmailActivityRead: 35,\n        GeoStatsRead: 36,\n        IpsAssignedRead: 37,\n        IpsPoolsCreate: 38,\n        IpsPoolsDelete: 39,\n        IpsPoolsIpsCreate: 40,\n        IpsPoolsIpsDelete: 41,\n        IpsPoolsIpsRead: 42,\n        IpsPoolsIpsUpdate: 43,\n        IpsPoolsRead: 44,\n        IpsPoolsUpdate: 45,\n        IpsRead: 46,\n        IpsWarmupCreate: 47,\n        IpsWarmupDelete: 48,\n        IpsWarmupRead: 49,\n        IpsWarmupUpdate: 50,\n        MailSettingsAddressWhitelistRead: 51,\n        MailSettingsAddressWhitelistUpdate: 52,\n        MailSettingsBouncePurgeRead: 53,\n        MailSettingsBouncePurgeUpdate: 54,\n        MailSettingsFooterRead: 55,\n        MailSettingsFooterUpdate: 56,\n        MailSettingsForwardBounceRead: 57,\n        MailSettingsForwardBounceUpdate: 58,\n        MailSettingsForwardSpamRead: 59,\n        MailSettingsForwardSpamUpdate: 60,\n        MailSettingsPlainContentRead: 61,\n        MailSettingsPlainContentUpdate: 62,\n        MailSettingsRead: 63,\n        MailSettingsTemplateRead: 64,\n        MailSettingsTemplateUpdate: 65,\n        MailBatchCreate: 66,\n        MailBatchDelete: 67,\n        MailBatchRead: 68,\n        MailBatchUpdate: 69,\n        MailSend: 70,\n        MailboxProvidersStatsRead: 71,\n        MarketingCampaignsCreate: 72,\n        MarketingCampaignsDelete: 73,\n        MarketingCampaignsRead: 74,\n        MarketingCampaignsUpdate: 75,\n        PartnerSettingsNewRelicRead: 76,\n        PartnerSettingsNewRelicUpdate: 77,\n        PartnerSettingsRead: 78,\n        StatsGlobalRead: 79,\n        StatsRead: 80,\n        SubusersCreate: 81,\n        SubusersCreditsCreate: 82,\n        SubusersCreditsDelete: 83,\n        SubusersCreditsRead: 84,\n        SubusersCreditsRemainingCreate: 85,\n        SubusersCreditsRemainingDelete: 86,\n        SubusersCreditsRemainingRead: 87,\n        SubusersCreditsRemainingUpdate: 88,\n        SubusersCreditsUpdate: 89,\n        SubusersDelete: 90,\n        SubusersMonitorCreate: 91,\n        SubusersMonitorDelete: 92,\n        SubusersMonitorRead: 93,\n        SubusersMonitorUpdate: 94,\n        SubusersRead: 95,\n        SubusersReputationsRead: 96,\n        SubusersStatsMonthlyRead: 97,\n        SubusersStatsRead: 98,\n        SubusersStatsSumsRead: 99,\n        SubusersSummaryRead: 100,\n        SubusersUpdate: 101,\n        SuppressionBlocksCreate: 102,\n        SuppressionBlocksDelete: 103,\n        SuppressionBlocksRead: 104,\n        SuppressionBlocksUpdate: 105,\n        SuppressionBouncesCreate: 106,\n        SuppressionBouncesDelete: 107,\n        SuppressionBouncesRead: 108,\n        SuppressionBouncesUpdate: 109,\n        SuppressionCreate: 110,\n        SuppressionDelete: 111,\n        SuppressionInvalidEmailsCreate: 112,\n        SuppressionInvalidEmailsDelete: 113,\n        SuppressionInvalidEmailsRead: 114,\n        SuppressionInvalidEmailsUpdate: 115,\n        SuppressionRead: 116,\n        SuppressionSpamReportsCreate: 117,\n        SuppressionSpamReportsDelete: 118,\n        SuppressionSpamReportsRead: 119,\n        SuppressionSpamReportsUpdate: 120,\n        SuppressionUnsubscribesCreate: 121,\n        SuppressionUnsubscribesDelete: 122,\n        SuppressionUnsubscribesRead: 123,\n        SuppressionUnsubscribesUpdate: 124,\n        SuppressionUpdate: 125,\n        TeammatesCreate: 126,\n        TeammatesRead: 127,\n        TeammatesUpdate: 128,\n        TeammatesDelete: 129,\n        TemplatesCreate: 130,\n        TemplatesDelete: 131,\n        TemplatesRead: 132,\n        TemplatesUpdate: 133,\n        TemplatesVersionsActivateCreate: 134,\n        TemplatesVersionsActivateDelete: 135,\n        TemplatesVersionsActivateRead: 136,\n        TemplatesVersionsActivateUpdate: 137,\n        TemplatesVersionsCreate: 138,\n        TemplatesVersionsDelete: 139,\n        TemplatesVersionsRead: 140,\n        TemplatesVersionsUpdate: 141,\n        TrackingSettingsClickRead: 142,\n        TrackingSettingsClickUpdate: 143,\n        TrackingSettingsGoogleAnalyticsRead: 144,\n        TrackingSettingsGoogleAnalyticsUpdate: 145,\n        TrackingSettingsOpenRead: 146,\n        TrackingSettingsOpenUpdate: 147,\n        TrackingSettingsRead: 148,\n        TrackingSettingsSubscriptionRead: 149,\n        TrackingSettingsSubscriptionUpdate: 150,\n        UserAccountRead: 151,\n        UserCreditsRead: 152,\n        UserEmailCreate: 153,\n        UserEmailDelete: 154,\n        UserEmailRead: 155,\n        UserEmailUpdate: 156,\n        UserMultifactorAuthenticationCreate: 157,\n        UserMultifactorAuthenticationDelete: 158,\n        UserMultifactorAuthenticationRead: 159,\n        UserMultifactorAuthenticationUpdate: 160,\n        UserPasswordRead: 161,\n        UserPasswordUpdate: 162,\n        UserProfileRead: 163,\n        UserProfileUpdate: 164,\n        UserScheduledSendsCreate: 165,\n        UserScheduledSendsDelete: 166,\n        UserScheduledSendsRead: 167,\n        UserScheduledSendsUpdate: 168,\n        UserSettingsEnforcedTlsRead: 169,\n        UserSettingsEnforcedTlsUpdate: 170,\n        UserTimezoneRead: 171,\n        UserUsernameRead: 172,\n        UserUsernameUpdate: 173,\n        UserWebhooksEventSettingsRead: 174,\n        UserWebhooksEventSettingsUpdate: 175,\n        UserWebhooksEventTestCreate: 176,\n        UserWebhooksEventTestRead: 177,\n        UserWebhooksEventTestUpdate: 178,\n        UserWebhooksParseSettingsCreate: 179,\n        UserWebhooksParseSettingsDelete: 180,\n        UserWebhooksParseSettingsRead: 181,\n        UserWebhooksParseSettingsUpdate: 182,\n        UserWebhooksParseStatsRead: 183,\n        WhitelabelCreate: 184,\n        WhitelabelDelete: 185,\n        WhitelabelRead: 186,\n        WhitelabelUpdate: 187,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: AccessSettingsActivityRead,\n        2: AccessSettingsWhitelistCreate,\n        3: AccessSettingsWhitelistDelete,\n        4: AccessSettingsWhitelistRead,\n        5: AccessSettingsWhitelistUpdate,\n        6: AlertsCreate,\n        7: AlertsDelete,\n        8: AlertsRead,\n        9: AlertsUpdate,\n        10: ApiKeysCreate,\n        11: ApiKeysDelete,\n        12: ApiKeysRead,\n        13: ApiKeysUpdate,\n        14: AsmGroupsCreate,\n        15: AsmGroupsDelete,\n        16: AsmGroupsRead,\n        17: AsmGroupsUpdate,\n        18: BillingCreate,\n        19: BillingDelete,\n        20: BillingRead,\n        21: BillingUpdate,\n        22: BrowsersStatsRead,\n        23: CategoriesCreate,\n        24: CategoriesDelete,\n        25: CategoriesRead,\n        26: CategoriesStatsRead,\n        27: CategoriesStatsSumsRead,\n        28: CategoriesUpdate,\n        29: ClientsDesktopStatsRead,\n        30: ClientsPhoneStatsRead,\n        31: ClientsStatsRead,\n        32: ClientsTabletStatsRead,\n        33: ClientsWebmailStatsRead,\n        34: DevicesStatsRead,\n        35: EmailActivityRead,\n        36: GeoStatsRead,\n        37: IpsAssignedRead,\n        38: IpsPoolsCreate,\n        39: IpsPoolsDelete,\n        40: IpsPoolsIpsCreate,\n        41: IpsPoolsIpsDelete,\n        42: IpsPoolsIpsRead,\n        43: IpsPoolsIpsUpdate,\n        44: IpsPoolsRead,\n        45: IpsPoolsUpdate,\n        46: IpsRead,\n        47: IpsWarmupCreate,\n        48: IpsWarmupDelete,\n        49: IpsWarmupRead,\n        50: IpsWarmupUpdate,\n        51: MailSettingsAddressWhitelistRead,\n        52: MailSettingsAddressWhitelistUpdate,\n        53: MailSettingsBouncePurgeRead,\n        54: MailSettingsBouncePurgeUpdate,\n        55: MailSettingsFooterRead,\n        56: MailSettingsFooterUpdate,\n        57: MailSettingsForwardBounceRead,\n        58: MailSettingsForwardBounceUpdate,\n        59: MailSettingsForwardSpamRead,\n        60: MailSettingsForwardSpamUpdate,\n        61: MailSettingsPlainContentRead,\n        62: MailSettingsPlainContentUpdate,\n        63: MailSettingsRead,\n        64: MailSettingsTemplateRead,\n        65: MailSettingsTemplateUpdate,\n        66: MailBatchCreate,\n        67: MailBatchDelete,\n        68: MailBatchRead,\n        69: MailBatchUpdate,\n        70: MailSend,\n        71: MailboxProvidersStatsRead,\n        72: MarketingCampaignsCreate,\n        73: MarketingCampaignsDelete,\n        74: MarketingCampaignsRead,\n        75: MarketingCampaignsUpdate,\n        76: PartnerSettingsNewRelicRead,\n        77: PartnerSettingsNewRelicUpdate,\n        78: PartnerSettingsRead,\n        79: StatsGlobalRead,\n        80: StatsRead,\n        81: SubusersCreate,\n        82: SubusersCreditsCreate,\n        83: SubusersCreditsDelete,\n        84: SubusersCreditsRead,\n        85: SubusersCreditsRemainingCreate,\n        86: SubusersCreditsRemainingDelete,\n        87: SubusersCreditsRemainingRead,\n        88: SubusersCreditsRemainingUpdate,\n        89: SubusersCreditsUpdate,\n        90: SubusersDelete,\n        91: SubusersMonitorCreate,\n        92: SubusersMonitorDelete,\n        93: SubusersMonitorRead,\n        94: SubusersMonitorUpdate,\n        95: SubusersRead,\n        96: SubusersReputationsRead,\n        97: SubusersStatsMonthlyRead,\n        98: SubusersStatsRead,\n        99: SubusersStatsSumsRead,\n        100: SubusersSummaryRead,\n        101: SubusersUpdate,\n        102: SuppressionBlocksCreate,\n        103: SuppressionBlocksDelete,\n        104: SuppressionBlocksRead,\n        105: SuppressionBlocksUpdate,\n        106: SuppressionBouncesCreate,\n        107: SuppressionBouncesDelete,\n        108: SuppressionBouncesRead,\n        109: SuppressionBouncesUpdate,\n        110: SuppressionCreate,\n        111: SuppressionDelete,\n        112: SuppressionInvalidEmailsCreate,\n        113: SuppressionInvalidEmailsDelete,\n        114: SuppressionInvalidEmailsRead,\n        115: SuppressionInvalidEmailsUpdate,\n        116: SuppressionRead,\n        117: SuppressionSpamReportsCreate,\n        118: SuppressionSpamReportsDelete,\n        119: SuppressionSpamReportsRead,\n        120: SuppressionSpamReportsUpdate,\n        121: SuppressionUnsubscribesCreate,\n        122: SuppressionUnsubscribesDelete,\n        123: SuppressionUnsubscribesRead,\n        124: SuppressionUnsubscribesUpdate,\n        125: SuppressionUpdate,\n        126: TeammatesCreate,\n        127: TeammatesRead,\n        128: TeammatesUpdate,\n        129: TeammatesDelete,\n        130: TemplatesCreate,\n        131: TemplatesDelete,\n        132: TemplatesRead,\n        133: TemplatesUpdate,\n        134: TemplatesVersionsActivateCreate,\n        135: TemplatesVersionsActivateDelete,\n        136: TemplatesVersionsActivateRead,\n        137: TemplatesVersionsActivateUpdate,\n        138: TemplatesVersionsCreate,\n        139: TemplatesVersionsDelete,\n        140: TemplatesVersionsRead,\n        141: TemplatesVersionsUpdate,\n        142: TrackingSettingsClickRead,\n        143: TrackingSettingsClickUpdate,\n        144: TrackingSettingsGoogleAnalyticsRead,\n        145: TrackingSettingsGoogleAnalyticsUpdate,\n        146: TrackingSettingsOpenRead,\n        147: TrackingSettingsOpenUpdate,\n        148: TrackingSettingsRead,\n        149: TrackingSettingsSubscriptionRead,\n        150: TrackingSettingsSubscriptionUpdate,\n        151: UserAccountRead,\n        152: UserCreditsRead,\n        153: UserEmailCreate,\n        154: UserEmailDelete,\n        155: UserEmailRead,\n        156: UserEmailUpdate,\n        157: UserMultifactorAuthenticationCreate,\n        158: UserMultifactorAuthenticationDelete,\n        159: UserMultifactorAuthenticationRead,\n        160: UserMultifactorAuthenticationUpdate,\n        161: UserPasswordRead,\n        162: UserPasswordUpdate,\n        163: UserProfileRead,\n        164: UserProfileUpdate,\n        165: UserScheduledSendsCreate,\n        166: UserScheduledSendsDelete,\n        167: UserScheduledSendsRead,\n        168: UserScheduledSendsUpdate,\n        169: UserSettingsEnforcedTlsRead,\n        170: UserSettingsEnforcedTlsUpdate,\n        171: UserTimezoneRead,\n        172: UserUsernameRead,\n        173: UserUsernameUpdate,\n        174: UserWebhooksEventSettingsRead,\n        175: UserWebhooksEventSettingsUpdate,\n        176: UserWebhooksEventTestCreate,\n        177: UserWebhooksEventTestRead,\n        178: UserWebhooksEventTestUpdate,\n        179: UserWebhooksParseSettingsCreate,\n        180: UserWebhooksParseSettingsDelete,\n        181: UserWebhooksParseSettingsRead,\n        182: UserWebhooksParseSettingsUpdate,\n        183: UserWebhooksParseStatsRead,\n        184: WhitelabelCreate,\n        185: WhitelabelDelete,\n        186: WhitelabelRead,\n        187: WhitelabelUpdate,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/sendgrid/permissions.yaml",
    "content": "permissions:\n  - access_settings.activity.read\n  - access_settings.whitelist.create\n  - access_settings.whitelist.delete\n  - access_settings.whitelist.read\n  - access_settings.whitelist.update\n  - alerts.create\n  - alerts.delete\n  - alerts.read\n  - alerts.update\n  - api_keys.create\n  - api_keys.delete\n  - api_keys.read\n  - api_keys.update\n  - asm.groups.create\n  - asm.groups.delete\n  - asm.groups.read\n  - asm.groups.update\n  - billing.create\n  - billing.delete\n  - billing.read\n  - billing.update\n  - browsers.stats.read\n  - categories.create\n  - categories.delete\n  - categories.read\n  - categories.stats.read\n  - categories.stats.sums.read\n  - categories.update\n  - clients.desktop.stats.read\n  - clients.phone.stats.read\n  - clients.stats.read\n  - clients.tablet.stats.read\n  - clients.webmail.stats.read\n  - devices.stats.read\n  - email_activity.read\n  - geo.stats.read\n  - ips.assigned.read\n  - ips.pools.create\n  - ips.pools.delete\n  - ips.pools.ips.create\n  - ips.pools.ips.delete\n  - ips.pools.ips.read\n  - ips.pools.ips.update\n  - ips.pools.read\n  - ips.pools.update\n  - ips.read\n  - ips.warmup.create\n  - ips.warmup.delete\n  - ips.warmup.read\n  - ips.warmup.update\n  - mail_settings.address_whitelist.read\n  - mail_settings.address_whitelist.update\n  - mail_settings.bounce_purge.read\n  - mail_settings.bounce_purge.update\n  - mail_settings.footer.read\n  - mail_settings.footer.update\n  - mail_settings.forward_bounce.read\n  - mail_settings.forward_bounce.update\n  - mail_settings.forward_spam.read\n  - mail_settings.forward_spam.update\n  - mail_settings.plain_content.read\n  - mail_settings.plain_content.update\n  - mail_settings.read\n  - mail_settings.template.read\n  - mail_settings.template.update\n  - mail.batch.create\n  - mail.batch.delete\n  - mail.batch.read\n  - mail.batch.update\n  - mail.send\n  - mailbox_providers.stats.read\n  - marketing_campaigns.create\n  - marketing_campaigns.delete\n  - marketing_campaigns.read\n  - marketing_campaigns.update\n  - partner_settings.new_relic.read\n  - partner_settings.new_relic.update\n  - partner_settings.read\n  - stats.global.read\n  - stats.read\n  - subusers.create\n  - subusers.credits.create\n  - subusers.credits.delete\n  - subusers.credits.read\n  - subusers.credits.remaining.create\n  - subusers.credits.remaining.delete\n  - subusers.credits.remaining.read\n  - subusers.credits.remaining.update\n  - subusers.credits.update\n  - subusers.delete\n  - subusers.monitor.create\n  - subusers.monitor.delete\n  - subusers.monitor.read\n  - subusers.monitor.update\n  - subusers.read\n  - subusers.reputations.read\n  - subusers.stats.monthly.read\n  - subusers.stats.read\n  - subusers.stats.sums.read\n  - subusers.summary.read\n  - subusers.update\n  - suppression.blocks.create\n  - suppression.blocks.delete\n  - suppression.blocks.read\n  - suppression.blocks.update\n  - suppression.bounces.create\n  - suppression.bounces.delete\n  - suppression.bounces.read\n  - suppression.bounces.update\n  - suppression.create\n  - suppression.delete\n  - suppression.invalid_emails.create\n  - suppression.invalid_emails.delete\n  - suppression.invalid_emails.read\n  - suppression.invalid_emails.update\n  - suppression.read\n  - suppression.spam_reports.create\n  - suppression.spam_reports.delete\n  - suppression.spam_reports.read\n  - suppression.spam_reports.update\n  - suppression.unsubscribes.create\n  - suppression.unsubscribes.delete\n  - suppression.unsubscribes.read\n  - suppression.unsubscribes.update\n  - suppression.update\n  - teammates.create\n  - teammates.read\n  - teammates.update\n  - teammates.delete\n  - templates.create\n  - templates.delete\n  - templates.read\n  - templates.update\n  - templates.versions.activate.create\n  - templates.versions.activate.delete\n  - templates.versions.activate.read\n  - templates.versions.activate.update\n  - templates.versions.create\n  - templates.versions.delete\n  - templates.versions.read\n  - templates.versions.update\n  - tracking_settings.click.read\n  - tracking_settings.click.update\n  - tracking_settings.google_analytics.read\n  - tracking_settings.google_analytics.update\n  - tracking_settings.open.read\n  - tracking_settings.open.update\n  - tracking_settings.read\n  - tracking_settings.subscription.read\n  - tracking_settings.subscription.update\n  - user.account.read\n  - user.credits.read\n  - user.email.create\n  - user.email.delete\n  - user.email.read\n  - user.email.update\n  - user.multifactor_authentication.create\n  - user.multifactor_authentication.delete\n  - user.multifactor_authentication.read\n  - user.multifactor_authentication.update\n  - user.password.read\n  - user.password.update\n  - user.profile.read\n  - user.profile.update\n  - user.scheduled_sends.create\n  - user.scheduled_sends.delete\n  - user.scheduled_sends.read\n  - user.scheduled_sends.update\n  - user.settings.enforced_tls.read\n  - user.settings.enforced_tls.update\n  - user.timezone.read\n  - user.username.read\n  - user.username.update\n  - user.webhooks.event.settings.read\n  - user.webhooks.event.settings.update\n  - user.webhooks.event.test.create\n  - user.webhooks.event.test.read\n  - user.webhooks.event.test.update\n  - user.webhooks.parse.settings.create\n  - user.webhooks.parse.settings.delete\n  - user.webhooks.parse.settings.read\n  - user.webhooks.parse.settings.update\n  - user.webhooks.parse.stats.read\n  - whitelabel.create\n  - whitelabel.delete\n  - whitelabel.read\n  - whitelabel.update\n"
  },
  {
    "path": "pkg/analyzer/analyzers/sendgrid/result_output.json",
    "content": "{\n    \"AnalyzerType\": 16,\n    \"Bindings\": [\n        {\n            \"Resource\": {\n                \"Name\": \"API Keys\",\n                \"FullyQualifiedName\": \"API Keys\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"api_keys.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"API Keys\",\n                \"FullyQualifiedName\": \"API Keys\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"api_keys.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"API Keys\",\n                \"FullyQualifiedName\": \"API Keys\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"api_keys.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"API Keys\",\n                \"FullyQualifiedName\": \"API Keys\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"api_keys.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Account\",\n                \"FullyQualifiedName\": \"User Account/Account\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.account.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Address Allow List\",\n                \"FullyQualifiedName\": \"Mail Settings/Address Allow List\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"mail_settings.address_whitelist.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Address Allow List\",\n                \"FullyQualifiedName\": \"Mail Settings/Address Allow List\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"mail_settings.address_whitelist.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Alerts\",\n                \"FullyQualifiedName\": \"Alerts\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"alerts.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Alerts\",\n                \"FullyQualifiedName\": \"Alerts\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"alerts.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Alerts\",\n                \"FullyQualifiedName\": \"Alerts\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"alerts.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Alerts\",\n                \"FullyQualifiedName\": \"Alerts\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"alerts.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Bounce Purge\",\n                \"FullyQualifiedName\": \"Mail Settings/Bounce Purge\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"mail_settings.bounce_purge.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Bounce Purge\",\n                \"FullyQualifiedName\": \"Mail Settings/Bounce Purge\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"mail_settings.bounce_purge.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Browser Stats\",\n                \"FullyQualifiedName\": \"Stats/Browser Stats\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Stats\",\n                    \"FullyQualifiedName\": \"Stats\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"browsers.stats.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Category Management\",\n                \"FullyQualifiedName\": \"Category Management\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"categories.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Category Management\",\n                \"FullyQualifiedName\": \"Category Management\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"categories.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Category Management\",\n                \"FullyQualifiedName\": \"Category Management\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"categories.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Category Management\",\n                \"FullyQualifiedName\": \"Category Management\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"categories.stats.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Category Management\",\n                \"FullyQualifiedName\": \"Category Management\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"categories.stats.sums.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Category Management\",\n                \"FullyQualifiedName\": \"Category Management\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"categories.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Click Tracking\",\n                \"FullyQualifiedName\": \"Tracking/Click Tracking\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Tracking\",\n                    \"FullyQualifiedName\": \"Tracking\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"tracking_settings.click.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Click Tracking\",\n                \"FullyQualifiedName\": \"Tracking/Click Tracking\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Tracking\",\n                    \"FullyQualifiedName\": \"Tracking\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"tracking_settings.click.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Credits\",\n                \"FullyQualifiedName\": \"User Account/Credits\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.credits.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Email\",\n                \"FullyQualifiedName\": \"User Account/Email\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.email.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Email\",\n                \"FullyQualifiedName\": \"User Account/Email\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.email.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Email\",\n                \"FullyQualifiedName\": \"User Account/Email\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.email.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Email\",\n                \"FullyQualifiedName\": \"User Account/Email\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.email.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Email Clients and Devices\",\n                \"FullyQualifiedName\": \"Stats/Email Clients and Devices\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Stats\",\n                    \"FullyQualifiedName\": \"Stats\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"clients.desktop.stats.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Email Clients and Devices\",\n                \"FullyQualifiedName\": \"Stats/Email Clients and Devices\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Stats\",\n                    \"FullyQualifiedName\": \"Stats\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"clients.phone.stats.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Email Clients and Devices\",\n                \"FullyQualifiedName\": \"Stats/Email Clients and Devices\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Stats\",\n                    \"FullyQualifiedName\": \"Stats\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"clients.stats.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Email Clients and Devices\",\n                \"FullyQualifiedName\": \"Stats/Email Clients and Devices\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Stats\",\n                    \"FullyQualifiedName\": \"Stats\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"clients.tablet.stats.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Email Clients and Devices\",\n                \"FullyQualifiedName\": \"Stats/Email Clients and Devices\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Stats\",\n                    \"FullyQualifiedName\": \"Stats\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"clients.webmail.stats.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Email Clients and Devices\",\n                \"FullyQualifiedName\": \"Stats/Email Clients and Devices\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Stats\",\n                    \"FullyQualifiedName\": \"Stats\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"devices.stats.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Enforced TLS\",\n                \"FullyQualifiedName\": \"User Account/Enforced TLS\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.settings.enforced_tls.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Enforced TLS\",\n                \"FullyQualifiedName\": \"User Account/Enforced TLS\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.settings.enforced_tls.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Event Notification\",\n                \"FullyQualifiedName\": \"Mail Settings/Event Notification\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.webhooks.event.settings.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Event Notification\",\n                \"FullyQualifiedName\": \"Mail Settings/Event Notification\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.webhooks.event.settings.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Event Notification\",\n                \"FullyQualifiedName\": \"Mail Settings/Event Notification\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.webhooks.event.test.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Event Notification\",\n                \"FullyQualifiedName\": \"Mail Settings/Event Notification\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.webhooks.event.test.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Event Notification\",\n                \"FullyQualifiedName\": \"Mail Settings/Event Notification\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.webhooks.event.test.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Footer\",\n                \"FullyQualifiedName\": \"Mail Settings/Footer\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"mail_settings.footer.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Footer\",\n                \"FullyQualifiedName\": \"Mail Settings/Footer\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"mail_settings.footer.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Forward Bounce\",\n                \"FullyQualifiedName\": \"Mail Settings/Forward Bounce\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"mail_settings.forward_bounce.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Forward Bounce\",\n                \"FullyQualifiedName\": \"Mail Settings/Forward Bounce\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"mail_settings.forward_bounce.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Forward Spam\",\n                \"FullyQualifiedName\": \"Mail Settings/Forward Spam\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"mail_settings.forward_spam.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Forward Spam\",\n                \"FullyQualifiedName\": \"Mail Settings/Forward Spam\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"mail_settings.forward_spam.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Geographical\",\n                \"FullyQualifiedName\": \"Stats/Geographical\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Stats\",\n                    \"FullyQualifiedName\": \"Stats\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"geo.stats.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Global Stats\",\n                \"FullyQualifiedName\": \"Stats/Global Stats\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Stats\",\n                    \"FullyQualifiedName\": \"Stats\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"stats.global.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Google Analytics\",\n                \"FullyQualifiedName\": \"Tracking/Google Analytics\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Tracking\",\n                    \"FullyQualifiedName\": \"Tracking\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"tracking_settings.google_analytics.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Google Analytics\",\n                \"FullyQualifiedName\": \"Tracking/Google Analytics\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Tracking\",\n                    \"FullyQualifiedName\": \"Tracking\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"tracking_settings.google_analytics.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"IP Management\",\n                \"FullyQualifiedName\": \"IP Management\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"ips.pools.ips.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Inbound Parse\",\n                \"FullyQualifiedName\": \"Inbound Parse\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"user.webhooks.parse.settings.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Inbound Parse\",\n                \"FullyQualifiedName\": \"Inbound Parse\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"user.webhooks.parse.settings.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Inbound Parse\",\n                \"FullyQualifiedName\": \"Inbound Parse\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"user.webhooks.parse.settings.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Inbound Parse\",\n                \"FullyQualifiedName\": \"Inbound Parse\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"user.webhooks.parse.settings.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Legacy Email Template\",\n                \"FullyQualifiedName\": \"Mail Settings/Legacy Email Template\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"mail_settings.template.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Legacy Email Template\",\n                \"FullyQualifiedName\": \"Mail Settings/Legacy Email Template\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"mail_settings.template.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Mail Send\",\n                \"FullyQualifiedName\": \"Mail Send/Mail Send\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Send\",\n                    \"FullyQualifiedName\": \"Mail Send\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"mail.send\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Mail Settings\",\n                \"FullyQualifiedName\": \"Mail Settings\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"mail_settings.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Mailbox Provider Stats\",\n                \"FullyQualifiedName\": \"Stats/Mailbox Provider Stats\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Stats\",\n                    \"FullyQualifiedName\": \"Stats\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"mailbox_providers.stats.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Multifactor Authentication\",\n                \"FullyQualifiedName\": \"User Account/Multifactor Authentication\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.multifactor_authentication.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Multifactor Authentication\",\n                \"FullyQualifiedName\": \"User Account/Multifactor Authentication\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.multifactor_authentication.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Multifactor Authentication\",\n                \"FullyQualifiedName\": \"User Account/Multifactor Authentication\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.multifactor_authentication.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Multifactor Authentication\",\n                \"FullyQualifiedName\": \"User Account/Multifactor Authentication\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.multifactor_authentication.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Open Tracking\",\n                \"FullyQualifiedName\": \"Tracking/Open Tracking\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Tracking\",\n                    \"FullyQualifiedName\": \"Tracking\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"tracking_settings.open.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Open Tracking\",\n                \"FullyQualifiedName\": \"Tracking/Open Tracking\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Tracking\",\n                    \"FullyQualifiedName\": \"Tracking\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"tracking_settings.open.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Parse Webhook\",\n                \"FullyQualifiedName\": \"Stats/Parse Webhook\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Stats\",\n                    \"FullyQualifiedName\": \"Stats\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.webhooks.parse.stats.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Partners\",\n                \"FullyQualifiedName\": \"Partners\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"partner_settings.new_relic.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Partners\",\n                \"FullyQualifiedName\": \"Partners\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"partner_settings.new_relic.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Partners\",\n                \"FullyQualifiedName\": \"Partners\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"partner_settings.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Password\",\n                \"FullyQualifiedName\": \"User Account/Password\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.password.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Password\",\n                \"FullyQualifiedName\": \"User Account/Password\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.password.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Plain Content\",\n                \"FullyQualifiedName\": \"Mail Settings/Plain Content\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"mail_settings.plain_content.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Plain Content\",\n                \"FullyQualifiedName\": \"Mail Settings/Plain Content\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Mail Settings\",\n                    \"FullyQualifiedName\": \"Mail Settings\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"mail_settings.plain_content.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Profile\",\n                \"FullyQualifiedName\": \"User Account/Profile\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.profile.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Profile\",\n                \"FullyQualifiedName\": \"User Account/Profile\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.profile.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Security\",\n                \"FullyQualifiedName\": \"Security\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"access_settings.activity.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Security\",\n                \"FullyQualifiedName\": \"Security\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"access_settings.whitelist.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Security\",\n                \"FullyQualifiedName\": \"Security\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"access_settings.whitelist.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Security\",\n                \"FullyQualifiedName\": \"Security\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"access_settings.whitelist.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Security\",\n                \"FullyQualifiedName\": \"Security\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"access_settings.whitelist.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Sender Authentication\",\n                \"FullyQualifiedName\": \"Sender Authentication\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"whitelabel.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Sender Authentication\",\n                \"FullyQualifiedName\": \"Sender Authentication\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"whitelabel.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Sender Authentication\",\n                \"FullyQualifiedName\": \"Sender Authentication\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"whitelabel.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Sender Authentication\",\n                \"FullyQualifiedName\": \"Sender Authentication\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"whitelabel.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Source Integration\",\n                \"FullyQualifiedName\": \"37006899\",\n                \"Type\": \"User\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"full_access\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Stats Overview\",\n                \"FullyQualifiedName\": \"Stats/Stats Overview\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Stats\",\n                    \"FullyQualifiedName\": \"Stats\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"stats.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Subscription Tracking\",\n                \"FullyQualifiedName\": \"Tracking/Subscription Tracking\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Tracking\",\n                    \"FullyQualifiedName\": \"Tracking\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"tracking_settings.subscription.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Subscription Tracking\",\n                \"FullyQualifiedName\": \"Tracking/Subscription Tracking\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Tracking\",\n                    \"FullyQualifiedName\": \"Tracking\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"tracking_settings.subscription.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Subuser Stats\",\n                \"FullyQualifiedName\": \"Stats/Subuser Stats\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Stats\",\n                    \"FullyQualifiedName\": \"Stats\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"subusers.stats.monthly.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Subuser Stats\",\n                \"FullyQualifiedName\": \"Stats/Subuser Stats\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Stats\",\n                    \"FullyQualifiedName\": \"Stats\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"subusers.stats.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Subuser Stats\",\n                \"FullyQualifiedName\": \"Stats/Subuser Stats\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Stats\",\n                    \"FullyQualifiedName\": \"Stats\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"subusers.stats.sums.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.blocks.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.blocks.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.blocks.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.blocks.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.bounces.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.bounces.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.bounces.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.bounces.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.invalid_emails.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.invalid_emails.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.invalid_emails.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.invalid_emails.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.spam_reports.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.spam_reports.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.spam_reports.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.spam_reports.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.unsubscribes.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.unsubscribes.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.unsubscribes.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.unsubscribes.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Supressions\",\n                \"FullyQualifiedName\": \"Suppressions/Supressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"suppression.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Teammates\",\n                \"FullyQualifiedName\": \"Teammates\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"teammates.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Teammates\",\n                \"FullyQualifiedName\": \"Teammates\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"teammates.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Teammates\",\n                \"FullyQualifiedName\": \"Teammates\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"teammates.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Teammates\",\n                \"FullyQualifiedName\": \"Teammates\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"teammates.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Template Engine\",\n                \"FullyQualifiedName\": \"Template Engine\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"templates.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Template Engine\",\n                \"FullyQualifiedName\": \"Template Engine\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"templates.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Template Engine\",\n                \"FullyQualifiedName\": \"Template Engine\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"templates.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Template Engine\",\n                \"FullyQualifiedName\": \"Template Engine\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"templates.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Template Engine\",\n                \"FullyQualifiedName\": \"Template Engine\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"templates.versions.activate.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Template Engine\",\n                \"FullyQualifiedName\": \"Template Engine\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"templates.versions.activate.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Template Engine\",\n                \"FullyQualifiedName\": \"Template Engine\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"templates.versions.activate.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Template Engine\",\n                \"FullyQualifiedName\": \"Template Engine\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"templates.versions.activate.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Template Engine\",\n                \"FullyQualifiedName\": \"Template Engine\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"templates.versions.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Template Engine\",\n                \"FullyQualifiedName\": \"Template Engine\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"templates.versions.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Template Engine\",\n                \"FullyQualifiedName\": \"Template Engine\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"templates.versions.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Template Engine\",\n                \"FullyQualifiedName\": \"Template Engine\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"templates.versions.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Timezone\",\n                \"FullyQualifiedName\": \"User Account/Timezone\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.timezone.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Tracking\",\n                \"FullyQualifiedName\": \"Tracking\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            },\n            \"Permission\": {\n                \"Value\": \"tracking_settings.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Unsubscribe Groups\",\n                \"FullyQualifiedName\": \"Suppressions/Unsubscribe Groups\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"asm.groups.create\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Unsubscribe Groups\",\n                \"FullyQualifiedName\": \"Suppressions/Unsubscribe Groups\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"asm.groups.delete\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Unsubscribe Groups\",\n                \"FullyQualifiedName\": \"Suppressions/Unsubscribe Groups\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"asm.groups.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Unsubscribe Groups\",\n                \"FullyQualifiedName\": \"Suppressions/Unsubscribe Groups\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"Suppressions\",\n                    \"FullyQualifiedName\": \"Suppressions\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"asm.groups.update\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Username\",\n                \"FullyQualifiedName\": \"User Account/Username\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.username.read\",\n                \"Parent\": null\n            }\n        },\n        {\n            \"Resource\": {\n                \"Name\": \"Username\",\n                \"FullyQualifiedName\": \"User Account/Username\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": {\n                    \"Name\": \"User Account\",\n                    \"FullyQualifiedName\": \"User Account\",\n                    \"Type\": \"category\",\n                    \"Metadata\": null,\n                    \"Parent\": null\n                }\n            },\n            \"Permission\": {\n                \"Value\": \"user.username.update\",\n                \"Parent\": null\n            }\n        }\n    ],\n    \"UnboundedResources\": [\n        {\n            \"Name\": \"Billing\",\n            \"FullyQualifiedName\": \"Billing\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": null\n        },\n        {\n            \"Name\": \"Design Library\",\n            \"FullyQualifiedName\": \"Design Library\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": null\n        },\n        {\n            \"Name\": \"Email Activity\",\n            \"FullyQualifiedName\": \"Email Activity\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": null\n        },\n        {\n            \"Name\": \"Email Testing\",\n            \"FullyQualifiedName\": \"Email Testing\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": null\n        },\n        {\n            \"Name\": \"Scheduled Sends\",\n            \"FullyQualifiedName\": \"Mail Send/Scheduled Sends\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": {\n                \"Name\": \"Mail Send\",\n                \"FullyQualifiedName\": \"Mail Send\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            }\n        },\n        {\n            \"Name\": \"BCC\",\n            \"FullyQualifiedName\": \"Mail Settings/BCC\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": {\n                \"Name\": \"Mail Settings\",\n                \"FullyQualifiedName\": \"Mail Settings\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            }\n        },\n        {\n            \"Name\": \"Spam Checker\",\n            \"FullyQualifiedName\": \"Mail Settings/Spam Checker\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": {\n                \"Name\": \"Mail Settings\",\n                \"FullyQualifiedName\": \"Mail Settings\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            }\n        },\n        {\n            \"Name\": \"Automation\",\n            \"FullyQualifiedName\": \"Marketing/Automation\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": {\n                \"Name\": \"Marketing\",\n                \"FullyQualifiedName\": \"Marketing\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            }\n        },\n        {\n            \"Name\": \"Marketing\",\n            \"FullyQualifiedName\": \"Marketing/Marketing\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": {\n                \"Name\": \"Marketing\",\n                \"FullyQualifiedName\": \"Marketing\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            }\n        },\n        {\n            \"Name\": \"Recipients Data Erasure\",\n            \"FullyQualifiedName\": \"Recipients Data Erasure\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": null\n        },\n        {\n            \"Name\": \"Category Stats\",\n            \"FullyQualifiedName\": \"Stats/Category Stats\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": {\n                \"Name\": \"Stats\",\n                \"FullyQualifiedName\": \"Stats\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            }\n        },\n        {\n            \"Name\": \"Unsubscribe Group Suppressions\",\n            \"FullyQualifiedName\": \"Suppressions/Unsubscribe Group Suppressions\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": {\n                \"Name\": \"Suppressions\",\n                \"FullyQualifiedName\": \"Suppressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            }\n        },\n        {\n            \"Name\": \"Global Suppressions\",\n            \"FullyQualifiedName\": \"Suppressions/Global Suppressions\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": {\n                \"Name\": \"Suppressions\",\n                \"FullyQualifiedName\": \"Suppressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            }\n        },\n        {\n            \"Name\": \"Credentials\",\n            \"FullyQualifiedName\": \"Credentials\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": null\n        },\n        {\n            \"Name\": \"Signup\",\n            \"FullyQualifiedName\": \"Signup\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": null\n        },\n        {\n            \"Name\": \"Blocks\",\n            \"FullyQualifiedName\": \"Suppressions/Blocks\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": {\n                \"Name\": \"Suppressions\",\n                \"FullyQualifiedName\": \"Suppressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            }\n        },\n        {\n            \"Name\": \"Bounces\",\n            \"FullyQualifiedName\": \"Suppressions/Bounces\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": {\n                \"Name\": \"Suppressions\",\n                \"FullyQualifiedName\": \"Suppressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            }\n        },\n        {\n            \"Name\": \"Invalid Emails\",\n            \"FullyQualifiedName\": \"Suppressions/Invalid Emails\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": {\n                \"Name\": \"Suppressions\",\n                \"FullyQualifiedName\": \"Suppressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            }\n        },\n        {\n            \"Name\": \"Spam Reports\",\n            \"FullyQualifiedName\": \"Suppressions/Spam Reports\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": {\n                \"Name\": \"Suppressions\",\n                \"FullyQualifiedName\": \"Suppressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            }\n        },\n        {\n            \"Name\": \"Unsubscribes\",\n            \"FullyQualifiedName\": \"Suppressions/Unsubscribes\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": {\n                \"Name\": \"Suppressions\",\n                \"FullyQualifiedName\": \"Suppressions\",\n                \"Type\": \"category\",\n                \"Metadata\": null,\n                \"Parent\": null\n            }\n        },\n        {\n            \"Name\": \"UI\",\n            \"FullyQualifiedName\": \"UI\",\n            \"Type\": \"category\",\n            \"Metadata\": null,\n            \"Parent\": null\n        }\n    ],\n    \"Metadata\": {\n        \"2fa_required\": true,\n        \"key_type\": \"full access\"\n    }\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/sendgrid/scopes.go",
    "content": "package sendgrid\n\nimport (\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n)\n\ntype SendgridScope struct {\n\tCategory       string\n\tSubCategory    string\n\tPrefixes       []string // Prefixes for the scope\n\tPermissions    []string\n\tPermissionType analyzers.PermissionType\n}\n\nfunc (s *SendgridScope) AddPermission(permission string) {\n\ts.Permissions = append(s.Permissions, permission)\n}\n\nfunc (s *SendgridScope) RunTests() {\n\tif len(s.Permissions) == 0 {\n\t\ts.PermissionType = analyzers.NONE\n\t\treturn\n\t}\n\tfor _, permission := range s.Permissions {\n\t\tif strings.Contains(permission, \".read\") {\n\t\t\ts.PermissionType = analyzers.READ\n\t\t} else {\n\t\t\ts.PermissionType = analyzers.READ_WRITE\n\t\t\treturn\n\t\t}\n\t}\n}\n\nvar SCOPES = []SendgridScope{\n\t// Billing\n\t{Category: \"Billing\", Prefixes: []string{\"billing\"}},\n\t// Restricted Access\n\t{Category: \"API Keys\", Prefixes: []string{\"api_keys\"}},\n\t{Category: \"Alerts\", Prefixes: []string{\"alerts\"}},\n\t{Category: \"Category Management\", Prefixes: []string{\"categories\"}},\n\t{Category: \"Design Library\", Prefixes: []string{\"design_library\"}},\n\t{Category: \"Email Activity\", Prefixes: []string{\"messages\"}},\n\t{Category: \"Email Testing\", Prefixes: []string{\"email_testing\"}},\n\t{Category: \"IP Management\", Prefixes: []string{\"ips\"}},\n\t{Category: \"Inbound Parse\", Prefixes: []string{\"user.webhooks.parse.settings\"}},\n\t{Category: \"Mail Send\", SubCategory: \"Mail Send\", Prefixes: []string{\"mail.send\"}},\n\t{Category: \"Mail Send\", SubCategory: \"Scheduled Sends\", Prefixes: []string{\"user.scheduled_sends, mail.batch\"}},\n\t{Category: \"Mail Settings\", SubCategory: \"Address Allow List\", Prefixes: []string{\"mail_settings.address_whitelist\"}},\n\t{Category: \"Mail Settings\", SubCategory: \"BCC\", Prefixes: []string{\"mail_settings.bcc\"}},\n\t{Category: \"Mail Settings\", SubCategory: \"Bounce Purge\", Prefixes: []string{\"mail_settings.bounce_purge\"}},\n\t{Category: \"Mail Settings\", SubCategory: \"Event Notification\", Prefixes: []string{\"user.webhooks.event\"}},\n\t{Category: \"Mail Settings\", SubCategory: \"Footer\", Prefixes: []string{\"mail_settings.footer\"}},\n\t{Category: \"Mail Settings\", SubCategory: \"Forward Bounce\", Prefixes: []string{\"mail_settings.forward_bounce\"}},\n\t{Category: \"Mail Settings\", SubCategory: \"Forward Spam\", Prefixes: []string{\"mail_settings.forward_spam\"}},\n\t{Category: \"Mail Settings\", SubCategory: \"Legacy Email Template\", Prefixes: []string{\"mail_settings.template\"}},\n\t{Category: \"Mail Settings\", SubCategory: \"Plain Content\", Prefixes: []string{\"mail_settings.plain_content\"}},\n\t{Category: \"Mail Settings\", SubCategory: \"Spam Checker\", Prefixes: []string{\"mail_settings.spam_check\"}},\n\t{Category: \"Marketing\", SubCategory: \"Automation\", Prefixes: []string{\"marketing.automation\"}},\n\t{Category: \"Marketing\", SubCategory: \"Marketing\", Prefixes: []string{\"marketing.read\"}},\n\t{Category: \"Partners\", Prefixes: []string{\"partner_settings\"}},\n\t{Category: \"Recipients Data Erasure\", Prefixes: []string{\"recipients\"}},\n\t{Category: \"Security\", Prefixes: []string{\"access_settings\"}},\n\t{Category: \"Sender Authentication\", Prefixes: []string{\"whitelabel\"}},\n\t{Category: \"Stats\", SubCategory: \"Browser Stats\", Prefixes: []string{\"browsers\"}},\n\t{Category: \"Stats\", SubCategory: \"Category Stats\", Prefixes: []string{\"categories.stats\"}},\n\t{Category: \"Stats\", SubCategory: \"Email Clients and Devices\", Prefixes: []string{\"clients\", \"devices\"}},\n\t{Category: \"Stats\", SubCategory: \"Geographical\", Prefixes: []string{\"geo\"}},\n\t{Category: \"Stats\", SubCategory: \"Global Stats\", Prefixes: []string{\"stats.global\"}},\n\t{Category: \"Stats\", SubCategory: \"Mailbox Provider Stats\", Prefixes: []string{\"mailbox_providers\"}},\n\t{Category: \"Stats\", SubCategory: \"Parse Webhook\", Prefixes: []string{\"user.webhooks.parse.stats\"}},\n\t{Category: \"Stats\", SubCategory: \"Stats Overview\", Prefixes: []string{\"stats.read\"}},\n\t{Category: \"Stats\", SubCategory: \"Subuser Stats\", Prefixes: []string{\"subusers\"}},\n\t{Category: \"Suppressions\", SubCategory: \"Supressions\", Prefixes: []string{\"suppression\"}},\n\t{Category: \"Suppressions\", SubCategory: \"Unsubscribe Groups\", Prefixes: []string{\"asm.groups\"}},\n\t{Category: \"Template Engine\", Prefixes: []string{\"templates\"}},\n\t{Category: \"Tracking\", SubCategory: \"Click Tracking\", Prefixes: []string{\"tracking_settings.click\"}},\n\t{Category: \"Tracking\", SubCategory: \"Google Analytics\", Prefixes: []string{\"tracking_settings.google_analytics\"}},\n\t{Category: \"Tracking\", SubCategory: \"Open Tracking\", Prefixes: []string{\"tracking_settings.open\"}},\n\t{Category: \"Tracking\", SubCategory: \"Subscription Tracking\", Prefixes: []string{\"tracking_settings.subscription\"}},\n\t{Category: \"User Account\", SubCategory: \"Enforced TLS\", Prefixes: []string{\"user.settings.enforced_tls\"}},\n\t{Category: \"User Account\", SubCategory: \"Timezone\", Prefixes: []string{\"user.timezone\"}},\n\t// Full Access Additional Categories\n\t{Category: \"Suppressions\", SubCategory: \"Unsubscribe Group Suppressions\", Prefixes: []string{\"asm.groups.suppressions\"}},\n\t{Category: \"Suppressions\", SubCategory: \"Global Suppressions\", Prefixes: []string{\"asm.suppressions.global\"}},\n\t{Category: \"Credentials\", Prefixes: []string{\"credentials\"}},\n\t{Category: \"Mail Settings\", Prefixes: []string{\"mail_settings\"}},\n\t{Category: \"Signup\", Prefixes: []string{\"signup\"}},\n\t{Category: \"Suppressions\", SubCategory: \"Blocks\", Prefixes: []string{\"suppression.blocks\"}},\n\t{Category: \"Suppressions\", SubCategory: \"Bounces\", Prefixes: []string{\"suppression.bounces\"}},\n\t{Category: \"Suppressions\", SubCategory: \"Invalid Emails\", Prefixes: []string{\"suppression.invalid_emails\"}},\n\t{Category: \"Suppressions\", SubCategory: \"Spam Reports\", Prefixes: []string{\"suppression.spam_reports\"}},\n\t{Category: \"Suppressions\", SubCategory: \"Unsubscribes\", Prefixes: []string{\"suppression.unsubscribes\"}},\n\t{Category: \"Teammates\", Prefixes: []string{\"teammates\"}},\n\t{Category: \"Tracking\", Prefixes: []string{\"tracking_settings\"}},\n\t{Category: \"UI\", Prefixes: []string{\"ui\"}},\n\t{Category: \"User Account\", SubCategory: \"Account\", Prefixes: []string{\"user.account\"}},\n\t{Category: \"User Account\", SubCategory: \"Credits\", Prefixes: []string{\"user.credits\"}},\n\t{Category: \"User Account\", SubCategory: \"Email\", Prefixes: []string{\"user.email\"}},\n\t{Category: \"User Account\", SubCategory: \"Multifactor Authentication\", Prefixes: []string{\"user.multifactor_authentication\"}},\n\t{Category: \"User Account\", SubCategory: \"Password\", Prefixes: []string{\"user.password\"}},\n\t{Category: \"User Account\", SubCategory: \"Profile\", Prefixes: []string{\"user.profile\"}},\n\t{Category: \"User Account\", SubCategory: \"Username\", Prefixes: []string{\"user.username\"}},\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/sendgrid/sendgrid.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go sendgrid\n\npackage sendgrid\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\tsg \"github.com/sendgrid/sendgrid-go\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\ntype ScopesJSON struct {\n\tScopes []string `json:\"scopes\"`\n}\n\ntype Profile struct {\n\tID        int    `json:\"userid\"`\n\tFirstName string `json:\"first_name\"`\n\tLastName  string `json:\"last_name\"`\n\tCompany   string `json:\"company\"`\n\tWebsite   string `json:\"website\"`\n\tCountry   string `json:\"country\"`\n}\ntype SecretInfo struct {\n\tUser      Profile\n\tRawScopes []string\n\tScopes    []SendgridScope\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeSendgrid }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credInfo[\"key\"]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"missing key in credInfo\")\n\t}\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[!] Error: %v\", err)\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid Sendgrid API Key\\n\\n\")\n\n\tif slices.Contains(info.RawScopes, \"user.email.read\") {\n\t\tcolor.Green(\"[*] Sendgrid Key Type: Full Access Key\")\n\t} else if slices.Contains(info.RawScopes, \"billing.read\") {\n\t\tcolor.Yellow(\"[*] Sendgrid Key Type: Billing Access Key\")\n\t} else {\n\t\tcolor.Yellow(\"[*] Sendgrid Key Type: Restricted Access Key\")\n\t}\n\n\tif slices.Contains(info.RawScopes, \"2fa_required\") {\n\t\tcolor.Yellow(\"[i] 2FA Required for this account\")\n\t}\n\n\tif info.User.FirstName != \"\" {\n\t\tprintProfile(info.User)\n\t}\n\n\tprintPermissions(info, cfg.ShowAll)\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\t// Setup custom HTTP client so we can log requests.\n\tsg.DefaultClient.HTTPClient = analyzers.NewAnalyzeClient(cfg)\n\n\t// get scopes\n\trawScopes, err := getScopes(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcategoryScope := processPermissions(rawScopes)\n\n\tvar secretInfo = &SecretInfo{\n\t\tRawScopes: rawScopes,\n\t\tScopes:    categoryScope,\n\t}\n\n\tif slices.Contains(rawScopes, \"user.email.read\") {\n\t\tprofile, err := getProfile(key)\n\t\tif err != nil {\n\t\t\t// if get profile fails return secretInfo with scopes for partial success\n\t\t\treturn secretInfo, nil\n\t\t}\n\n\t\tsecretInfo.User = *profile\n\t}\n\n\treturn secretInfo, nil\n}\n\nfunc getScopes(key string) ([]string, error) {\n\treq := sg.GetRequest(key, \"/v3/scopes\", \"https://api.sendgrid.com\")\n\treq.Method = \"GET\"\n\tresp, err := sg.API(req)\n\tif resp.StatusCode == 401 || resp.StatusCode == 403 {\n\t\treturn nil, fmt.Errorf(\"invalid api key\")\n\t} else if resp.StatusCode != 200 {\n\t\treturn nil, fmt.Errorf(\"%v\", resp.StatusCode)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Unmarshal the JSON response into a struct\n\tvar jsonScopes ScopesJSON\n\tif err := json.Unmarshal([]byte(resp.Body), &jsonScopes); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn jsonScopes.Scopes, nil\n}\n\nfunc getProfile(key string) (*Profile, error) {\n\treq := sg.GetRequest(key, \"/v3/user/profile\", \"https://api.sendgrid.com\")\n\treq.Method = \"GET\"\n\tresp, err := sg.API(req)\n\tif resp.StatusCode == 401 || resp.StatusCode == 403 {\n\t\treturn nil, fmt.Errorf(\"invalid api key\")\n\t} else if resp.StatusCode != 200 {\n\t\treturn nil, fmt.Errorf(\"%v\", resp.StatusCode)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Unmarshal the JSON response into a struct\n\tvar profile Profile\n\tif err := json.Unmarshal([]byte(resp.Body), &profile); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &profile, nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tvar keyType string\n\tif slices.Contains(info.RawScopes, \"user.email.read\") {\n\t\tkeyType = \"full access\"\n\t} else if slices.Contains(info.RawScopes, \"billing.read\") {\n\t\tkeyType = \"billing access\"\n\t} else {\n\t\tkeyType = \"restricted access\"\n\t}\n\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeSendgrid,\n\t\tMetadata: map[string]any{\n\t\t\t\"key_type\":     keyType,\n\t\t\t\"2fa_required\": slices.Contains(info.RawScopes, \"2fa_required\"),\n\t\t},\n\t\tBindings:           []analyzers.Binding{},\n\t\tUnboundedResources: []analyzers.Resource{},\n\t}\n\n\t// add profile information to analyzer result\n\tif info.User.ID != 0 && info.User.FirstName != \"\" {\n\t\tresult.Bindings = append(result.Bindings, analyzers.Binding{\n\t\t\tResource: analyzers.Resource{\n\t\t\t\tName:               info.User.FirstName + \" \" + info.User.LastName,\n\t\t\t\tFullyQualifiedName: fmt.Sprintf(\"%d\", info.User.ID),\n\t\t\t\tType:               \"User\",\n\t\t\t},\n\t\t\tPermission: analyzers.Permission{\n\t\t\t\tValue: \"full_access\", // if token has all permissions than we can get user information\n\t\t\t},\n\t\t})\n\t}\n\n\tfor _, scope := range info.Scopes {\n\t\tresource := getCategoryResource(scope)\n\n\t\tif len(scope.Permissions) == 0 {\n\t\t\tresult.UnboundedResources = append(result.UnboundedResources, *resource)\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, permission := range scope.Permissions {\n\t\t\tresult.Bindings = append(result.Bindings, analyzers.Binding{\n\t\t\t\tResource: *resource,\n\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\tValue: permission,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t}\n\n\treturn &result\n}\n\nfunc getCategoryResource(scope SendgridScope) *analyzers.Resource {\n\tcategoryResource := &analyzers.Resource{\n\t\tName:               scope.Category,\n\t\tFullyQualifiedName: scope.Category,\n\t\tType:               \"category\",\n\t\tMetadata:           nil,\n\t}\n\n\tif scope.SubCategory != \"\" {\n\t\treturn &analyzers.Resource{\n\t\t\tName:               scope.SubCategory,\n\t\t\tFullyQualifiedName: fmt.Sprintf(\"%s/%s\", scope.Category, scope.SubCategory),\n\t\t\tType:               \"category\",\n\t\t\tMetadata:           nil,\n\t\t\tParent:             categoryResource,\n\t\t}\n\t}\n\n\treturn categoryResource\n}\n\n// getCategoryFromScope returns the category for a given scope.\n// It will return the most specific category possible.\n// For example, if the scope is \"mail.send.read\", it will return \"Mail Send\", not just \"Mail\"\n// since it's searching \"mail.send.read\" -> \"mail.send\" -> \"mail\"\nfunc getScopeIndex(categories []SendgridScope, scope string) int {\n\tsplitScope := strings.Split(scope, \".\")\n\tfor i := len(splitScope); i > 0; i-- {\n\t\tsearchScope := strings.Join(splitScope[:i], \".\")\n\t\tfor i, s := range categories {\n\t\t\tfor _, prefix := range s.Prefixes {\n\t\t\t\tif strings.HasPrefix(searchScope, prefix) {\n\t\t\t\t\treturn i\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc processPermissions(rawScopes []string) []SendgridScope {\n\tcategoryPermissions := make([]SendgridScope, len(SCOPES))\n\n\t// copy all scope categories to the categoryPermissions slice\n\tcopy(categoryPermissions, SCOPES)\n\tfor _, scope := range rawScopes {\n\t\t// Skip these scopes since they are not useful for this analysis\n\t\tif scope == \"2fa_required\" || scope == \"sender_verification_eligible\" {\n\t\t\tcontinue\n\t\t}\n\n\t\t// must be part of generated permissions\n\t\tif _, ok := StringToPermission[scope]; !ok {\n\t\t\tcontinue\n\t\t}\n\t\tind := getScopeIndex(categoryPermissions, scope)\n\t\tif ind == -1 {\n\t\t\t//color.Red(\"[!] Scope not found: %v\", scope)\n\t\t\tcontinue\n\t\t}\n\t\ts := &categoryPermissions[ind]\n\t\ts.AddPermission(scope)\n\t}\n\n\t// Run tests to determine the permission type\n\tfor i := range categoryPermissions {\n\t\tcategoryPermissions[i].RunTests()\n\t}\n\n\treturn categoryPermissions\n}\n\nfunc printProfile(profile Profile) {\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\n\tt.AppendHeader(table.Row{\"UserID\", \"Name\", \"Company\", \"Website\", \"Country\"})\n\tt.AppendRow(table.Row{profile.ID, profile.FirstName + \" \" + profile.LastName, profile.Company, profile.Website, profile.Country})\n\n\tt.Render()\n}\n\nfunc printPermissions(info *SecretInfo, show_all bool) {\n\tfmt.Print(\"\\n\\n\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tif show_all {\n\t\tt.AppendHeader(table.Row{\"Scope\", \"Sub-Scope\", \"Access\", \"Permissions\"})\n\t} else {\n\t\tt.AppendHeader(table.Row{\"Scope\", \"Sub-Scope\", \"Access\"})\n\t}\n\t// Print the scopes\n\tfor _, s := range info.Scopes {\n\t\twriter := analyzers.GetWriterFromStatus(s.PermissionType)\n\t\tif show_all {\n\t\t\tt.AppendRow([]interface{}{writer(s.Category), writer(s.SubCategory), writer(s.PermissionType), writer(strings.Join(s.Permissions, \"\\n\"))})\n\t\t} else if s.PermissionType != analyzers.NONE {\n\t\t\tt.AppendRow([]interface{}{writer(s.Category), writer(s.SubCategory), writer(s.PermissionType)})\n\t\t}\n\t}\n\tt.Render()\n\tfmt.Print(\"\\n\\n\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/sendgrid/sendgrid_test.go",
    "content": "package sendgrid\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed result_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    []byte // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"Valid Sendgrid key\",\n\t\t\tkey:     testSecrets.MustGetField(\"SENDGRID\"),\n\t\t\twant:    expectedOutput,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\tfmt.Println(string(gotJSON))\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/shopify/expected_output.json",
    "content": "{\n   \"AnalyzerType\": 15,\n   \"Bindings\": [\n    {\n     \"Resource\": {\n      \"Name\": \"Analytics\",\n      \"FullyQualifiedName\": \"727f01-d6.myshopify.com/detectors@trufflesec.com/Analytics\",\n      \"Type\": \"category\",\n      \"Metadata\": null,\n      \"Parent\": {\n       \"Name\": \"My Store\",\n       \"FullyQualifiedName\": \"727f01-d6.myshopify.com/detectors@trufflesec.com\",\n       \"Type\": \"shop\",\n       \"Metadata\": {\n        \"created_at\": \"2024-08-16T17:16:17+05:00\"\n       },\n       \"Parent\": null\n      }\n     },\n     \"Permission\": {\n      \"Value\": \"read\",\n      \"Parent\": null\n     }\n    },\n    {\n     \"Resource\": {\n      \"Name\": \"Applications\",\n      \"FullyQualifiedName\": \"727f01-d6.myshopify.com/detectors@trufflesec.com/Applications\",\n      \"Type\": \"category\",\n      \"Metadata\": null,\n      \"Parent\": {\n       \"Name\": \"My Store\",\n       \"FullyQualifiedName\": \"727f01-d6.myshopify.com/detectors@trufflesec.com\",\n       \"Type\": \"shop\",\n       \"Metadata\": {\n        \"created_at\": \"2024-08-16T17:16:17+05:00\"\n       },\n       \"Parent\": null\n      }\n     },\n     \"Permission\": {\n      \"Value\": \"read\",\n      \"Parent\": null\n     }\n    },\n    {\n     \"Resource\": {\n      \"Name\": \"Assigned fulfillment orders\",\n      \"FullyQualifiedName\": \"727f01-d6.myshopify.com/detectors@trufflesec.com/Assigned fulfillment orders\",\n      \"Type\": \"category\",\n      \"Metadata\": null,\n      \"Parent\": {\n       \"Name\": \"My Store\",\n       \"FullyQualifiedName\": \"727f01-d6.myshopify.com/detectors@trufflesec.com\",\n       \"Type\": \"shop\",\n       \"Metadata\": {\n        \"created_at\": \"2024-08-16T17:16:17+05:00\"\n       },\n       \"Parent\": null\n      }\n     },\n     \"Permission\": {\n      \"Value\": \"full_access\",\n      \"Parent\": null\n     }\n    },\n    {\n     \"Resource\": {\n      \"Name\": \"Customers\",\n      \"FullyQualifiedName\": \"727f01-d6.myshopify.com/detectors@trufflesec.com/Customers\",\n      \"Type\": \"category\",\n      \"Metadata\": null,\n      \"Parent\": {\n       \"Name\": \"My Store\",\n       \"FullyQualifiedName\": \"727f01-d6.myshopify.com/detectors@trufflesec.com\",\n       \"Type\": \"shop\",\n       \"Metadata\": {\n        \"created_at\": \"2024-08-16T17:16:17+05:00\"\n       },\n       \"Parent\": null\n      }\n     },\n     \"Permission\": {\n      \"Value\": \"full_access\",\n      \"Parent\": null\n     }\n    },\n    {\n     \"Resource\": {\n      \"Name\": \"Discovery\",\n      \"FullyQualifiedName\": \"727f01-d6.myshopify.com/detectors@trufflesec.com/Discovery\",\n      \"Type\": \"category\",\n      \"Metadata\": null,\n      \"Parent\": {\n       \"Name\": \"My Store\",\n       \"FullyQualifiedName\": \"727f01-d6.myshopify.com/detectors@trufflesec.com\",\n       \"Type\": \"shop\",\n       \"Metadata\": {\n        \"created_at\": \"2024-08-16T17:16:17+05:00\"\n       },\n       \"Parent\": null\n      }\n     },\n     \"Permission\": {\n      \"Value\": \"full_access\",\n      \"Parent\": null\n     }\n    },\n    {\n     \"Resource\": {\n      \"Name\": \"Merchant-managed fulfillment orders\",\n      \"FullyQualifiedName\": \"727f01-d6.myshopify.com/detectors@trufflesec.com/Merchant-managed fulfillment orders\",\n      \"Type\": \"category\",\n      \"Metadata\": null,\n      \"Parent\": {\n       \"Name\": \"My Store\",\n       \"FullyQualifiedName\": \"727f01-d6.myshopify.com/detectors@trufflesec.com\",\n       \"Type\": \"shop\",\n       \"Metadata\": {\n        \"created_at\": \"2024-08-16T17:16:17+05:00\"\n       },\n       \"Parent\": null\n      }\n     },\n     \"Permission\": {\n      \"Value\": \"full_access\",\n      \"Parent\": null\n     }\n    },\n    {\n     \"Resource\": {\n      \"Name\": \"Reports\",\n      \"FullyQualifiedName\": \"727f01-d6.myshopify.com/detectors@trufflesec.com/Reports\",\n      \"Type\": \"category\",\n      \"Metadata\": null,\n      \"Parent\": {\n       \"Name\": \"My Store\",\n       \"FullyQualifiedName\": \"727f01-d6.myshopify.com/detectors@trufflesec.com\",\n       \"Type\": \"shop\",\n       \"Metadata\": {\n        \"created_at\": \"2024-08-16T17:16:17+05:00\"\n       },\n       \"Parent\": null\n      }\n     },\n     \"Permission\": {\n      \"Value\": \"full_access\",\n      \"Parent\": null\n     }\n    },\n    {\n     \"Resource\": {\n      \"Name\": \"cart_transforms\",\n      \"FullyQualifiedName\": \"727f01-d6.myshopify.com/detectors@trufflesec.com/cart_transforms\",\n      \"Type\": \"category\",\n      \"Metadata\": null,\n      \"Parent\": {\n       \"Name\": \"My Store\",\n       \"FullyQualifiedName\": \"727f01-d6.myshopify.com/detectors@trufflesec.com\",\n       \"Type\": \"shop\",\n       \"Metadata\": {\n        \"created_at\": \"2024-08-16T17:16:17+05:00\"\n       },\n       \"Parent\": null\n      }\n     },\n     \"Permission\": {\n      \"Value\": \"full_access\",\n      \"Parent\": null\n     }\n    }\n   ],\n   \"UnboundedResources\": null,\n   \"Metadata\": {\n    \"status_code\": 200\n   }\n  }"
  },
  {
    "path": "pkg/analyzer/analyzers/shopify/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage shopify\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    Read Permission = iota\n    Write Permission = iota\n    FullAccess Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        Read: \"read\",\n        Write: \"write\",\n        FullAccess: \"full_access\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"read\": Read,\n        \"write\": Write,\n        \"full_access\": FullAccess,\n    }\n\n    PermissionIDs = map[Permission]int{\n        Read: 1,\n        Write: 2,\n        FullAccess: 3,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: Read,\n        2: Write,\n        3: FullAccess,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/shopify/permissions.yaml",
    "content": "permissions:\n  - read\n  - write\n  - full_access\n"
  },
  {
    "path": "pkg/analyzer/analyzers/shopify/scopes.json",
    "content": "{\n  \"categories\": {\n    \"Analytics\": {\n      \"description\": \"View store metrics\", \n      \"scopes\": {\n          \"read_analytics\": \"Read\"\n      }\n    },\n    \"Applications\": {\n        \"description\": \"View or manage apps\", \n        \"scopes\": {\n            \"read_apps\": \"Read\"\n        }\n    },\n    \"Assigned fulfillment orders\": {\n        \"description\": \"View or manage fulfillment orders\", \n        \"scopes\": {\n            \"write_assigned_fulfillment_orders\": \"Write\", \n            \"read_assigned_fulfillment_orders\": \"Read\"\n        }\n    },\n    \"Browsing behavior\": {\n        \"description\": \"View or manage online-store browsing behavior including page views, cart updates, product views and searches\", \n        \"scopes\": {\n            \"read_customer_events\": \"Read\"\n        }\n    },\n    \"Custom pixels\": {\n        \"description\": \"View or manage custom pixels\", \n        \"scopes\": {\n            \"write_custom_pixels\": \"Write\", \n            \"read_custom_pixels\": \"Read\"\n        }\n    },\n    \"Customers\": {\n        \"description\": \"View or manage customers, customer addresses, order history, and customer groups\", \n        \"scopes\": {\n            \"write_customers\": \"Write\", \n            \"read_customers\": \"Read\"\n        }\n    },\n    \"Discounts\": {\n        \"description\": \"View or manage automatic discounts and discount codes\", \n        \"scopes\": {\n            \"write_discounts\": \"Write\", \n            \"read_discounts\": \"Read\"\n        }\n    },\n    \"Discovery\": {\n        \"description\": \"View or manage Discovery API\", \n        \"scopes\": {\n            \"write_discovery\": \"Write\", \n            \"read_discovery\": \"Read\"\n        }\n    },\n    \"Draft orders\": {\n        \"description\": \"View or manage orders created by merchants on behalf of customers\", \n        \"scopes\": {\n            \"write_draft_orders\": \"Write\", \n            \"read_draft_orders\": \"Read\"\n        }\n    },\n    \"Files\": {\n        \"description\": \"View or manage files\", \n        \"scopes\": {\n            \"write_files\": \"Write\", \n            \"read_files\": \"Read\"\n        }\n    },\n    \"Fulfillment services\": {\n        \"description\": \"View or manage fulfillment services\", \n        \"scopes\": {\n            \"write_fulfillments\": \"Write\", \n            \"read_fulfillments\": \"Read\"\n        }\n    },\n    \"Gift cards\": {\n        \"description\": \"View or manage gift cards\", \n        \"scopes\": {\n            \"write_gift_cards\": \"Write\", \n            \"read_gift_cards\": \"Read\"\n        }\n    },\n    \"Inventory\": {\n        \"description\": \"View or manage inventory across multiple locations\", \n        \"scopes\": {\n            \"write_inventory\": \"Write\", \n            \"read_inventory\": \"Read\"\n        }\n    },\n    \"Legal policies\": {\n        \"description\": \"View or manage a shop's  legal policies\", \n        \"scopes\": {\n            \"write_legal_policies\": \"Write\", \n            \"read_legal_policies\": \"Read\"\n        }\n    },\n    \"Locations\": {\n        \"description\": \"View the geographic location of stores, headquarters, and warehouses\", \n        \"scopes\": {\n            \"write_locations\": \"Write\", \n            \"read_locations\": \"Read\"\n        }\n    },\n    \"Marketing events\": {\n        \"description\": \"View or manage marketing events and engagement data\", \n        \"scopes\": {\n            \"write_marketing_events\": \"Write\", \n            \"read_marketing_events\": \"Read\"\n        }\n    },\n    \"Merchant-managed fulfillment orders\": {\n        \"description\": \"View or manage fulfillment orders assigned to merchant-managed locations\", \n        \"scopes\": {\n            \"write_merchant_managed_fulfillment_orders\": \"Write\", \n            \"read_merchant_managed_fulfillment_orders\": \"Read\"\n        }\n    },\n    \"Metaobject definitions\": {\n        \"description\": \"View or manage definitions\", \n        \"scopes\": {\n            \"write_metaobject_definitions\": \"Write\", \n            \"read_metaobject_definitions\": \"Read\"\n        }\n    },\n    \"Metaobject entries\": {\n        \"description\": \"View or manage entries\", \n        \"scopes\": {\n            \"write_metaobjects\": \"Write\", \n            \"read_metaobjects\": \"Read\"\n        }\n    },\n    \"Online Store navigation\": {\n        \"description\": \"View menus for display on the storefront\", \n        \"scopes\": {\n            \"write_online_store_navigation\": \"Write\", \n            \"read_online_store_navigation\": \"Read\"\n        }\n    },\n    \"Online Store pages\": {\n        \"description\": \"View or manage Online Store pages\", \n        \"scopes\": {\n            \"write_online_store_pages\": \"Write\", \n            \"read_online_store_pages\": \"Read\"\n        }\n    },\n    \"Order editing\": {\n        \"description\": \"View or manage edits to orders\", \n        \"scopes\": {\n            \"write_order_edits\": \"Write\", \n            \"read_order_edits\": \"Read\"\n        }\n    },\n    \"Orders\": {\n        \"description\": \"View or manage orders, transactions, fulfillments, and abandoned checkouts\", \n        \"scopes\": {\n            \"write_orders\": \"Write\", \n            \"read_orders\": \"Read\"\n        }\n    },\n    \"Packing slip management\": {\n        \"description\": \"Edit and preview packing slip template\", \n        \"scopes\": {\n            \"write_packing_slip_templates\": \"Write\", \n            \"read_packing_slip_templates\": \"Read\"\n        }\n    },\n    \"Payment customizations\": {\n        \"description\": \"View or manage payment customizations\", \n        \"scopes\": {\n            \"write_payment_customizations\": \"Write\", \n            \"read_payment_customizations\": \"Read\"\n        }\n    },\n    \"Payment terms\": {\n        \"description\": \"View or manage payment terms\", \n        \"scopes\": {\n            \"write_payment_terms\": \"Write\", \n            \"read_payment_terms\": \"Read\"\n        }\n    },\n    \"Pixels\": {\n        \"description\": \"View or manage pixels\", \n        \"scopes\": {\n            \"write_pixels\": \"Write\", \n            \"read_pixels\": \"Read\"\n        }\n    },\n    \"Price rules\": {\n        \"description\": \"View or manage conditional discounts\", \n        \"scopes\": {\n            \"write_price_rules\": \"Write\", \n            \"read_price_rules\": \"Read\"\n        }\n    },\n    \"Product feeds\": {\n        \"description\": \"View or manage product feeds\", \n        \"scopes\": {\n            \"write_product_feeds\": \"Write\", \n            \"read_product_feeds\": \"Read\"\n        }\n    },\n    \"Product listings\": {\n        \"description\": \"View or manage product or collection listings\", \n        \"scopes\": {\n            \"write_product_listings\": \"Write\", \n            \"read_product_listings\": \"Read\"\n        }\n    },\n    \"Products\": {\n        \"description\": \"View or manage products, variants, and collections\", \n        \"scopes\": {\n            \"write_products\": \"Write\", \n            \"read_products\": \"Read\"\n        }\n    },\n    \"Publications\": {\n        \"description\": \"View or manage groups of products that have been published to an app\", \n        \"scopes\": {\n            \"write_publications\": \"Write\", \n            \"read_publications\": \"Read\"\n        }\n    },\n    \"Purchase options\": {\n        \"description\": \"View or manage purchase options owned by this app\", \n        \"scopes\": {\n            \"write_purchase_options\": \"Write\", \n            \"read_purchase_options\": \"Read\"\n        }\n    },\n    \"Reports\": {\n        \"description\": \"View or manage reports on the Reports page in the Shopify admin\", \n        \"scopes\": {\n            \"write_reports\": \"Write\", \n            \"read_reports\": \"Read\"\n        }\n    },\n    \"Resource feedback\": {\n        \"description\": \"View or manage the status of shops and resources\", \n        \"scopes\": {\n            \"write_resource_feedbacks\": \"Write\", \n            \"read_resource_feedbacks\": \"Read\"\n        }\n    },\n    \"Returns\": {\n        \"description\": \"View or manage returns\", \n        \"scopes\": {\n            \"write_returns\": \"Write\", \n            \"read_returns\": \"Read\"\n        }\n    },\n    \"Sales channels\": {\n        \"description\": \"View or manage sales channels\", \n        \"scopes\": {\n            \"write_channels\": \"Write\", \n            \"read_channels\": \"Read\"\n        }\n    },\n    \"Script tags\": {\n        \"description\": \"View or manage the JavaScript code in storefront or orders status pages\", \n        \"scopes\": {\n            \"write_script_tags\": \"Write\", \n            \"read_script_tags\": \"Read\"\n        }\n    },\n    \"Shipping\": {\n        \"description\": \"View or manage shipping carriers, countries, and provinces\", \n        \"scopes\": {\n            \"write_shipping\": \"Write\", \n            \"read_shipping\": \"Read\"\n        }\n    },\n    \"Shop locales\": {\n        \"description\": \"View or manage available locales for a shop\", \n        \"scopes\": {\n            \"write_locales\": \"Write\", \n            \"read_locales\": \"Read\"\n        }\n    },\n    \"Shopify Markets\": {\n        \"description\": \"View or manage Shopify Markets configuration\", \n        \"scopes\": {\n            \"write_markets\": \"Write\", \n            \"read_markets\": \"Read\"\n        }\n    },\n    \"Shopify Payments accounts\": {\n        \"description\": \"View Shopify Payments accounts\", \n        \"scopes\": {\n            \"read_shopify_payments_accounts\": \"Read\"\n        }\n    },\n    \"Shopify Payments bank accounts\": {\n        \"description\": \"View bank accounts that can receive Shopify Payment payouts\", \n        \"scopes\": {\n            \"read_shopify_payments_bank_accounts\": \"Read\"\n        }\n    },\n    \"Shopify Payments disputes\": {\n        \"description\": \"View Shopify Payment disputes raised by buyers\", \n        \"scopes\": {\n            \"write_shopify_payments_disputes\": \"Write\", \n            \"read_shopify_payments_disputes\": \"Read\"\n        }\n    },\n    \"Shopify Payments payouts\": {\n        \"description\": \"View Shopify Payments payouts and the account's current balance\", \n        \"scopes\": {\n            \"read_shopify_payments_payouts\": \"Read\"\n        }\n    },\n    \"Store content\": {\n        \"description\": \"View or manage articles, blogs, comments, pages, and redirects\", \n        \"scopes\": {\n            \"write_content\": \"Write\", \n            \"read_content\": \"Read\"\n        }\n    },\n    \"Store credit account transactions\": {\n        \"description\": \"View or create store credit transactions\", \n        \"scopes\": {\n            \"write_store_credit_account_transactions\": \"Write\", \n            \"read_store_credit_account_transactions\": \"Read\"\n        }\n    },\n    \"Store credit accounts\": {\n        \"description\": \"View a customer's store credit balance and currency\", \n        \"scopes\": {\n            \"read_store_credit_accounts\": \"Read\"\n        }\n    },\n    \"Themes\": {\n        \"description\": \"View or manage theme templates and assets\", \n        \"scopes\": {\n            \"write_themes\": \"Write\", \n            \"read_themes\": \"Read\"\n        }\n    },\n    \"Third-party fulfillment orders\": {\n        \"description\": \"View or manage fulfillment orders assigned to a location managed by any fulfillment service\", \n        \"scopes\": {\n            \"write_third_party_fulfillment_orders\": \"Write\", \n            \"read_third_party_fulfillment_orders\": \"Read\"\n        }\n    },\n    \"Translations\": {\n        \"description\": \"View or manage content that can be translated\", \n        \"scopes\": {\n            \"write_translations\": \"Write\", \n            \"read_translations\": \"Read\"\n        }\n    },\n    \"all_cart_transforms\": {\n        \"description\": \"\", \n        \"scopes\": {\n            \"read_all_cart_transforms\": \"Read\"\n        }\n    },\n    \"all_checkout_completion_target_customizations\": {\n        \"description\": \"\", \n        \"scopes\": {\n            \"write_all_checkout_completion_target_customizations\": \"Write\", \n            \"read_all_checkout_completion_target_customizations\": \"Read\"\n        }\n    },\n    \"cart_transforms\": {\n        \"description\": \"\", \n        \"scopes\": {\n            \"write_cart_transforms\": \"Write\", \n            \"read_cart_transforms\": \"Read\"\n        }\n    },\n    \"cash_tracking\": {\n        \"description\": \"\", \n        \"scopes\": {\n            \"read_cash_tracking\": \"Read\"\n        }\n    },\n    \"companies\": {\n        \"description\": \"\", \n        \"scopes\": {\n            \"write_companies\": \"Write\", \n            \"read_companies\": \"Read\"\n        }\n    },\n    \"custom_fulfillment_services\": {\n        \"description\": \"\", \n        \"scopes\": {\n            \"write_custom_fulfillment_services\": \"Write\", \n            \"read_custom_fulfillment_services\": \"Read\"\n        }\n    },\n    \"customer_data_erasure\": {\n        \"description\": \"\", \n        \"scopes\": {\n            \"write_customer_data_erasure\": \"Write\", \n            \"read_customer_data_erasure\": \"Read\"\n        }\n    },\n    \"customer_merge\": {\n        \"description\": \"\", \n        \"scopes\": {\n            \"write_customer_merge\": \"Write\", \n            \"read_customer_merge\": \"Read\"\n        }\n    },\n    \"delivery_customizations\": {\n        \"description\": \"\", \n        \"scopes\": {\n            \"write_delivery_customizations\": \"Write\", \n            \"read_delivery_customizations\": \"Read\"\n        }\n    },\n    \"delivery_option_generators\": {\n        \"description\": \"\", \n        \"scopes\": {\n            \"write_delivery_option_generators\": \"Write\", \n            \"read_delivery_option_generators\": \"Read\"\n        }\n    },\n    \"discounts_allocator_functions\": {\n        \"description\": \"\", \n        \"scopes\": {\n            \"write_discounts_allocator_functions\": \"Write\", \n            \"read_discounts_allocator_functions\": \"Read\"\n        }\n    },\n    \"fulfillment_constraint_rules\": {\n        \"description\": \"\", \n        \"scopes\": {\n            \"write_fulfillment_constraint_rules\": \"Write\", \n            \"read_fulfillment_constraint_rules\": \"Read\"\n        }\n    },\n    \"gates\": {\n        \"description\": \"\", \n        \"scopes\": {\n            \"write_gates\": \"Write\", \n            \"read_gates\": \"Read\"\n        }\n    },\n    \"order_submission_rules\": {\n        \"description\": \"\", \n        \"scopes\": {\n            \"write_order_submission_rules\": \"Write\", \n            \"read_order_submission_rules\": \"Read\"\n        }\n    },\n    \"privacy_settings\": {\n        \"description\": \"\", \n        \"scopes\": {\n            \"write_privacy_settings\": \"Write\", \n            \"read_privacy_settings\": \"Read\"\n        }\n    },\n    \"shopify_payments_provider_accounts_sensitive\": {\n        \"description\": \"\", \n        \"scopes\": {\n            \"read_shopify_payments_provider_accounts_sensitive\": \"Read\"\n        }\n    },\n    \"validations\": {\n        \"description\": \"\", \n        \"scopes\": {\n            \"write_validations\": \"Write\", \n            \"read_validations\": \"Read\"\n        }\n    }\n  }\n}"
  },
  {
    "path": "pkg/analyzer/analyzers/shopify/shopify.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go shopify\n\npackage shopify\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nvar (\n\t// order the categories\n\tcategoryOrder = []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\"}\n)\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeShopify }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credInfo[\"key\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"key not found in credentialInfo\")\n\t}\n\n\tstoreUrl, ok := credInfo[\"store_url\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"store_url not found in credentialInfo\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, key, storeUrl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeShopify,\n\t\tMetadata: map[string]any{\n\t\t\t\"status_code\": info.StatusCode,\n\t\t},\n\t}\n\n\tresource := &analyzers.Resource{\n\t\tName:               info.ShopInfo.Shop.Name,\n\t\tFullyQualifiedName: info.ShopInfo.Shop.Domain + \"/\" + info.ShopInfo.Shop.Email,\n\t\tType:               \"shop\",\n\t\tMetadata: map[string]any{\n\t\t\t\"created_at\": info.ShopInfo.Shop.CreatedAt,\n\t\t},\n\t\tParent: nil,\n\t}\n\tresult.Bindings = make([]analyzers.Binding, 0)\n\n\tfor _, category := range categoryOrder {\n\t\tif val, ok := info.Scopes[category]; ok {\n\t\t\tcateogryResource := &analyzers.Resource{\n\t\t\t\tName:               category,\n\t\t\t\tFullyQualifiedName: resource.FullyQualifiedName + \"/\" + category, // shop.domain/shop.email/category\n\t\t\t\tType:               \"category\",\n\t\t\t\tParent:             resource,\n\t\t\t}\n\n\t\t\tif sliceContains(val.Scopes, \"Read\") && sliceContains(val.Scopes, \"Write\") {\n\t\t\t\tresult.Bindings = append(result.Bindings, analyzers.Binding{\n\t\t\t\t\tResource: *cateogryResource,\n\t\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\t\tValue: PermissionStrings[FullAccess],\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfor _, scope := range val.Scopes {\n\t\t\t\tlowerScope := strings.ToLower(scope)\n\t\t\t\tif _, ok := StringToPermission[lowerScope]; !ok { // skip unknown scopes/permission\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tresult.Bindings = append(result.Bindings, analyzers.Binding{\n\t\t\t\t\tResource: *cateogryResource,\n\t\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\t\tValue: lowerScope,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &result\n}\n\n//go:embed scopes.json\nvar scopesConfig []byte\n\nfunc sliceContains(slice []string, value string) bool {\n\tfor _, v := range slice {\n\t\tif v == value {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\ntype OutputScopes struct {\n\tDescription string\n\tScopes      []string\n}\n\nfunc (o OutputScopes) PrintScopes() string {\n\t// Custom rules unique to this analyzer\n\tvar scopes []string\n\tif sliceContains(o.Scopes, \"Read\") && sliceContains(o.Scopes, \"Write\") {\n\t\tscopes = append(scopes, \"Read & Write\")\n\t\tfor _, scope := range o.Scopes {\n\t\t\tif scope != \"Read\" && scope != \"Write\" {\n\t\t\t\tscopes = append(scopes, scope)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tscopes = append(scopes, o.Scopes...)\n\t}\n\treturn strings.Join(scopes, \", \")\n}\n\n// Category represents the structure of each category in the JSON\ntype CategoryJSON struct {\n\tDescription string            `json:\"description\"`\n\tScopes      map[string]string `json:\"scopes\"`\n}\n\n// Data represents the overall JSON structure\ntype ScopeDataJSON struct {\n\tCategories map[string]CategoryJSON `json:\"categories\"`\n}\n\n// Function to determine the appropriate scope\nfunc determineScopes(data ScopeDataJSON, input string) map[string]OutputScopes {\n\t// Split the input string into individual scopes\n\tinputScopes := strings.Split(input, \", \")\n\n\t// Map to store scopes found for each category\n\tscopeResults := make(map[string]OutputScopes)\n\n\t// Populate categoryScopes map with individual scopes found\n\tfor _, scope := range inputScopes {\n\t\tfor category, catData := range data.Categories {\n\t\t\tif scopeType, exists := catData.Scopes[scope]; exists {\n\t\t\t\tif _, ok := scopeResults[category]; !ok {\n\t\t\t\t\tscopeResults[category] = OutputScopes{Description: catData.Description}\n\t\t\t\t}\n\t\t\t\t// Extract the struct from the map\n\t\t\t\toutputData := scopeResults[category]\n\n\t\t\t\t// Modify the struct (ex: append \"Read\" or \"Write\" to the Scopes slice)\n\t\t\t\toutputData.Scopes = append(outputData.Scopes, scopeType)\n\n\t\t\t\t// Reassign the modified struct back to the map\n\t\t\t\tscopeResults[category] = outputData\n\t\t\t}\n\t\t}\n\t}\n\n\treturn scopeResults\n}\n\ntype ShopInfoJSON struct {\n\tShop struct {\n\t\tDomain    string `json:\"domain\"`\n\t\tName      string `json:\"name\"`\n\t\tEmail     string `json:\"email\"`\n\t\tCreatedAt string `json:\"created_at\"`\n\t} `json:\"shop\"`\n}\n\ntype SecretInfo struct {\n\tStatusCode int\n\tShopInfo   ShopInfoJSON\n\tScopes     map[string]OutputScopes\n}\n\nfunc getShopInfo(cfg *config.Config, key string, store string) (ShopInfoJSON, error) {\n\tvar shopInfo ShopInfoJSON\n\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(\"GET\", fmt.Sprintf(\"https://%s/admin/api/2024-04/shop.json\", store), nil)\n\tif err != nil {\n\t\treturn shopInfo, err\n\t}\n\n\treq.Header.Set(\"X-Shopify-Access-Token\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn shopInfo, err\n\t}\n\n\tdefer resp.Body.Close()\n\n\terr = json.NewDecoder(resp.Body).Decode(&shopInfo)\n\tif err != nil {\n\t\treturn shopInfo, err\n\t}\n\treturn shopInfo, nil\n}\n\ntype AccessScopesJSON struct {\n\tAccessScopes []struct {\n\t\tHandle string `json:\"handle\"`\n\t} `json:\"access_scopes\"`\n}\n\nfunc (a AccessScopesJSON) String() string {\n\tvar scopes []string\n\tfor _, scope := range a.AccessScopes {\n\t\tscopes = append(scopes, scope.Handle)\n\t}\n\treturn strings.Join(scopes, \", \")\n}\n\nfunc getAccessScopes(cfg *config.Config, key string, store string) (AccessScopesJSON, int, error) {\n\tvar accessScopes AccessScopesJSON\n\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(\"GET\", fmt.Sprintf(\"https://%s/admin/oauth/access_scopes.json\", store), nil)\n\tif err != nil {\n\t\treturn accessScopes, -1, err\n\t}\n\n\treq.Header.Set(\"X-Shopify-Access-Token\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn accessScopes, resp.StatusCode, err\n\t}\n\n\tdefer resp.Body.Close()\n\n\terr = json.NewDecoder(resp.Body).Decode(&accessScopes)\n\tif err != nil {\n\t\treturn accessScopes, resp.StatusCode, err\n\t}\n\treturn accessScopes, resp.StatusCode, nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string, storeURL string) {\n\t// ToDo: Add in logging\n\tif cfg.LoggingEnabled {\n\t\tcolor.Red(\"[x] Logging is not supported for this analyzer.\")\n\t\treturn\n\t}\n\n\tinfo, err := AnalyzePermissions(cfg, key, storeURL)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error: %s\", err.Error())\n\t\treturn\n\t}\n\n\tif info.StatusCode != 200 {\n\t\tcolor.Red(\"[x] Invalid Shopfiy API Key and Store URL combination\")\n\t\treturn\n\t}\n\tcolor.Green(\"[i] Valid Shopify API Key\\n\\n\")\n\n\tcolor.Yellow(\"[i] Shop Information\\n\")\n\tcolor.Yellow(\"Name: %s\", info.ShopInfo.Shop.Name)\n\tcolor.Yellow(\"Email: %s\", info.ShopInfo.Shop.Email)\n\tcolor.Yellow(\"Created At: %s\\n\\n\", info.ShopInfo.Shop.CreatedAt)\n\n\tprintAccessScopes(info.Scopes)\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string, storeURL string) (*SecretInfo, error) {\n\n\taccessScopes, statusCode, err := getAccessScopes(cfg, key, storeURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tshopInfo, err := getShopInfo(cfg, key, storeURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar data ScopeDataJSON\n\tif err := json.Unmarshal(scopesConfig, &data); err != nil {\n\t\treturn nil, err\n\t}\n\tscopes := determineScopes(data, accessScopes.String())\n\n\treturn &SecretInfo{\n\t\tStatusCode: statusCode,\n\t\tShopInfo:   shopInfo,\n\t\tScopes:     scopes,\n\t}, nil\n}\n\nfunc printAccessScopes(accessScopes map[string]OutputScopes) {\n\tcolor.Yellow(\"[i] Access Scopes\\n\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Scope\", \"Description\", \"Access\"})\n\n\tfor _, category := range categoryOrder {\n\t\tif val, ok := accessScopes[category]; ok {\n\t\t\tt.AppendRow([]interface{}{color.GreenString(category), color.GreenString(val.Description), color.GreenString(val.PrintScopes())})\n\t\t}\n\t}\n\tt.Render()\n\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/shopify/shopify_test.go",
    "content": "package shopify\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tsecret := testSecrets.MustGetField(\"SHOPIFY_ADMIN_SECRET\")\n\tdomain := testSecrets.MustGetField(\"SHOPIFY_DOMAIN\")\n\n\ttests := []struct {\n\t\tname     string\n\t\tkey      string\n\t\tstoreUrl string\n\t\twant     string\n\t\twantErr  bool\n\t}{\n\t\t{\n\t\t\tname:     \"valid Shopify key\",\n\t\t\tkey:      secret,\n\t\t\tstoreUrl: domain,\n\t\t\twant:     string(expectedOutput),\n\t\t\twantErr:  false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key, \"store_url\": tt.storeUrl})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/slack/expected_output.json",
    "content": "{\n    \"AnalyzerType\": 16,\n    \"Bindings\": [\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"conversations.history\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"conversations.replies\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"channels.info\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"conversations.info\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"conversations.list\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"conversations.members\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"groups.info\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"im.list\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"mpim.list\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"users.conversations\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"emoji.list\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"files.info\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"files.list\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"stars.list\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"pins.list\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"usergroups.list\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"usergroups.users.list\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"dnd.info\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"dnd.teamInfo\",\n          \"Parent\": null\n        }\n      },\n      {\n        \"Resource\": {\n          \"Name\": \"marge.haskell.bridge\",\n          \"FullyQualifiedName\": \"TSMCXP5FH/USMD5JM0F\",\n          \"Type\": \"user\",\n          \"Metadata\": {\n            \"scopes\": [\n              \"identify\",\n              \"channels:history\",\n              \"groups:history\",\n              \"im:history\",\n              \"channels:read\",\n              \"emoji:read\",\n              \"files:read\",\n              \"groups:read\",\n              \"im:read\",\n              \"stars:read\",\n              \"pins:read\",\n              \"usergroups:read\",\n              \"dnd:read\",\n              \"calls:read\"\n            ],\n            \"team\": \"ct.org\",\n            \"team_id\": \"TSMCXP5FH\",\n            \"url\": \"https://ctorgworkspace.slack.com/\"\n          },\n          \"Parent\": null\n        },\n        \"Permission\": {\n          \"Value\": \"calls.info\",\n          \"Parent\": null\n        }\n      }\n    ],\n    \"UnboundedResources\": null,\n    \"Metadata\": null\n  }"
  },
  {
    "path": "pkg/analyzer/analyzers/slack/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage slack\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    AdminAnalyticsRead Permission = iota\n    AdminAnalyticsGetfile Permission = iota\n    AdminAppActivitiesRead Permission = iota\n    AdminAppsActivitiesList Permission = iota\n    AdminAppsWrite Permission = iota\n    AdminAppsApprove Permission = iota\n    AdminAppsClearresolution Permission = iota\n    AdminAppsConfigSet Permission = iota\n    AdminAppsRequestsCancel Permission = iota\n    AdminAppsRestrict Permission = iota\n    AdminAppsUninstall Permission = iota\n    AdminAppsRead Permission = iota\n    AdminAppsApprovedList Permission = iota\n    AdminAppsConfigLookup Permission = iota\n    AdminAppsRequestsList Permission = iota\n    AdminAppsRestrictedList Permission = iota\n    AdminUsersWrite Permission = iota\n    AdminAuthPolicyAssignentities Permission = iota\n    AdminAuthPolicyRemoveentities Permission = iota\n    AdminUsersAssign Permission = iota\n    AdminUsersInvite Permission = iota\n    AdminUsersRemove Permission = iota\n    AdminUsersSessionClearsettings Permission = iota\n    AdminUsersSessionInvalidate Permission = iota\n    AdminUsersSessionReset Permission = iota\n    AdminUsersSessionResetbulk Permission = iota\n    AdminUsersSessionSetsettings Permission = iota\n    AdminUsersSetadmin Permission = iota\n    AdminUsersSetexpiration Permission = iota\n    AdminUsersSetowner Permission = iota\n    AdminUsersSetregular Permission = iota\n    AdminUsersRead Permission = iota\n    AdminAuthPolicyGetentities Permission = iota\n    AdminUsersList Permission = iota\n    AdminUsersSessionGetsettings Permission = iota\n    AdminUsersSessionList Permission = iota\n    AdminUsersUnsupportedversionsExport Permission = iota\n    AdminBarriersWrite Permission = iota\n    AdminBarriersCreate Permission = iota\n    AdminBarriersDelete Permission = iota\n    AdminBarriersUpdate Permission = iota\n    AdminBarriersRead Permission = iota\n    AdminBarriersList Permission = iota\n    AdminConversationsWrite Permission = iota\n    AdminConversationsArchive Permission = iota\n    AdminConversationsBulkarchive Permission = iota\n    AdminConversationsBulkdelete Permission = iota\n    AdminConversationsBulkmove Permission = iota\n    AdminConversationsConverttoprivate Permission = iota\n    AdminConversationsConverttopublic Permission = iota\n    AdminConversationsCreate Permission = iota\n    AdminConversationsDelete Permission = iota\n    AdminConversationsDisconnectshared Permission = iota\n    AdminConversationsInvite Permission = iota\n    AdminConversationsRemovecustomretention Permission = iota\n    AdminConversationsRename Permission = iota\n    AdminConversationsRestrictaccessAddgroup Permission = iota\n    AdminConversationsRestrictaccessRemovegroup Permission = iota\n    AdminConversationsSetconversationprefs Permission = iota\n    AdminConversationsSetcustomretention Permission = iota\n    AdminConversationsSetteams Permission = iota\n    AdminConversationsUnarchive Permission = iota\n    AdminConversationsRead Permission = iota\n    AdminConversationsEkmListoriginalconnectedchannelinfo Permission = iota\n    AdminConversationsGetconversationprefs Permission = iota\n    AdminConversationsGetcustomretention Permission = iota\n    AdminConversationsGetteams Permission = iota\n    AdminConversationsLookup Permission = iota\n    AdminConversationsRestrictaccessListgroups Permission = iota\n    AdminConversationsSearch Permission = iota\n    AdminTeamsWrite Permission = iota\n    AdminEmojiAdd Permission = iota\n    AdminEmojiAddalias Permission = iota\n    AdminEmojiRemove Permission = iota\n    AdminTeamsCreate Permission = iota\n    AdminTeamsSettingsSetdefaultchannels Permission = iota\n    AdminTeamsSettingsSetdescription Permission = iota\n    AdminTeamsSettingsSetdiscoverability Permission = iota\n    AdminTeamsSettingsSeticon Permission = iota\n    AdminTeamsSettingsSetname Permission = iota\n    AdminUsergroupsAddteams Permission = iota\n    AdminTeamsRead Permission = iota\n    AdminEmojiList Permission = iota\n    AdminTeamsAdminsList Permission = iota\n    AdminTeamsList Permission = iota\n    AdminTeamsOwnersList Permission = iota\n    AdminTeamsSettingsInfo Permission = iota\n    AdminWorkflowsRead Permission = iota\n    AdminFunctionsList Permission = iota\n    AdminFunctionsPermissionsLookup Permission = iota\n    AdminWorkflowsPermissionsLookup Permission = iota\n    AdminWorkflowsSearch Permission = iota\n    AdminWorkflowsWrite Permission = iota\n    AdminFunctionsPermissionsSet Permission = iota\n    AdminWorkflowsCollaboratorsAdd Permission = iota\n    AdminWorkflowsCollaboratorsRemove Permission = iota\n    AdminWorkflowsUnpublish Permission = iota\n    AdminInvitesWrite Permission = iota\n    AdminInviterequestsApprove Permission = iota\n    AdminInviterequestsDeny Permission = iota\n    AdminInvitesRead Permission = iota\n    AdminInviterequestsApprovedList Permission = iota\n    AdminInviterequestsDeniedList Permission = iota\n    AdminInviterequestsList Permission = iota\n    AdminRolesWrite Permission = iota\n    AdminRolesAddassignments Permission = iota\n    AdminRolesRemoveassignments Permission = iota\n    AdminRolesRead Permission = iota\n    AdminRolesListassignments Permission = iota\n    AdminUsergroupsWrite Permission = iota\n    AdminUsergroupsAddchannels Permission = iota\n    AdminUsergroupsRemovechannels Permission = iota\n    AdminUsergroupsRead Permission = iota\n    AdminUsergroupsListchannels Permission = iota\n    HostingRead Permission = iota\n    AppsActivitiesList Permission = iota\n    ConnectionsWrite Permission = iota\n    AppsConnectionsOpen Permission = iota\n    Token Permission = iota\n    AppsDatastoreBulkdelete Permission = iota\n    AppsDatastoreBulkget Permission = iota\n    AppsDatastoreBulkput Permission = iota\n    AppsDatastoreDelete Permission = iota\n    AppsDatastoreGet Permission = iota\n    AppsDatastorePut Permission = iota\n    AppsDatastoreQuery Permission = iota\n    AppsDatastoreUpdate Permission = iota\n    DatastoreRead Permission = iota\n    AppsDatastoreCount Permission = iota\n    AuthorizationsRead Permission = iota\n    AppsEventAuthorizationsList Permission = iota\n    Bot Permission = iota\n    AuthRevoke Permission = iota\n    AuthTest Permission = iota\n    ChatGetpermalink Permission = iota\n    ChatScheduledmessagesList Permission = iota\n    DialogOpen Permission = iota\n    FunctionsCompleteerror Permission = iota\n    FunctionsCompletesuccess Permission = iota\n    RtmConnect Permission = iota\n    RtmStart Permission = iota\n    ViewsOpen Permission = iota\n    ViewsPublish Permission = iota\n    ViewsPush Permission = iota\n    ViewsUpdate Permission = iota\n    BookmarksWrite Permission = iota\n    BookmarksAdd Permission = iota\n    BookmarksEdit Permission = iota\n    BookmarksRemove Permission = iota\n    BookmarksRead Permission = iota\n    BookmarksList Permission = iota\n    UsersRead Permission = iota\n    BotsInfo Permission = iota\n    UsersGetpresence Permission = iota\n    UsersInfo Permission = iota\n    UsersList Permission = iota\n    CallsWrite Permission = iota\n    CallsAdd Permission = iota\n    CallsEnd Permission = iota\n    CallsParticipantsAdd Permission = iota\n    CallsParticipantsRemove Permission = iota\n    CallsUpdate Permission = iota\n    CallsRead Permission = iota\n    CallsInfo Permission = iota\n    ChannelsManage Permission = iota\n    ChannelsCreate Permission = iota\n    ChannelsMark Permission = iota\n    ConversationsArchive Permission = iota\n    ConversationsClose Permission = iota\n    ConversationsCreate Permission = iota\n    ConversationsKick Permission = iota\n    ConversationsLeave Permission = iota\n    ConversationsMark Permission = iota\n    ConversationsOpen Permission = iota\n    ConversationsRename Permission = iota\n    ConversationsUnarchive Permission = iota\n    GroupsCreate Permission = iota\n    GroupsMark Permission = iota\n    ImMark Permission = iota\n    ImOpen Permission = iota\n    MpimMark Permission = iota\n    MpimOpen Permission = iota\n    ChannelsRead Permission = iota\n    ChannelsInfo Permission = iota\n    ConversationsInfo Permission = iota\n    ConversationsList Permission = iota\n    ConversationsMembers Permission = iota\n    GroupsInfo Permission = iota\n    ImList Permission = iota\n    MpimList Permission = iota\n    UsersConversations Permission = iota\n    ChannelsWriteInvites Permission = iota\n    ChannelsInvite Permission = iota\n    ConversationsInvite Permission = iota\n    GroupsInvite Permission = iota\n    ChatWrite Permission = iota\n    ChatDelete Permission = iota\n    ChatDeletescheduledmessage Permission = iota\n    ChatMemessage Permission = iota\n    ChatPostephemeral Permission = iota\n    ChatPostmessage Permission = iota\n    ChatSchedulemessage Permission = iota\n    ChatUpdate Permission = iota\n    LinksWrite Permission = iota\n    ChatUnfurl Permission = iota\n    ConversationsConnectWrite Permission = iota\n    ConversationsAcceptsharedinvite Permission = iota\n    ConversationsInviteshared Permission = iota\n    ConversationsConnectManage Permission = iota\n    ConversationsApprovesharedinvite Permission = iota\n    ConversationsDeclinesharedinvite Permission = iota\n    ConversationsListconnectinvites Permission = iota\n    ChannelsHistory Permission = iota\n    ConversationsHistory Permission = iota\n    ConversationsReplies Permission = iota\n    ChannelsJoin Permission = iota\n    ConversationsJoin Permission = iota\n    ChannelsWriteTopic Permission = iota\n    ConversationsSetpurpose Permission = iota\n    ConversationsSettopic Permission = iota\n    DndWrite Permission = iota\n    DndEnddnd Permission = iota\n    DndEndsnooze Permission = iota\n    DndSetsnooze Permission = iota\n    DndRead Permission = iota\n    DndInfo Permission = iota\n    DndTeaminfo Permission = iota\n    EmojiRead Permission = iota\n    EmojiList Permission = iota\n    FilesWrite Permission = iota\n    FilesCommentsDelete Permission = iota\n    FilesCompleteuploadexternal Permission = iota\n    FilesDelete Permission = iota\n    FilesGetuploadurlexternal Permission = iota\n    FilesRevokepublicurl Permission = iota\n    FilesSharedpublicurl Permission = iota\n    FilesUpload Permission = iota\n    FilesRead Permission = iota\n    FilesInfo Permission = iota\n    FilesList Permission = iota\n    RemoteFilesWrite Permission = iota\n    FilesRemoteAdd Permission = iota\n    FilesRemoteRemove Permission = iota\n    FilesRemoteUpdate Permission = iota\n    RemoteFilesRead Permission = iota\n    FilesRemoteInfo Permission = iota\n    FilesRemoteList Permission = iota\n    RemoteFilesShare Permission = iota\n    FilesRemoteShare Permission = iota\n    AppConfigurationsWrite Permission = iota\n    FunctionsDistributionsPermissionsAdd Permission = iota\n    FunctionsDistributionsPermissionsRemove Permission = iota\n    FunctionsDistributionsPermissionsSet Permission = iota\n    AppConfigurationsRead Permission = iota\n    FunctionsDistributionsPermissionsList Permission = iota\n    Conversations Permission = iota\n    GroupsOpen Permission = iota\n    TokensBasic Permission = iota\n    MigrationExchange Permission = iota\n    Email Permission = iota\n    OpenidConnectUserinfo Permission = iota\n    PinsWrite Permission = iota\n    PinsAdd Permission = iota\n    PinsRemove Permission = iota\n    PinsRead Permission = iota\n    PinsList Permission = iota\n    ReactionsWrite Permission = iota\n    ReactionsAdd Permission = iota\n    ReactionsRemove Permission = iota\n    ReactionsRead Permission = iota\n    ReactionsGet Permission = iota\n    ReactionsList Permission = iota\n    RemindersWrite Permission = iota\n    RemindersAdd Permission = iota\n    RemindersComplete Permission = iota\n    RemindersDelete Permission = iota\n    RemindersRead Permission = iota\n    RemindersInfo Permission = iota\n    RemindersList Permission = iota\n    SearchRead Permission = iota\n    SearchAll Permission = iota\n    SearchFiles Permission = iota\n    SearchMessages Permission = iota\n    StarsWrite Permission = iota\n    StarsAdd Permission = iota\n    StarsRemove Permission = iota\n    StarsRead Permission = iota\n    StarsList Permission = iota\n    Admin Permission = iota\n    TeamAccesslogs Permission = iota\n    TeamBillableinfo Permission = iota\n    TeamIntegrationlogs Permission = iota\n    TeamBillingRead Permission = iota\n    TeamBillingInfo Permission = iota\n    TeamRead Permission = iota\n    TeamInfo Permission = iota\n    TeamPreferencesRead Permission = iota\n    TeamPreferencesList Permission = iota\n    UsersProfileRead Permission = iota\n    TeamProfileGet Permission = iota\n    UsersProfileGet Permission = iota\n    UsergroupsWrite Permission = iota\n    UsergroupsCreate Permission = iota\n    UsergroupsDisable Permission = iota\n    UsergroupsEnable Permission = iota\n    UsergroupsUpdate Permission = iota\n    UsergroupsUsersUpdate Permission = iota\n    UsergroupsRead Permission = iota\n    UsergroupsList Permission = iota\n    UsergroupsUsersList Permission = iota\n    UsersProfileWrite Permission = iota\n    UsersDeletephoto Permission = iota\n    UsersProfileSet Permission = iota\n    UsersSetphoto Permission = iota\n    IdentityBasic Permission = iota\n    UsersIdentity Permission = iota\n    UsersReadEmail Permission = iota\n    UsersLookupbyemail Permission = iota\n    UsersWrite Permission = iota\n    UsersSetactive Permission = iota\n    UsersSetpresence Permission = iota\n    WorkflowStepsExecute Permission = iota\n    WorkflowsStepcompleted Permission = iota\n    WorkflowsStepfailed Permission = iota\n    WorkflowsUpdatestep Permission = iota\n    TriggersWrite Permission = iota\n    WorkflowsTriggersPermissionsAdd Permission = iota\n    WorkflowsTriggersPermissionsRemove Permission = iota\n    WorkflowsTriggersPermissionsSet Permission = iota\n    TriggersRead Permission = iota\n    WorkflowsTriggersPermissionsList Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        AdminAnalyticsRead: \"admin.analytics:read\",\n        AdminAnalyticsGetfile: \"admin.analytics.getFile\",\n        AdminAppActivitiesRead: \"admin.app_activities:read\",\n        AdminAppsActivitiesList: \"admin.apps.activities.list\",\n        AdminAppsWrite: \"admin.apps:write\",\n        AdminAppsApprove: \"admin.apps.approve\",\n        AdminAppsClearresolution: \"admin.apps.clearResolution\",\n        AdminAppsConfigSet: \"admin.apps.config.set\",\n        AdminAppsRequestsCancel: \"admin.apps.requests.cancel\",\n        AdminAppsRestrict: \"admin.apps.restrict\",\n        AdminAppsUninstall: \"admin.apps.uninstall\",\n        AdminAppsRead: \"admin.apps:read\",\n        AdminAppsApprovedList: \"admin.apps.approved.list\",\n        AdminAppsConfigLookup: \"admin.apps.config.lookup\",\n        AdminAppsRequestsList: \"admin.apps.requests.list\",\n        AdminAppsRestrictedList: \"admin.apps.restricted.list\",\n        AdminUsersWrite: \"admin.users:write\",\n        AdminAuthPolicyAssignentities: \"admin.auth.policy.assignEntities\",\n        AdminAuthPolicyRemoveentities: \"admin.auth.policy.removeEntities\",\n        AdminUsersAssign: \"admin.users.assign\",\n        AdminUsersInvite: \"admin.users.invite\",\n        AdminUsersRemove: \"admin.users.remove\",\n        AdminUsersSessionClearsettings: \"admin.users.session.clearSettings\",\n        AdminUsersSessionInvalidate: \"admin.users.session.invalidate\",\n        AdminUsersSessionReset: \"admin.users.session.reset\",\n        AdminUsersSessionResetbulk: \"admin.users.session.resetBulk\",\n        AdminUsersSessionSetsettings: \"admin.users.session.setSettings\",\n        AdminUsersSetadmin: \"admin.users.setAdmin\",\n        AdminUsersSetexpiration: \"admin.users.setExpiration\",\n        AdminUsersSetowner: \"admin.users.setOwner\",\n        AdminUsersSetregular: \"admin.users.setRegular\",\n        AdminUsersRead: \"admin.users:read\",\n        AdminAuthPolicyGetentities: \"admin.auth.policy.getEntities\",\n        AdminUsersList: \"admin.users.list\",\n        AdminUsersSessionGetsettings: \"admin.users.session.getSettings\",\n        AdminUsersSessionList: \"admin.users.session.list\",\n        AdminUsersUnsupportedversionsExport: \"admin.users.unsupportedVersions.export\",\n        AdminBarriersWrite: \"admin.barriers:write\",\n        AdminBarriersCreate: \"admin.barriers.create\",\n        AdminBarriersDelete: \"admin.barriers.delete\",\n        AdminBarriersUpdate: \"admin.barriers.update\",\n        AdminBarriersRead: \"admin.barriers:read\",\n        AdminBarriersList: \"admin.barriers.list\",\n        AdminConversationsWrite: \"admin.conversations:write\",\n        AdminConversationsArchive: \"admin.conversations.archive\",\n        AdminConversationsBulkarchive: \"admin.conversations.bulkArchive\",\n        AdminConversationsBulkdelete: \"admin.conversations.bulkDelete\",\n        AdminConversationsBulkmove: \"admin.conversations.bulkMove\",\n        AdminConversationsConverttoprivate: \"admin.conversations.convertToPrivate\",\n        AdminConversationsConverttopublic: \"admin.conversations.convertToPublic\",\n        AdminConversationsCreate: \"admin.conversations.create\",\n        AdminConversationsDelete: \"admin.conversations.delete\",\n        AdminConversationsDisconnectshared: \"admin.conversations.disconnectShared\",\n        AdminConversationsInvite: \"admin.conversations.invite\",\n        AdminConversationsRemovecustomretention: \"admin.conversations.removeCustomRetention\",\n        AdminConversationsRename: \"admin.conversations.rename\",\n        AdminConversationsRestrictaccessAddgroup: \"admin.conversations.restrictAccess.addGroup\",\n        AdminConversationsRestrictaccessRemovegroup: \"admin.conversations.restrictAccess.removeGroup\",\n        AdminConversationsSetconversationprefs: \"admin.conversations.setConversationPrefs\",\n        AdminConversationsSetcustomretention: \"admin.conversations.setCustomRetention\",\n        AdminConversationsSetteams: \"admin.conversations.setTeams\",\n        AdminConversationsUnarchive: \"admin.conversations.unarchive\",\n        AdminConversationsRead: \"admin.conversations:read\",\n        AdminConversationsEkmListoriginalconnectedchannelinfo: \"admin.conversations.ekm.listOriginalConnectedChannelInfo\",\n        AdminConversationsGetconversationprefs: \"admin.conversations.getConversationPrefs\",\n        AdminConversationsGetcustomretention: \"admin.conversations.getCustomRetention\",\n        AdminConversationsGetteams: \"admin.conversations.getTeams\",\n        AdminConversationsLookup: \"admin.conversations.lookup\",\n        AdminConversationsRestrictaccessListgroups: \"admin.conversations.restrictAccess.listGroups\",\n        AdminConversationsSearch: \"admin.conversations.search\",\n        AdminTeamsWrite: \"admin.teams:write\",\n        AdminEmojiAdd: \"admin.emoji.add\",\n        AdminEmojiAddalias: \"admin.emoji.addAlias\",\n        AdminEmojiRemove: \"admin.emoji.remove\",\n        AdminTeamsCreate: \"admin.teams.create\",\n        AdminTeamsSettingsSetdefaultchannels: \"admin.teams.settings.setDefaultChannels\",\n        AdminTeamsSettingsSetdescription: \"admin.teams.settings.setDescription\",\n        AdminTeamsSettingsSetdiscoverability: \"admin.teams.settings.setDiscoverability\",\n        AdminTeamsSettingsSeticon: \"admin.teams.settings.setIcon\",\n        AdminTeamsSettingsSetname: \"admin.teams.settings.setName\",\n        AdminUsergroupsAddteams: \"admin.usergroups.addTeams\",\n        AdminTeamsRead: \"admin.teams:read\",\n        AdminEmojiList: \"admin.emoji.list\",\n        AdminTeamsAdminsList: \"admin.teams.admins.list\",\n        AdminTeamsList: \"admin.teams.list\",\n        AdminTeamsOwnersList: \"admin.teams.owners.list\",\n        AdminTeamsSettingsInfo: \"admin.teams.settings.info\",\n        AdminWorkflowsRead: \"admin.workflows:read\",\n        AdminFunctionsList: \"admin.functions.list\",\n        AdminFunctionsPermissionsLookup: \"admin.functions.permissions.lookup\",\n        AdminWorkflowsPermissionsLookup: \"admin.workflows.permissions.lookup\",\n        AdminWorkflowsSearch: \"admin.workflows.search\",\n        AdminWorkflowsWrite: \"admin.workflows:write\",\n        AdminFunctionsPermissionsSet: \"admin.functions.permissions.set\",\n        AdminWorkflowsCollaboratorsAdd: \"admin.workflows.collaborators.add\",\n        AdminWorkflowsCollaboratorsRemove: \"admin.workflows.collaborators.remove\",\n        AdminWorkflowsUnpublish: \"admin.workflows.unpublish\",\n        AdminInvitesWrite: \"admin.invites:write\",\n        AdminInviterequestsApprove: \"admin.inviteRequests.approve\",\n        AdminInviterequestsDeny: \"admin.inviteRequests.deny\",\n        AdminInvitesRead: \"admin.invites:read\",\n        AdminInviterequestsApprovedList: \"admin.inviteRequests.approved.list\",\n        AdminInviterequestsDeniedList: \"admin.inviteRequests.denied.list\",\n        AdminInviterequestsList: \"admin.inviteRequests.list\",\n        AdminRolesWrite: \"admin.roles:write\",\n        AdminRolesAddassignments: \"admin.roles.addAssignments\",\n        AdminRolesRemoveassignments: \"admin.roles.removeAssignments\",\n        AdminRolesRead: \"admin.roles:read\",\n        AdminRolesListassignments: \"admin.roles.listAssignments\",\n        AdminUsergroupsWrite: \"admin.usergroups:write\",\n        AdminUsergroupsAddchannels: \"admin.usergroups.addChannels\",\n        AdminUsergroupsRemovechannels: \"admin.usergroups.removeChannels\",\n        AdminUsergroupsRead: \"admin.usergroups:read\",\n        AdminUsergroupsListchannels: \"admin.usergroups.listChannels\",\n        HostingRead: \"hosting:read\",\n        AppsActivitiesList: \"apps.activities.list\",\n        ConnectionsWrite: \"connections:write\",\n        AppsConnectionsOpen: \"apps.connections.open\",\n        Token: \"token\",\n        AppsDatastoreBulkdelete: \"apps.datastore.bulkDelete\",\n        AppsDatastoreBulkget: \"apps.datastore.bulkGet\",\n        AppsDatastoreBulkput: \"apps.datastore.bulkPut\",\n        AppsDatastoreDelete: \"apps.datastore.delete\",\n        AppsDatastoreGet: \"apps.datastore.get\",\n        AppsDatastorePut: \"apps.datastore.put\",\n        AppsDatastoreQuery: \"apps.datastore.query\",\n        AppsDatastoreUpdate: \"apps.datastore.update\",\n        DatastoreRead: \"datastore:read\",\n        AppsDatastoreCount: \"apps.datastore.count\",\n        AuthorizationsRead: \"authorizations:read\",\n        AppsEventAuthorizationsList: \"apps.event.authorizations.list\",\n        Bot: \"bot\",\n        AuthRevoke: \"auth.revoke\",\n        AuthTest: \"auth.test\",\n        ChatGetpermalink: \"chat.getPermalink\",\n        ChatScheduledmessagesList: \"chat.scheduledMessages.list\",\n        DialogOpen: \"dialog.open\",\n        FunctionsCompleteerror: \"functions.completeError\",\n        FunctionsCompletesuccess: \"functions.completeSuccess\",\n        RtmConnect: \"rtm.connect\",\n        RtmStart: \"rtm.start\",\n        ViewsOpen: \"views.open\",\n        ViewsPublish: \"views.publish\",\n        ViewsPush: \"views.push\",\n        ViewsUpdate: \"views.update\",\n        BookmarksWrite: \"bookmarks:write\",\n        BookmarksAdd: \"bookmarks.add\",\n        BookmarksEdit: \"bookmarks.edit\",\n        BookmarksRemove: \"bookmarks.remove\",\n        BookmarksRead: \"bookmarks:read\",\n        BookmarksList: \"bookmarks.list\",\n        UsersRead: \"users:read\",\n        BotsInfo: \"bots.info\",\n        UsersGetpresence: \"users.getPresence\",\n        UsersInfo: \"users.info\",\n        UsersList: \"users.list\",\n        CallsWrite: \"calls:write\",\n        CallsAdd: \"calls.add\",\n        CallsEnd: \"calls.end\",\n        CallsParticipantsAdd: \"calls.participants.add\",\n        CallsParticipantsRemove: \"calls.participants.remove\",\n        CallsUpdate: \"calls.update\",\n        CallsRead: \"calls:read\",\n        CallsInfo: \"calls.info\",\n        ChannelsManage: \"channels:manage\",\n        ChannelsCreate: \"channels.create\",\n        ChannelsMark: \"channels.mark\",\n        ConversationsArchive: \"conversations.archive\",\n        ConversationsClose: \"conversations.close\",\n        ConversationsCreate: \"conversations.create\",\n        ConversationsKick: \"conversations.kick\",\n        ConversationsLeave: \"conversations.leave\",\n        ConversationsMark: \"conversations.mark\",\n        ConversationsOpen: \"conversations.open\",\n        ConversationsRename: \"conversations.rename\",\n        ConversationsUnarchive: \"conversations.unarchive\",\n        GroupsCreate: \"groups.create\",\n        GroupsMark: \"groups.mark\",\n        ImMark: \"im.mark\",\n        ImOpen: \"im.open\",\n        MpimMark: \"mpim.mark\",\n        MpimOpen: \"mpim.open\",\n        ChannelsRead: \"channels:read\",\n        ChannelsInfo: \"channels.info\",\n        ConversationsInfo: \"conversations.info\",\n        ConversationsList: \"conversations.list\",\n        ConversationsMembers: \"conversations.members\",\n        GroupsInfo: \"groups.info\",\n        ImList: \"im.list\",\n        MpimList: \"mpim.list\",\n        UsersConversations: \"users.conversations\",\n        ChannelsWriteInvites: \"channels:write.invites\",\n        ChannelsInvite: \"channels.invite\",\n        ConversationsInvite: \"conversations.invite\",\n        GroupsInvite: \"groups.invite\",\n        ChatWrite: \"chat:write\",\n        ChatDelete: \"chat.delete\",\n        ChatDeletescheduledmessage: \"chat.deleteScheduledMessage\",\n        ChatMemessage: \"chat.meMessage\",\n        ChatPostephemeral: \"chat.postEphemeral\",\n        ChatPostmessage: \"chat.postMessage\",\n        ChatSchedulemessage: \"chat.scheduleMessage\",\n        ChatUpdate: \"chat.update\",\n        LinksWrite: \"links:write\",\n        ChatUnfurl: \"chat.unfurl\",\n        ConversationsConnectWrite: \"conversations.connect:write\",\n        ConversationsAcceptsharedinvite: \"conversations.acceptSharedInvite\",\n        ConversationsInviteshared: \"conversations.inviteShared\",\n        ConversationsConnectManage: \"conversations.connect:manage\",\n        ConversationsApprovesharedinvite: \"conversations.approveSharedInvite\",\n        ConversationsDeclinesharedinvite: \"conversations.declineSharedInvite\",\n        ConversationsListconnectinvites: \"conversations.listConnectInvites\",\n        ChannelsHistory: \"channels:history\",\n        ConversationsHistory: \"conversations.history\",\n        ConversationsReplies: \"conversations.replies\",\n        ChannelsJoin: \"channels:join\",\n        ConversationsJoin: \"conversations.join\",\n        ChannelsWriteTopic: \"channels:write.topic\",\n        ConversationsSetpurpose: \"conversations.setPurpose\",\n        ConversationsSettopic: \"conversations.setTopic\",\n        DndWrite: \"dnd:write\",\n        DndEnddnd: \"dnd.endDnd\",\n        DndEndsnooze: \"dnd.endSnooze\",\n        DndSetsnooze: \"dnd.setSnooze\",\n        DndRead: \"dnd:read\",\n        DndInfo: \"dnd.info\",\n        DndTeaminfo: \"dnd.teamInfo\",\n        EmojiRead: \"emoji:read\",\n        EmojiList: \"emoji.list\",\n        FilesWrite: \"files:write\",\n        FilesCommentsDelete: \"files.comments.delete\",\n        FilesCompleteuploadexternal: \"files.completeUploadExternal\",\n        FilesDelete: \"files.delete\",\n        FilesGetuploadurlexternal: \"files.getUploadURLExternal\",\n        FilesRevokepublicurl: \"files.revokePublicURL\",\n        FilesSharedpublicurl: \"files.sharedPublicURL\",\n        FilesUpload: \"files.upload\",\n        FilesRead: \"files:read\",\n        FilesInfo: \"files.info\",\n        FilesList: \"files.list\",\n        RemoteFilesWrite: \"remote_files:write\",\n        FilesRemoteAdd: \"files.remote.add\",\n        FilesRemoteRemove: \"files.remote.remove\",\n        FilesRemoteUpdate: \"files.remote.update\",\n        RemoteFilesRead: \"remote_files:read\",\n        FilesRemoteInfo: \"files.remote.info\",\n        FilesRemoteList: \"files.remote.list\",\n        RemoteFilesShare: \"remote_files:share\",\n        FilesRemoteShare: \"files.remote.share\",\n        AppConfigurationsWrite: \"app_configurations:write\",\n        FunctionsDistributionsPermissionsAdd: \"functions.distributions.permissions.add\",\n        FunctionsDistributionsPermissionsRemove: \"functions.distributions.permissions.remove\",\n        FunctionsDistributionsPermissionsSet: \"functions.distributions.permissions.set\",\n        AppConfigurationsRead: \"app_configurations:read\",\n        FunctionsDistributionsPermissionsList: \"functions.distributions.permissions.list\",\n        Conversations: \"conversations\",\n        GroupsOpen: \"groups.open\",\n        TokensBasic: \"tokens.basic\",\n        MigrationExchange: \"migration.exchange\",\n        Email: \"email\",\n        OpenidConnectUserinfo: \"openid.connect.userInfo\",\n        PinsWrite: \"pins:write\",\n        PinsAdd: \"pins.add\",\n        PinsRemove: \"pins.remove\",\n        PinsRead: \"pins:read\",\n        PinsList: \"pins.list\",\n        ReactionsWrite: \"reactions:write\",\n        ReactionsAdd: \"reactions.add\",\n        ReactionsRemove: \"reactions.remove\",\n        ReactionsRead: \"reactions:read\",\n        ReactionsGet: \"reactions.get\",\n        ReactionsList: \"reactions.list\",\n        RemindersWrite: \"reminders:write\",\n        RemindersAdd: \"reminders.add\",\n        RemindersComplete: \"reminders.complete\",\n        RemindersDelete: \"reminders.delete\",\n        RemindersRead: \"reminders:read\",\n        RemindersInfo: \"reminders.info\",\n        RemindersList: \"reminders.list\",\n        SearchRead: \"search:read\",\n        SearchAll: \"search.all\",\n        SearchFiles: \"search.files\",\n        SearchMessages: \"search.messages\",\n        StarsWrite: \"stars:write\",\n        StarsAdd: \"stars.add\",\n        StarsRemove: \"stars.remove\",\n        StarsRead: \"stars:read\",\n        StarsList: \"stars.list\",\n        Admin: \"admin\",\n        TeamAccesslogs: \"team.accessLogs\",\n        TeamBillableinfo: \"team.billableInfo\",\n        TeamIntegrationlogs: \"team.integrationLogs\",\n        TeamBillingRead: \"team.billing:read\",\n        TeamBillingInfo: \"team.billing.info\",\n        TeamRead: \"team:read\",\n        TeamInfo: \"team.info\",\n        TeamPreferencesRead: \"team.preferences:read\",\n        TeamPreferencesList: \"team.preferences.list\",\n        UsersProfileRead: \"users.profile:read\",\n        TeamProfileGet: \"team.profile.get\",\n        UsersProfileGet: \"users.profile.get\",\n        UsergroupsWrite: \"usergroups:write\",\n        UsergroupsCreate: \"usergroups.create\",\n        UsergroupsDisable: \"usergroups.disable\",\n        UsergroupsEnable: \"usergroups.enable\",\n        UsergroupsUpdate: \"usergroups.update\",\n        UsergroupsUsersUpdate: \"usergroups.users.update\",\n        UsergroupsRead: \"usergroups:read\",\n        UsergroupsList: \"usergroups.list\",\n        UsergroupsUsersList: \"usergroups.users.list\",\n        UsersProfileWrite: \"users.profile:write\",\n        UsersDeletephoto: \"users.deletePhoto\",\n        UsersProfileSet: \"users.profile.set\",\n        UsersSetphoto: \"users.setPhoto\",\n        IdentityBasic: \"identity.basic\",\n        UsersIdentity: \"users.identity\",\n        UsersReadEmail: \"users:read.email\",\n        UsersLookupbyemail: \"users.lookupByEmail\",\n        UsersWrite: \"users:write\",\n        UsersSetactive: \"users.setActive\",\n        UsersSetpresence: \"users.setPresence\",\n        WorkflowStepsExecute: \"workflow.steps:execute\",\n        WorkflowsStepcompleted: \"workflows.stepCompleted\",\n        WorkflowsStepfailed: \"workflows.stepFailed\",\n        WorkflowsUpdatestep: \"workflows.updateStep\",\n        TriggersWrite: \"triggers:write\",\n        WorkflowsTriggersPermissionsAdd: \"workflows.triggers.permissions.add\",\n        WorkflowsTriggersPermissionsRemove: \"workflows.triggers.permissions.remove\",\n        WorkflowsTriggersPermissionsSet: \"workflows.triggers.permissions.set\",\n        TriggersRead: \"triggers:read\",\n        WorkflowsTriggersPermissionsList: \"workflows.triggers.permissions.list\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"admin.analytics:read\": AdminAnalyticsRead,\n        \"admin.analytics.getFile\": AdminAnalyticsGetfile,\n        \"admin.app_activities:read\": AdminAppActivitiesRead,\n        \"admin.apps.activities.list\": AdminAppsActivitiesList,\n        \"admin.apps:write\": AdminAppsWrite,\n        \"admin.apps.approve\": AdminAppsApprove,\n        \"admin.apps.clearResolution\": AdminAppsClearresolution,\n        \"admin.apps.config.set\": AdminAppsConfigSet,\n        \"admin.apps.requests.cancel\": AdminAppsRequestsCancel,\n        \"admin.apps.restrict\": AdminAppsRestrict,\n        \"admin.apps.uninstall\": AdminAppsUninstall,\n        \"admin.apps:read\": AdminAppsRead,\n        \"admin.apps.approved.list\": AdminAppsApprovedList,\n        \"admin.apps.config.lookup\": AdminAppsConfigLookup,\n        \"admin.apps.requests.list\": AdminAppsRequestsList,\n        \"admin.apps.restricted.list\": AdminAppsRestrictedList,\n        \"admin.users:write\": AdminUsersWrite,\n        \"admin.auth.policy.assignEntities\": AdminAuthPolicyAssignentities,\n        \"admin.auth.policy.removeEntities\": AdminAuthPolicyRemoveentities,\n        \"admin.users.assign\": AdminUsersAssign,\n        \"admin.users.invite\": AdminUsersInvite,\n        \"admin.users.remove\": AdminUsersRemove,\n        \"admin.users.session.clearSettings\": AdminUsersSessionClearsettings,\n        \"admin.users.session.invalidate\": AdminUsersSessionInvalidate,\n        \"admin.users.session.reset\": AdminUsersSessionReset,\n        \"admin.users.session.resetBulk\": AdminUsersSessionResetbulk,\n        \"admin.users.session.setSettings\": AdminUsersSessionSetsettings,\n        \"admin.users.setAdmin\": AdminUsersSetadmin,\n        \"admin.users.setExpiration\": AdminUsersSetexpiration,\n        \"admin.users.setOwner\": AdminUsersSetowner,\n        \"admin.users.setRegular\": AdminUsersSetregular,\n        \"admin.users:read\": AdminUsersRead,\n        \"admin.auth.policy.getEntities\": AdminAuthPolicyGetentities,\n        \"admin.users.list\": AdminUsersList,\n        \"admin.users.session.getSettings\": AdminUsersSessionGetsettings,\n        \"admin.users.session.list\": AdminUsersSessionList,\n        \"admin.users.unsupportedVersions.export\": AdminUsersUnsupportedversionsExport,\n        \"admin.barriers:write\": AdminBarriersWrite,\n        \"admin.barriers.create\": AdminBarriersCreate,\n        \"admin.barriers.delete\": AdminBarriersDelete,\n        \"admin.barriers.update\": AdminBarriersUpdate,\n        \"admin.barriers:read\": AdminBarriersRead,\n        \"admin.barriers.list\": AdminBarriersList,\n        \"admin.conversations:write\": AdminConversationsWrite,\n        \"admin.conversations.archive\": AdminConversationsArchive,\n        \"admin.conversations.bulkArchive\": AdminConversationsBulkarchive,\n        \"admin.conversations.bulkDelete\": AdminConversationsBulkdelete,\n        \"admin.conversations.bulkMove\": AdminConversationsBulkmove,\n        \"admin.conversations.convertToPrivate\": AdminConversationsConverttoprivate,\n        \"admin.conversations.convertToPublic\": AdminConversationsConverttopublic,\n        \"admin.conversations.create\": AdminConversationsCreate,\n        \"admin.conversations.delete\": AdminConversationsDelete,\n        \"admin.conversations.disconnectShared\": AdminConversationsDisconnectshared,\n        \"admin.conversations.invite\": AdminConversationsInvite,\n        \"admin.conversations.removeCustomRetention\": AdminConversationsRemovecustomretention,\n        \"admin.conversations.rename\": AdminConversationsRename,\n        \"admin.conversations.restrictAccess.addGroup\": AdminConversationsRestrictaccessAddgroup,\n        \"admin.conversations.restrictAccess.removeGroup\": AdminConversationsRestrictaccessRemovegroup,\n        \"admin.conversations.setConversationPrefs\": AdminConversationsSetconversationprefs,\n        \"admin.conversations.setCustomRetention\": AdminConversationsSetcustomretention,\n        \"admin.conversations.setTeams\": AdminConversationsSetteams,\n        \"admin.conversations.unarchive\": AdminConversationsUnarchive,\n        \"admin.conversations:read\": AdminConversationsRead,\n        \"admin.conversations.ekm.listOriginalConnectedChannelInfo\": AdminConversationsEkmListoriginalconnectedchannelinfo,\n        \"admin.conversations.getConversationPrefs\": AdminConversationsGetconversationprefs,\n        \"admin.conversations.getCustomRetention\": AdminConversationsGetcustomretention,\n        \"admin.conversations.getTeams\": AdminConversationsGetteams,\n        \"admin.conversations.lookup\": AdminConversationsLookup,\n        \"admin.conversations.restrictAccess.listGroups\": AdminConversationsRestrictaccessListgroups,\n        \"admin.conversations.search\": AdminConversationsSearch,\n        \"admin.teams:write\": AdminTeamsWrite,\n        \"admin.emoji.add\": AdminEmojiAdd,\n        \"admin.emoji.addAlias\": AdminEmojiAddalias,\n        \"admin.emoji.remove\": AdminEmojiRemove,\n        \"admin.teams.create\": AdminTeamsCreate,\n        \"admin.teams.settings.setDefaultChannels\": AdminTeamsSettingsSetdefaultchannels,\n        \"admin.teams.settings.setDescription\": AdminTeamsSettingsSetdescription,\n        \"admin.teams.settings.setDiscoverability\": AdminTeamsSettingsSetdiscoverability,\n        \"admin.teams.settings.setIcon\": AdminTeamsSettingsSeticon,\n        \"admin.teams.settings.setName\": AdminTeamsSettingsSetname,\n        \"admin.usergroups.addTeams\": AdminUsergroupsAddteams,\n        \"admin.teams:read\": AdminTeamsRead,\n        \"admin.emoji.list\": AdminEmojiList,\n        \"admin.teams.admins.list\": AdminTeamsAdminsList,\n        \"admin.teams.list\": AdminTeamsList,\n        \"admin.teams.owners.list\": AdminTeamsOwnersList,\n        \"admin.teams.settings.info\": AdminTeamsSettingsInfo,\n        \"admin.workflows:read\": AdminWorkflowsRead,\n        \"admin.functions.list\": AdminFunctionsList,\n        \"admin.functions.permissions.lookup\": AdminFunctionsPermissionsLookup,\n        \"admin.workflows.permissions.lookup\": AdminWorkflowsPermissionsLookup,\n        \"admin.workflows.search\": AdminWorkflowsSearch,\n        \"admin.workflows:write\": AdminWorkflowsWrite,\n        \"admin.functions.permissions.set\": AdminFunctionsPermissionsSet,\n        \"admin.workflows.collaborators.add\": AdminWorkflowsCollaboratorsAdd,\n        \"admin.workflows.collaborators.remove\": AdminWorkflowsCollaboratorsRemove,\n        \"admin.workflows.unpublish\": AdminWorkflowsUnpublish,\n        \"admin.invites:write\": AdminInvitesWrite,\n        \"admin.inviteRequests.approve\": AdminInviterequestsApprove,\n        \"admin.inviteRequests.deny\": AdminInviterequestsDeny,\n        \"admin.invites:read\": AdminInvitesRead,\n        \"admin.inviteRequests.approved.list\": AdminInviterequestsApprovedList,\n        \"admin.inviteRequests.denied.list\": AdminInviterequestsDeniedList,\n        \"admin.inviteRequests.list\": AdminInviterequestsList,\n        \"admin.roles:write\": AdminRolesWrite,\n        \"admin.roles.addAssignments\": AdminRolesAddassignments,\n        \"admin.roles.removeAssignments\": AdminRolesRemoveassignments,\n        \"admin.roles:read\": AdminRolesRead,\n        \"admin.roles.listAssignments\": AdminRolesListassignments,\n        \"admin.usergroups:write\": AdminUsergroupsWrite,\n        \"admin.usergroups.addChannels\": AdminUsergroupsAddchannels,\n        \"admin.usergroups.removeChannels\": AdminUsergroupsRemovechannels,\n        \"admin.usergroups:read\": AdminUsergroupsRead,\n        \"admin.usergroups.listChannels\": AdminUsergroupsListchannels,\n        \"hosting:read\": HostingRead,\n        \"apps.activities.list\": AppsActivitiesList,\n        \"connections:write\": ConnectionsWrite,\n        \"apps.connections.open\": AppsConnectionsOpen,\n        \"token\": Token,\n        \"apps.datastore.bulkDelete\": AppsDatastoreBulkdelete,\n        \"apps.datastore.bulkGet\": AppsDatastoreBulkget,\n        \"apps.datastore.bulkPut\": AppsDatastoreBulkput,\n        \"apps.datastore.delete\": AppsDatastoreDelete,\n        \"apps.datastore.get\": AppsDatastoreGet,\n        \"apps.datastore.put\": AppsDatastorePut,\n        \"apps.datastore.query\": AppsDatastoreQuery,\n        \"apps.datastore.update\": AppsDatastoreUpdate,\n        \"datastore:read\": DatastoreRead,\n        \"apps.datastore.count\": AppsDatastoreCount,\n        \"authorizations:read\": AuthorizationsRead,\n        \"apps.event.authorizations.list\": AppsEventAuthorizationsList,\n        \"bot\": Bot,\n        \"auth.revoke\": AuthRevoke,\n        \"auth.test\": AuthTest,\n        \"chat.getPermalink\": ChatGetpermalink,\n        \"chat.scheduledMessages.list\": ChatScheduledmessagesList,\n        \"dialog.open\": DialogOpen,\n        \"functions.completeError\": FunctionsCompleteerror,\n        \"functions.completeSuccess\": FunctionsCompletesuccess,\n        \"rtm.connect\": RtmConnect,\n        \"rtm.start\": RtmStart,\n        \"views.open\": ViewsOpen,\n        \"views.publish\": ViewsPublish,\n        \"views.push\": ViewsPush,\n        \"views.update\": ViewsUpdate,\n        \"bookmarks:write\": BookmarksWrite,\n        \"bookmarks.add\": BookmarksAdd,\n        \"bookmarks.edit\": BookmarksEdit,\n        \"bookmarks.remove\": BookmarksRemove,\n        \"bookmarks:read\": BookmarksRead,\n        \"bookmarks.list\": BookmarksList,\n        \"users:read\": UsersRead,\n        \"bots.info\": BotsInfo,\n        \"users.getPresence\": UsersGetpresence,\n        \"users.info\": UsersInfo,\n        \"users.list\": UsersList,\n        \"calls:write\": CallsWrite,\n        \"calls.add\": CallsAdd,\n        \"calls.end\": CallsEnd,\n        \"calls.participants.add\": CallsParticipantsAdd,\n        \"calls.participants.remove\": CallsParticipantsRemove,\n        \"calls.update\": CallsUpdate,\n        \"calls:read\": CallsRead,\n        \"calls.info\": CallsInfo,\n        \"channels:manage\": ChannelsManage,\n        \"channels.create\": ChannelsCreate,\n        \"channels.mark\": ChannelsMark,\n        \"conversations.archive\": ConversationsArchive,\n        \"conversations.close\": ConversationsClose,\n        \"conversations.create\": ConversationsCreate,\n        \"conversations.kick\": ConversationsKick,\n        \"conversations.leave\": ConversationsLeave,\n        \"conversations.mark\": ConversationsMark,\n        \"conversations.open\": ConversationsOpen,\n        \"conversations.rename\": ConversationsRename,\n        \"conversations.unarchive\": ConversationsUnarchive,\n        \"groups.create\": GroupsCreate,\n        \"groups.mark\": GroupsMark,\n        \"im.mark\": ImMark,\n        \"im.open\": ImOpen,\n        \"mpim.mark\": MpimMark,\n        \"mpim.open\": MpimOpen,\n        \"channels:read\": ChannelsRead,\n        \"channels.info\": ChannelsInfo,\n        \"conversations.info\": ConversationsInfo,\n        \"conversations.list\": ConversationsList,\n        \"conversations.members\": ConversationsMembers,\n        \"groups.info\": GroupsInfo,\n        \"im.list\": ImList,\n        \"mpim.list\": MpimList,\n        \"users.conversations\": UsersConversations,\n        \"channels:write.invites\": ChannelsWriteInvites,\n        \"channels.invite\": ChannelsInvite,\n        \"conversations.invite\": ConversationsInvite,\n        \"groups.invite\": GroupsInvite,\n        \"chat:write\": ChatWrite,\n        \"chat.delete\": ChatDelete,\n        \"chat.deleteScheduledMessage\": ChatDeletescheduledmessage,\n        \"chat.meMessage\": ChatMemessage,\n        \"chat.postEphemeral\": ChatPostephemeral,\n        \"chat.postMessage\": ChatPostmessage,\n        \"chat.scheduleMessage\": ChatSchedulemessage,\n        \"chat.update\": ChatUpdate,\n        \"links:write\": LinksWrite,\n        \"chat.unfurl\": ChatUnfurl,\n        \"conversations.connect:write\": ConversationsConnectWrite,\n        \"conversations.acceptSharedInvite\": ConversationsAcceptsharedinvite,\n        \"conversations.inviteShared\": ConversationsInviteshared,\n        \"conversations.connect:manage\": ConversationsConnectManage,\n        \"conversations.approveSharedInvite\": ConversationsApprovesharedinvite,\n        \"conversations.declineSharedInvite\": ConversationsDeclinesharedinvite,\n        \"conversations.listConnectInvites\": ConversationsListconnectinvites,\n        \"channels:history\": ChannelsHistory,\n        \"conversations.history\": ConversationsHistory,\n        \"conversations.replies\": ConversationsReplies,\n        \"channels:join\": ChannelsJoin,\n        \"conversations.join\": ConversationsJoin,\n        \"channels:write.topic\": ChannelsWriteTopic,\n        \"conversations.setPurpose\": ConversationsSetpurpose,\n        \"conversations.setTopic\": ConversationsSettopic,\n        \"dnd:write\": DndWrite,\n        \"dnd.endDnd\": DndEnddnd,\n        \"dnd.endSnooze\": DndEndsnooze,\n        \"dnd.setSnooze\": DndSetsnooze,\n        \"dnd:read\": DndRead,\n        \"dnd.info\": DndInfo,\n        \"dnd.teamInfo\": DndTeaminfo,\n        \"emoji:read\": EmojiRead,\n        \"emoji.list\": EmojiList,\n        \"files:write\": FilesWrite,\n        \"files.comments.delete\": FilesCommentsDelete,\n        \"files.completeUploadExternal\": FilesCompleteuploadexternal,\n        \"files.delete\": FilesDelete,\n        \"files.getUploadURLExternal\": FilesGetuploadurlexternal,\n        \"files.revokePublicURL\": FilesRevokepublicurl,\n        \"files.sharedPublicURL\": FilesSharedpublicurl,\n        \"files.upload\": FilesUpload,\n        \"files:read\": FilesRead,\n        \"files.info\": FilesInfo,\n        \"files.list\": FilesList,\n        \"remote_files:write\": RemoteFilesWrite,\n        \"files.remote.add\": FilesRemoteAdd,\n        \"files.remote.remove\": FilesRemoteRemove,\n        \"files.remote.update\": FilesRemoteUpdate,\n        \"remote_files:read\": RemoteFilesRead,\n        \"files.remote.info\": FilesRemoteInfo,\n        \"files.remote.list\": FilesRemoteList,\n        \"remote_files:share\": RemoteFilesShare,\n        \"files.remote.share\": FilesRemoteShare,\n        \"app_configurations:write\": AppConfigurationsWrite,\n        \"functions.distributions.permissions.add\": FunctionsDistributionsPermissionsAdd,\n        \"functions.distributions.permissions.remove\": FunctionsDistributionsPermissionsRemove,\n        \"functions.distributions.permissions.set\": FunctionsDistributionsPermissionsSet,\n        \"app_configurations:read\": AppConfigurationsRead,\n        \"functions.distributions.permissions.list\": FunctionsDistributionsPermissionsList,\n        \"conversations\": Conversations,\n        \"groups.open\": GroupsOpen,\n        \"tokens.basic\": TokensBasic,\n        \"migration.exchange\": MigrationExchange,\n        \"email\": Email,\n        \"openid.connect.userInfo\": OpenidConnectUserinfo,\n        \"pins:write\": PinsWrite,\n        \"pins.add\": PinsAdd,\n        \"pins.remove\": PinsRemove,\n        \"pins:read\": PinsRead,\n        \"pins.list\": PinsList,\n        \"reactions:write\": ReactionsWrite,\n        \"reactions.add\": ReactionsAdd,\n        \"reactions.remove\": ReactionsRemove,\n        \"reactions:read\": ReactionsRead,\n        \"reactions.get\": ReactionsGet,\n        \"reactions.list\": ReactionsList,\n        \"reminders:write\": RemindersWrite,\n        \"reminders.add\": RemindersAdd,\n        \"reminders.complete\": RemindersComplete,\n        \"reminders.delete\": RemindersDelete,\n        \"reminders:read\": RemindersRead,\n        \"reminders.info\": RemindersInfo,\n        \"reminders.list\": RemindersList,\n        \"search:read\": SearchRead,\n        \"search.all\": SearchAll,\n        \"search.files\": SearchFiles,\n        \"search.messages\": SearchMessages,\n        \"stars:write\": StarsWrite,\n        \"stars.add\": StarsAdd,\n        \"stars.remove\": StarsRemove,\n        \"stars:read\": StarsRead,\n        \"stars.list\": StarsList,\n        \"admin\": Admin,\n        \"team.accessLogs\": TeamAccesslogs,\n        \"team.billableInfo\": TeamBillableinfo,\n        \"team.integrationLogs\": TeamIntegrationlogs,\n        \"team.billing:read\": TeamBillingRead,\n        \"team.billing.info\": TeamBillingInfo,\n        \"team:read\": TeamRead,\n        \"team.info\": TeamInfo,\n        \"team.preferences:read\": TeamPreferencesRead,\n        \"team.preferences.list\": TeamPreferencesList,\n        \"users.profile:read\": UsersProfileRead,\n        \"team.profile.get\": TeamProfileGet,\n        \"users.profile.get\": UsersProfileGet,\n        \"usergroups:write\": UsergroupsWrite,\n        \"usergroups.create\": UsergroupsCreate,\n        \"usergroups.disable\": UsergroupsDisable,\n        \"usergroups.enable\": UsergroupsEnable,\n        \"usergroups.update\": UsergroupsUpdate,\n        \"usergroups.users.update\": UsergroupsUsersUpdate,\n        \"usergroups:read\": UsergroupsRead,\n        \"usergroups.list\": UsergroupsList,\n        \"usergroups.users.list\": UsergroupsUsersList,\n        \"users.profile:write\": UsersProfileWrite,\n        \"users.deletePhoto\": UsersDeletephoto,\n        \"users.profile.set\": UsersProfileSet,\n        \"users.setPhoto\": UsersSetphoto,\n        \"identity.basic\": IdentityBasic,\n        \"users.identity\": UsersIdentity,\n        \"users:read.email\": UsersReadEmail,\n        \"users.lookupByEmail\": UsersLookupbyemail,\n        \"users:write\": UsersWrite,\n        \"users.setActive\": UsersSetactive,\n        \"users.setPresence\": UsersSetpresence,\n        \"workflow.steps:execute\": WorkflowStepsExecute,\n        \"workflows.stepCompleted\": WorkflowsStepcompleted,\n        \"workflows.stepFailed\": WorkflowsStepfailed,\n        \"workflows.updateStep\": WorkflowsUpdatestep,\n        \"triggers:write\": TriggersWrite,\n        \"workflows.triggers.permissions.add\": WorkflowsTriggersPermissionsAdd,\n        \"workflows.triggers.permissions.remove\": WorkflowsTriggersPermissionsRemove,\n        \"workflows.triggers.permissions.set\": WorkflowsTriggersPermissionsSet,\n        \"triggers:read\": TriggersRead,\n        \"workflows.triggers.permissions.list\": WorkflowsTriggersPermissionsList,\n    }\n\n    PermissionIDs = map[Permission]int{\n        AdminAnalyticsRead: 1,\n        AdminAnalyticsGetfile: 2,\n        AdminAppActivitiesRead: 3,\n        AdminAppsActivitiesList: 4,\n        AdminAppsWrite: 5,\n        AdminAppsApprove: 6,\n        AdminAppsClearresolution: 7,\n        AdminAppsConfigSet: 8,\n        AdminAppsRequestsCancel: 9,\n        AdminAppsRestrict: 10,\n        AdminAppsUninstall: 11,\n        AdminAppsRead: 12,\n        AdminAppsApprovedList: 13,\n        AdminAppsConfigLookup: 14,\n        AdminAppsRequestsList: 15,\n        AdminAppsRestrictedList: 16,\n        AdminUsersWrite: 17,\n        AdminAuthPolicyAssignentities: 18,\n        AdminAuthPolicyRemoveentities: 19,\n        AdminUsersAssign: 20,\n        AdminUsersInvite: 21,\n        AdminUsersRemove: 22,\n        AdminUsersSessionClearsettings: 23,\n        AdminUsersSessionInvalidate: 24,\n        AdminUsersSessionReset: 25,\n        AdminUsersSessionResetbulk: 26,\n        AdminUsersSessionSetsettings: 27,\n        AdminUsersSetadmin: 28,\n        AdminUsersSetexpiration: 29,\n        AdminUsersSetowner: 30,\n        AdminUsersSetregular: 31,\n        AdminUsersRead: 32,\n        AdminAuthPolicyGetentities: 33,\n        AdminUsersList: 34,\n        AdminUsersSessionGetsettings: 35,\n        AdminUsersSessionList: 36,\n        AdminUsersUnsupportedversionsExport: 37,\n        AdminBarriersWrite: 38,\n        AdminBarriersCreate: 39,\n        AdminBarriersDelete: 40,\n        AdminBarriersUpdate: 41,\n        AdminBarriersRead: 42,\n        AdminBarriersList: 43,\n        AdminConversationsWrite: 44,\n        AdminConversationsArchive: 45,\n        AdminConversationsBulkarchive: 46,\n        AdminConversationsBulkdelete: 47,\n        AdminConversationsBulkmove: 48,\n        AdminConversationsConverttoprivate: 49,\n        AdminConversationsConverttopublic: 50,\n        AdminConversationsCreate: 51,\n        AdminConversationsDelete: 52,\n        AdminConversationsDisconnectshared: 53,\n        AdminConversationsInvite: 54,\n        AdminConversationsRemovecustomretention: 55,\n        AdminConversationsRename: 56,\n        AdminConversationsRestrictaccessAddgroup: 57,\n        AdminConversationsRestrictaccessRemovegroup: 58,\n        AdminConversationsSetconversationprefs: 59,\n        AdminConversationsSetcustomretention: 60,\n        AdminConversationsSetteams: 61,\n        AdminConversationsUnarchive: 62,\n        AdminConversationsRead: 63,\n        AdminConversationsEkmListoriginalconnectedchannelinfo: 64,\n        AdminConversationsGetconversationprefs: 65,\n        AdminConversationsGetcustomretention: 66,\n        AdminConversationsGetteams: 67,\n        AdminConversationsLookup: 68,\n        AdminConversationsRestrictaccessListgroups: 69,\n        AdminConversationsSearch: 70,\n        AdminTeamsWrite: 71,\n        AdminEmojiAdd: 72,\n        AdminEmojiAddalias: 73,\n        AdminEmojiRemove: 74,\n        AdminTeamsCreate: 75,\n        AdminTeamsSettingsSetdefaultchannels: 76,\n        AdminTeamsSettingsSetdescription: 77,\n        AdminTeamsSettingsSetdiscoverability: 78,\n        AdminTeamsSettingsSeticon: 79,\n        AdminTeamsSettingsSetname: 80,\n        AdminUsergroupsAddteams: 81,\n        AdminTeamsRead: 82,\n        AdminEmojiList: 83,\n        AdminTeamsAdminsList: 84,\n        AdminTeamsList: 85,\n        AdminTeamsOwnersList: 86,\n        AdminTeamsSettingsInfo: 87,\n        AdminWorkflowsRead: 88,\n        AdminFunctionsList: 89,\n        AdminFunctionsPermissionsLookup: 90,\n        AdminWorkflowsPermissionsLookup: 91,\n        AdminWorkflowsSearch: 92,\n        AdminWorkflowsWrite: 93,\n        AdminFunctionsPermissionsSet: 94,\n        AdminWorkflowsCollaboratorsAdd: 95,\n        AdminWorkflowsCollaboratorsRemove: 96,\n        AdminWorkflowsUnpublish: 97,\n        AdminInvitesWrite: 98,\n        AdminInviterequestsApprove: 99,\n        AdminInviterequestsDeny: 100,\n        AdminInvitesRead: 101,\n        AdminInviterequestsApprovedList: 102,\n        AdminInviterequestsDeniedList: 103,\n        AdminInviterequestsList: 104,\n        AdminRolesWrite: 105,\n        AdminRolesAddassignments: 106,\n        AdminRolesRemoveassignments: 107,\n        AdminRolesRead: 108,\n        AdminRolesListassignments: 109,\n        AdminUsergroupsWrite: 110,\n        AdminUsergroupsAddchannels: 111,\n        AdminUsergroupsRemovechannels: 112,\n        AdminUsergroupsRead: 113,\n        AdminUsergroupsListchannels: 114,\n        HostingRead: 115,\n        AppsActivitiesList: 116,\n        ConnectionsWrite: 117,\n        AppsConnectionsOpen: 118,\n        Token: 119,\n        AppsDatastoreBulkdelete: 120,\n        AppsDatastoreBulkget: 121,\n        AppsDatastoreBulkput: 122,\n        AppsDatastoreDelete: 123,\n        AppsDatastoreGet: 124,\n        AppsDatastorePut: 125,\n        AppsDatastoreQuery: 126,\n        AppsDatastoreUpdate: 127,\n        DatastoreRead: 128,\n        AppsDatastoreCount: 129,\n        AuthorizationsRead: 130,\n        AppsEventAuthorizationsList: 131,\n        Bot: 132,\n        AuthRevoke: 133,\n        AuthTest: 134,\n        ChatGetpermalink: 135,\n        ChatScheduledmessagesList: 136,\n        DialogOpen: 137,\n        FunctionsCompleteerror: 138,\n        FunctionsCompletesuccess: 139,\n        RtmConnect: 140,\n        RtmStart: 141,\n        ViewsOpen: 142,\n        ViewsPublish: 143,\n        ViewsPush: 144,\n        ViewsUpdate: 145,\n        BookmarksWrite: 146,\n        BookmarksAdd: 147,\n        BookmarksEdit: 148,\n        BookmarksRemove: 149,\n        BookmarksRead: 150,\n        BookmarksList: 151,\n        UsersRead: 152,\n        BotsInfo: 153,\n        UsersGetpresence: 154,\n        UsersInfo: 155,\n        UsersList: 156,\n        CallsWrite: 157,\n        CallsAdd: 158,\n        CallsEnd: 159,\n        CallsParticipantsAdd: 160,\n        CallsParticipantsRemove: 161,\n        CallsUpdate: 162,\n        CallsRead: 163,\n        CallsInfo: 164,\n        ChannelsManage: 165,\n        ChannelsCreate: 166,\n        ChannelsMark: 167,\n        ConversationsArchive: 168,\n        ConversationsClose: 169,\n        ConversationsCreate: 170,\n        ConversationsKick: 171,\n        ConversationsLeave: 172,\n        ConversationsMark: 173,\n        ConversationsOpen: 174,\n        ConversationsRename: 175,\n        ConversationsUnarchive: 176,\n        GroupsCreate: 177,\n        GroupsMark: 178,\n        ImMark: 179,\n        ImOpen: 180,\n        MpimMark: 181,\n        MpimOpen: 182,\n        ChannelsRead: 183,\n        ChannelsInfo: 184,\n        ConversationsInfo: 185,\n        ConversationsList: 186,\n        ConversationsMembers: 187,\n        GroupsInfo: 188,\n        ImList: 189,\n        MpimList: 190,\n        UsersConversations: 191,\n        ChannelsWriteInvites: 192,\n        ChannelsInvite: 193,\n        ConversationsInvite: 194,\n        GroupsInvite: 195,\n        ChatWrite: 196,\n        ChatDelete: 197,\n        ChatDeletescheduledmessage: 198,\n        ChatMemessage: 199,\n        ChatPostephemeral: 200,\n        ChatPostmessage: 201,\n        ChatSchedulemessage: 202,\n        ChatUpdate: 203,\n        LinksWrite: 204,\n        ChatUnfurl: 205,\n        ConversationsConnectWrite: 206,\n        ConversationsAcceptsharedinvite: 207,\n        ConversationsInviteshared: 208,\n        ConversationsConnectManage: 209,\n        ConversationsApprovesharedinvite: 210,\n        ConversationsDeclinesharedinvite: 211,\n        ConversationsListconnectinvites: 212,\n        ChannelsHistory: 213,\n        ConversationsHistory: 214,\n        ConversationsReplies: 215,\n        ChannelsJoin: 216,\n        ConversationsJoin: 217,\n        ChannelsWriteTopic: 218,\n        ConversationsSetpurpose: 219,\n        ConversationsSettopic: 220,\n        DndWrite: 221,\n        DndEnddnd: 222,\n        DndEndsnooze: 223,\n        DndSetsnooze: 224,\n        DndRead: 225,\n        DndInfo: 226,\n        DndTeaminfo: 227,\n        EmojiRead: 228,\n        EmojiList: 229,\n        FilesWrite: 230,\n        FilesCommentsDelete: 231,\n        FilesCompleteuploadexternal: 232,\n        FilesDelete: 233,\n        FilesGetuploadurlexternal: 234,\n        FilesRevokepublicurl: 235,\n        FilesSharedpublicurl: 236,\n        FilesUpload: 237,\n        FilesRead: 238,\n        FilesInfo: 239,\n        FilesList: 240,\n        RemoteFilesWrite: 241,\n        FilesRemoteAdd: 242,\n        FilesRemoteRemove: 243,\n        FilesRemoteUpdate: 244,\n        RemoteFilesRead: 245,\n        FilesRemoteInfo: 246,\n        FilesRemoteList: 247,\n        RemoteFilesShare: 248,\n        FilesRemoteShare: 249,\n        AppConfigurationsWrite: 250,\n        FunctionsDistributionsPermissionsAdd: 251,\n        FunctionsDistributionsPermissionsRemove: 252,\n        FunctionsDistributionsPermissionsSet: 253,\n        AppConfigurationsRead: 254,\n        FunctionsDistributionsPermissionsList: 255,\n        Conversations: 256,\n        GroupsOpen: 257,\n        TokensBasic: 258,\n        MigrationExchange: 259,\n        Email: 260,\n        OpenidConnectUserinfo: 261,\n        PinsWrite: 262,\n        PinsAdd: 263,\n        PinsRemove: 264,\n        PinsRead: 265,\n        PinsList: 266,\n        ReactionsWrite: 267,\n        ReactionsAdd: 268,\n        ReactionsRemove: 269,\n        ReactionsRead: 270,\n        ReactionsGet: 271,\n        ReactionsList: 272,\n        RemindersWrite: 273,\n        RemindersAdd: 274,\n        RemindersComplete: 275,\n        RemindersDelete: 276,\n        RemindersRead: 277,\n        RemindersInfo: 278,\n        RemindersList: 279,\n        SearchRead: 280,\n        SearchAll: 281,\n        SearchFiles: 282,\n        SearchMessages: 283,\n        StarsWrite: 284,\n        StarsAdd: 285,\n        StarsRemove: 286,\n        StarsRead: 287,\n        StarsList: 288,\n        Admin: 289,\n        TeamAccesslogs: 290,\n        TeamBillableinfo: 291,\n        TeamIntegrationlogs: 292,\n        TeamBillingRead: 293,\n        TeamBillingInfo: 294,\n        TeamRead: 295,\n        TeamInfo: 296,\n        TeamPreferencesRead: 297,\n        TeamPreferencesList: 298,\n        UsersProfileRead: 299,\n        TeamProfileGet: 300,\n        UsersProfileGet: 301,\n        UsergroupsWrite: 302,\n        UsergroupsCreate: 303,\n        UsergroupsDisable: 304,\n        UsergroupsEnable: 305,\n        UsergroupsUpdate: 306,\n        UsergroupsUsersUpdate: 307,\n        UsergroupsRead: 308,\n        UsergroupsList: 309,\n        UsergroupsUsersList: 310,\n        UsersProfileWrite: 311,\n        UsersDeletephoto: 312,\n        UsersProfileSet: 313,\n        UsersSetphoto: 314,\n        IdentityBasic: 315,\n        UsersIdentity: 316,\n        UsersReadEmail: 317,\n        UsersLookupbyemail: 318,\n        UsersWrite: 319,\n        UsersSetactive: 320,\n        UsersSetpresence: 321,\n        WorkflowStepsExecute: 322,\n        WorkflowsStepcompleted: 323,\n        WorkflowsStepfailed: 324,\n        WorkflowsUpdatestep: 325,\n        TriggersWrite: 326,\n        WorkflowsTriggersPermissionsAdd: 327,\n        WorkflowsTriggersPermissionsRemove: 328,\n        WorkflowsTriggersPermissionsSet: 329,\n        TriggersRead: 330,\n        WorkflowsTriggersPermissionsList: 331,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: AdminAnalyticsRead,\n        2: AdminAnalyticsGetfile,\n        3: AdminAppActivitiesRead,\n        4: AdminAppsActivitiesList,\n        5: AdminAppsWrite,\n        6: AdminAppsApprove,\n        7: AdminAppsClearresolution,\n        8: AdminAppsConfigSet,\n        9: AdminAppsRequestsCancel,\n        10: AdminAppsRestrict,\n        11: AdminAppsUninstall,\n        12: AdminAppsRead,\n        13: AdminAppsApprovedList,\n        14: AdminAppsConfigLookup,\n        15: AdminAppsRequestsList,\n        16: AdminAppsRestrictedList,\n        17: AdminUsersWrite,\n        18: AdminAuthPolicyAssignentities,\n        19: AdminAuthPolicyRemoveentities,\n        20: AdminUsersAssign,\n        21: AdminUsersInvite,\n        22: AdminUsersRemove,\n        23: AdminUsersSessionClearsettings,\n        24: AdminUsersSessionInvalidate,\n        25: AdminUsersSessionReset,\n        26: AdminUsersSessionResetbulk,\n        27: AdminUsersSessionSetsettings,\n        28: AdminUsersSetadmin,\n        29: AdminUsersSetexpiration,\n        30: AdminUsersSetowner,\n        31: AdminUsersSetregular,\n        32: AdminUsersRead,\n        33: AdminAuthPolicyGetentities,\n        34: AdminUsersList,\n        35: AdminUsersSessionGetsettings,\n        36: AdminUsersSessionList,\n        37: AdminUsersUnsupportedversionsExport,\n        38: AdminBarriersWrite,\n        39: AdminBarriersCreate,\n        40: AdminBarriersDelete,\n        41: AdminBarriersUpdate,\n        42: AdminBarriersRead,\n        43: AdminBarriersList,\n        44: AdminConversationsWrite,\n        45: AdminConversationsArchive,\n        46: AdminConversationsBulkarchive,\n        47: AdminConversationsBulkdelete,\n        48: AdminConversationsBulkmove,\n        49: AdminConversationsConverttoprivate,\n        50: AdminConversationsConverttopublic,\n        51: AdminConversationsCreate,\n        52: AdminConversationsDelete,\n        53: AdminConversationsDisconnectshared,\n        54: AdminConversationsInvite,\n        55: AdminConversationsRemovecustomretention,\n        56: AdminConversationsRename,\n        57: AdminConversationsRestrictaccessAddgroup,\n        58: AdminConversationsRestrictaccessRemovegroup,\n        59: AdminConversationsSetconversationprefs,\n        60: AdminConversationsSetcustomretention,\n        61: AdminConversationsSetteams,\n        62: AdminConversationsUnarchive,\n        63: AdminConversationsRead,\n        64: AdminConversationsEkmListoriginalconnectedchannelinfo,\n        65: AdminConversationsGetconversationprefs,\n        66: AdminConversationsGetcustomretention,\n        67: AdminConversationsGetteams,\n        68: AdminConversationsLookup,\n        69: AdminConversationsRestrictaccessListgroups,\n        70: AdminConversationsSearch,\n        71: AdminTeamsWrite,\n        72: AdminEmojiAdd,\n        73: AdminEmojiAddalias,\n        74: AdminEmojiRemove,\n        75: AdminTeamsCreate,\n        76: AdminTeamsSettingsSetdefaultchannels,\n        77: AdminTeamsSettingsSetdescription,\n        78: AdminTeamsSettingsSetdiscoverability,\n        79: AdminTeamsSettingsSeticon,\n        80: AdminTeamsSettingsSetname,\n        81: AdminUsergroupsAddteams,\n        82: AdminTeamsRead,\n        83: AdminEmojiList,\n        84: AdminTeamsAdminsList,\n        85: AdminTeamsList,\n        86: AdminTeamsOwnersList,\n        87: AdminTeamsSettingsInfo,\n        88: AdminWorkflowsRead,\n        89: AdminFunctionsList,\n        90: AdminFunctionsPermissionsLookup,\n        91: AdminWorkflowsPermissionsLookup,\n        92: AdminWorkflowsSearch,\n        93: AdminWorkflowsWrite,\n        94: AdminFunctionsPermissionsSet,\n        95: AdminWorkflowsCollaboratorsAdd,\n        96: AdminWorkflowsCollaboratorsRemove,\n        97: AdminWorkflowsUnpublish,\n        98: AdminInvitesWrite,\n        99: AdminInviterequestsApprove,\n        100: AdminInviterequestsDeny,\n        101: AdminInvitesRead,\n        102: AdminInviterequestsApprovedList,\n        103: AdminInviterequestsDeniedList,\n        104: AdminInviterequestsList,\n        105: AdminRolesWrite,\n        106: AdminRolesAddassignments,\n        107: AdminRolesRemoveassignments,\n        108: AdminRolesRead,\n        109: AdminRolesListassignments,\n        110: AdminUsergroupsWrite,\n        111: AdminUsergroupsAddchannels,\n        112: AdminUsergroupsRemovechannels,\n        113: AdminUsergroupsRead,\n        114: AdminUsergroupsListchannels,\n        115: HostingRead,\n        116: AppsActivitiesList,\n        117: ConnectionsWrite,\n        118: AppsConnectionsOpen,\n        119: Token,\n        120: AppsDatastoreBulkdelete,\n        121: AppsDatastoreBulkget,\n        122: AppsDatastoreBulkput,\n        123: AppsDatastoreDelete,\n        124: AppsDatastoreGet,\n        125: AppsDatastorePut,\n        126: AppsDatastoreQuery,\n        127: AppsDatastoreUpdate,\n        128: DatastoreRead,\n        129: AppsDatastoreCount,\n        130: AuthorizationsRead,\n        131: AppsEventAuthorizationsList,\n        132: Bot,\n        133: AuthRevoke,\n        134: AuthTest,\n        135: ChatGetpermalink,\n        136: ChatScheduledmessagesList,\n        137: DialogOpen,\n        138: FunctionsCompleteerror,\n        139: FunctionsCompletesuccess,\n        140: RtmConnect,\n        141: RtmStart,\n        142: ViewsOpen,\n        143: ViewsPublish,\n        144: ViewsPush,\n        145: ViewsUpdate,\n        146: BookmarksWrite,\n        147: BookmarksAdd,\n        148: BookmarksEdit,\n        149: BookmarksRemove,\n        150: BookmarksRead,\n        151: BookmarksList,\n        152: UsersRead,\n        153: BotsInfo,\n        154: UsersGetpresence,\n        155: UsersInfo,\n        156: UsersList,\n        157: CallsWrite,\n        158: CallsAdd,\n        159: CallsEnd,\n        160: CallsParticipantsAdd,\n        161: CallsParticipantsRemove,\n        162: CallsUpdate,\n        163: CallsRead,\n        164: CallsInfo,\n        165: ChannelsManage,\n        166: ChannelsCreate,\n        167: ChannelsMark,\n        168: ConversationsArchive,\n        169: ConversationsClose,\n        170: ConversationsCreate,\n        171: ConversationsKick,\n        172: ConversationsLeave,\n        173: ConversationsMark,\n        174: ConversationsOpen,\n        175: ConversationsRename,\n        176: ConversationsUnarchive,\n        177: GroupsCreate,\n        178: GroupsMark,\n        179: ImMark,\n        180: ImOpen,\n        181: MpimMark,\n        182: MpimOpen,\n        183: ChannelsRead,\n        184: ChannelsInfo,\n        185: ConversationsInfo,\n        186: ConversationsList,\n        187: ConversationsMembers,\n        188: GroupsInfo,\n        189: ImList,\n        190: MpimList,\n        191: UsersConversations,\n        192: ChannelsWriteInvites,\n        193: ChannelsInvite,\n        194: ConversationsInvite,\n        195: GroupsInvite,\n        196: ChatWrite,\n        197: ChatDelete,\n        198: ChatDeletescheduledmessage,\n        199: ChatMemessage,\n        200: ChatPostephemeral,\n        201: ChatPostmessage,\n        202: ChatSchedulemessage,\n        203: ChatUpdate,\n        204: LinksWrite,\n        205: ChatUnfurl,\n        206: ConversationsConnectWrite,\n        207: ConversationsAcceptsharedinvite,\n        208: ConversationsInviteshared,\n        209: ConversationsConnectManage,\n        210: ConversationsApprovesharedinvite,\n        211: ConversationsDeclinesharedinvite,\n        212: ConversationsListconnectinvites,\n        213: ChannelsHistory,\n        214: ConversationsHistory,\n        215: ConversationsReplies,\n        216: ChannelsJoin,\n        217: ConversationsJoin,\n        218: ChannelsWriteTopic,\n        219: ConversationsSetpurpose,\n        220: ConversationsSettopic,\n        221: DndWrite,\n        222: DndEnddnd,\n        223: DndEndsnooze,\n        224: DndSetsnooze,\n        225: DndRead,\n        226: DndInfo,\n        227: DndTeaminfo,\n        228: EmojiRead,\n        229: EmojiList,\n        230: FilesWrite,\n        231: FilesCommentsDelete,\n        232: FilesCompleteuploadexternal,\n        233: FilesDelete,\n        234: FilesGetuploadurlexternal,\n        235: FilesRevokepublicurl,\n        236: FilesSharedpublicurl,\n        237: FilesUpload,\n        238: FilesRead,\n        239: FilesInfo,\n        240: FilesList,\n        241: RemoteFilesWrite,\n        242: FilesRemoteAdd,\n        243: FilesRemoteRemove,\n        244: FilesRemoteUpdate,\n        245: RemoteFilesRead,\n        246: FilesRemoteInfo,\n        247: FilesRemoteList,\n        248: RemoteFilesShare,\n        249: FilesRemoteShare,\n        250: AppConfigurationsWrite,\n        251: FunctionsDistributionsPermissionsAdd,\n        252: FunctionsDistributionsPermissionsRemove,\n        253: FunctionsDistributionsPermissionsSet,\n        254: AppConfigurationsRead,\n        255: FunctionsDistributionsPermissionsList,\n        256: Conversations,\n        257: GroupsOpen,\n        258: TokensBasic,\n        259: MigrationExchange,\n        260: Email,\n        261: OpenidConnectUserinfo,\n        262: PinsWrite,\n        263: PinsAdd,\n        264: PinsRemove,\n        265: PinsRead,\n        266: PinsList,\n        267: ReactionsWrite,\n        268: ReactionsAdd,\n        269: ReactionsRemove,\n        270: ReactionsRead,\n        271: ReactionsGet,\n        272: ReactionsList,\n        273: RemindersWrite,\n        274: RemindersAdd,\n        275: RemindersComplete,\n        276: RemindersDelete,\n        277: RemindersRead,\n        278: RemindersInfo,\n        279: RemindersList,\n        280: SearchRead,\n        281: SearchAll,\n        282: SearchFiles,\n        283: SearchMessages,\n        284: StarsWrite,\n        285: StarsAdd,\n        286: StarsRemove,\n        287: StarsRead,\n        288: StarsList,\n        289: Admin,\n        290: TeamAccesslogs,\n        291: TeamBillableinfo,\n        292: TeamIntegrationlogs,\n        293: TeamBillingRead,\n        294: TeamBillingInfo,\n        295: TeamRead,\n        296: TeamInfo,\n        297: TeamPreferencesRead,\n        298: TeamPreferencesList,\n        299: UsersProfileRead,\n        300: TeamProfileGet,\n        301: UsersProfileGet,\n        302: UsergroupsWrite,\n        303: UsergroupsCreate,\n        304: UsergroupsDisable,\n        305: UsergroupsEnable,\n        306: UsergroupsUpdate,\n        307: UsergroupsUsersUpdate,\n        308: UsergroupsRead,\n        309: UsergroupsList,\n        310: UsergroupsUsersList,\n        311: UsersProfileWrite,\n        312: UsersDeletephoto,\n        313: UsersProfileSet,\n        314: UsersSetphoto,\n        315: IdentityBasic,\n        316: UsersIdentity,\n        317: UsersReadEmail,\n        318: UsersLookupbyemail,\n        319: UsersWrite,\n        320: UsersSetactive,\n        321: UsersSetpresence,\n        322: WorkflowStepsExecute,\n        323: WorkflowsStepcompleted,\n        324: WorkflowsStepfailed,\n        325: WorkflowsUpdatestep,\n        326: TriggersWrite,\n        327: WorkflowsTriggersPermissionsAdd,\n        328: WorkflowsTriggersPermissionsRemove,\n        329: WorkflowsTriggersPermissionsSet,\n        330: TriggersRead,\n        331: WorkflowsTriggersPermissionsList,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/slack/permissions.yaml",
    "content": "permissions:\n  - admin.analytics.getFile\n  - admin.apps.activities.list\n  - admin.apps.approve\n  - admin.apps.clearResolution\n  - admin.apps.config.set\n  - admin.apps.requests.cancel\n  - admin.apps.restrict\n  - admin.apps.uninstall\n  - admin.apps.approved.list\n  - admin.apps.config.lookup\n  - admin.apps.requests.list\n  - admin.apps.restricted.list\n  - admin.auth.policy.assignEntities\n  - admin.auth.policy.removeEntities\n  - admin.users.assign\n  - admin.users.invite\n  - admin.users.remove\n  - admin.users.session.clearSettings\n  - admin.users.session.invalidate\n  - admin.users.session.reset\n  - admin.users.session.resetBulk\n  - admin.users.session.setSettings\n  - admin.users.setAdmin\n  - admin.users.setExpiration\n  - admin.users.setOwner\n  - admin.users.setRegular\n  - admin.auth.policy.getEntities\n  - admin.users.list\n  - admin.users.session.getSettings\n  - admin.users.session.list\n  - admin.users.unsupportedVersions.export\n  - admin.barriers.create\n  - admin.barriers.delete\n  - admin.barriers.update\n  - admin.barriers.list\n  - admin.conversations.archive\n  - admin.conversations.bulkArchive\n  - admin.conversations.bulkDelete\n  - admin.conversations.bulkMove\n  - admin.conversations.convertToPrivate\n  - admin.conversations.convertToPublic\n  - admin.conversations.create\n  - admin.conversations.delete\n  - admin.conversations.disconnectShared\n  - admin.conversations.invite\n  - admin.conversations.removeCustomRetention\n  - admin.conversations.rename\n  - admin.conversations.restrictAccess.addGroup\n  - admin.conversations.restrictAccess.removeGroup\n  - admin.conversations.setConversationPrefs\n  - admin.conversations.setCustomRetention\n  - admin.conversations.setTeams\n  - admin.conversations.unarchive\n  - admin.conversations.ekm.listOriginalConnectedChannelInfo\n  - admin.conversations.getConversationPrefs\n  - admin.conversations.getCustomRetention\n  - admin.conversations.getTeams\n  - admin.conversations.lookup\n  - admin.conversations.restrictAccess.listGroups\n  - admin.conversations.search\n  - admin.emoji.add\n  - admin.emoji.addAlias\n  - admin.emoji.remove\n  - admin.teams.create\n  - admin.teams.settings.setDefaultChannels\n  - admin.teams.settings.setDescription\n  - admin.teams.settings.setDiscoverability\n  - admin.teams.settings.setIcon\n  - admin.teams.settings.setName\n  - admin.usergroups.addTeams\n  - admin.emoji.list\n  - admin.teams.admins.list\n  - admin.teams.list\n  - admin.teams.owners.list\n  - admin.teams.settings.info\n  - admin.functions.list\n  - admin.functions.permissions.lookup\n  - admin.workflows.permissions.lookup\n  - admin.workflows.search\n  - admin.functions.permissions.set\n  - admin.workflows.collaborators.add\n  - admin.workflows.collaborators.remove\n  - admin.workflows.unpublish\n  - admin.inviteRequests.approve\n  - admin.inviteRequests.deny\n  - admin.inviteRequests.approved.list\n  - admin.inviteRequests.denied.list\n  - admin.inviteRequests.list\n  - admin.roles.addAssignments\n  - admin.roles.removeAssignments\n  - admin.roles.listAssignments\n  - admin.usergroups.addChannels\n  - admin.usergroups.removeChannels\n  - admin.usergroups.listChannels\n  - apps.activities.list\n  - apps.connections.open\n  - token\n  - apps.datastore.bulkDelete\n  - apps.datastore.bulkGet\n  - apps.datastore.bulkPut\n  - apps.datastore.delete\n  - apps.datastore.get\n  - apps.datastore.put\n  - apps.datastore.query\n  - apps.datastore.update\n  - apps.datastore.count\n  - apps.event.authorizations.list\n  - bot\n  - auth.revoke\n  - auth.test\n  - chat.getPermalink\n  - chat.scheduledMessages.list\n  - dialog.open\n  - functions.completeError\n  - functions.completeSuccess\n  - rtm.connect\n  - rtm.start\n  - views.open\n  - views.publish\n  - views.push\n  - views.update\n  - bookmarks.add\n  - bookmarks.edit\n  - bookmarks.remove\n  - bookmarks.list\n  - bots.info\n  - users.getPresence\n  - users.info\n  - users.list\n  - calls.add\n  - calls.end\n  - calls.participants.add\n  - calls.participants.remove\n  - calls.update\n  - calls.info\n  - channels.create\n  - channels.mark\n  - conversations.archive\n  - conversations.close\n  - conversations.create\n  - conversations.kick\n  - conversations.leave\n  - conversations.mark\n  - conversations.open\n  - conversations.rename\n  - conversations.unarchive\n  - groups.create\n  - groups.mark\n  - im.mark\n  - im.open\n  - mpim.mark\n  - mpim.open\n  - channels.info\n  - conversations.info\n  - conversations.list\n  - conversations.members\n  - groups.info\n  - im.list\n  - mpim.list\n  - users.conversations\n  - channels.invite\n  - conversations.invite\n  - groups.invite\n  - chat.delete\n  - chat.deleteScheduledMessage\n  - chat.meMessage\n  - chat.postEphemeral\n  - chat.postMessage\n  - chat.scheduleMessage\n  - chat.update\n  - chat.unfurl\n  - conversations.acceptSharedInvite\n  - conversations.inviteShared\n  - conversations.approveSharedInvite\n  - conversations.declineSharedInvite\n  - conversations.listConnectInvites\n  - conversations.history\n  - conversations.replies\n  - conversations.join\n  - conversations.setPurpose\n  - conversations.setTopic\n  - dnd.endDnd\n  - dnd.endSnooze\n  - dnd.setSnooze\n  - dnd.info\n  - dnd.teamInfo\n  - emoji.list\n  - files.comments.delete\n  - files.completeUploadExternal\n  - files.delete\n  - files.getUploadURLExternal\n  - files.revokePublicURL\n  - files.sharedPublicURL\n  - files.upload\n  - files.info\n  - files.list\n  - files.remote.add\n  - files.remote.remove\n  - files.remote.update\n  - files.remote.info\n  - files.remote.list\n  - files.remote.share\n  - functions.distributions.permissions.add\n  - functions.distributions.permissions.remove\n  - functions.distributions.permissions.set\n  - functions.distributions.permissions.list\n  - conversations\n  - groups.open\n  - tokens.basic\n  - migration.exchange\n  - email\n  - openid.connect.userInfo\n  - pins.add\n  - pins.remove\n  - pins.list\n  - reactions.add\n  - reactions.remove\n  - reactions.get\n  - reactions.list\n  - reminders.add\n  - reminders.complete\n  - reminders.delete\n  - reminders.info\n  - reminders.list\n  - search.all\n  - search.files\n  - search.messages\n  - stars.add\n  - stars.remove\n  - stars.list\n  - admin\n  - team.accessLogs\n  - team.billableInfo\n  - team.integrationLogs\n  - team.billing.info\n  - team.info\n  - team.preferences.list\n  - team.profile.get\n  - users.profile.get\n  - usergroups.create\n  - usergroups.disable\n  - usergroups.enable\n  - usergroups.update\n  - usergroups.users.update\n  - usergroups.list\n  - usergroups.users.list\n  - users.deletePhoto\n  - users.profile.set\n  - users.setPhoto\n  - identity.basic\n  - users.identity\n  - users.lookupByEmail\n  - users.setActive\n  - users.setPresence\n  - workflows.stepCompleted\n  - workflows.stepFailed\n  - workflows.updateStep\n  - workflows.triggers.permissions.add\n  - workflows.triggers.permissions.remove\n  - workflows.triggers.permissions.set\n  - workflows.triggers.permissions.list\n\n"
  },
  {
    "path": "pkg/analyzer/analyzers/slack/scopes.go",
    "content": "package slack\n\n// SCOPES := []string{string} {\n\n// \t\"admin.analytics:read\" : {\n// \t\t\"admin.analytics.getFile\",\n// \t\t\"admin.analytics.getUsage\",\n// \t\t\"admin.analytics.listFiles\",\n// \t}\n// }\n\nvar scope_mapping = map[string][]string{\n\t\"admin.analytics:read\":         {\"admin.analytics.getFile\"},\n\t\"admin.app_activities:read\":    {\"admin.apps.activities.list\"},\n\t\"admin.apps:write\":             {\"admin.apps.approve\", \"admin.apps.clearResolution\", \"admin.apps.config.set\", \"admin.apps.requests.cancel\", \"admin.apps.restrict\", \"admin.apps.uninstall\"},\n\t\"admin.apps:read\":              {\"admin.apps.approved.list\", \"admin.apps.config.lookup\", \"admin.apps.requests.list\", \"admin.apps.restricted.list\"},\n\t\"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\"},\n\t\"admin.users:read\":             {\"admin.auth.policy.getEntities\", \"admin.users.list\", \"admin.users.session.getSettings\", \"admin.users.session.list\", \"admin.users.unsupportedVersions.export\"},\n\t\"admin.barriers:write\":         {\"admin.barriers.create\", \"admin.barriers.delete\", \"admin.barriers.update\"},\n\t\"admin.barriers:read\":          {\"admin.barriers.list\"},\n\t\"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\"},\n\t\"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\"},\n\t\"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\"},\n\t\"admin.teams:read\":             {\"admin.emoji.list\", \"admin.teams.admins.list\", \"admin.teams.list\", \"admin.teams.owners.list\", \"admin.teams.settings.info\"},\n\t\"admin.workflows:read\":         {\"admin.functions.list\", \"admin.functions.permissions.lookup\", \"admin.workflows.permissions.lookup\", \"admin.workflows.search\"},\n\t\"admin.workflows:write\":        {\"admin.functions.permissions.set\", \"admin.workflows.collaborators.add\", \"admin.workflows.collaborators.remove\", \"admin.workflows.unpublish\"},\n\t\"admin.invites:write\":          {\"admin.inviteRequests.approve\", \"admin.inviteRequests.deny\"},\n\t\"admin.invites:read\":           {\"admin.inviteRequests.approved.list\", \"admin.inviteRequests.denied.list\", \"admin.inviteRequests.list\"},\n\t\"admin.roles:write\":            {\"admin.roles.addAssignments\", \"admin.roles.removeAssignments\"},\n\t\"admin.roles:read\":             {\"admin.roles.listAssignments\"},\n\t\"admin.usergroups:write\":       {\"admin.usergroups.addChannels\", \"admin.usergroups.removeChannels\"},\n\t\"admin.usergroups:read\":        {\"admin.usergroups.listChannels\"},\n\t\"hosting:read\":                 {\"apps.activities.list\"},\n\t\"connections:write\":            {\"apps.connections.open\"},\n\t\"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\"},\n\t\"datastore:read\":               {\"apps.datastore.count\"},\n\t\"authorizations:read\":          {\"apps.event.authorizations.list\"},\n\t\"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\"},\n\t\"bookmarks:write\":              {\"bookmarks.add\", \"bookmarks.edit\", \"bookmarks.remove\"},\n\t\"bookmarks:read\":               {\"bookmarks.list\"},\n\t\"users:read\":                   {\"bots.info\", \"users.getPresence\", \"users.info\", \"users.list\"},\n\t\"calls:write\":                  {\"calls.add\", \"calls.end\", \"calls.participants.add\", \"calls.participants.remove\", \"calls.update\"},\n\t\"calls:read\":                   {\"calls.info\"},\n\t\"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\"},\n\t\"channels:read\":                {\"channels.info\", \"conversations.info\", \"conversations.list\", \"conversations.members\", \"groups.info\", \"im.list\", \"mpim.list\", \"users.conversations\"},\n\t\"channels:write.invites\":       {\"channels.invite\", \"conversations.invite\", \"groups.invite\"},\n\t\"chat:write\":                   {\"chat.delete\", \"chat.deleteScheduledMessage\", \"chat.meMessage\", \"chat.postEphemeral\", \"chat.postMessage\", \"chat.scheduleMessage\", \"chat.update\"},\n\t\"links:write\":                  {\"chat.unfurl\"},\n\t\"conversations.connect:write\":  {\"conversations.acceptSharedInvite\", \"conversations.inviteShared\"},\n\t\"conversations.connect:manage\": {\"conversations.approveSharedInvite\", \"conversations.declineSharedInvite\", \"conversations.listConnectInvites\"},\n\t\"channels:history\":             {\"conversations.history\", \"conversations.replies\"},\n\t\"channels:join\":                {\"conversations.join\"},\n\t\"channels:write.topic\":         {\"conversations.setPurpose\", \"conversations.setTopic\"},\n\t\"dnd:write\":                    {\"dnd.endDnd\", \"dnd.endSnooze\", \"dnd.setSnooze\"},\n\t\"dnd:read\":                     {\"dnd.info\", \"dnd.teamInfo\"},\n\t\"emoji:read\":                   {\"emoji.list\"},\n\t\"files:write\":                  {\"files.comments.delete\", \"files.completeUploadExternal\", \"files.delete\", \"files.getUploadURLExternal\", \"files.revokePublicURL\", \"files.sharedPublicURL\", \"files.upload\"},\n\t\"files:read\":                   {\"files.info\", \"files.list\"},\n\t\"remote_files:write\":           {\"files.remote.add\", \"files.remote.remove\", \"files.remote.update\"},\n\t\"remote_files:read\":            {\"files.remote.info\", \"files.remote.list\"},\n\t\"remote_files:share\":           {\"files.remote.share\"},\n\t\"app_configurations:write\":     {\"functions.distributions.permissions.add\", \"functions.distributions.permissions.remove\", \"functions.distributions.permissions.set\"},\n\t\"app_configurations:read\":      {\"functions.distributions.permissions.list\"},\n\t\"conversations\":                {\"groups.open\"},\n\t\"tokens.basic\":                 {\"migration.exchange\"},\n\t\"email\":                        {\"openid.connect.userInfo\"},\n\t\"pins:write\":                   {\"pins.add\", \"pins.remove\"},\n\t\"pins:read\":                    {\"pins.list\"},\n\t\"reactions:write\":              {\"reactions.add\", \"reactions.remove\"},\n\t\"reactions:read\":               {\"reactions.get\", \"reactions.list\"},\n\t\"reminders:write\":              {\"reminders.add\", \"reminders.complete\", \"reminders.delete\"},\n\t\"reminders:read\":               {\"reminders.info\", \"reminders.list\"},\n\t\"search:read\":                  {\"search.all\", \"search.files\", \"search.messages\"},\n\t\"stars:write\":                  {\"stars.add\", \"stars.remove\"},\n\t\"stars:read\":                   {\"stars.list\"},\n\t\"admin\":                        {\"team.accessLogs\", \"team.billableInfo\", \"team.integrationLogs\"},\n\t\"team.billing:read\":            {\"team.billing.info\"},\n\t\"team:read\":                    {\"team.info\"},\n\t\"team.preferences:read\":        {\"team.preferences.list\"},\n\t\"users.profile:read\":           {\"team.profile.get\", \"users.profile.get\"},\n\t\"usergroups:write\":             {\"usergroups.create\", \"usergroups.disable\", \"usergroups.enable\", \"usergroups.update\", \"usergroups.users.update\"},\n\t\"usergroups:read\":              {\"usergroups.list\", \"usergroups.users.list\"},\n\t\"users.profile:write\":          {\"users.deletePhoto\", \"users.profile.set\", \"users.setPhoto\"},\n\t\"identity.basic\":               {\"users.identity\"},\n\t\"users:read.email\":             {\"users.lookupByEmail\"},\n\t\"users:write\":                  {\"users.setActive\", \"users.setPresence\"},\n\t\"workflow.steps:execute\":       {\"workflows.stepCompleted\", \"workflows.stepFailed\", \"workflows.updateStep\"},\n\t\"triggers:write\":               {\"workflows.triggers.permissions.add\", \"workflows.triggers.permissions.remove\", \"workflows.triggers.permissions.set\"},\n\t\"triggers:read\":                {\"workflows.triggers.permissions.list\"},\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/slack/slack.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go slack\n\npackage slack\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeSlack }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credInfo[\"key\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"key not found in credentialInfo\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeSlack,\n\t\tMetadata:     nil,\n\t}\n\n\tresourceType := \"user\"\n\tfullyQualifiedName := info.User.TeamId + \"/\" + info.User.UserId\n\tif info.User.BotId != \"\" {\n\t\tresourceType = \"bot\"\n\t\tfullyQualifiedName = info.User.BotId\n\t}\n\tresource := analyzers.Resource{\n\t\tName:               info.User.User,\n\t\tFullyQualifiedName: fullyQualifiedName,\n\t\tType:               resourceType,\n\t\tMetadata: map[string]any{\n\t\t\t\"url\":     info.User.Url,\n\t\t\t\"team\":    info.User.Team,\n\t\t\t\"team_id\": info.User.TeamId,\n\t\t\t\"scopes\":  strings.Split(info.Scopes, \",\"),\n\t\t},\n\t}\n\n\t// extract all permissions\n\tpermissions := extractPermissions(info)\n\n\tresult.Bindings = analyzers.BindAllPermissions(resource, permissions...)\n\n\treturn &result\n}\n\nfunc extractPermissions(info *SecretInfo) []analyzers.Permission {\n\tvar permissions []analyzers.Permission\n\n\tfor _, scope := range strings.Split(info.Scopes, \",\") {\n\t\tperms, ok := scope_mapping[scope]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, perm := range perms {\n\t\t\tif _, ok := StringToPermission[perm]; !ok {\n\t\t\t\t// not in out generated permissions,\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpermissions = append(permissions, analyzers.Permission{\n\t\t\t\tValue:  perm,\n\t\t\t\tParent: nil,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn permissions\n}\n\n// Add in showAll to printScopes + deal with testing enterprise + add scope details\n\ntype SlackUserData struct {\n\tOk           bool   `json:\"ok\"`\n\tUrl          string `json:\"url\"`\n\tTeam         string `json:\"team\"`\n\tUser         string `json:\"user\"`\n\tTeamId       string `json:\"team_id\"`\n\tUserId       string `json:\"user_id\"`\n\tBotId        string `json:\"bot_id\"`\n\tIsEnterprise bool   `json:\"is_enterprise\"`\n}\n\ntype SecretInfo struct {\n\tScopes string\n\tUser   SlackUserData\n}\n\nfunc getSlackOAuthScopes(cfg *config.Config, key string) (scopes string, userData SlackUserData, err error) {\n\tuserData = SlackUserData{}\n\tscopes = \"\"\n\n\t// URL to which the request will be sent\n\turl := \"https://slack.com/api/auth.test\"\n\n\t// Create a client to send the request\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\t// Create the request\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn scopes, userData, err\n\t}\n\n\t// Add the Authorization header to the request\n\treq.Header.Add(\"Authorization\", \"Bearer \"+key)\n\n\t// Send the request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn scopes, userData, err\n\t}\n\tdefer resp.Body.Close() // Close the response body when the function returns\n\n\t// print body\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn scopes, userData, err\n\t}\n\n\t// Unmarshal the response body into the SlackUserData struct\n\tif err := json.Unmarshal(body, &userData); err != nil {\n\t\treturn scopes, userData, err\n\t}\n\n\t// Print all headers received from the server\n\tscopes = resp.Header.Get(\"X-Oauth-Scopes\")\n\treturn scopes, userData, err\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error: %v\", err)\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid Slack API Key\\n\\n\")\n\tprintIdentityInfo(info.User)\n\tprintScopes(strings.Split(info.Scopes, \",\"))\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\tscopes, userData, err := getSlackOAuthScopes(cfg, key)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error getting Slack OAuth scopes: %w\", err)\n\t}\n\n\tif !userData.Ok {\n\t\treturn nil, fmt.Errorf(\"invalid Slack token\")\n\t}\n\n\treturn &SecretInfo{\n\t\tScopes: scopes,\n\t\tUser:   userData,\n\t}, nil\n}\n\nfunc printIdentityInfo(userData SlackUserData) {\n\tif userData.Url != \"\" {\n\t\tcolor.Green(\"URL: %v\", userData.Url)\n\t}\n\tif userData.Team != \"\" {\n\t\tcolor.Green(\"Team: %v\", userData.Team)\n\t}\n\tif userData.User != \"\" {\n\t\tcolor.Green(\"User: %v\", userData.User)\n\t}\n\tif userData.TeamId != \"\" {\n\t\tcolor.Green(\"Team ID: %v\", userData.TeamId)\n\t}\n\tif userData.UserId != \"\" {\n\t\tcolor.Green(\"User ID: %v\", userData.UserId)\n\t}\n\tif userData.BotId != \"\" {\n\t\tcolor.Green(\"Bot ID: %v\", userData.BotId)\n\t}\n\tfmt.Println(\"\")\n\tif userData.IsEnterprise {\n\t\tcolor.Green(\"[!] Slack is Enterprise\")\n\t} else {\n\t\tcolor.Yellow(\"[-] Slack is not Enterprise\")\n\t}\n\tfmt.Println(\"\")\n}\n\nfunc printScopes(scopes []string) {\n\tt := table.NewWriter()\n\t// if !showAll {\n\t// \tt.SetOutputMirror(os.Stdout)\n\t// \tt.AppendHeader(table.Row{\"Scopes\"})\n\t// \tfor _, scope := range scopes {\n\t// \t\tt.AppendRow([]interface{}{color.GreenString(scope)})\n\t// \t}\n\t// } else {\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Scope\", \"Permissions\"})\n\tfor _, scope := range scopes {\n\t\tperms := scope_mapping[scope]\n\t\tif perms == nil {\n\t\t\tt.AppendRow([]interface{}{color.GreenString(scope), color.GreenString(\"\")})\n\t\t} else {\n\t\t\tt.AppendRow([]interface{}{color.GreenString(scope), color.GreenString(strings.Join(perms, \", \"))})\n\t\t}\n\n\t}\n\t//}\n\n\tt.Render()\n\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/slack/slack_test.go",
    "content": "package slack\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid Slack key\",\n\t\t\tkey:     testSecrets.MustGetField(\"SLACK\"),\n\t\t\twant:    string(expectedOutput),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/sourcegraph/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage sourcegraph\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    NoAccess Permission = iota\n    UserRead Permission = iota\n    SiteAdminFull Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        UserRead: \"user:read\",\n        SiteAdminFull: \"site_admin:full\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"user:read\": UserRead,\n        \"site_admin:full\": SiteAdminFull,\n    }\n\n    PermissionIDs = map[Permission]int{\n        UserRead: 0,\n        SiteAdminFull: 1,\n    }\n\n    IdToPermission = map[int]Permission{\n        0: UserRead,\n        1: SiteAdminFull,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/sourcegraph/permissions.yaml",
    "content": "permissions:\n- user:read\n- site_admin:full\n"
  },
  {
    "path": "pkg/analyzer/analyzers/sourcegraph/sourcegraph.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go sourcegraph\npackage sourcegraph\n\n// ToDo: Add support for custom domain\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeSourcegraph }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credInfo[\"key\"]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"missing key in credInfo\")\n\t}\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\n\tpermission := PermissionStrings[UserRead]\n\tif info.IsSiteAdmin {\n\t\tpermission = PermissionStrings[SiteAdminFull]\n\t}\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeSourcegraph,\n\t\tMetadata:     nil,\n\t\tBindings: []analyzers.Binding{\n\t\t\t{\n\t\t\t\tResource: analyzers.Resource{\n\t\t\t\t\tName:               info.User.Data.CurrentUser.Username,\n\t\t\t\t\tFullyQualifiedName: \"sourcegraph/\" + info.User.Data.CurrentUser.Email,\n\t\t\t\t\tType:               \"user\",\n\t\t\t\t\tMetadata: map[string]any{\n\t\t\t\t\t\t\"created_at\": info.User.Data.CurrentUser.CreatedAt,\n\t\t\t\t\t\t\"email\":      info.User.Data.CurrentUser.Email,\n\t\t\t\t\t},\n\t\t\t\t\tParent: nil,\n\t\t\t\t},\n\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\tValue: permission,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\treturn &result\n}\n\ntype GraphQLError struct {\n\tMessage string   `json:\"message\"`\n\tPath    []string `json:\"path\"`\n}\n\ntype GraphQLResponse struct {\n\tErrors []GraphQLError `json:\"errors\"`\n\tData   interface{}    `json:\"data\"`\n}\n\ntype UserInfoJSON struct {\n\tData struct {\n\t\tCurrentUser struct {\n\t\t\tUsername  string `json:\"username\"`\n\t\t\tEmail     string `json:\"email\"`\n\t\t\tSiteAdmin bool   `json:\"siteAdmin\"`\n\t\t\tCreatedAt string `json:\"createdAt\"`\n\t\t} `json:\"currentUser\"`\n\t} `json:\"data\"`\n}\n\ntype SecretInfo struct {\n\tUser        UserInfoJSON\n\tIsSiteAdmin bool\n}\n\nfunc getUserInfo(cfg *config.Config, key string) (UserInfoJSON, error) {\n\tvar userInfo UserInfoJSON\n\n\t// POST request is considered as non-safe and sourcegraph has graphql APIs. They do not change any state.\n\t// We are using unrestricted client to avoid error for non-safe API request.\n\tclient := analyzers.NewAnalyzeClientUnrestricted(cfg)\n\tpayload := \"{\\\"query\\\":\\\"query { currentUser { username, email, siteAdmin, createdAt } }\\\"}\"\n\treq, err := http.NewRequest(\"POST\", \"https://sourcegraph.com/.api/graphql\", strings.NewReader(payload))\n\tif err != nil {\n\t\treturn userInfo, err\n\t}\n\n\treq.Header.Set(\"Authorization\", \"token \"+key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn userInfo, err\n\t}\n\n\tdefer resp.Body.Close()\n\n\terr = json.NewDecoder(resp.Body).Decode(&userInfo)\n\tif err != nil {\n\t\treturn userInfo, err\n\t}\n\treturn userInfo, nil\n}\n\nfunc checkSiteAdmin(cfg *config.Config, key string) (bool, error) {\n\tquery := `\n\t{\n\t    \"query\": \"query webhooks($first: Int, $after: String, $kind: ExternalServiceKind) { webhooks(first: $first, after: $after, kind: $kind) { totalCount } }\",\n\t    \"variables\": {\n\t        \"first\": 10,\n\t        \"after\": \"\",\n\t        \"kind\": \"GITHUB\"\n\t    }\n\t}`\n\n\t// POST request is considered as non-safe and sourcegraph has graphql APIs. They do not change any state.\n\t// We are using unrestricted client to avoid error for non-safe API request.\n\tclient := analyzers.NewAnalyzeClientUnrestricted(cfg)\n\treq, err := http.NewRequest(\"POST\", \"https://sourcegraph.com/.api/graphql\", strings.NewReader(query))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Set(\"Authorization\", \"token \"+key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer resp.Body.Close()\n\n\tvar response GraphQLResponse\n\n\terr = json.NewDecoder(resp.Body).Decode(&response)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif len(response.Errors) > 0 {\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\t// ToDo: Add in logging\n\tif cfg.LoggingEnabled {\n\t\tcolor.Red(\"[x] Logging is not supported for this analyzer.\")\n\t\treturn\n\t}\n\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error: %s\", err.Error())\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid Sourcegraph Access Token\\n\\n\")\n\tcolor.Yellow(\"[i] Sourcegraph User Information\\n\")\n\tcolor.Green(\"Username: %s\\n\", info.User.Data.CurrentUser.Username)\n\tcolor.Green(\"Email: %s\\n\", info.User.Data.CurrentUser.Email)\n\tcolor.Green(\"Created At: %s\\n\\n\", info.User.Data.CurrentUser.CreatedAt)\n\n\tif info.IsSiteAdmin {\n\t\tcolor.Green(\"[!] Token Permissions: Site Admin\")\n\t} else {\n\t\t// This is the default for all access tokens as of 6/11/24\n\t\tcolor.Yellow(\"[i] Token Permissions: user:full (default)\")\n\t}\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\tuserInfo, err := getUserInfo(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif userInfo.Data.CurrentUser.Username == \"\" {\n\t\treturn nil, fmt.Errorf(\"invalid Sourcegraph Access Token\")\n\t}\n\n\tisSiteAdmin, err := checkSiteAdmin(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &SecretInfo{\n\t\tUser:        userInfo,\n\t\tIsSiteAdmin: isSiteAdmin,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/sourcegraph/sourcegraph_test.go",
    "content": "package sourcegraph\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tsecret := testSecrets.MustGetField(\"SOURCEGRAPH\")\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"valid SourceGraph key\",\n\t\t\tkey:  secret,\n\t\t\twant: `{\n\t\t\t\t\"AnalyzerType\": 17,\n\t\t\t\t\"Bindings\": [\n\t\t\t\t\t{\n\t\t\t\t\t\"Resource\": {\n\t\t\t\t\t\t\"Name\": \"ahrav\",\n\t\t\t\t\t\t\"FullyQualifiedName\": \"sourcegraph/ahravdutta02@gmail.com\",\n\t\t\t\t\t\t\"Type\": \"user\",\n\t\t\t\t\t\t\"Metadata\": {\n\t\t\t\t\t\t\t\"created_at\": \"2023-07-23T04:16:31Z\",\n\t\t\t\t\t\t\t\"email\": \"ahravdutta02@gmail.com\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"Parent\": null\n\t\t\t\t\t},\n\t\t\t\t\t\"Permission\": {\n\t\t\t\t\t\t\"Value\": \"user:read\",\n\t\t\t\t\t\t\"Parent\": null\n\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\t\"UnboundedResources\": null,\n\t\t\t\t\"Metadata\": null\n\t\t\t\t}`,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/square/expected_output.json",
    "content": "{\"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\"}}"
  },
  {
    "path": "pkg/analyzer/analyzers/square/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage square\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    BankAccountsRead Permission = iota\n    AppointmentsWrite Permission = iota\n    AppointmentsAllWrite Permission = iota\n    AppointmentsRead Permission = iota\n    AppointmentsAllRead Permission = iota\n    AppointmentsBusinessSettingsRead Permission = iota\n    PaymentsRead Permission = iota\n    PaymentsWrite Permission = iota\n    CashDrawerRead Permission = iota\n    ItemsWrite Permission = iota\n    ItemsRead Permission = iota\n    OrdersWrite Permission = iota\n    OrdersRead Permission = iota\n    CustomersWrite Permission = iota\n    CustomersRead Permission = iota\n    DeviceCredentialManagement Permission = iota\n    DevicesRead Permission = iota\n    DisputesWrite Permission = iota\n    DisputesRead Permission = iota\n    EmployeesRead Permission = iota\n    GiftcardsRead Permission = iota\n    GiftcardsWrite Permission = iota\n    InventoryWrite Permission = iota\n    InventoryRead Permission = iota\n    InvoicesWrite Permission = iota\n    InvoicesRead Permission = iota\n    TimecardsSettingsWrite Permission = iota\n    TimecardsWrite Permission = iota\n    TimecardsSettingsRead Permission = iota\n    TimecardsRead Permission = iota\n    MerchantProfileWrite Permission = iota\n    MerchantProfileRead Permission = iota\n    LoyaltyRead Permission = iota\n    LoyaltyWrite Permission = iota\n    PaymentsWriteInPerson Permission = iota\n    PaymentsWriteSharedOnfile Permission = iota\n    PaymentsWriteAdditionalRecipients Permission = iota\n    PayoutsRead Permission = iota\n    OnlineStoreSiteRead Permission = iota\n    OnlineStoreSnippetsWrite Permission = iota\n    OnlineStoreSnippetsRead Permission = iota\n    SubscriptionsWrite Permission = iota\n    SubscriptionsRead Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        BankAccountsRead: \"bank_accounts_read\",\n        AppointmentsWrite: \"appointments_write\",\n        AppointmentsAllWrite: \"appointments_all_write\",\n        AppointmentsRead: \"appointments_read\",\n        AppointmentsAllRead: \"appointments_all_read\",\n        AppointmentsBusinessSettingsRead: \"appointments_business_settings_read\",\n        PaymentsRead: \"payments_read\",\n        PaymentsWrite: \"payments_write\",\n        CashDrawerRead: \"cash_drawer_read\",\n        ItemsWrite: \"items_write\",\n        ItemsRead: \"items_read\",\n        OrdersWrite: \"orders_write\",\n        OrdersRead: \"orders_read\",\n        CustomersWrite: \"customers_write\",\n        CustomersRead: \"customers_read\",\n        DeviceCredentialManagement: \"device_credential_management\",\n        DevicesRead: \"devices_read\",\n        DisputesWrite: \"disputes_write\",\n        DisputesRead: \"disputes_read\",\n        EmployeesRead: \"employees_read\",\n        GiftcardsRead: \"giftcards_read\",\n        GiftcardsWrite: \"giftcards_write\",\n        InventoryWrite: \"inventory_write\",\n        InventoryRead: \"inventory_read\",\n        InvoicesWrite: \"invoices_write\",\n        InvoicesRead: \"invoices_read\",\n        TimecardsSettingsWrite: \"timecards_settings_write\",\n        TimecardsWrite: \"timecards_write\",\n        TimecardsSettingsRead: \"timecards_settings_read\",\n        TimecardsRead: \"timecards_read\",\n        MerchantProfileWrite: \"merchant_profile_write\",\n        MerchantProfileRead: \"merchant_profile_read\",\n        LoyaltyRead: \"loyalty_read\",\n        LoyaltyWrite: \"loyalty_write\",\n        PaymentsWriteInPerson: \"payments_write_in_person\",\n        PaymentsWriteSharedOnfile: \"payments_write_shared_onfile\",\n        PaymentsWriteAdditionalRecipients: \"payments_write_additional_recipients\",\n        PayoutsRead: \"payouts_read\",\n        OnlineStoreSiteRead: \"online_store_site_read\",\n        OnlineStoreSnippetsWrite: \"online_store_snippets_write\",\n        OnlineStoreSnippetsRead: \"online_store_snippets_read\",\n        SubscriptionsWrite: \"subscriptions_write\",\n        SubscriptionsRead: \"subscriptions_read\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"bank_accounts_read\": BankAccountsRead,\n        \"appointments_write\": AppointmentsWrite,\n        \"appointments_all_write\": AppointmentsAllWrite,\n        \"appointments_read\": AppointmentsRead,\n        \"appointments_all_read\": AppointmentsAllRead,\n        \"appointments_business_settings_read\": AppointmentsBusinessSettingsRead,\n        \"payments_read\": PaymentsRead,\n        \"payments_write\": PaymentsWrite,\n        \"cash_drawer_read\": CashDrawerRead,\n        \"items_write\": ItemsWrite,\n        \"items_read\": ItemsRead,\n        \"orders_write\": OrdersWrite,\n        \"orders_read\": OrdersRead,\n        \"customers_write\": CustomersWrite,\n        \"customers_read\": CustomersRead,\n        \"device_credential_management\": DeviceCredentialManagement,\n        \"devices_read\": DevicesRead,\n        \"disputes_write\": DisputesWrite,\n        \"disputes_read\": DisputesRead,\n        \"employees_read\": EmployeesRead,\n        \"giftcards_read\": GiftcardsRead,\n        \"giftcards_write\": GiftcardsWrite,\n        \"inventory_write\": InventoryWrite,\n        \"inventory_read\": InventoryRead,\n        \"invoices_write\": InvoicesWrite,\n        \"invoices_read\": InvoicesRead,\n        \"timecards_settings_write\": TimecardsSettingsWrite,\n        \"timecards_write\": TimecardsWrite,\n        \"timecards_settings_read\": TimecardsSettingsRead,\n        \"timecards_read\": TimecardsRead,\n        \"merchant_profile_write\": MerchantProfileWrite,\n        \"merchant_profile_read\": MerchantProfileRead,\n        \"loyalty_read\": LoyaltyRead,\n        \"loyalty_write\": LoyaltyWrite,\n        \"payments_write_in_person\": PaymentsWriteInPerson,\n        \"payments_write_shared_onfile\": PaymentsWriteSharedOnfile,\n        \"payments_write_additional_recipients\": PaymentsWriteAdditionalRecipients,\n        \"payouts_read\": PayoutsRead,\n        \"online_store_site_read\": OnlineStoreSiteRead,\n        \"online_store_snippets_write\": OnlineStoreSnippetsWrite,\n        \"online_store_snippets_read\": OnlineStoreSnippetsRead,\n        \"subscriptions_write\": SubscriptionsWrite,\n        \"subscriptions_read\": SubscriptionsRead,\n    }\n\n    PermissionIDs = map[Permission]int{\n        BankAccountsRead: 1,\n        AppointmentsWrite: 2,\n        AppointmentsAllWrite: 3,\n        AppointmentsRead: 4,\n        AppointmentsAllRead: 5,\n        AppointmentsBusinessSettingsRead: 6,\n        PaymentsRead: 7,\n        PaymentsWrite: 8,\n        CashDrawerRead: 9,\n        ItemsWrite: 10,\n        ItemsRead: 11,\n        OrdersWrite: 12,\n        OrdersRead: 13,\n        CustomersWrite: 14,\n        CustomersRead: 15,\n        DeviceCredentialManagement: 16,\n        DevicesRead: 17,\n        DisputesWrite: 18,\n        DisputesRead: 19,\n        EmployeesRead: 20,\n        GiftcardsRead: 21,\n        GiftcardsWrite: 22,\n        InventoryWrite: 23,\n        InventoryRead: 24,\n        InvoicesWrite: 25,\n        InvoicesRead: 26,\n        TimecardsSettingsWrite: 27,\n        TimecardsWrite: 28,\n        TimecardsSettingsRead: 29,\n        TimecardsRead: 30,\n        MerchantProfileWrite: 31,\n        MerchantProfileRead: 32,\n        LoyaltyRead: 33,\n        LoyaltyWrite: 34,\n        PaymentsWriteInPerson: 35,\n        PaymentsWriteSharedOnfile: 36,\n        PaymentsWriteAdditionalRecipients: 37,\n        PayoutsRead: 38,\n        OnlineStoreSiteRead: 39,\n        OnlineStoreSnippetsWrite: 40,\n        OnlineStoreSnippetsRead: 41,\n        SubscriptionsWrite: 42,\n        SubscriptionsRead: 43,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: BankAccountsRead,\n        2: AppointmentsWrite,\n        3: AppointmentsAllWrite,\n        4: AppointmentsRead,\n        5: AppointmentsAllRead,\n        6: AppointmentsBusinessSettingsRead,\n        7: PaymentsRead,\n        8: PaymentsWrite,\n        9: CashDrawerRead,\n        10: ItemsWrite,\n        11: ItemsRead,\n        12: OrdersWrite,\n        13: OrdersRead,\n        14: CustomersWrite,\n        15: CustomersRead,\n        16: DeviceCredentialManagement,\n        17: DevicesRead,\n        18: DisputesWrite,\n        19: DisputesRead,\n        20: EmployeesRead,\n        21: GiftcardsRead,\n        22: GiftcardsWrite,\n        23: InventoryWrite,\n        24: InventoryRead,\n        25: InvoicesWrite,\n        26: InvoicesRead,\n        27: TimecardsSettingsWrite,\n        28: TimecardsWrite,\n        29: TimecardsSettingsRead,\n        30: TimecardsRead,\n        31: MerchantProfileWrite,\n        32: MerchantProfileRead,\n        33: LoyaltyRead,\n        34: LoyaltyWrite,\n        35: PaymentsWriteInPerson,\n        36: PaymentsWriteSharedOnfile,\n        37: PaymentsWriteAdditionalRecipients,\n        38: PayoutsRead,\n        39: OnlineStoreSiteRead,\n        40: OnlineStoreSnippetsWrite,\n        41: OnlineStoreSnippetsRead,\n        42: SubscriptionsWrite,\n        43: SubscriptionsRead,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/square/permissions.yaml",
    "content": "permissions:\n  - bank_accounts_read\n  - appointments_write\n  - appointments_all_write\n  - appointments_read\n  - appointments_all_read\n  - appointments_business_settings_read\n  - payments_read\n  - payments_write\n  - cash_drawer_read\n  - items_write\n  - items_read\n  - orders_write\n  - orders_read\n  - customers_write\n  - customers_read\n  - device_credential_management\n  - devices_read\n  - disputes_write\n  - disputes_read\n  - employees_read\n  - giftcards_read\n  - giftcards_write\n  - inventory_write\n  - inventory_read\n  - invoices_write\n  - invoices_read\n  - timecards_settings_write\n  - timecards_write\n  - timecards_settings_read\n  - timecards_read\n  - merchant_profile_write\n  - merchant_profile_read\n  - loyalty_read\n  - loyalty_write\n  - payments_write_in_person\n  - payments_write_shared_onfile\n  - payments_write_additional_recipients\n  - payouts_read\n  - online_store_site_read\n  - online_store_snippets_write\n  - online_store_snippets_read\n  - subscriptions_write\n  - subscriptions_read\n"
  },
  {
    "path": "pkg/analyzer/analyzers/square/scopes.go",
    "content": "package square\n\nvar permissions_slice = []map[string]map[string][]string{\n\t{\n\t\t\"Bank Accounts\": {\n\t\t\t\"GetBankAccount\":       []string{\"BANK_ACCOUNTS_READ\"},\n\t\t\t\"ListBankAccounts\":     []string{\"BANK_ACCOUNTS_READ\"},\n\t\t\t\"GetBankAccountByV1Id\": []string{\"BANK_ACCOUNTS_READ\"},\n\t\t},\n\t},\n\t{\n\t\t\"Bookings\": {\n\t\t\t\"CreateBooking (buyer-level)\":       []string{\"APPOINTMENTS_WRITE\"},\n\t\t\t\"CreateBooking (seller-level)\":      []string{\"APPOINTMENTS_WRITE\", \"APPOINTMENTS_ALL_WRITE\"},\n\t\t\t\"SearchAvailability (buyer-level)\":  []string{\"APPOINTMENTS_READ\"},\n\t\t\t\"SearchAvailability (seller-level)\": []string{\"APPOINTMENTS_READ\", \"APPOINTMENTS_ALL_READ\"},\n\t\t\t\"RetrieveBusinessBookingProfile\":    []string{\"APPOINTMENTS_BUSINESS_SETTINGS_READ\"},\n\t\t\t\"ListTeamMemberBookingProfiles\":     []string{\"APPOINTMENTS_BUSINESS_SETTINGS_READ\"},\n\t\t\t\"RetrieveTeamMemberBookingProfile\":  []string{\"APPOINTMENTS_BUSINESS_SETTINGS_READ\"},\n\t\t\t\"ListBookings (buyer-level)\":        []string{\"APPOINTMENTS_READ\"},\n\t\t\t\"ListBookings (seller-level)\":       []string{\"APPOINTMENTS_READ\", \"APPOINTMENTS_ALL_READ\"},\n\t\t\t\"RetrieveBooking (buyer-level)\":     []string{\"APPOINTMENTS_READ\"},\n\t\t\t\"RetrieveBooking (seller-level)\":    []string{\"APPOINTMENTS_READ\", \"APPOINTMENTS_ALL_READ\"},\n\t\t\t\"UpdateBooking (buyer-level)\":       []string{\"APPOINTMENTS_WRITE\"},\n\t\t\t\"UpdateBooking (seller-level)\":      []string{\"APPOINTMENTS_WRITE\", \"APPOINTMENTS_ALL_WRITE\"},\n\t\t\t\"CancelBooking (buyer-level)\":       []string{\"APPOINTMENTS_WRITE\"},\n\t\t\t\"CancelBooking (seller-level)\":      []string{\"APPOINTMENTS_WRITE\", \"APPOINTMENTS_ALL_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Booking Custom Attributes\": {\n\t\t\t\"CreateBookingCustomAttributeDefinition (buyer-level)\":    []string{\"APPOINTMENTS_WRITE\"},\n\t\t\t\"CreateBookingCustomAttributeDefinition (seller-level)\":   []string{\"APPOINTMENTS_WRITE\", \"APPOINTMENTS_ALL_WRITE\"},\n\t\t\t\"UpdateBookingCustomAttributeDefinition (buyer-level)\":    []string{\"APPOINTMENTS_WRITE\"},\n\t\t\t\"UpdateBookingCustomAttributeDefinition (seller-level)\":   []string{\"APPOINTMENTS_WRITE\", \"APPOINTMENTS_ALL_WRITE\"},\n\t\t\t\"ListBookingCustomAttributeDefinitions (buyer-level)\":     []string{\"APPOINTMENTS_READ\"},\n\t\t\t\"ListBookingCustomAttributeDefinitions (seller-level)\":    []string{\"APPOINTMENTS_READ\", \"APPOINTMENTS_ALL_READ\"},\n\t\t\t\"RetrieveBookingCustomAttributeDefinition (buyer-level)\":  []string{\"APPOINTMENTS_READ\"},\n\t\t\t\"RetrieveBookingCustomAttributeDefinition (seller-level)\": []string{\"APPOINTMENTS_READ\", \"APPOINTMENTS_ALL_READ\"},\n\t\t\t\"DeleteBookingCustomAttributeDefinition (buyer-level)\":    []string{\"APPOINTMENTS_WRITE\"},\n\t\t\t\"DeleteBookingCustomAttributeDefinition (seller-level)\":   []string{\"APPOINTMENTS_WRITE\", \"APPOINTMENTS_ALL_WRITE\"},\n\t\t\t\"UpsertBookingCustomAttribute (buyer-level)\":              []string{\"APPOINTMENTS_WRITE\"},\n\t\t\t\"UpsertBookingCustomAttribute (seller-level)\":             []string{\"APPOINTMENTS_WRITE\", \"APPOINTMENTS_ALL_WRITE\"},\n\t\t\t\"BulkUpsertBookingCustomAttributes (buyer-level)\":         []string{\"APPOINTMENTS_WRITE\"},\n\t\t\t\"BulkUpsertBookingCustomAttributes (seller-level)\":        []string{\"APPOINTMENTS_WRITE\", \"APPOINTMENTS_ALL_WRITE\"},\n\t\t\t\"ListBookingCustomAttributes (buyer-level)\":               []string{\"APPOINTMENTS_READ\"},\n\t\t\t\"ListBookingCustomAttributes (seller-level)\":              []string{\"APPOINTMENTS_READ\", \"APPOINTMENTS_ALL_READ\"},\n\t\t\t\"RetrieveBookingCustomAttribute (buyer-level)\":            []string{\"APPOINTMENTS_READ\"},\n\t\t\t\"RetrieveBookingCustomAttribute (seller-level)\":           []string{\"APPOINTMENTS_READ\", \"APPOINTMENTS_ALL_READ\"},\n\t\t\t\"DeleteBookingCustomAttribute (buyer-level)\":              []string{\"APPOINTMENTS_WRITE\"},\n\t\t\t\"DeleteBookingCustomAttribute (seller-level)\":             []string{\"APPOINTMENTS_WRITE\", \"APPOINTMENTS_ALL_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Cards\": {\n\t\t\t\"ListCards\":    []string{\"PAYMENTS_READ\"},\n\t\t\t\"CreateCard\":   []string{\"PAYMENTS_WRITE\"},\n\t\t\t\"RetrieveCard\": []string{\"PAYMENTS_READ\"},\n\t\t\t\"DisableCard\":  []string{\"PAYMENTS_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Cash Drawer Shifts\": {\n\t\t\t\"ListCashDrawerShifts\":      []string{\"CASH_DRAWER_READ\"},\n\t\t\t\"ListCashDrawerShiftEvents\": []string{\"CASH_DRAWER_READ\"},\n\t\t\t\"RetrieveCashDrawerShift\":   []string{\"CASH_DRAWER_READ\"},\n\t\t},\n\t},\n\t{\n\t\t\"Catalog\": {\n\t\t\t\"BatchDeleteCatalogObjects\":   []string{\"ITEMS_WRITE\"},\n\t\t\t\"BatchUpsertCatalogObjects\":   []string{\"ITEMS_WRITE\"},\n\t\t\t\"BatchRetrieveCatalogObjects\": []string{\"ITEMS_READ\"},\n\t\t\t\"CatalogInfo\":                 []string{\"ITEMS_READ\"},\n\t\t\t\"CreateCatalogImage\":          []string{\"ITEMS_WRITE\"},\n\t\t\t\"DeleteCatalogObject\":         []string{\"ITEMS_WRITE\"},\n\t\t\t\"ListCatalog\":                 []string{\"ITEMS_READ\"},\n\t\t\t\"RetrieveCatalogObject\":       []string{\"ITEMS_READ\"},\n\t\t\t\"SearchCatalogItems\":          []string{\"ITEMS_READ\"},\n\t\t\t\"SearchCatalogObjects\":        []string{\"ITEMS_READ\"},\n\t\t\t\"UpdateItemTaxes\":             []string{\"ITEMS_WRITE\"},\n\t\t\t\"UpdateItemModifierLists\":     []string{\"ITEMS_WRITE\"},\n\t\t\t\"UpsertCatalogObject\":         []string{\"ITEMS_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Checkout\": {\n\t\t\t\"CreatePaymentLink\": []string{\"ORDERS_WRITE\", \"ORDERS_READ\", \"PAYMENTS_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Customers\": {\n\t\t\t\"AddGroupToCustomer\":              []string{\"CUSTOMERS_WRITE\"},\n\t\t\t\"BulkCreateCustomers\":             []string{\"CUSTOMERS_WRITE\"},\n\t\t\t\"BulkDeleteCustomers\":             []string{\"CUSTOMERS_WRITE\"},\n\t\t\t\"BulkRetrieveCustomers\":           []string{\"CUSTOMERS_READ\"},\n\t\t\t\"BulkUpdateCustomers\":             []string{\"CUSTOMERS_WRITE\"},\n\t\t\t\"CreateCustomer\":                  []string{\"CUSTOMERS_WRITE\"},\n\t\t\t\"CreateCustomerCard (deprecated)\": []string{\"CUSTOMERS_WRITE\"},\n\t\t\t\"DeleteCustomer\":                  []string{\"CUSTOMERS_WRITE\"},\n\t\t\t\"DeleteCustomerCard (deprecated)\": []string{\"CUSTOMERS_WRITE\"},\n\t\t\t\"ListCustomers\":                   []string{\"CUSTOMERS_READ\"},\n\t\t\t\"RemoveGroupFromCustomer\":         []string{\"CUSTOMERS_WRITE\"},\n\t\t\t\"RetrieveCustomer\":                []string{\"CUSTOMERS_READ\"},\n\t\t\t\"SearchCustomers\":                 []string{\"CUSTOMERS_READ\"},\n\t\t\t\"UpdateCustomer\":                  []string{\"CUSTOMERS_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Customer Custom Attributes\": {\n\t\t\t\"CreateCustomerCustomAttributeDefinition\":   []string{\"CUSTOMERS_WRITE\"},\n\t\t\t\"UpdateCustomerCustomAttributeDefinition\":   []string{\"CUSTOMERS_WRITE\"},\n\t\t\t\"ListCustomerCustomAttributeDefinitions\":    []string{\"CUSTOMERS_READ\"},\n\t\t\t\"RetrieveCustomerCustomAttributeDefinition\": []string{\"CUSTOMERS_READ\"},\n\t\t\t\"DeleteCustomerCustomAttributeDefinition\":   []string{\"CUSTOMERS_WRITE\"},\n\t\t\t\"UpsertCustomerCustomAttribute\":             []string{\"CUSTOMERS_WRITE\"},\n\t\t\t\"BulkUpsertCustomerCustomAttributes\":        []string{\"CUSTOMERS_WRITE\"},\n\t\t\t\"ListCustomerCustomAttributes\":              []string{\"CUSTOMERS_READ\"},\n\t\t\t\"RetrieveCustomerCustomAttribute\":           []string{\"CUSTOMERS_READ\"},\n\t\t\t\"DeleteCustomerCustomAttribute\":             []string{\"CUSTOMERS_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Customer Groups\": {\n\t\t\t\"CreateCustomerGroup\":   []string{\"CUSTOMERS_WRITE\"},\n\t\t\t\"DeleteCustomerGroup\":   []string{\"CUSTOMERS_WRITE\"},\n\t\t\t\"ListCustomerGroups\":    []string{\"CUSTOMERS_READ\"},\n\t\t\t\"RetrieveCustomerGroup\": []string{\"CUSTOMERS_READ\"},\n\t\t\t\"UpdateCustomerGroup\":   []string{\"CUSTOMERS_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Customer Segments\": {\n\t\t\t\"ListCustomerSegments\":    []string{\"CUSTOMERS_READ\"},\n\t\t\t\"RetrieveCustomerSegment\": []string{\"CUSTOMERS_READ\"},\n\t\t},\n\t},\n\t{\n\t\t\"Devices\": {\n\t\t\t\"CreateDeviceCode\": []string{\"DEVICE_CREDENTIAL_MANAGEMENT\"},\n\t\t\t\"GetDeviceCode\":    []string{\"DEVICE_CREDENTIAL_MANAGEMENT\"},\n\t\t\t\"ListDeviceCodes\":  []string{\"DEVICE_CREDENTIAL_MANAGEMENT\"},\n\t\t\t\"ListDevices\":      []string{\"DEVICES_READ\"},\n\t\t\t\"GetDevice\":        []string{\"DEVICES_READ\"},\n\t\t},\n\t},\n\t{\n\t\t\"Disputes\": {\n\t\t\t\"AcceptDispute\":             []string{\"DISPUTES_WRITE\"},\n\t\t\t\"CreateDisputeEvidenceFile\": []string{\"DISPUTES_WRITE\"},\n\t\t\t\"CreateDisputeEvidenceText\": []string{\"DISPUTES_WRITE\"},\n\t\t\t\"ListDisputeEvidence\":       []string{\"DISPUTES_READ\"},\n\t\t\t\"ListDisputes\":              []string{\"DISPUTES_READ\"},\n\t\t\t\"DeleteDisputeEvidence\":     []string{\"DISPUTES_WRITE\"},\n\t\t\t\"RetrieveDispute\":           []string{\"DISPUTES_READ\"},\n\t\t\t\"RetrieveDisputeEvidence\":   []string{\"DISPUTES_READ\"},\n\t\t\t\"SubmitEvidence\":            []string{\"DISPUTES_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Employees\": {\n\t\t\t\"ListEmployees (deprecated)\":    []string{\"EMPLOYEES_READ\"},\n\t\t\t\"RetrieveEmployee (deprecated)\": []string{\"EMPLOYEES_READ\"},\n\t\t},\n\t},\n\t{\n\t\t\"Gift Cards\": {\n\t\t\t\"ListGiftCards\":              []string{\"GIFTCARDS_READ\"},\n\t\t\t\"CreateGiftCard\":             []string{\"GIFTCARDS_WRITE\"},\n\t\t\t\"RetrieveGiftCard\":           []string{\"GIFTCARDS_READ\"},\n\t\t\t\"RetrieveGiftCardFromGAN\":    []string{\"GIFTCARDS_READ\"},\n\t\t\t\"RetrieveGiftCardFromNonce\":  []string{\"GIFTCARDS_READ\"},\n\t\t\t\"LinkCustomerToGiftCard\":     []string{\"GIFTCARDS_WRITE\"},\n\t\t\t\"UnlinkCustomerFromGiftCard\": []string{\"GIFTCARDS_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Gift Card Activities\": {\n\t\t\t\"ListGiftCardActivities\": []string{\"GIFTCARDS_READ\"},\n\t\t\t\"CreateGiftCardActivity\": []string{\"GIFTCARDS_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Inventory\": {\n\t\t\t\"BatchChangeInventory\":           []string{\"INVENTORY_WRITE\"},\n\t\t\t\"BatchRetrieveInventoryCounts\":   []string{\"INVENTORY_READ\"},\n\t\t\t\"BatchRetrieveInventoryChanges\":  []string{\"INVENTORY_READ\"},\n\t\t\t\"RetrieveInventoryAdjustment\":    []string{\"INVENTORY_READ\"},\n\t\t\t\"RetrieveInventoryChanges\":       []string{\"INVENTORY_READ\"},\n\t\t\t\"RetrieveInventoryCount\":         []string{\"INVENTORY_READ\"},\n\t\t\t\"RetrieveInventoryPhysicalCount\": []string{\"INVENTORY_READ\"},\n\t\t},\n\t},\n\t{\n\t\t\"Invoices\": {\n\t\t\t\"CreateInvoice\":           []string{\"INVOICES_WRITE\", \"ORDERS_WRITE\"},\n\t\t\t\"PublishInvoice\":          []string{\"CUSTOMERS_READ\", \"PAYMENTS_WRITE\", \"INVOICES_WRITE\", \"ORDERS_WRITE\"},\n\t\t\t\"GetInvoice\":              []string{\"INVOICES_READ\"},\n\t\t\t\"ListInvoices\":            []string{\"INVOICES_READ\"},\n\t\t\t\"SearchInvoices\":          []string{\"INVOICES_READ\"},\n\t\t\t\"CreateInvoiceAttachment\": []string{\"INVOICES_WRITE\", \"ORDERS_WRITE\"},\n\t\t\t\"DeleteInvoiceAttachment\": []string{\"INVOICES_WRITE\", \"ORDERS_WRITE\"},\n\t\t\t\"UpdateInvoice\":           []string{\"INVOICES_WRITE\", \"ORDERS_WRITE\"},\n\t\t\t\"DeleteInvoice\":           []string{\"INVOICES_WRITE\", \"ORDERS_WRITE\"},\n\t\t\t\"CancelInvoice\":           []string{\"INVOICES_WRITE\", \"ORDERS_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Labor\": {\n\t\t\t\"CreateBreakType\":      []string{\"TIMECARDS_SETTINGS_WRITE\"},\n\t\t\t\"CreateShift\":          []string{\"TIMECARDS_WRITE\"},\n\t\t\t\"DeleteBreakType\":      []string{\"TIMECARDS_SETTINGS_WRITE\"},\n\t\t\t\"DeleteShift\":          []string{\"TIMECARDS_WRITE\"},\n\t\t\t\"GetBreakType\":         []string{\"TIMECARDS_SETTINGS_READ\"},\n\t\t\t\"GetTeamMemberWage\":    []string{\"EMPLOYEES_READ\"},\n\t\t\t\"GetShift\":             []string{\"TIMECARDS_READ\"},\n\t\t\t\"ListBreakTypes\":       []string{\"TIMECARDS_SETTINGS_READ\"},\n\t\t\t\"ListTeamMemberWages\":  []string{\"EMPLOYEES_READ\"},\n\t\t\t\"ListWorkweekConfigs\":  []string{\"TIMECARDS_SETTINGS_READ\"},\n\t\t\t\"SearchShifts\":         []string{\"TIMECARDS_READ\"},\n\t\t\t\"UpdateShift\":          []string{\"TIMECARDS_WRITE\", \"TIMECARDS_READ\"},\n\t\t\t\"UpdateWorkweekConfig\": []string{\"TIMECARDS_SETTINGS_WRITE\", \"TIMECARDS_SETTINGS_READ\"},\n\t\t\t\"UpdateBreakType\":      []string{\"TIMECARDS_SETTINGS_WRITE\", \"TIMECARDS_SETTINGS_READ\"},\n\t\t},\n\t},\n\t{\n\t\t\"Locations\": {\n\t\t\t\"CreateLocation\":   []string{\"MERCHANT_PROFILE_WRITE\"},\n\t\t\t\"ListLocations\":    []string{\"MERCHANT_PROFILE_READ\"},\n\t\t\t\"RetrieveLocation\": []string{\"MERCHANT_PROFILE_READ\"},\n\t\t\t\"UpdateLocation\":   []string{\"MERCHANT_PROFILE_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Location Custom Attributes\": {\n\t\t\t\"CreateLocationCustomAttributeDefinition\":   []string{\"MERCHANT_PROFILE_WRITE\"},\n\t\t\t\"UpdateLocationCustomAttributeDefinition\":   []string{\"MERCHANT_PROFILE_WRITE\"},\n\t\t\t\"ListLocationCustomAttributeDefinitions\":    []string{\"MERCHANT_PROFILE_READ\"},\n\t\t\t\"RetrieveLocationCustomAttributeDefinition\": []string{\"MERCHANT_PROFILE_READ\"},\n\t\t\t\"DeleteLocationCustomAttributeDefinition\":   []string{\"MERCHANT_PROFILE_WRITE\"},\n\t\t\t\"UpsertLocationCustomAttribute\":             []string{\"MERCHANT_PROFILE_WRITE\"},\n\t\t\t\"BulkUpsertLocationCustomAttributes\":        []string{\"MERCHANT_PROFILE_WRITE\"},\n\t\t\t\"ListLocationCustomAttributes\":              []string{\"MERCHANT_PROFILE_READ\"},\n\t\t\t\"RetrieveLocationCustomAttribute\":           []string{\"MERCHANT_PROFILE_READ\"},\n\t\t\t\"DeleteLocationCustomAttribute\":             []string{\"MERCHANT_PROFILE_WRITE\"},\n\t\t\t\"BulkDeleteLocationCustomAttributes\":        []string{\"MERCHANT_PROFILE_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Loyalty\": {\n\t\t\t\"RetrieveLoyaltyProgram\":           []string{\"LOYALTY_READ\"},\n\t\t\t\"ListLoyaltyPrograms (deprecated)\": []string{\"LOYALTY_READ\"},\n\t\t\t\"CreateLoyaltyPromotion\":           []string{\"LOYALTY_WRITE\"},\n\t\t\t\"ListLoyaltyPromotions\":            []string{\"LOYALTY_READ\"},\n\t\t\t\"RetrieveLoyaltyPromotion\":         []string{\"LOYALTY_READ\"},\n\t\t\t\"CancelLoyaltyPromotion\":           []string{\"LOYALTY_WRITE\"},\n\t\t\t\"CreateLoyaltyAccount\":             []string{\"LOYALTY_WRITE\"},\n\t\t\t\"RetrieveLoyaltyAccount\":           []string{\"LOYALTY_READ\"},\n\t\t\t\"SearchLoyaltyAccounts\":            []string{\"LOYALTY_READ\"},\n\t\t\t\"AccumulateLoyaltyPoints\":          []string{\"LOYALTY_WRITE\"},\n\t\t\t\"AdjustLoyaltyPoints\":              []string{\"LOYALTY_WRITE\"},\n\t\t\t\"CalculateLoyaltyPoints\":           []string{\"LOYALTY_READ\"},\n\t\t\t\"CreateLoyaltyReward\":              []string{\"LOYALTY_WRITE\"},\n\t\t\t\"RedeemLoyaltyReward\":              []string{\"LOYALTY_WRITE\"},\n\t\t\t\"RetrieveLoyaltyReward\":            []string{\"LOYALTY_READ\"},\n\t\t\t\"SearchLoyaltyRewards\":             []string{\"LOYALTY_READ\"},\n\t\t\t\"DeleteLoyaltyReward\":              []string{\"LOYALTY_WRITE\"},\n\t\t\t\"SearchLoyaltyEvents\":              []string{\"LOYALTY_READ\"},\n\t\t},\n\t},\n\t{\n\t\t\"Merchants\": {\n\t\t\t\"ListMerchants\":    []string{\"MERCHANT_PROFILE_READ\"},\n\t\t\t\"RetrieveMerchant\": []string{\"MERCHANT_PROFILE_READ\"},\n\t\t},\n\t},\n\t{\n\t\t\"Merchant Custom Attributes\": {\n\t\t\t\"CreateMerchantCustomAttributeDefinition\":   []string{\"MERCHANT_PROFILE_WRITE\"},\n\t\t\t\"UpdateMerchantCustomAttributeDefinition\":   []string{\"MERCHANT_PROFILE_WRITE\"},\n\t\t\t\"ListMerchantCustomAttributeDefinitions\":    []string{\"MERCHANT_PROFILE_READ\"},\n\t\t\t\"RetrieveMerchantCustomAttributeDefinition\": []string{\"MERCHANT_PROFILE_READ\"},\n\t\t\t\"DeleteMerchantCustomAttributeDefinition\":   []string{\"MERCHANT_PROFILE_WRITE\"},\n\t\t\t\"UpsertMerchantCustomAttribute\":             []string{\"MERCHANT_PROFILE_WRITE\"},\n\t\t\t\"BulkUpsertMerchantCustomAttributes\":        []string{\"MERCHANT_PROFILE_WRITE\"},\n\t\t\t\"ListMerchantCustomAttributes\":              []string{\"MERCHANT_PROFILE_READ\"},\n\t\t\t\"RetrieveMerchantCustomAttribute\":           []string{\"MERCHANT_PROFILE_READ\"},\n\t\t\t\"DeleteMerchantCustomAttribute\":             []string{\"MERCHANT_PROFILE_WRITE\"},\n\t\t\t\"BulkDeleteMerchantCustomAttributes\":        []string{\"MERCHANT_PROFILE_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Mobile Authorization\": {\n\t\t\t\"CreateMobileAuthorizationCode\": []string{\"PAYMENTS_WRITE_IN_PERSON\"},\n\t\t},\n\t},\n\t{\n\t\t\"Orders\": {\n\t\t\t\"CloneOrder\":          []string{\"ORDERS_WRITE\"},\n\t\t\t\"CreateOrder\":         []string{\"ORDERS_WRITE\"},\n\t\t\t\"BatchRetrieveOrders\": []string{\"ORDERS_READ\"},\n\t\t\t\"PayOrder\":            []string{\"ORDERS_WRITE\", \"PAYMENTS_WRITE\"},\n\t\t\t\"RetrieveOrder\":       []string{\"ORDERS_WRITE\", \"ORDERS_READ\"},\n\t\t\t\"SearchOrders\":        []string{\"ORDERS_READ\"},\n\t\t\t\"UpdateOrder\":         []string{\"ORDERS_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Order Custom Attributes\": {\n\t\t\t\"CreateOrderCustomAttributeDefinition\":   []string{\"ORDERS_WRITE\"},\n\t\t\t\"UpdateOrderCustomAttributeDefinition\":   []string{\"ORDERS_WRITE\"},\n\t\t\t\"ListOrderCustomAttributeDefinitions\":    []string{\"ORDERS_READ\"},\n\t\t\t\"RetrieveOrderCustomAttributeDefinition\": []string{\"ORDERS_READ\"},\n\t\t\t\"DeleteOrderCustomAttributeDefinition\":   []string{\"ORDERS_WRITE\"},\n\t\t\t\"UpsertOrderCustomAttribute\":             []string{\"ORDERS_WRITE\"},\n\t\t\t\"BulkUpsertOrderCustomAttributes\":        []string{\"ORDERS_WRITE\"},\n\t\t\t\"ListOrderCustomAttributes\":              []string{\"ORDERS_READ\"},\n\t\t\t\"RetrieveOrderCustomAttribute\":           []string{\"ORDERS_READ\"},\n\t\t\t\"DeleteOrderCustomAttribute\":             []string{\"ORDERS_WRITE\"},\n\t\t\t\"BulkDeleteOrderCustomAttributes\":        []string{\"ORDERS_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Payments and Refunds\": {\n\t\t\t\"CancelPayment\":                 []string{\"PAYMENTS_WRITE\"},\n\t\t\t\"CancelPaymentByIdempotencyKey\": []string{\"PAYMENTS_WRITE\"},\n\t\t\t\"CompletePayment\":               []string{\"PAYMENTS_WRITE\"},\n\t\t\t\"CreatePayment\":                 []string{\"PAYMENTS_WRITE\", \"PAYMENTS_WRITE_SHARED_ONFILE\", \"PAYMENTS_WRITE_ADDITIONAL_RECIPIENTS\"},\n\t\t\t\"GetPayment\":                    []string{\"PAYMENTS_READ\"},\n\t\t\t\"GetPaymentRefund\":              []string{\"PAYMENTS_READ\"},\n\t\t\t\"ListPayments\":                  []string{\"PAYMENTS_READ\"},\n\t\t\t\"ListPaymentRefunds\":            []string{\"PAYMENTS_READ\"},\n\t\t\t\"RefundPayment\":                 []string{\"PAYMENTS_WRITE\", \"PAYMENTS_WRITE_ADDITIONAL_RECIPIENTS\"},\n\t\t},\n\t},\n\t{\n\t\t\"Payouts\": {\n\t\t\t\"ListPayouts\":       []string{\"PAYOUTS_READ\"},\n\t\t\t\"GetPayout\":         []string{\"PAYOUTS_READ\"},\n\t\t\t\"ListPayoutEntries\": []string{\"PAYOUTS_READ\"},\n\t\t},\n\t},\n\t{\n\t\t\"Sites\": {\n\t\t\t\"ListSites\": []string{\"ONLINE_STORE_SITE_READ\"},\n\t\t},\n\t},\n\t{\n\t\t\"Snippets\": {\n\t\t\t\"UpsertSnippet\":   []string{\"ONLINE_STORE_SNIPPETS_WRITE\"},\n\t\t\t\"RetrieveSnippet\": []string{\"ONLINE_STORE_SNIPPETS_READ\"},\n\t\t\t\"DeleteSnippet\":   []string{\"ONLINE_STORE_SNIPPETS_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Subscriptions\": {\n\t\t\t\"CreateSubscription\":       []string{\"CUSTOMERS_READ\", \"PAYMENTS_WRITE\", \"SUBSCRIPTIONS_WRITE\", \"ITEMS_READ\", \"ORDERS_WRITE\", \"INVOICES_WRITE\"},\n\t\t\t\"SearchSubscriptions\":      []string{\"SUBSCRIPTIONS_READ\"},\n\t\t\t\"RetrieveSubscription\":     []string{\"SUBSCRIPTIONS_READ\"},\n\t\t\t\"UpdateSubscription\":       []string{\"CUSTOMERS_READ\", \"PAYMENTS_WRITE\", \"SUBSCRIPTIONS_WRITE\", \"ITEMS_READ\", \"ORDERS_WRITE\", \"INVOICES_WRITE\"},\n\t\t\t\"CancelSubscription\":       []string{\"SUBSCRIPTIONS_WRITE\"},\n\t\t\t\"ListSubscriptionEvents\":   []string{\"SUBSCRIPTIONS_READ\"},\n\t\t\t\"ResumeSubscription\":       []string{\"CUSTOMERS_READ\", \"PAYMENTS_WRITE\", \"SUBSCRIPTIONS_WRITE\", \"ITEMS_READ\", \"ORDERS_WRITE\", \"INVOICES_WRITE\"},\n\t\t\t\"PauseSubscription\":        []string{\"CUSTOMERS_READ\", \"PAYMENTS_WRITE\", \"SUBSCRIPTIONS_WRITE\", \"ITEMS_READ\", \"ORDERS_WRITE\", \"INVOICES_WRITE\"},\n\t\t\t\"SwapPlan\":                 []string{\"CUSTOMERS_READ\", \"PAYMENTS_WRITE\", \"SUBSCRIPTIONS_WRITE\", \"ITEMS_READ\", \"ORDERS_WRITE\", \"INVOICES_WRITE\"},\n\t\t\t\"DeleteSubscriptionAction\": []string{\"SUBSCRIPTIONS_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Team\": {\n\t\t\t\"BulkCreateTeamMembers\": []string{\"EMPLOYEES_WRITE\"},\n\t\t\t\"BulkUpdateTeamMembers\": []string{\"EMPLOYEES_WRITE\"},\n\t\t\t\"CreateTeamMember\":      []string{\"EMPLOYEES_WRITE\"},\n\t\t\t\"UpdateTeamMember\":      []string{\"EMPLOYEES_WRITE\"},\n\t\t\t\"RetrieveTeamMember\":    []string{\"EMPLOYEES_READ\"},\n\t\t\t\"RetrieveWageSetting\":   []string{\"EMPLOYEES_READ\"},\n\t\t\t\"SearchTeamMembers\":     []string{\"EMPLOYEES_READ\"},\n\t\t\t\"UpdateWageSetting\":     []string{\"EMPLOYEES_WRITE\"},\n\t\t},\n\t},\n\t{\n\t\t\"Terminal\": {\n\t\t\t\"CreateTerminalCheckout\":  []string{\"PAYMENTS_WRITE\"},\n\t\t\t\"CancelTerminalCheckout\":  []string{\"PAYMENTS_WRITE\"},\n\t\t\t\"GetTerminalCheckout\":     []string{\"PAYMENTS_READ\"},\n\t\t\t\"SearchTerminalCheckouts\": []string{\"PAYMENTS_READ\"},\n\t\t\t\"CreateTerminalRefund\":    []string{\"PAYMENTS_WRITE\"},\n\t\t\t\"CancelTerminalRefund\":    []string{\"PAYMENTS_WRITE\"},\n\t\t\t\"GetTerminalRefund\":       []string{\"PAYMENTS_READ\"},\n\t\t\t\"SearchTerminalRefunds\":   []string{\"PAYMENTS_READ\"},\n\t\t\t\"CreateTerminalAction\":    []string{\"PAYMENTS_WRITE\"},\n\t\t\t\"CancelTerminalAction\":    []string{\"PAYMENTS_WRITE\"},\n\t\t\t\"GetTerminalAction\":       []string{\"PAYMENTS_READ\", \"CUSTOMERS_READ\"},\n\t\t\t\"SearchTerminalAction\":    []string{\"PAYMENTS_READ\"},\n\t\t},\n\t},\n\t{\n\t\t\"Vendors\": {\n\t\t\t\"BulkCreateVendors\":   []string{\"VENDOR_WRITE\"},\n\t\t\t\"BulkRetrieveVendors\": []string{\"VENDOR_READ\"},\n\t\t\t\"BulkUpdateVendors\":   []string{\"VENDOR_WRITE\"},\n\t\t\t\"CreateVendor\":        []string{\"VENDOR_WRITE\"},\n\t\t\t\"SearchVendors\":       []string{\"VENDOR_READ\"},\n\t\t\t\"RetrieveVendor\":      []string{\"VENDOR_READ\"},\n\t\t\t\"UpdateVendors\":       []string{\"VENDOR_WRITE\"},\n\t\t},\n\t},\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/square/square.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go square\n\npackage square\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeSquare }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credInfo[\"key\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"key not found in credentialInfo\")\n\t}\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\tresult := analyzers.AnalyzerResult{\n\t\tAnalyzerType:       analyzers.AnalyzerTypeSquare,\n\t\tUnboundedResources: []analyzers.Resource{},\n\t\tMetadata: map[string]any{\n\t\t\t\"expires_at\":  info.Permissions.ExpiresAt,\n\t\t\t\"client_id\":   info.Permissions.ClientID,\n\t\t\t\"merchant_id\": info.Permissions.MerchantID,\n\t\t},\n\t}\n\n\tbindings, unboundedResources := getBindingsAndUnboundedResources(info.Permissions.Scopes)\n\n\tresult.Bindings = bindings\n\tresult.UnboundedResources = append(result.UnboundedResources, unboundedResources...)\n\tresult.UnboundedResources = append(result.UnboundedResources, getTeamMembersResources(info.Team)...)\n\n\treturn &result\n}\n\n// Convert given list of team members into resources\nfunc getTeamMembersResources(team TeamJSON) []analyzers.Resource {\n\tteamMembersResources := make([]analyzers.Resource, len(team.TeamMembers))\n\n\tfor idx, teamMember := range team.TeamMembers {\n\t\tteamMembersResources[idx] = analyzers.Resource{\n\t\t\tName:               teamMember.FirstName + \" \" + teamMember.LastName,\n\t\t\tFullyQualifiedName: teamMember.Email,\n\t\t\tType:               \"team_member\",\n\t\t\tMetadata: map[string]any{\n\t\t\t\t\"is_owner\":   teamMember.IsOwner,\n\t\t\t\t\"created_at\": teamMember.CreatedAt,\n\t\t\t},\n\t\t}\n\t}\n\n\treturn teamMembersResources\n}\n\n// Build a list of Bindings and UnboundedResources by referencing the category permissions list and\n// checking with the given scopes\nfunc getBindingsAndUnboundedResources(scopes []string) ([]analyzers.Binding, []analyzers.Resource) {\n\tbindings := []analyzers.Binding{}\n\tunboundedResources := []analyzers.Resource{}\n\tfor _, permissions_category := range permissions_slice {\n\t\tfor category, permissions := range permissions_category {\n\t\t\tparentResource := analyzers.Resource{\n\t\t\t\tName:               category,\n\t\t\t\tFullyQualifiedName: category,\n\t\t\t\tType:               \"category\",\n\t\t\t\tMetadata:           nil,\n\t\t\t\tParent:             nil,\n\t\t\t}\n\t\t\tcategoryBinding := make([]analyzers.Binding, 0)\n\t\t\tfor endpoint, requiredPermissions := range permissions {\n\t\t\t\tresource := analyzers.Resource{\n\t\t\t\t\tName:               endpoint,\n\t\t\t\t\tFullyQualifiedName: endpoint,\n\t\t\t\t\tType:               \"endpoint\",\n\t\t\t\t\tMetadata:           nil,\n\t\t\t\t\tParent:             &parentResource,\n\t\t\t\t}\n\t\t\t\tfor _, permission := range requiredPermissions {\n\t\t\t\t\tif _, ok := StringToPermission[permission]; !ok { // skip unknown permissions\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif contains(scopes, permission) {\n\t\t\t\t\t\tcategoryBinding = append(categoryBinding, analyzers.Binding{\n\t\t\t\t\t\t\tResource: resource,\n\t\t\t\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\t\t\t\tValue: permission,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif len(categoryBinding) == 0 {\n\t\t\t\tunboundedResources = append(unboundedResources, parentResource)\n\t\t\t} else {\n\t\t\t\tbindings = append(bindings, categoryBinding...)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn bindings, unboundedResources\n}\n\ntype TeamJSON struct {\n\tTeamMembers []struct {\n\t\tIsOwner   bool   `json:\"is_owner\"`\n\t\tFirstName string `json:\"given_name\"`\n\t\tLastName  string `json:\"family_name\"`\n\t\tEmail     string `json:\"email_address\"`\n\t\tCreatedAt string `json:\"created_at\"`\n\t} `json:\"team_members\"`\n}\n\ntype PermissionsJSON struct {\n\tScopes     []string `json:\"scopes\"`\n\tExpiresAt  string   `json:\"expires_at\"`\n\tClientID   string   `json:\"client_id\"`\n\tMerchantID string   `json:\"merchant_id\"`\n}\n\ntype SecretInfo struct {\n\tPermissions PermissionsJSON\n\tTeam        TeamJSON\n}\n\nfunc getPermissions(cfg *config.Config, key string) (PermissionsJSON, error) {\n\tvar permissions PermissionsJSON\n\n\t// POST request is considered as non-safe. Square Post request does not change any state.\n\t// We are using unrestricted client to avoid error for non-safe API request.\n\tclient := analyzers.NewAnalyzeClientUnrestricted(cfg)\n\treq, err := http.NewRequest(\"POST\", \"https://connect.squareup.com/oauth2/token/status\", nil)\n\tif err != nil {\n\t\treturn permissions, err\n\t}\n\n\treq.Header.Add(\"Authorization\", \"Bearer \"+key)\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Square-Version\", \"2024-06-04\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn permissions, err\n\t}\n\n\tif resp.StatusCode != 200 {\n\t\treturn permissions, nil\n\t}\n\n\tdefer resp.Body.Close()\n\n\terr = json.NewDecoder(resp.Body).Decode(&permissions)\n\tif err != nil {\n\t\treturn permissions, err\n\t}\n\treturn permissions, nil\n}\n\nfunc getUsers(cfg *config.Config, key string) (TeamJSON, error) {\n\tvar team TeamJSON\n\n\t// POST request is considered as non-safe. Square Post request does not change any state.\n\t// We are using unrestricted client to avoid error for non-safe API request.\n\tclient := analyzers.NewAnalyzeClientUnrestricted(cfg)\n\treq, err := http.NewRequest(\"POST\", \"https://connect.squareup.com/v2/team-members/search\", nil)\n\tif err != nil {\n\t\treturn team, err\n\t}\n\n\treq.Header.Add(\"Authorization\", \"Bearer \"+key)\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Square-Version\", \"2024-06-04\")\n\n\tq := req.URL.Query()\n\tq.Add(\"limit\", \"200\")\n\treq.URL.RawQuery = q.Encode()\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn team, err\n\t}\n\n\tif resp.StatusCode != 200 {\n\t\treturn team, nil\n\t}\n\n\tdefer resp.Body.Close()\n\n\terr = json.NewDecoder(resp.Body).Decode(&team)\n\tif err != nil {\n\t\treturn team, err\n\t}\n\treturn team, nil\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\tpermissions, err := getPermissions(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tteam, err := getUsers(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &SecretInfo{\n\t\tPermissions: permissions,\n\t\tTeam:        team,\n\t}, nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\t// ToDo: Add in logging\n\tif cfg.LoggingEnabled {\n\t\tcolor.Red(\"[x] Logging is not supported for this analyzer.\")\n\t\treturn\n\t}\n\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error: %s\", err.Error())\n\t\treturn\n\t}\n\n\tif info.Permissions.MerchantID == \"\" {\n\t\tcolor.Red(\"[x] Invalid Square API Key\")\n\t\treturn\n\t}\n\tcolor.Green(\"[!] Valid Square API Key\\n\\n\")\n\tcolor.Yellow(\"Merchant ID: %s\", info.Permissions.MerchantID)\n\tcolor.Yellow(\"Client ID: %s\", info.Permissions.ClientID)\n\tif info.Permissions.ExpiresAt == \"\" {\n\t\tcolor.Green(\"Expires: Never\\n\\n\")\n\t} else {\n\t\tcolor.Yellow(\"Expires: %s\\n\\n\", info.Permissions.ExpiresAt)\n\t}\n\tprintPermissions(info.Permissions.Scopes, cfg.ShowAll)\n\n\tprintTeamMembers(info.Team)\n}\n\nfunc contains(s []string, e string) bool {\n\tfor _, a := range s {\n\t\tif a == e {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc printPermissions(scopes []string, showAll bool) {\n\tisAccessToken := true\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"API Category\", \"Accessible Endpoints\"})\n\tfor _, permissions_slice := range permissions_slice {\n\t\tfor category, permissions := range permissions_slice {\n\t\t\taccessibleEndpoints := []string{}\n\t\t\tfor endpoint, requiredPermissions := range permissions {\n\t\t\t\thasAllPermissions := true\n\t\t\t\tfor _, permission := range requiredPermissions {\n\t\t\t\t\tif !contains(scopes, permission) {\n\t\t\t\t\t\thasAllPermissions = false\n\t\t\t\t\t\tisAccessToken = false\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif hasAllPermissions {\n\t\t\t\t\taccessibleEndpoints = append(accessibleEndpoints, endpoint)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif len(accessibleEndpoints) == 0 {\n\t\t\t\tt.AppendRow([]interface{}{category, \"\"})\n\t\t\t} else {\n\t\t\t\tt.AppendRow([]interface{}{color.GreenString(category), color.GreenString(strings.Join(accessibleEndpoints, \", \"))})\n\t\t\t}\n\t\t}\n\t}\n\tif isAccessToken {\n\t\tcolor.Green(\"[i] Permissions: Full Access\")\n\t} else {\n\t\tcolor.Yellow(\"[i] Permissions:\")\n\t}\n\tif !isAccessToken || showAll {\n\t\tt.SetColumnConfigs([]table.ColumnConfig{\n\t\t\t{Number: 2, WidthMax: 100},\n\t\t})\n\t\tt.Render()\n\t}\n}\n\nfunc printTeamMembers(team TeamJSON) {\n\tif len(team.TeamMembers) == 0 {\n\t\tcolor.Red(\"\\n[x] No team members found\")\n\t\treturn\n\t}\n\tcolor.Yellow(\"\\n[i] Team Members (don't imply any permissions)\")\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"First Name\", \"Last Name\", \"Email\", \"Owner\", \"Created At\"})\n\tfor _, member := range team.TeamMembers {\n\t\tt.AppendRow([]interface{}{color.GreenString(member.FirstName), color.GreenString(member.LastName), color.GreenString(member.Email), color.GreenString(strconv.FormatBool(member.IsOwner)), color.GreenString(member.CreatedAt)})\n\t}\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/square/square_test.go",
    "content": "package square\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    []byte // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid Square key\",\n\t\t\tkey:     testSecrets.MustGetField(\"SQUARE_SECRET\"),\n\t\t\twant:    expectedOutput,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal(tt.want, &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// // Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// // Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/stripe/expected_output.json",
    "content": "{\"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\"}}"
  },
  {
    "path": "pkg/analyzer/analyzers/stripe/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage stripe\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    ConnectedAccountRead Permission = iota\n    AccountLinkWrite Permission = iota\n    ApplePayDomainRead Permission = iota\n    ApplePayDomainWrite Permission = iota\n    ApplicationFeeRead Permission = iota\n    ApplicationFeeWrite Permission = iota\n    BalanceRead Permission = iota\n    BalanceTransactionSourceRead Permission = iota\n    BillingClockRead Permission = iota\n    BillingClockWrite Permission = iota\n    ChargeRead Permission = iota\n    ChargeWrite Permission = iota\n    CheckoutSessionRead Permission = iota\n    CheckoutSessionWrite Permission = iota\n    TerminalConfigurationRead Permission = iota\n    TerminalConfigurationWrite Permission = iota\n    TerminalConnectionTokenWrite Permission = iota\n    CouponRead Permission = iota\n    CouponWrite Permission = iota\n    CreditNoteRead Permission = iota\n    CreditNoteWrite Permission = iota\n    CustomerPortalRead Permission = iota\n    CustomerPortalWrite Permission = iota\n    CustomerRead Permission = iota\n    CustomerWrite Permission = iota\n    DisputeRead Permission = iota\n    DisputeWrite Permission = iota\n    EditLinkWrite Permission = iota\n    ElementsWrite Permission = iota\n    EventRead Permission = iota\n    FileRead Permission = iota\n    FileWrite Permission = iota\n    InvoiceRead Permission = iota\n    InvoiceWrite Permission = iota\n    IssuingAuthorizationRead Permission = iota\n    IssuingAuthorizationWrite Permission = iota\n    IssuingCardRead Permission = iota\n    IssuingCardWrite Permission = iota\n    IssuingCardholderRead Permission = iota\n    IssuingCardholderWrite Permission = iota\n    IssuingDisputeRead Permission = iota\n    IssuingDisputeWrite Permission = iota\n    IssuingTransactionRead Permission = iota\n    IssuingTransactionWrite Permission = iota\n    TerminalLocationRead Permission = iota\n    TerminalLocationWrite Permission = iota\n    MandateRead Permission = iota\n    MandateWrite Permission = iota\n    OrderRead Permission = iota\n    OrderWrite Permission = iota\n    PaymentIntentRead Permission = iota\n    PaymentIntentWrite Permission = iota\n    PaymentLinksRead Permission = iota\n    PaymentLinksWrite Permission = iota\n    PaymentMethodRead Permission = iota\n    PaymentMethodWrite Permission = iota\n    PayoutRead Permission = iota\n    PayoutWrite Permission = iota\n    PlanRead Permission = iota\n    PlanWrite Permission = iota\n    ProductRead Permission = iota\n    ProductWrite Permission = iota\n    PromotionCodeRead Permission = iota\n    PromotionCodeWrite Permission = iota\n    QuoteRead Permission = iota\n    QuoteWrite Permission = iota\n    TerminalReaderRead Permission = iota\n    TerminalReaderWrite Permission = iota\n    ReportRunsAndReportTypesRead Permission = iota\n    ReviewRead Permission = iota\n    ReviewWrite Permission = iota\n    SecretWrite Permission = iota\n    SetupIntentRead Permission = iota\n    SetupIntentWrite Permission = iota\n    ShippingRateRead Permission = iota\n    ShippingRateWrite Permission = iota\n    SkuRead Permission = iota\n    SkuWrite Permission = iota\n    SourceRead Permission = iota\n    SourceWrite Permission = iota\n    SubscriptionRead Permission = iota\n    SubscriptionWrite Permission = iota\n    TaxRateRead Permission = iota\n    TaxRateWrite Permission = iota\n    TaxSettingsRead Permission = iota\n    TaxSettingsWrite Permission = iota\n    TaxCalculationsAndTransactionsRead Permission = iota\n    TaxCalculationsAndTransactionsWrite Permission = iota\n    TokenRead Permission = iota\n    TokenWrite Permission = iota\n    TopUpRead Permission = iota\n    TopUpWrite Permission = iota\n    TransferRead Permission = iota\n    TransferWrite Permission = iota\n    UsageRecordRead Permission = iota\n    UsageRecordWrite Permission = iota\n    UserEmailRead Permission = iota\n    WebhookRead Permission = iota\n    WebhookWrite Permission = iota\n    IssuingCardSensitiveRead Permission = iota\n    FundingInstructionRead Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        ConnectedAccountRead: \"connected_account_read\",\n        AccountLinkWrite: \"account_link_write\",\n        ApplePayDomainRead: \"apple_pay_domain_read\",\n        ApplePayDomainWrite: \"apple_pay_domain_write\",\n        ApplicationFeeRead: \"application_fee_read\",\n        ApplicationFeeWrite: \"application_fee_write\",\n        BalanceRead: \"balance_read\",\n        BalanceTransactionSourceRead: \"balance_transaction_source_read\",\n        BillingClockRead: \"billing_clock_read\",\n        BillingClockWrite: \"billing_clock_write\",\n        ChargeRead: \"charge_read\",\n        ChargeWrite: \"charge_write\",\n        CheckoutSessionRead: \"checkout_session_read\",\n        CheckoutSessionWrite: \"checkout_session_write\",\n        TerminalConfigurationRead: \"terminal_configuration_read\",\n        TerminalConfigurationWrite: \"terminal_configuration_write\",\n        TerminalConnectionTokenWrite: \"terminal_connection_token_write\",\n        CouponRead: \"coupon_read\",\n        CouponWrite: \"coupon_write\",\n        CreditNoteRead: \"credit_note_read\",\n        CreditNoteWrite: \"credit_note_write\",\n        CustomerPortalRead: \"customer_portal_read\",\n        CustomerPortalWrite: \"customer_portal_write\",\n        CustomerRead: \"customer_read\",\n        CustomerWrite: \"customer_write\",\n        DisputeRead: \"dispute_read\",\n        DisputeWrite: \"dispute_write\",\n        EditLinkWrite: \"edit_link_write\",\n        ElementsWrite: \"elements_write\",\n        EventRead: \"event_read\",\n        FileRead: \"file_read\",\n        FileWrite: \"file_write\",\n        InvoiceRead: \"invoice_read\",\n        InvoiceWrite: \"invoice_write\",\n        IssuingAuthorizationRead: \"issuing_authorization_read\",\n        IssuingAuthorizationWrite: \"issuing_authorization_write\",\n        IssuingCardRead: \"issuing_card_read\",\n        IssuingCardWrite: \"issuing_card_write\",\n        IssuingCardholderRead: \"issuing_cardholder_read\",\n        IssuingCardholderWrite: \"issuing_cardholder_write\",\n        IssuingDisputeRead: \"issuing_dispute_read\",\n        IssuingDisputeWrite: \"issuing_dispute_write\",\n        IssuingTransactionRead: \"issuing_transaction_read\",\n        IssuingTransactionWrite: \"issuing_transaction_write\",\n        TerminalLocationRead: \"terminal_location_read\",\n        TerminalLocationWrite: \"terminal_location_write\",\n        MandateRead: \"mandate_read\",\n        MandateWrite: \"mandate_write\",\n        OrderRead: \"order_read\",\n        OrderWrite: \"order_write\",\n        PaymentIntentRead: \"payment_intent_read\",\n        PaymentIntentWrite: \"payment_intent_write\",\n        PaymentLinksRead: \"payment_links_read\",\n        PaymentLinksWrite: \"payment_links_write\",\n        PaymentMethodRead: \"payment_method_read\",\n        PaymentMethodWrite: \"payment_method_write\",\n        PayoutRead: \"payout_read\",\n        PayoutWrite: \"payout_write\",\n        PlanRead: \"plan_read\",\n        PlanWrite: \"plan_write\",\n        ProductRead: \"product_read\",\n        ProductWrite: \"product_write\",\n        PromotionCodeRead: \"promotion_code_read\",\n        PromotionCodeWrite: \"promotion_code_write\",\n        QuoteRead: \"quote_read\",\n        QuoteWrite: \"quote_write\",\n        TerminalReaderRead: \"terminal_reader_read\",\n        TerminalReaderWrite: \"terminal_reader_write\",\n        ReportRunsAndReportTypesRead: \"report_runs_and_report_types_read\",\n        ReviewRead: \"review_read\",\n        ReviewWrite: \"review_write\",\n        SecretWrite: \"secret_write\",\n        SetupIntentRead: \"setup_intent_read\",\n        SetupIntentWrite: \"setup_intent_write\",\n        ShippingRateRead: \"shipping_rate_read\",\n        ShippingRateWrite: \"shipping_rate_write\",\n        SkuRead: \"sku_read\",\n        SkuWrite: \"sku_write\",\n        SourceRead: \"source_read\",\n        SourceWrite: \"source_write\",\n        SubscriptionRead: \"subscription_read\",\n        SubscriptionWrite: \"subscription_write\",\n        TaxRateRead: \"tax_rate_read\",\n        TaxRateWrite: \"tax_rate_write\",\n        TaxSettingsRead: \"tax_settings_read\",\n        TaxSettingsWrite: \"tax_settings_write\",\n        TaxCalculationsAndTransactionsRead: \"tax_calculations_and_transactions_read\",\n        TaxCalculationsAndTransactionsWrite: \"tax_calculations_and_transactions_write\",\n        TokenRead: \"token_read\",\n        TokenWrite: \"token_write\",\n        TopUpRead: \"top_up_read\",\n        TopUpWrite: \"top_up_write\",\n        TransferRead: \"transfer_read\",\n        TransferWrite: \"transfer_write\",\n        UsageRecordRead: \"usage_record_read\",\n        UsageRecordWrite: \"usage_record_write\",\n        UserEmailRead: \"user_email_read\",\n        WebhookRead: \"webhook_read\",\n        WebhookWrite: \"webhook_write\",\n        IssuingCardSensitiveRead: \"issuing_card_sensitive_read\",\n        FundingInstructionRead: \"funding_instruction_read\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"connected_account_read\": ConnectedAccountRead,\n        \"account_link_write\": AccountLinkWrite,\n        \"apple_pay_domain_read\": ApplePayDomainRead,\n        \"apple_pay_domain_write\": ApplePayDomainWrite,\n        \"application_fee_read\": ApplicationFeeRead,\n        \"application_fee_write\": ApplicationFeeWrite,\n        \"balance_read\": BalanceRead,\n        \"balance_transaction_source_read\": BalanceTransactionSourceRead,\n        \"billing_clock_read\": BillingClockRead,\n        \"billing_clock_write\": BillingClockWrite,\n        \"charge_read\": ChargeRead,\n        \"charge_write\": ChargeWrite,\n        \"checkout_session_read\": CheckoutSessionRead,\n        \"checkout_session_write\": CheckoutSessionWrite,\n        \"terminal_configuration_read\": TerminalConfigurationRead,\n        \"terminal_configuration_write\": TerminalConfigurationWrite,\n        \"terminal_connection_token_write\": TerminalConnectionTokenWrite,\n        \"coupon_read\": CouponRead,\n        \"coupon_write\": CouponWrite,\n        \"credit_note_read\": CreditNoteRead,\n        \"credit_note_write\": CreditNoteWrite,\n        \"customer_portal_read\": CustomerPortalRead,\n        \"customer_portal_write\": CustomerPortalWrite,\n        \"customer_read\": CustomerRead,\n        \"customer_write\": CustomerWrite,\n        \"dispute_read\": DisputeRead,\n        \"dispute_write\": DisputeWrite,\n        \"edit_link_write\": EditLinkWrite,\n        \"elements_write\": ElementsWrite,\n        \"event_read\": EventRead,\n        \"file_read\": FileRead,\n        \"file_write\": FileWrite,\n        \"invoice_read\": InvoiceRead,\n        \"invoice_write\": InvoiceWrite,\n        \"issuing_authorization_read\": IssuingAuthorizationRead,\n        \"issuing_authorization_write\": IssuingAuthorizationWrite,\n        \"issuing_card_read\": IssuingCardRead,\n        \"issuing_card_write\": IssuingCardWrite,\n        \"issuing_cardholder_read\": IssuingCardholderRead,\n        \"issuing_cardholder_write\": IssuingCardholderWrite,\n        \"issuing_dispute_read\": IssuingDisputeRead,\n        \"issuing_dispute_write\": IssuingDisputeWrite,\n        \"issuing_transaction_read\": IssuingTransactionRead,\n        \"issuing_transaction_write\": IssuingTransactionWrite,\n        \"terminal_location_read\": TerminalLocationRead,\n        \"terminal_location_write\": TerminalLocationWrite,\n        \"mandate_read\": MandateRead,\n        \"mandate_write\": MandateWrite,\n        \"order_read\": OrderRead,\n        \"order_write\": OrderWrite,\n        \"payment_intent_read\": PaymentIntentRead,\n        \"payment_intent_write\": PaymentIntentWrite,\n        \"payment_links_read\": PaymentLinksRead,\n        \"payment_links_write\": PaymentLinksWrite,\n        \"payment_method_read\": PaymentMethodRead,\n        \"payment_method_write\": PaymentMethodWrite,\n        \"payout_read\": PayoutRead,\n        \"payout_write\": PayoutWrite,\n        \"plan_read\": PlanRead,\n        \"plan_write\": PlanWrite,\n        \"product_read\": ProductRead,\n        \"product_write\": ProductWrite,\n        \"promotion_code_read\": PromotionCodeRead,\n        \"promotion_code_write\": PromotionCodeWrite,\n        \"quote_read\": QuoteRead,\n        \"quote_write\": QuoteWrite,\n        \"terminal_reader_read\": TerminalReaderRead,\n        \"terminal_reader_write\": TerminalReaderWrite,\n        \"report_runs_and_report_types_read\": ReportRunsAndReportTypesRead,\n        \"review_read\": ReviewRead,\n        \"review_write\": ReviewWrite,\n        \"secret_write\": SecretWrite,\n        \"setup_intent_read\": SetupIntentRead,\n        \"setup_intent_write\": SetupIntentWrite,\n        \"shipping_rate_read\": ShippingRateRead,\n        \"shipping_rate_write\": ShippingRateWrite,\n        \"sku_read\": SkuRead,\n        \"sku_write\": SkuWrite,\n        \"source_read\": SourceRead,\n        \"source_write\": SourceWrite,\n        \"subscription_read\": SubscriptionRead,\n        \"subscription_write\": SubscriptionWrite,\n        \"tax_rate_read\": TaxRateRead,\n        \"tax_rate_write\": TaxRateWrite,\n        \"tax_settings_read\": TaxSettingsRead,\n        \"tax_settings_write\": TaxSettingsWrite,\n        \"tax_calculations_and_transactions_read\": TaxCalculationsAndTransactionsRead,\n        \"tax_calculations_and_transactions_write\": TaxCalculationsAndTransactionsWrite,\n        \"token_read\": TokenRead,\n        \"token_write\": TokenWrite,\n        \"top_up_read\": TopUpRead,\n        \"top_up_write\": TopUpWrite,\n        \"transfer_read\": TransferRead,\n        \"transfer_write\": TransferWrite,\n        \"usage_record_read\": UsageRecordRead,\n        \"usage_record_write\": UsageRecordWrite,\n        \"user_email_read\": UserEmailRead,\n        \"webhook_read\": WebhookRead,\n        \"webhook_write\": WebhookWrite,\n        \"issuing_card_sensitive_read\": IssuingCardSensitiveRead,\n        \"funding_instruction_read\": FundingInstructionRead,\n    }\n\n    PermissionIDs = map[Permission]int{\n        ConnectedAccountRead: 1,\n        AccountLinkWrite: 2,\n        ApplePayDomainRead: 3,\n        ApplePayDomainWrite: 4,\n        ApplicationFeeRead: 5,\n        ApplicationFeeWrite: 6,\n        BalanceRead: 7,\n        BalanceTransactionSourceRead: 8,\n        BillingClockRead: 9,\n        BillingClockWrite: 10,\n        ChargeRead: 11,\n        ChargeWrite: 12,\n        CheckoutSessionRead: 13,\n        CheckoutSessionWrite: 14,\n        TerminalConfigurationRead: 15,\n        TerminalConfigurationWrite: 16,\n        TerminalConnectionTokenWrite: 17,\n        CouponRead: 18,\n        CouponWrite: 19,\n        CreditNoteRead: 20,\n        CreditNoteWrite: 21,\n        CustomerPortalRead: 22,\n        CustomerPortalWrite: 23,\n        CustomerRead: 24,\n        CustomerWrite: 25,\n        DisputeRead: 26,\n        DisputeWrite: 27,\n        EditLinkWrite: 28,\n        ElementsWrite: 29,\n        EventRead: 30,\n        FileRead: 31,\n        FileWrite: 32,\n        InvoiceRead: 33,\n        InvoiceWrite: 34,\n        IssuingAuthorizationRead: 35,\n        IssuingAuthorizationWrite: 36,\n        IssuingCardRead: 37,\n        IssuingCardWrite: 38,\n        IssuingCardholderRead: 39,\n        IssuingCardholderWrite: 40,\n        IssuingDisputeRead: 41,\n        IssuingDisputeWrite: 42,\n        IssuingTransactionRead: 43,\n        IssuingTransactionWrite: 44,\n        TerminalLocationRead: 45,\n        TerminalLocationWrite: 46,\n        MandateRead: 47,\n        MandateWrite: 48,\n        OrderRead: 49,\n        OrderWrite: 50,\n        PaymentIntentRead: 51,\n        PaymentIntentWrite: 52,\n        PaymentLinksRead: 53,\n        PaymentLinksWrite: 54,\n        PaymentMethodRead: 55,\n        PaymentMethodWrite: 56,\n        PayoutRead: 57,\n        PayoutWrite: 58,\n        PlanRead: 59,\n        PlanWrite: 60,\n        ProductRead: 61,\n        ProductWrite: 62,\n        PromotionCodeRead: 63,\n        PromotionCodeWrite: 64,\n        QuoteRead: 65,\n        QuoteWrite: 66,\n        TerminalReaderRead: 67,\n        TerminalReaderWrite: 68,\n        ReportRunsAndReportTypesRead: 69,\n        ReviewRead: 70,\n        ReviewWrite: 71,\n        SecretWrite: 72,\n        SetupIntentRead: 73,\n        SetupIntentWrite: 74,\n        ShippingRateRead: 75,\n        ShippingRateWrite: 76,\n        SkuRead: 77,\n        SkuWrite: 78,\n        SourceRead: 79,\n        SourceWrite: 80,\n        SubscriptionRead: 81,\n        SubscriptionWrite: 82,\n        TaxRateRead: 83,\n        TaxRateWrite: 84,\n        TaxSettingsRead: 85,\n        TaxSettingsWrite: 86,\n        TaxCalculationsAndTransactionsRead: 87,\n        TaxCalculationsAndTransactionsWrite: 88,\n        TokenRead: 89,\n        TokenWrite: 90,\n        TopUpRead: 91,\n        TopUpWrite: 92,\n        TransferRead: 93,\n        TransferWrite: 94,\n        UsageRecordRead: 95,\n        UsageRecordWrite: 96,\n        UserEmailRead: 97,\n        WebhookRead: 98,\n        WebhookWrite: 99,\n        IssuingCardSensitiveRead: 100,\n        FundingInstructionRead: 101,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: ConnectedAccountRead,\n        2: AccountLinkWrite,\n        3: ApplePayDomainRead,\n        4: ApplePayDomainWrite,\n        5: ApplicationFeeRead,\n        6: ApplicationFeeWrite,\n        7: BalanceRead,\n        8: BalanceTransactionSourceRead,\n        9: BillingClockRead,\n        10: BillingClockWrite,\n        11: ChargeRead,\n        12: ChargeWrite,\n        13: CheckoutSessionRead,\n        14: CheckoutSessionWrite,\n        15: TerminalConfigurationRead,\n        16: TerminalConfigurationWrite,\n        17: TerminalConnectionTokenWrite,\n        18: CouponRead,\n        19: CouponWrite,\n        20: CreditNoteRead,\n        21: CreditNoteWrite,\n        22: CustomerPortalRead,\n        23: CustomerPortalWrite,\n        24: CustomerRead,\n        25: CustomerWrite,\n        26: DisputeRead,\n        27: DisputeWrite,\n        28: EditLinkWrite,\n        29: ElementsWrite,\n        30: EventRead,\n        31: FileRead,\n        32: FileWrite,\n        33: InvoiceRead,\n        34: InvoiceWrite,\n        35: IssuingAuthorizationRead,\n        36: IssuingAuthorizationWrite,\n        37: IssuingCardRead,\n        38: IssuingCardWrite,\n        39: IssuingCardholderRead,\n        40: IssuingCardholderWrite,\n        41: IssuingDisputeRead,\n        42: IssuingDisputeWrite,\n        43: IssuingTransactionRead,\n        44: IssuingTransactionWrite,\n        45: TerminalLocationRead,\n        46: TerminalLocationWrite,\n        47: MandateRead,\n        48: MandateWrite,\n        49: OrderRead,\n        50: OrderWrite,\n        51: PaymentIntentRead,\n        52: PaymentIntentWrite,\n        53: PaymentLinksRead,\n        54: PaymentLinksWrite,\n        55: PaymentMethodRead,\n        56: PaymentMethodWrite,\n        57: PayoutRead,\n        58: PayoutWrite,\n        59: PlanRead,\n        60: PlanWrite,\n        61: ProductRead,\n        62: ProductWrite,\n        63: PromotionCodeRead,\n        64: PromotionCodeWrite,\n        65: QuoteRead,\n        66: QuoteWrite,\n        67: TerminalReaderRead,\n        68: TerminalReaderWrite,\n        69: ReportRunsAndReportTypesRead,\n        70: ReviewRead,\n        71: ReviewWrite,\n        72: SecretWrite,\n        73: SetupIntentRead,\n        74: SetupIntentWrite,\n        75: ShippingRateRead,\n        76: ShippingRateWrite,\n        77: SkuRead,\n        78: SkuWrite,\n        79: SourceRead,\n        80: SourceWrite,\n        81: SubscriptionRead,\n        82: SubscriptionWrite,\n        83: TaxRateRead,\n        84: TaxRateWrite,\n        85: TaxSettingsRead,\n        86: TaxSettingsWrite,\n        87: TaxCalculationsAndTransactionsRead,\n        88: TaxCalculationsAndTransactionsWrite,\n        89: TokenRead,\n        90: TokenWrite,\n        91: TopUpRead,\n        92: TopUpWrite,\n        93: TransferRead,\n        94: TransferWrite,\n        95: UsageRecordRead,\n        96: UsageRecordWrite,\n        97: UserEmailRead,\n        98: WebhookRead,\n        99: WebhookWrite,\n        100: IssuingCardSensitiveRead,\n        101: FundingInstructionRead,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/stripe/permissions.yaml",
    "content": "permissions:\n  - connected_account_read\n  - account_link_write\n  - apple_pay_domain_read\n  - apple_pay_domain_write\n  - application_fee_read\n  - application_fee_write\n  - balance_read\n  - balance_transaction_source_read\n  - billing_clock_read\n  - billing_clock_write\n  - charge_read\n  - charge_write\n  - checkout_session_read\n  - checkout_session_write\n  - terminal_configuration_read\n  - terminal_configuration_write\n  - terminal_connection_token_write\n  - coupon_read\n  - coupon_write\n  - credit_note_read\n  - credit_note_write\n  - customer_portal_read\n  - customer_portal_write\n  - customer_read\n  - customer_write\n  - dispute_read\n  - dispute_write\n  - edit_link_write\n  - elements_write\n  - event_read\n  - file_read\n  - file_write\n  - invoice_read\n  - invoice_write\n  - issuing_authorization_read\n  - issuing_authorization_write\n  - issuing_card_read\n  - issuing_card_write\n  - issuing_cardholder_read\n  - issuing_cardholder_write\n  - issuing_dispute_read\n  - issuing_dispute_write\n  - issuing_transaction_read\n  - issuing_transaction_write\n  - terminal_location_read\n  - terminal_location_write\n  - mandate_read\n  - mandate_write\n  - order_read\n  - order_write\n  - payment_intent_read\n  - payment_intent_write\n  - payment_links_read\n  - payment_links_write\n  - payment_method_read\n  - payment_method_write\n  - payout_read\n  - payout_write\n  - plan_read\n  - plan_write\n  - product_read\n  - product_write\n  - promotion_code_read\n  - promotion_code_write\n  - quote_read\n  - quote_write\n  - terminal_reader_read\n  - terminal_reader_write\n  - report_runs_and_report_types_read\n  - review_read\n  - review_write\n  - secret_write\n  - setup_intent_read\n  - setup_intent_write\n  - shipping_rate_read\n  - shipping_rate_write\n  - sku_read\n  - sku_write\n  - source_read\n  - source_write\n  - subscription_read\n  - subscription_write\n  - tax_rate_read\n  - tax_rate_write\n  - tax_settings_read\n  - tax_settings_write\n  - tax_calculations_and_transactions_read\n  - tax_calculations_and_transactions_write\n  - token_read\n  - token_write\n  - top_up_read\n  - top_up_write\n  - transfer_read\n  - transfer_write\n  - usage_record_read\n  - usage_record_write\n  - user_email_read\n  - webhook_read\n  - webhook_write\n  - issuing_card_sensitive_read\n  - funding_instruction_read\n"
  },
  {
    "path": "pkg/analyzer/analyzers/stripe/restricted.yaml",
    "content": "categories:\n  Core:\n    Apple Pay Domains:\n      Read:\n        Scope: rak_apple_pay_domain_read\n        Endpoint: https://api.stripe.com/v1/apple_pay/domains\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://any-api.com/stripe_com/stripe_com/docs/_v1_apple_pay_domains/GetApplePayDomains\n        Note: ''\n      Write:\n        Scope: rak_apple_pay_domain_write\n        Endpoint: https://api.stripe.com/v1/apple_pay/domains\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: ''\n        Note: ''\n    Balance:\n      Read:\n        Scope: rak_balance_read\n        Endpoint: https://api.stripe.com/v1/balance\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/balance\n        Note: ''\n    Balance transaction sources:\n      Read:\n        Scope: ''\n        Endpoint: ''\n        Method: ''\n        Payload: ''\n        Valid: []\n        Invalid: []\n        Docs: Can't find a relevant endpoint\n        Note: 'I think we just build this one based off of all the others? Note that\n          this permission also implies the following permissions: Application Fees\n          (Read), Balance (Read), Financing Transactions (Read), Payouts (Read), Transfers\n          (Read), and Balance Transfers (Read)'\n    Balance Transfer:\n      Read:\n        Scope: ''\n        Endpoint: ''\n        Method: ''\n        Payload: ''\n        Valid: []\n        Invalid: []\n        Docs: Can't find a relevant endpoint\n        Note: Not sure this exists anymore\n      Write:\n        Scope: ''\n        Endpoint: ''\n        Method: ''\n        Payload: ''\n        Valid: []\n        Invalid: []\n        Docs: Can't find a relevant endpoint\n        Note: Not sure this exists anymore\n    Test clocks:\n      Read:\n        Scope: rak_billing_clock_read\n        Endpoint: https://api.stripe.com/v1/test_helpers/test_clocks\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/test_clocks/list\n        Note: ''\n      Write:\n        Scope: rak_billing_clock_write\n        Endpoint: https://api.stripe.com/v1/test_helpers/test_clocks\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/test_clocks/create\n        Note: ''\n    Charges:\n      Read:\n        Scope: rak_charge_read\n        Endpoint: https://api.stripe.com/v1/charges\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/charges/list\n        Note: ''\n      Write:\n        Scope: rak_charge_write\n        Endpoint: https://api.stripe.com/v1/charges\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/charges/update\n        Note: ''\n    Confirmation token:\n      Read:\n        Scope: rak_confirmation_token_read\n        Endpoint: https://api.stripe.com/v1/confirmation_tokens/nowaythiscanexist\n        Method: GET\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/confirmation_tokens/retrieve\n        Note: ''\n    Confirmation token (client):\n      Read:\n        Scope: ''\n        Endpoint: ''\n        Method: ''\n        Payload: ''\n        Valid: []\n        Invalid: []\n        Docs: Can't find a relevant endpoint\n        Note: Not sure this exists anymore\n      Write:\n        Scope: rak_confirmation_token_client_write\n        Endpoint: https://api.stripe.com/v1/test_helpers/confirmation_tokens\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/confirmation_tokens/test_create\n        Note: ''\n    Customers:\n      Read:\n        Scope: rak_customer_read\n        Endpoint: https://api.stripe.com/v1/customers\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/customers/list\n        Note: ''\n      Write:\n        Scope: rak_customer_write\n        Endpoint: https://api.stripe.com/v1/customers/nowaythiscanexist\n        Method: POST\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/customers/update\n        Note: Couldn't use \"Create Customer\", b/c default with no payload creates\n          a customer.\n    Customer session:\n      Read:\n        Scope: ''\n        Endpoint: ''\n        Method: ''\n        Payload: ''\n        Valid: []\n        Invalid: []\n        Docs: Can't find a relevant endpoint\n        Note: Not sure this exists anymore\n      Write:\n        Scope: rak_customer_session_write\n        Endpoint: https://api.stripe.com/v1/customer_sessions\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/customer_sessions/create\n        Note: ''\n    Disputes:\n      Read:\n        Scope: rak_dispute_read\n        Endpoint: https://api.stripe.com/v1/disputes\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/disputes/list\n        Note: ''\n      Write:\n        Scope: rak_dispute_write\n        Endpoint: https://api.stripe.com/v1/disputes/nowaycanthisexist\n        Method: POST\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/disputes/update\n        Note: ''\n    Events:\n      Read:\n        Scope: rak_event_read\n        Endpoint: https://api.stripe.com/v1/events\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/events/list\n        Note: ''\n    Ephemeral keys:\n      Read:\n        Scope: ''\n        Endpoint: ''\n        Method: ''\n        Payload: ''\n        Valid: []\n        Invalid: []\n        Docs: Can't find a relevant endpoint\n        Note: ''\n      Write:\n        Scope: rak_ephemeral_key_write\n        Endpoint: https://api.stripe.com/v1/ephemeral_keys\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://any-api.com/stripe_com/stripe_com/docs/_v1_ephemeral_keys_key_\n        Note: ''\n    Files:\n      Read:\n        Scope: rak_file_read\n        Endpoint: https://api.stripe.com/v1/files\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: ''\n        Note: ''\n      Write:\n        Scope: ''\n        Endpoint: https://files.stripe.com/v1/files\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: ''\n        Note: On 403, it mistakenly says \"rak_dispute_write\" missing\n    Funding Instructions:\n      Read:\n        Scope: ''\n        Endpoint: https://api.stripe.com/v1/issuing/funding_instructions\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/issuing/funding_instructions/list\n        Note: On 403, it mistakently says \"rak_topup_read\"\n      Write:\n        Scope: ''\n        Endpoint: https://api.stripe.com/v1/issuing/funding_instructions\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/issuing/funding_instructions/create\n        Note: Same as read but says \"write\"\n    PaymentIntents:\n      Read:\n        Scope: rak_payment_intent_read\n        Endpoint: https://api.stripe.com/v1/payment_intents\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/payment_intents/list\n        Note: ''\n      Write:\n        Scope: rak_payment_intent_write\n        Endpoint: https://api.stripe.com/v1/payment_intents\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/payment_intents/create\n        Note: ''\n    PaymentMethods:\n      Read:\n        Scope: rak_payment_method_read\n        Endpoint: https://api.stripe.com/v1/payment_methods\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://any-api.com/stripe_com/stripe_com/docs/_v1_payment_methods/GetPaymentMethods\n        Note: ''\n      Write:\n        Scope: rak_payment_method_write\n        Endpoint: https://api.stripe.com/v1/payment_methods/nowaycanthisexist\n        Method: POST\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://any-api.com/stripe_com/stripe_com/docs/_v1_payment_methods_payment_method_/PostPaymentMethodsPaymentMethod\n        Note: ''\n    Payment Method Domains:\n      Read:\n        Scope: ''\n        Endpoint: https://api.stripe.com/v1/payment_method_domains\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/payment_method_domains/list\n        Note: ''\n      Write:\n        Scope: ''\n        Endpoint: https://api.stripe.com/v1/payment_method_domains\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/payment_method_domains/create\n        Note: ''\n    Payouts:\n      Read:\n        Scope: rak_payout_read\n        Endpoint: https://api.stripe.com/v1/payouts\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/payouts/list\n        Note: ''\n      Write:\n        Scope: rak_payout_write\n        Endpoint: https://api.stripe.com/v1/payouts\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/payouts/create\n        Note: ''\n    Products:\n      Read:\n        Scope: rak_product_read\n        Endpoint: https://api.stripe.com/v1/products\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/products/list\n        Note: ''\n      Write:\n        Scope: rak_product_write\n        Endpoint: https://api.stripe.com/v1/products\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/products/create\n        Note: ''\n    Shipping Rates:\n      Read:\n        Scope: rak_shipping_rate_read\n        Endpoint: https://api.stripe.com/v1/shipping_rates\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/shipping_rates/list\n        Note: ''\n      Write:\n        Scope: rak_shipping_rate_write\n        Endpoint: https://api.stripe.com/v1/shipping_rates\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/shipping_rates/create\n        Note: ''\n    SetupIntents:\n      Read:\n        Scope: rak_setup_intent_read\n        Endpoint: https://api.stripe.com/v1/setup_intents\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/setup_intents/list\n        Note: ''\n      Write:\n        Scope: rak_setup_intent_write\n        Endpoint: https://api.stripe.com/v1/setup_intents/nowaycanthisexist\n        Method: POST\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/setup_intents/create\n        Note: ''\n    Sources:\n      Read:\n        Scope: rak_source_read\n        Endpoint: https://api.stripe.com/v1/sources/nowaycanthisexist\n        Method: GET\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/sources/retrieve\n        Note: ''\n      Write:\n        Scope: rak_source_write\n        Endpoint: https://api.stripe.com/v1/sources/nowaycanthisexist\n        Method: POST\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/sources/update\n        Note: ''\n    Tokens:\n      Read:\n        Scope: rak_token_read\n        Endpoint: https://api.stripe.com/v1/tokens/nowaycanthisexist\n        Method: GET\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/tokens/retrieve\n        Note: ''\n      Write:\n        Scope: rak_token_write\n        Endpoint: https://api.stripe.com/v1/tokens\n        Method: POST\n        Payload: '\"card[number]\"=4242424242424242'\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/tokens/create_card\n        Note: ''\n  Checkout:\n    Checkout Sessions:\n      Read:\n        Scope: rak_checkout_session_read\n        Endpoint: https://api.stripe.com/v1/checkout/sessions\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/checkout/sessions/list\n        Note: ''\n      Write:\n        Scope: rak_checkout_session_write\n        Endpoint: https://api.stripe.com/v1/checkout/sessions\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/checkout/sessions/create\n        Note: ''\n  Billing:\n    Coupons:\n      Read:\n        Scope: rak_coupon_read\n        Endpoint: https://api.stripe.com/v1/coupons\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/coupons/list\n        Note: ''\n      Write:\n        Scope: rak_coupon_write\n        Endpoint: https://api.stripe.com/v1/coupons\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/coupons/create\n        Note: ''\n    Promotion Codes:\n      Read:\n        Scope: rak_promotion_code_read\n        Endpoint: https://api.stripe.com/v1/promotion_codes\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/promotion_codes/list\n        Note: ''\n      Write:\n        Scope: rak_promotion_code_write\n        Endpoint: https://api.stripe.com/v1/promotion_codes\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/promotion_codes/create\n        Note: ''\n    Credit notes:\n      Read:\n        Scope: rak_credit_note_read\n        Endpoint: https://api.stripe.com/v1/credit_notes\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/credit_notes/list\n        Note: ''\n      Write:\n        Scope: rak_credit_note_write\n        Endpoint: https://api.stripe.com/v1/credit_notes/nowaythiscanexsit\n        Method: POST\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/credit_notes/update\n        Note: ''\n    Customer portal:\n      Read:\n        Scope: ''\n        Endpoint: ''\n        Method: ''\n        Payload: ''\n        Valid: []\n        Invalid: []\n        Docs: Can't find a relevant endpoint\n        Note: ''\n      Write:\n        Scope: rak_customer_portal_write\n        Endpoint: https://api.stripe.com/v1/billing_portal/sessions\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/customer_portal/sessions/create\n        Note: ''\n    Invoices:\n      Read:\n        Scope: ''\n        Endpoint: https://api.stripe.com/v1/invoices\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/invoices/list\n        Note: Wrong scope in error message.\n      Write:\n        Scope: rak_invoice_write\n        Endpoint: https://api.stripe.com/v1/invoices\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/invoices/create\n        Note: ''\n    Prices:\n      Read:\n        Scope: rak_plan_read\n        Endpoint: https://api.stripe.com/v1/prices\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/prices/list\n        Note: ''\n      Write:\n        Scope: rak_plan_write\n        Endpoint: https://api.stripe.com/v1/prices\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/prices/create\n        Note: ''\n    Subscriptions:\n      Read:\n        Scope: rak_subscription_read\n        Endpoint: https://api.stripe.com/v1/subscriptions\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/subscriptions/list\n        Note: ''\n      Write:\n        Scope: rak_subscription_write\n        Endpoint: https://api.stripe.com/v1/subscriptions\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/subscriptions/create\n        Note: ''\n    Quote:\n      Read:\n        Scope: rak_quote_read\n        Endpoint: https://api.stripe.com/v1/quotes\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/quotes/list\n        Note: ''\n      Write:\n        Scope: rak_quote_write\n        Endpoint: https://api.stripe.com/v1/quotes/nowaythiscanexist\n        Method: POST\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/quotes/update\n        Note: ''\n    Tax IDs:\n      Read:\n        Scope: rak_tax_id_read\n        Endpoint: https://api.stripe.com/v1/tax_ids\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/tax_ids/list\n        Note: ''\n      Write:\n        Scope: rak_tax_id_write\n        Endpoint: https://api.stripe.com/v1/tax_ids\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/tax_ids/create\n        Note: ''\n    Tax Rates:\n      Read:\n        Scope: rak_tax_rate_read\n        Endpoint: https://api.stripe.com/v1/tax_rates\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/tax_rates/list\n        Note: ''\n      Write:\n        Scope: rak_tax_rate_write\n        Endpoint: https://api.stripe.com/v1/tax_rates\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/tax_rates/create\n        Note: ''\n    Usage Records:\n      Read:\n        Scope: rak_usage_record_read\n        Endpoint: https://api.stripe.com/v1/subscription_items/nowaythiscanexist/usage_record_summaries\n        Method: GET\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/usage_records/subscription_item_summary_list\n        Note: ''\n      Write:\n        Scope: rak_usage_record_write\n        Endpoint: https://api.stripe.com/v1/subscription_items/nowaythiscanexist/usage_records\n        Method: POST\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/usage_records/create\n        Note: ''\n    Meters:\n      Read:\n        Scope: rak_billing_meter_read\n        Endpoint: https://api.stripe.com/v1/billing/meters\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/billing/meter/list\n        Note: ''\n      Write:\n        Scope: rak_billing_meter_write\n        Endpoint: https://api.stripe.com/v1/billing/meters\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/billing/meter/create\n        Note: ''\n    Meter Events:\n      Read:\n        Scope: rak_billing_meter_event_read\n        Endpoint: https://api.stripe.com/v1/billing/meters/nowaythiscanexist/event_summaries\n        Method: GET\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/billing/meter-event_summary/list\n        Note: ''\n      Write:\n        Scope: rak_billing_meter_event_write\n        Endpoint: https://api.stripe.com/v1/billing/meter_events\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/billing/meter-event/create\n        Note: ''\n    Meter Event Adjustments:\n      Write:\n        Scope: rak_billing_meter_event_adjustment_write\n        Endpoint: https://api.stripe.com/v1/billing/meter_event_adjustments\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/billing/meter-event_adjustment/create\n        Note: ''\n  Connect:\n    Application Fees:\n      Read:\n        Scope: rak_application_fee_read\n        Endpoint: https://api.stripe.com/v1/application_fees\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/application_fees/list\n        Note: ''\n      Write:\n        Scope: rak_application_fee_write\n        Endpoint: https://api.stripe.com/v1/application_fees/nowaythiscanexist/refunds\n        Method: POST\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/fee_refunds/create\n        Note: ''\n    Login Links:\n      Write:\n        Scope: rak_edit_link_write\n        Endpoint: https://api.stripe.com/v1/account/login_links\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://any-api.com/stripe_com/stripe_com/docs/_v1_account_login_links/PostAccountLoginLinks\n        Note: ''\n    Account Links:\n      Write:\n        Scope: rak_account_link_write\n        Endpoint: https://api.stripe.com/v1/account_links\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/account_links\n        Note: ''\n    Top-ups:\n      Read:\n        Scope: rak_topup_read\n        Endpoint: https://api.stripe.com/v1/topups\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/topups/list\n        Note: ''\n      Write:\n        Scope: rak_topup_write\n        Endpoint: https://api.stripe.com/v1/topups\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/topups/create\n        Note: ''\n    Transfers:\n      Read:\n        Scope: rak_transfer_read\n        Endpoint: https://api.stripe.com/v1/transfers\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/transfers/list\n        Note: ''\n      Write:\n        Scope: rak_transfer_write\n        Endpoint: https://api.stripe.com/v1/transfers\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/transfers/create\n        Note: ''\n  Orders:\n    Orders:\n      Read:\n        Scope: rak_order_read\n        Endpoint: https://api.stripe.com/v1/orders\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://any-api.com/stripe_com/stripe_com/docs/_v1_orders/GetOrders\n        Note: ''\n      Write:\n        Scope: rak_order_write\n        Endpoint: https://api.stripe.com/v1/orders\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://any-api.com/stripe_com/stripe_com/docs/_v1_orders/PostOrders\n        Note: ''\n    SKUs:\n      Read:\n        Scope: ''\n        Endpoint: ''\n        Method: ''\n        Payload: ''\n        Valid: []\n        Invalid: []\n        Docs: Can't find a relevant endpoint\n        Note: Seems like any key has 200 over these.\n      Write:\n        Scope: rak_sku_write\n        Endpoint: https://api.stripe.com/v1/skus\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://any-api.com/stripe_com/stripe_com/docs/_v1_skus/PostSkus\n        Note: ''\n  Issuing:\n    Authorizations:\n      Read:\n        Scope: rak_issuing_authorization_read\n        Endpoint: https://api.stripe.com/v1/issuing/authorizations/nowaythiscanexist\n        Method: GET\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/issuing/authorizations/retrieve\n        Note: ''\n      Write:\n        Scope: rak_issuing_authorization_write\n        Endpoint: https://api.stripe.com/v1/issuing/authorizations/nowaythiscanexist\n        Method: POST\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/issuing/authorizations/update\n        Note: ''\n    Cardholders:\n      Read:\n        Scope: rak_issuing_cardholder_read\n        Endpoint: https://api.stripe.com/v1/issuing/cardholders/nowaythiscanexist\n        Method: GET\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/issuing/cardholders/retrieve\n        Note: ''\n      Write:\n        Scope: rak_issuing_cardholder_write\n        Endpoint: https://api.stripe.com/v1/issuing/cardholders\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/issuing/cardholders/create\n        Note: ''\n    Cards:\n      Read:\n        Scope: rak_issuing_card_read\n        Endpoint: https://api.stripe.com/v1/issuing/cards/nowaythiscanexist\n        Method: GET\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/issuing/cards/retrieve\n        Note: ''\n      Write:\n        Scope: rak_issuing_card_write\n        Endpoint: https://api.stripe.com/v1/issuing/cards\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/issuing/cards/create\n        Note: ''\n    Disputes:\n      Read:\n        Scope: rak_issuing_dispute_read\n        Endpoint: https://api.stripe.com/v1/issuing/disputes/nowaythiscanexist\n        Method: GET\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/issuing/disputes/retrieve\n        Note: ''\n      Write:\n        Scope: rak_issuing_dispute_write\n        Endpoint: https://api.stripe.com/v1/issuing/disputes/nowaythiscanexist\n        Method: POST\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/issuing/disputes/update\n        Note: ''\n    Tokens:\n      Read:\n        Scope: rak_issuing_network_token_read\n        Endpoint: https://api.stripe.com/v1/issuing/tokens/nowaythiscanexist\n        Method: GET\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/issuing/tokens/retrieve\n        Note: ''\n      Write:\n        Scope: rak_issuing_network_token_write\n        Endpoint: https://api.stripe.com/v1/issuing/tokens/nowaythiscanexist\n        Method: POST\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/issuing/tokens/update\n        Note: ''\n    Token Network Data:\n      Read:\n        Scope: ''\n        Endpoint: ''\n        Method: ''\n        Payload: ''\n        Valid: []\n        Invalid: []\n        Docs: Can't find a relevant endpoint\n        Note: ''\n    Transactions:\n      Read:\n        Scope: rak_issuing_transaction_read\n        Endpoint: https://api.stripe.com/v1/issuing/transactions/nowaythiscanexist\n        Method: GET\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/issuing/transactions/retrieve\n        Note: ''\n      Write:\n        Scope: rak_issuing_transaction_write\n        Endpoint: https://api.stripe.com/v1/issuing/transactions/nowaythiscanexist\n        Method: POST\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/issuing/transactions/update\n        Note: ''\n  Reporting:\n    Report Runs and Report Types:\n      Read:\n        Scope: rak_financial_statement_read\n        Endpoint: https://api.stripe.com/v1/reporting/report_runs\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/reporting/report_run/list\n        Note: ''\n  Identity:\n    Verification Sessions and Reports:\n      Read:\n        Scope: rak_identity_product_read\n        Endpoint: https://api.stripe.com/v1/identity/verification_sessions\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/identity/verification_sessions/list\n        Note: ''\n      Write:\n        Scope: rak_identity_product_write\n        Endpoint: https://api.stripe.com/v1/identity/verification_sessions\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/identity/verification_sessions/create\n        Note: ''\n    Access recent detailed verification results:\n      Read:\n        Scope: ''\n        Endpoint: ''\n        Method: ''\n        Payload: ''\n        Valid: []\n        Invalid: []\n        Docs: Can't find a relevant endpoint\n        Note: Skip for now b/c requires account with data\n    Access all detailed verification results:\n      Read:\n        Scope: ''\n        Endpoint: ''\n        Method: ''\n        Payload: ''\n        Valid: []\n        Invalid: []\n        Docs: Can't find a relevant endpoint\n        Note: Skip for now b/c requires account with data + this one requires IP allowlisting\n  Webhook:\n    Webhook Endpoints:\n      Read:\n        Scope: rak_webhook_read\n        Endpoint: https://api.stripe.com/v1/webhook_endpoints\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/webhook_endpoints/list\n        Note: ''\n      Write:\n        Scope: rak_webhook_write\n        Endpoint: https://api.stripe.com/v1/webhook_endpoints\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/webhook_endpoints/create\n        Note: ''\n  Stripe CLI:\n    Debugging tools:\n      Write:\n        Scope: ''\n        Endpoint: ''\n        Method: ''\n        Payload: ''\n        Valid: []\n        Invalid: []\n        Docs: Can't find a relevant endpoint\n        Note: ''\n  Payment Links:\n    Payment Links:\n      Read:\n        Scope: rak_payment_links_read\n        Endpoint: https://api.stripe.com/v1/payment_links\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/payment_links/payment_links/list\n        Note: ''\n      Write:\n        Scope: rak_payment_links_write\n        Endpoint: https://api.stripe.com/v1/payment_links\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/payment_links/payment_links/create\n        Note: ''\n  Terminal:\n    Configurations:\n      Read:\n        Scope: rak_terminal_configuration_read\n        Endpoint: https://api.stripe.com/v1/terminal/configurations\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/terminal/configuration/list\n        Note: ''\n      Write:\n        Scope: rak_terminal_configuration_write\n        Endpoint: https://api.stripe.com/v1/terminal/configurations/nowaythiscanexist\n        Method: POST\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/terminal/configuration/update\n        Note: ''\n    Locations:\n      Read:\n        Scope: rak_terminal_location_read\n        Endpoint: https://api.stripe.com/v1/terminal/locations\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/terminal/locations/list\n        Note: ''\n      Write:\n        Scope: rak_terminal_location_write\n        Endpoint: https://api.stripe.com/v1/terminal/locations\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/terminal/locations/create\n        Note: ''\n    Readers:\n      Read:\n        Scope: rak_terminal_reader_read\n        Endpoint: https://api.stripe.com/v1/terminal/readers\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/terminal/readers/list\n        Note: ''\n      Write:\n        Scope: rak_terminal_reader_write\n        Endpoint: https://api.stripe.com/v1/terminal/readers\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/terminal/readers/create\n        Note: ''\n    Connection Tokens:\n      Write:\n        Scope: rak_terminal_connection_token_write\n        Endpoint: ''\n        Method: POST\n        Payload: ''\n        Valid: []\n        Invalid: []\n        Docs: ''\n        Note: Skip b/c requires a state change.\n  Tax:\n    Tax Calculations and Transactions:\n      Read:\n        Scope: rak_tax_transaction_read\n        Endpoint: https://api.stripe.com/v1/tax/calculations/nowaycanthisexist/line_items\n        Method: GET\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/tax/calculations/line_items\n        Note: ''\n      Write:\n        Scope: rak_tax_transaction_write\n        Endpoint: https://api.stripe.com/v1/tax/calculations\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/tax/calculations/create\n        Note: ''\n    Tax Settings and Registrations:\n      Read:\n        Scope: rak_tax_settings_read\n        Endpoint: https://api.stripe.com/v1/tax/settings\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/tax/settings/retrieve\n        Note: ''\n      Write:\n        Scope: rak_tax_settings_write\n        Endpoint: https://api.stripe.com/v1/tax/registrations/nowaycanthisexist\n        Method: POST\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/tax/registrations/update\n        Note: ''\n  Radar:\n    Reviews:\n      Read:\n        Scope: rak_review_read\n        Endpoint: https://api.stripe.com/v1/reviews\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/radar/reviews/list\n        Note: ''\n      Write:\n        Scope: rak_review_write\n        Endpoint: https://api.stripe.com/v1/reviews/nowaycanthisexist/approve\n        Method: POST\n        Payload: ''\n        Valid:\n        - 404\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/radar/reviews/approve\n        Note: ''\n  Climate:\n    Climate Orders:\n      Read:\n        Scope: rak_climate_order_read\n        Endpoint: https://api.stripe.com/v1/climate/orders\n        Method: GET\n        Payload: ''\n        Valid:\n        - 200\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/climate/order/list\n        Note: ''\n      Write:\n        Scope: rak_climate_order_write\n        Endpoint: https://api.stripe.com/v1/climate/orders\n        Method: POST\n        Payload: ''\n        Valid:\n        - 400\n        Invalid:\n        - 403\n        Docs: https://docs.stripe.com/api/climate/order/create\n        Note: ''\n"
  },
  {
    "path": "pkg/analyzer/analyzers/stripe/stripe.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go stripe\n\npackage stripe\n\nimport (\n\t\"bytes\"\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/jedib0t/go-pretty/v6/table\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"gopkg.in/yaml.v2\"\n)\n\nvar _ analyzers.Analyzer = (*Analyzer)(nil)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypeStripe }\n\nfunc (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credInfo[\"key\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"key not found in credentialInfo\")\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn secretInfoToAnalyzerResult(info), nil\n}\n\nfunc secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {\n\tif info == nil {\n\t\treturn nil\n\t}\n\tresult := &analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeStripe,\n\t\tMetadata: map[string]any{\n\t\t\t\"key_type\": info.KeyType,\n\t\t\t\"key_env\":  info.KeyEnv,\n\t\t},\n\t}\n\n\t// create list of bindings using permissions, with category being the parent and unbounded resource\n\tresult.Bindings = []analyzers.Binding{}\n\tresult.UnboundedResources = []analyzers.Resource{}\n\tfor _, permissionCategory := range info.Permissions {\n\t\tparentResource := &analyzers.Resource{\n\t\t\tName:               permissionCategory.Name,\n\t\t\tFullyQualifiedName: permissionCategory.Name,\n\t\t\tType:               \"category\",\n\t\t\tMetadata:           nil,\n\t\t\tParent:             nil,\n\t\t}\n\t\tif len(permissionCategory.Permissions) == 0 {\n\t\t\tresult.UnboundedResources = append(result.UnboundedResources, *parentResource)\n\t\t} else {\n\t\t\tfor _, permission := range permissionCategory.Permissions {\n\t\t\t\tif _, ok := StringToPermission[*permission.Value]; !ok { // skip unknown scopes/permission\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tresult.Bindings = append(result.Bindings, analyzers.Binding{\n\t\t\t\t\tResource: *parentResource,\n\t\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\t\tValue: fmt.Sprintf(\"%s:%s\", permission.Name, *permission.Value),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result\n\n}\n\nconst (\n\tSECRET_PREFIX      = \"sk_\"\n\tPUBLISHABLE_PREFIX = \"pk_\"\n\tRESTRICTED_PREFIX  = \"rk_\"\n\tLIVE_PREFIX        = \"live_\"\n\tTEST_PREFIX        = \"test_\"\n\tSECRET             = \"Secret\"\n\tPUBLISHABLE        = \"Publishable\"\n\tRESTRICTED         = \"Restricted\"\n\tLIVE               = \"Live\"\n\tTEST               = \"Test\"\n)\n\n//go:embed restricted.yaml\nvar restrictedConfig []byte\n\ntype PermissionStruct struct {\n\tName  string\n\tValue *string\n}\n\ntype PermissionsCategory struct {\n\tName        string\n\tPermissions []PermissionStruct\n}\n\ntype HttpStatusTest struct {\n\tEndpoint        string      `yaml:\"Endpoint\"`\n\tMethod          string      `yaml:\"Method\"`\n\tPayload         interface{} `yaml:\"Payload\"`\n\tValidStatuses   []int       `yaml:\"Valid\"`\n\tInvalidStatuses []int       `yaml:\"Invalid\"`\n}\n\ntype Category map[string]map[string]HttpStatusTest\n\ntype Config struct {\n\tCategories map[string]Category `yaml:\"categories\"`\n}\n\ntype SecretInfo struct {\n\tKeyType     string\n\tKeyEnv      string\n\tValid       bool\n\tPermissions []PermissionsCategory\n}\n\nfunc (h *HttpStatusTest) RunTest(cfg *config.Config, headers map[string]string) (bool, error) {\n\t// If body data, marshal to JSON\n\tvar data io.Reader\n\tif h.Payload != nil {\n\t\tjsonData, err := json.Marshal(h.Payload)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tdata = bytes.NewBuffer(jsonData)\n\t}\n\n\t// Create new HTTP request\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(h.Method, h.Endpoint, data)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Add custom headers if provided\n\tfor key, value := range headers {\n\t\treq.Header.Set(key, value)\n\t}\n\n\t// Execute HTTP Request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer resp.Body.Close()\n\n\t// Check response status code\n\tswitch {\n\tcase StatusContains(resp.StatusCode, h.ValidStatuses):\n\t\treturn true, nil\n\tcase StatusContains(resp.StatusCode, h.InvalidStatuses):\n\t\treturn false, nil\n\tdefault:\n\t\tfmt.Println(h)\n\t\tfmt.Println(resp.Body)\n\t\tfmt.Println(resp.StatusCode)\n\t\treturn false, errors.New(\"error checking response status code\")\n\t}\n}\n\nfunc StatusContains(status int, vals []int) bool {\n\tfor _, v := range vals {\n\t\tif status == v {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc checkKeyType(key string) (string, error) {\n\tif strings.HasPrefix(key, SECRET_PREFIX) {\n\t\treturn SECRET, nil\n\t} else if strings.HasPrefix(key, PUBLISHABLE_PREFIX) {\n\t\treturn PUBLISHABLE, nil\n\t} else if strings.HasPrefix(key, RESTRICTED_PREFIX) {\n\t\treturn RESTRICTED, nil\n\t}\n\treturn \"\", errors.New(\"Invalid Stripe key format\")\n}\n\nfunc checkKeyEnv(key string) (string, error) {\n\t//remove first 3 characters\n\tkey = key[3:]\n\tif strings.HasPrefix(key, LIVE_PREFIX) {\n\t\treturn LIVE, nil\n\t}\n\tif strings.HasPrefix(key, TEST_PREFIX) {\n\t\treturn TEST, nil\n\t}\n\treturn \"\", errors.New(\"invalid Stripe key format\")\n}\n\nfunc checkValidity(cfg *config.Config, key string) (bool, error) {\n\t// Create a new request\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\treq, err := http.NewRequest(\"GET\", \"https://api.stripe.com/v1/charges\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Add Authorization header\n\treq.Header.Add(\"Authorization\", \"Bearer \"+key)\n\n\t// Send the request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer resp.Body.Close()\n\n\t// Check the response. Valid is 200 (secret/restricted) or 403 (restricted)\n\tif resp.StatusCode == 200 || resp.StatusCode == 403 {\n\t\treturn true, nil\n\t}\n\treturn false, nil\n}\n\nfunc AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {\n\t// Check if secret, publishable, or restricted key\n\tvar keyType, keyEnv string\n\tkeyType, err := checkKeyType(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Check if live or test key\n\tkeyEnv, err = checkKeyEnv(key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Check if key is valid\n\tvalid, err := checkValidity(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpermissions, err := getRestrictedPermissions(cfg, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// Additional details\n\t// get total customers\n\t// get total charges\n\n\treturn &SecretInfo{\n\t\tKeyType:     keyType,\n\t\tKeyEnv:      keyEnv,\n\t\tValid:       valid,\n\t\tPermissions: permissions,\n\t}, nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, key string) {\n\tinfo, err := AnalyzePermissions(cfg, key)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error: %s\", err.Error())\n\t\treturn\n\t}\n\n\tif info.KeyType == PUBLISHABLE {\n\t\tcolor.Red(\"[x] This is a publishable Stripe key. It is not considered secret.\")\n\t\treturn\n\t}\n\n\tif !info.Valid {\n\t\tcolor.Red(\"[x] Invalid Stripe API Key\\n\")\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid Stripe API Key\\n\\n\")\n\n\tif info.KeyType == SECRET {\n\t\tcolor.Green(\"[i] Key Type: %s\", info.KeyType)\n\t} else if info.KeyType == RESTRICTED {\n\t\tcolor.Yellow(\"[i] Key Type: %s\", info.KeyType)\n\t}\n\n\tif info.KeyEnv == LIVE {\n\t\tcolor.Green(\"[i] Key Environment: %s\", info.KeyEnv)\n\t} else if info.KeyEnv == TEST {\n\t\tcolor.Red(\"[i] Key Environment: %s\", info.KeyEnv)\n\t}\n\n\tfmt.Println(\"\")\n\n\tif info.KeyType == SECRET {\n\t\tcolor.Green(\"[i] Permissions: Full Access\")\n\t\treturn\n\t}\n\n\tprintRestrictedPermissions(info.Permissions, cfg.ShowAll)\n}\n\nfunc getRestrictedPermissions(cfg *config.Config, key string) ([]PermissionsCategory, error) {\n\tvar config Config\n\tif err := yaml.Unmarshal(restrictedConfig, &config); err != nil {\n\t\tfmt.Println(\"Error unmarshalling YAML:\", err)\n\t\treturn nil, err\n\t}\n\n\toutput := make([]PermissionsCategory, 0)\n\n\tfor category, scopes := range config.Categories {\n\t\tpermissions := make([]PermissionStruct, 0)\n\t\tfor name, scope := range scopes {\n\t\t\tvalue := \"\"\n\t\t\ttestCount := 0\n\t\t\tfor typ, test := range scope {\n\t\t\t\tif test.Endpoint == \"\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\ttestCount++\n\t\t\t\tstatus, err := test.RunTest(cfg, map[string]string{\"Authorization\": \"Bearer \" + key})\n\t\t\t\tif err != nil {\n\t\t\t\t\tcolor.Red(\"[x] Error running test: %s\", err.Error())\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tif status {\n\t\t\t\t\tvalue = typ\n\t\t\t\t}\n\t\t\t\tif value == \"Write\" {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif testCount > 0 {\n\t\t\t\tpermissions = append(permissions, PermissionStruct{Name: name, Value: &value})\n\t\t\t}\n\t\t}\n\t\toutput = append(output, PermissionsCategory{Name: category, Permissions: permissions})\n\t}\n\n\t// sort the output\n\torder := []string{\"Core\", \"Checkout\", \"Billing\", \"Connect\", \"Orders\", \"Issuing\", \"Reporting\", \"Identity\", \"Webhook\", \"Stripe CLI\", \"Payment Links\", \"Terminal\", \"Tax\", \"Radar\", \"Climate\"}\n\t// ToDo: order the permissions within each category\n\n\t// Create a map for quick lookup of the order\n\torderMap := make(map[string]int)\n\tfor i, name := range order {\n\t\torderMap[name] = i\n\t}\n\n\t// Sort the categories according to the desired order\n\tsort.Slice(output, func(i, j int) bool {\n\t\treturn orderMap[output[i].Name] < orderMap[output[j].Name]\n\t})\n\n\treturn output, nil\n\n}\n\nfunc printRestrictedPermissions(permissions []PermissionsCategory, show_all bool) {\n\tt := table.NewWriter()\n\tt.SetOutputMirror(os.Stdout)\n\tt.AppendHeader(table.Row{\"Category\", \"Permission\", \"Access\"})\n\tfor _, category := range permissions {\n\t\tfor _, permission := range category.Permissions {\n\t\t\tif *permission.Value != \"\" || show_all {\n\t\t\t\tt.AppendRow([]interface{}{category.Name, permission.Name, *permission.Value})\n\t\t\t}\n\t\t}\n\t}\n\tt.Render()\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/stripe/stripe_test.go",
    "content": "package stripe\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n//go:embed expected_output.json\nvar expectedOutput []byte\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tkey     string\n\t\twant    []byte // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid Stripe restricted key\",\n\t\t\tkey:     testSecrets.MustGetField(\"STRIPE_SECRET\"),\n\t\t\twant:    expectedOutput,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{Cfg: &config.Config{}}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(got.Bindings)\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Bindings need to be in the same order to be comparable\n\t\t\tsortBindings(wantObj.Bindings)\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented, wantIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\twantIndented, err = json.MarshalIndent(wantObj, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal want to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = %s, want %s\", gotIndented, wantIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort bindings\nfunc sortBindings(bindings []analyzers.Binding) {\n\tsort.SliceStable(bindings, func(i, j int) bool {\n\t\tif bindings[i].Resource.Name == bindings[j].Resource.Name {\n\t\t\treturn bindings[i].Permission.Value < bindings[j].Permission.Value\n\t\t}\n\t\treturn bindings[i].Resource.Name < bindings[j].Resource.Name\n\t})\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/twilio/permissions.go",
    "content": "// Code generated by go generate; DO NOT EDIT.\npackage twilio\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n    AccountManagementRead Permission = iota\n    AccountManagementWrite Permission = iota\n    SubaccountConfigurationRead Permission = iota\n    SubaccountConfigurationWrite Permission = iota\n    KeyManagementRead Permission = iota\n    KeyManagementWrite Permission = iota\n    ServiceVerificationRead Permission = iota\n    ServiceVerificationWrite Permission = iota\n    SmsRead Permission = iota\n    SmsWrite Permission = iota\n    VoiceRead Permission = iota\n    VoiceWrite Permission = iota\n    MessagingRead Permission = iota\n    MessagingWrite Permission = iota\n    CallManagementRead Permission = iota\n    CallManagementWrite Permission = iota\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n        AccountManagementRead: \"account_management:read\",\n        AccountManagementWrite: \"account_management:write\",\n        SubaccountConfigurationRead: \"subaccount_configuration:read\",\n        SubaccountConfigurationWrite: \"subaccount_configuration:write\",\n        KeyManagementRead: \"key_management:read\",\n        KeyManagementWrite: \"key_management:write\",\n        ServiceVerificationRead: \"service_verification:read\",\n        ServiceVerificationWrite: \"service_verification:write\",\n        SmsRead: \"sms:read\",\n        SmsWrite: \"sms:write\",\n        VoiceRead: \"voice:read\",\n        VoiceWrite: \"voice:write\",\n        MessagingRead: \"messaging:read\",\n        MessagingWrite: \"messaging:write\",\n        CallManagementRead: \"call_management:read\",\n        CallManagementWrite: \"call_management:write\",\n    }\n\n    StringToPermission = map[string]Permission{\n        \"account_management:read\": AccountManagementRead,\n        \"account_management:write\": AccountManagementWrite,\n        \"subaccount_configuration:read\": SubaccountConfigurationRead,\n        \"subaccount_configuration:write\": SubaccountConfigurationWrite,\n        \"key_management:read\": KeyManagementRead,\n        \"key_management:write\": KeyManagementWrite,\n        \"service_verification:read\": ServiceVerificationRead,\n        \"service_verification:write\": ServiceVerificationWrite,\n        \"sms:read\": SmsRead,\n        \"sms:write\": SmsWrite,\n        \"voice:read\": VoiceRead,\n        \"voice:write\": VoiceWrite,\n        \"messaging:read\": MessagingRead,\n        \"messaging:write\": MessagingWrite,\n        \"call_management:read\": CallManagementRead,\n        \"call_management:write\": CallManagementWrite,\n    }\n\n    PermissionIDs = map[Permission]int{\n        AccountManagementRead: 1,\n        AccountManagementWrite: 2,\n        SubaccountConfigurationRead: 3,\n        SubaccountConfigurationWrite: 4,\n        KeyManagementRead: 5,\n        KeyManagementWrite: 6,\n        ServiceVerificationRead: 7,\n        ServiceVerificationWrite: 8,\n        SmsRead: 9,\n        SmsWrite: 10,\n        VoiceRead: 11,\n        VoiceWrite: 12,\n        MessagingRead: 13,\n        MessagingWrite: 14,\n        CallManagementRead: 15,\n        CallManagementWrite: 16,\n    }\n\n    IdToPermission = map[int]Permission{\n        1: AccountManagementRead,\n        2: AccountManagementWrite,\n        3: SubaccountConfigurationRead,\n        4: SubaccountConfigurationWrite,\n        5: KeyManagementRead,\n        6: KeyManagementWrite,\n        7: ServiceVerificationRead,\n        8: ServiceVerificationWrite,\n        9: SmsRead,\n        10: SmsWrite,\n        11: VoiceRead,\n        12: VoiceWrite,\n        13: MessagingRead,\n        14: MessagingWrite,\n        15: CallManagementRead,\n        16: CallManagementWrite,\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/twilio/permissions.yaml",
    "content": "permissions:\n  - account_management:read\n  - account_management:write\n  - subaccount_configuration:read\n  - subaccount_configuration:write\n  - key_management:read\n  - key_management:write\n  - service_verification:read\n  - service_verification:write\n  - sms:read\n  - sms:write\n  - voice:read\n  - voice:write\n  - messaging:read\n  - messaging:write\n  - call_management:read\n  - call_management:write\n"
  },
  {
    "path": "pkg/analyzer/analyzers/twilio/twilio.go",
    "content": "//go:generate generate_permissions permissions.yaml permissions.go twilio\n\npackage twilio\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\ntype Analyzer struct {\n\tCfg *config.Config\n}\n\nfunc (a *Analyzer) Type() analyzers.AnalyzerType {\n\treturn analyzers.AnalyzerTypeTwilio\n}\n\nfunc (a *Analyzer) Analyze(ctx context.Context, credentialInfo map[string]string) (*analyzers.AnalyzerResult, error) {\n\tkey, ok := credentialInfo[\"key\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"key not found in credentialInfo\")\n\t}\n\n\tsid, ok := credentialInfo[\"sid\"]\n\tif !ok {\n\t\treturn nil, errors.New(\"sid not found in credentialInfo\")\n\t}\n\n\tif a.Cfg == nil {\n\t\ta.Cfg = &config.Config{} // You might need to adjust this based on how you want to handle config\n\t}\n\n\tinfo, err := AnalyzePermissions(a.Cfg, sid, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// List parent and subaccounts\n\taccounts, err := listTwilioAccounts(a.Cfg, sid, key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar permissions []Permission\n\tif info.AccountStatusCode == 200 {\n\t\tpermissions = []Permission{\n\t\t\tAccountManagementRead,\n\t\t\tAccountManagementWrite,\n\t\t\tSubaccountConfigurationRead,\n\t\t\tSubaccountConfigurationWrite,\n\t\t\tKeyManagementRead,\n\t\t\tKeyManagementWrite,\n\t\t\tServiceVerificationRead,\n\t\t\tServiceVerificationWrite,\n\t\t\tSmsRead,\n\t\t\tSmsWrite,\n\t\t\tVoiceRead,\n\t\t\tVoiceWrite,\n\t\t\tMessagingRead,\n\t\t\tMessagingWrite,\n\t\t\tCallManagementRead,\n\t\t\tCallManagementWrite,\n\t\t}\n\t} else if info.AccountStatusCode == 401 {\n\t\tpermissions = []Permission{\n\t\t\tServiceVerificationRead,\n\t\t\tServiceVerificationWrite,\n\t\t\tSmsRead,\n\t\t\tSmsWrite,\n\t\t\tVoiceRead,\n\t\t\tVoiceWrite,\n\t\t\tMessagingRead,\n\t\t\tMessagingWrite,\n\t\t\tCallManagementRead,\n\t\t\tCallManagementWrite,\n\t\t}\n\t}\n\n\tvar (\n\t\tbindings                  []analyzers.Binding\n\t\tparentAccountSID          = \"\"\n\t\tparentAccountFriendlyName = \"\"\n\t)\n\n\tif len(info.ServicesRes.Services) > 0 {\n\t\tparentAccountSID = info.ServicesRes.Services[0].AccountSID\n\t\tparentAccountFriendlyName = info.ServicesRes.Services[0].FriendlyName\n\t}\n\n\tfor _, account := range accounts {\n\t\taccountType := \"Account\"\n\t\tif parentAccountSID != \"\" && account.SID != parentAccountSID {\n\t\t\taccountType = \"SubAccount\"\n\t\t}\n\t\tresource := analyzers.Resource{\n\t\t\tName:               account.FriendlyName,\n\t\t\tFullyQualifiedName: \"twilio.com/account/\" + account.SID,\n\t\t\tType:               accountType,\n\t\t}\n\t\tif parentAccountSID != \"\" && account.SID != parentAccountSID {\n\t\t\tresource.Parent = &analyzers.Resource{\n\t\t\t\tName:               parentAccountFriendlyName,\n\t\t\t\tFullyQualifiedName: \"twilio.com/account/\" + parentAccountSID,\n\t\t\t\tType:               \"Account\",\n\t\t\t}\n\t\t}\n\n\t\tfor _, perm := range permissions {\n\t\t\tpermStr, _ := perm.ToString()\n\t\t\tbindings = append(bindings, analyzers.Binding{\n\t\t\t\tResource: resource,\n\t\t\t\tPermission: analyzers.Permission{\n\t\t\t\t\tValue: permStr,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t}\n\n\treturn &analyzers.AnalyzerResult{\n\t\tAnalyzerType: analyzers.AnalyzerTypeTwilio,\n\t\tBindings:     bindings,\n\t}, nil\n}\n\ntype secretInfo struct {\n\tServicesRes       serviceResponse\n\tAccountStatusCode int\n}\n\nconst (\n\tAUTHENTICATED_NO_PERMISSION = 70051\n\tINVALID_CREDENTIALS         = 20003\n)\n\n// getAccountsStatusCode returns the status code from the Accounts endpoint\n// this is used to determine whether the key is scoped as main or standard, since standard has no access here.\nfunc getAccountsStatusCode(cfg *config.Config, sid string, secret string) (int, error) {\n\t// create http client\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\t// create request\n\treq, err := http.NewRequest(\"GET\", \"https://api.twilio.com/2010-04-01/Accounts\", nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\t// add basicAuth\n\treq.SetBasicAuth(sid, secret)\n\n\t// send request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer resp.Body.Close()\n\treturn resp.StatusCode, nil\n}\n\ntype serviceResponse struct {\n\tCode     int       `json:\"code\"`\n\tServices []service `json:\"services\"`\n}\n\ntype service struct {\n\tFriendlyName string `json:\"friendly_name\"` // friendly name of a service\n\tSID          string `json:\"sid\"`           // object id of service\n\tAccountSID   string `json:\"account_sid\"`   // account sid\n}\n\n// getVerifyServicesStatusCode returns the status code and the JSON response from the Verify Services endpoint\n// only the code value is captured in the JSON response and this is only shown when the key is invalid or has no permissions\nfunc getVerifyServicesStatusCode(cfg *config.Config, sid string, secret string) (serviceResponse, error) {\n\tvar serviceRes serviceResponse\n\n\t// create http client\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\t// create request\n\treq, err := http.NewRequest(\"GET\", \"https://verify.twilio.com/v2/Services\", nil)\n\tif err != nil {\n\t\treturn serviceRes, err\n\t}\n\n\t// add basicAuth\n\treq.SetBasicAuth(sid, secret)\n\n\t// send request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn serviceRes, err\n\t}\n\tdefer resp.Body.Close()\n\n\t// read response\n\tif err := json.NewDecoder(resp.Body).Decode(&serviceRes); err != nil {\n\t\treturn serviceRes, err\n\t}\n\n\treturn serviceRes, nil\n}\n\nfunc listTwilioAccounts(cfg *config.Config, sid, secret string) ([]service, error) {\n\t// create http client\n\tclient := analyzers.NewAnalyzeClient(cfg)\n\n\t// create request\n\treq, err := http.NewRequest(\"GET\", \"https://api.twilio.com/2010-04-01/Accounts.json\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// add basicAuth\n\treq.SetBasicAuth(sid, secret)\n\n\t// send request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tvar result struct {\n\t\tAccounts []service `json:\"accounts\"`\n\t}\n\n\t// read response\n\tif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn result.Accounts, nil\n}\n\nfunc AnalyzePermissions(cfg *config.Config, sid, secret string) (*secretInfo, error) {\n\tservicesRes, err := getVerifyServicesStatusCode(cfg, sid, secret)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstatusCode, err := getAccountsStatusCode(cfg, sid, secret)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &secretInfo{\n\t\tServicesRes:       servicesRes,\n\t\tAccountStatusCode: statusCode,\n\t}, nil\n}\n\nfunc AnalyzeAndPrintPermissions(cfg *config.Config, sid, secret string) {\n\tinfo, err := AnalyzePermissions(cfg, sid, secret)\n\tif err != nil {\n\t\tcolor.Red(\"[x] Error: %s\", err.Error())\n\t\treturn\n\t}\n\n\tif info.ServicesRes.Code == INVALID_CREDENTIALS {\n\t\tcolor.Red(\"[x] Invalid Twilio API Key\")\n\t\treturn\n\t}\n\n\tif info.ServicesRes.Code == AUTHENTICATED_NO_PERMISSION {\n\t\tprintRestrictedKeyMsg()\n\t\treturn\n\t}\n\n\tprintPermissions(info.AccountStatusCode)\n}\n\n// printPermissions prints the permissions based on the status code\n// 200 means the key is main, 401 means the key is standard\nfunc printPermissions(statusCode int) {\n\n\tif statusCode != 200 && statusCode != 401 {\n\t\tcolor.Red(\"[x] Invalid Twilio API Key\")\n\t\treturn\n\t}\n\n\tcolor.Green(\"[!] Valid Twilio API Key\\n\")\n\tcolor.Green(\"[i] Expires: Never\")\n\n\tif statusCode == 401 {\n\t\tcolor.Yellow(\"[i] Key type: Standard\")\n\t\tcolor.Yellow(\"[i] Permissions: All EXCEPT key management and account/subaccount configuration.\")\n\n\t} else if statusCode == 200 {\n\t\tcolor.Green(\"[i] Key type: Main (aka Admin)\")\n\t\tcolor.Green(\"[i] Permissions: All\")\n\t}\n}\n\n// printRestrictedKeyMsg prints the message for a restricted key\n// this is a temporary measure since the restricted key type is still in beta\nfunc printRestrictedKeyMsg() {\n\tcolor.Green(\"[!] Valid Twilio API Key\\n\")\n\tcolor.Green(\"[i] Expires: Never\")\n\tcolor.Yellow(\"[i] Key type: Restricted\")\n\tcolor.Yellow(\"[i] Permissions: Limited\")\n\tfmt.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.\")\n}\n"
  },
  {
    "path": "pkg/analyzer/analyzers/twilio/twilio_test.go",
    "content": "package twilio\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestAnalyzer_Analyze(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tsid     string\n\t\tkey     string\n\t\twant    string // JSON string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"valid Twilio key\",\n\t\t\tsid:  testSecrets.MustGetField(\"TWILLIO_ID\"),\n\t\t\tkey:  testSecrets.MustGetField(\"TWILLIO_API\"),\n\t\t\twant: `            {\n             \"AnalyzerType\": 20,\n             \"Bindings\": [\n              {\n               \"Resource\": {\n                \"Name\": \"My first Twilio account\",\n                \"FullyQualifiedName\": \"twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5\",\n                \"Type\": \"Account\",\n                \"Metadata\": null,\n                \"Parent\": null\n               },\n               \"Permission\": {\n                \"Value\": \"account_management:read\",\n                \"Parent\": null\n               }\n              },\n              {\n               \"Resource\": {\n                \"Name\": \"My first Twilio account\",\n                \"FullyQualifiedName\": \"twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5\",\n                \"Type\": \"Account\",\n                \"Metadata\": null,\n                \"Parent\": null\n               },\n               \"Permission\": {\n                \"Value\": \"account_management:write\",\n                \"Parent\": null\n               }\n              },\n              {\n               \"Resource\": {\n                \"Name\": \"My first Twilio account\",\n                \"FullyQualifiedName\": \"twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5\",\n                \"Type\": \"Account\",\n                \"Metadata\": null,\n                \"Parent\": null\n               },\n               \"Permission\": {\n                \"Value\": \"subaccount_configuration:read\",\n                \"Parent\": null\n               }\n              },\n              {\n               \"Resource\": {\n                \"Name\": \"My first Twilio account\",\n                \"FullyQualifiedName\": \"twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5\",\n                \"Type\": \"Account\",\n                \"Metadata\": null,\n                \"Parent\": null\n               },\n               \"Permission\": {\n                \"Value\": \"subaccount_configuration:write\",\n                \"Parent\": null\n               }\n              },\n              {\n               \"Resource\": {\n                \"Name\": \"My first Twilio account\",\n                \"FullyQualifiedName\": \"twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5\",\n                \"Type\": \"Account\",\n                \"Metadata\": null,\n                \"Parent\": null\n               },\n               \"Permission\": {\n                \"Value\": \"key_management:read\",\n                \"Parent\": null\n               }\n              },\n              {\n               \"Resource\": {\n                \"Name\": \"My first Twilio account\",\n                \"FullyQualifiedName\": \"twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5\",\n                \"Type\": \"Account\",\n                \"Metadata\": null,\n                \"Parent\": null\n               },\n               \"Permission\": {\n                \"Value\": \"key_management:write\",\n                \"Parent\": null\n               }\n              },\n              {\n               \"Resource\": {\n                \"Name\": \"My first Twilio account\",\n                \"FullyQualifiedName\": \"twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5\",\n                \"Type\": \"Account\",\n                \"Metadata\": null,\n                \"Parent\": null\n               },\n               \"Permission\": {\n                \"Value\": \"service_verification:read\",\n                \"Parent\": null\n               }\n              },\n              {\n               \"Resource\": {\n                \"Name\": \"My first Twilio account\",\n                \"FullyQualifiedName\": \"twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5\",\n                \"Type\": \"Account\",\n                \"Metadata\": null,\n                \"Parent\": null\n               },\n               \"Permission\": {\n                \"Value\": \"service_verification:write\",\n                \"Parent\": null\n               }\n              },\n              {\n               \"Resource\": {\n                \"Name\": \"My first Twilio account\",\n                \"FullyQualifiedName\": \"twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5\",\n                \"Type\": \"Account\",\n                \"Metadata\": null,\n                \"Parent\": null\n               },\n               \"Permission\": {\n                \"Value\": \"sms:read\",\n                \"Parent\": null\n               }\n              },\n              {\n               \"Resource\": {\n                \"Name\": \"My first Twilio account\",\n                \"FullyQualifiedName\": \"twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5\",\n                \"Type\": \"Account\",\n                \"Metadata\": null,\n                \"Parent\": null\n               },\n               \"Permission\": {\n                \"Value\": \"sms:write\",\n                \"Parent\": null\n               }\n              },\n              {\n               \"Resource\": {\n                \"Name\": \"My first Twilio account\",\n                \"FullyQualifiedName\": \"twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5\",\n                \"Type\": \"Account\",\n                \"Metadata\": null,\n                \"Parent\": null\n               },\n               \"Permission\": {\n                \"Value\": \"voice:read\",\n                \"Parent\": null\n               }\n              },\n              {\n               \"Resource\": {\n                \"Name\": \"My first Twilio account\",\n                \"FullyQualifiedName\": \"twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5\",\n                \"Type\": \"Account\",\n                \"Metadata\": null,\n                \"Parent\": null\n               },\n               \"Permission\": {\n                \"Value\": \"voice:write\",\n                \"Parent\": null\n               }\n              },\n              {\n               \"Resource\": {\n                \"Name\": \"My first Twilio account\",\n                \"FullyQualifiedName\": \"twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5\",\n                \"Type\": \"Account\",\n                \"Metadata\": null,\n                \"Parent\": null\n               },\n               \"Permission\": {\n                \"Value\": \"messaging:read\",\n                \"Parent\": null\n               }\n              },\n              {\n               \"Resource\": {\n                \"Name\": \"My first Twilio account\",\n                \"FullyQualifiedName\": \"twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5\",\n                \"Type\": \"Account\",\n                \"Metadata\": null,\n                \"Parent\": null\n               },\n               \"Permission\": {\n                \"Value\": \"messaging:write\",\n                \"Parent\": null\n               }\n              },\n              {\n               \"Resource\": {\n                \"Name\": \"My first Twilio account\",\n                \"FullyQualifiedName\": \"twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5\",\n                \"Type\": \"Account\",\n                \"Metadata\": null,\n                \"Parent\": null\n               },\n               \"Permission\": {\n                \"Value\": \"call_management:read\",\n                \"Parent\": null\n               }\n              },\n              {\n               \"Resource\": {\n                \"Name\": \"My first Twilio account\",\n                \"FullyQualifiedName\": \"twilio.com/account/ACa5b6165773490f33f226d71e7ffacff5\",\n                \"Type\": \"Account\",\n                \"Metadata\": null,\n                \"Parent\": null\n               },\n               \"Permission\": {\n                \"Value\": \"call_management:write\",\n                \"Parent\": null\n               }\n              }\n             ],\n             \"UnboundedResources\": null,\n             \"Metadata\": null\n            }`,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ta := Analyzer{}\n\t\t\tgot, err := a.Analyze(ctx, map[string]string{\"key\": tt.key, \"sid\": tt.sid})\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Marshal the actual result to JSON\n\t\t\tgotJSON, err := json.Marshal(got)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal got to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Parse the expected JSON string\n\t\t\tvar wantObj analyzers.AnalyzerResult\n\t\t\tif err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {\n\t\t\t\tt.Fatalf(\"could not unmarshal want JSON string: %s\", err)\n\t\t\t}\n\n\t\t\t// Marshal the expected result to JSON (to normalize)\n\t\t\twantJSON, err := json.Marshal(wantObj)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"could not marshal want to JSON: %s\", err)\n\t\t\t}\n\n\t\t\t// Compare the JSON strings\n\t\t\tif string(gotJSON) != string(wantJSON) {\n\t\t\t\t// Pretty-print both JSON strings for easier comparison\n\t\t\t\tvar gotIndented []byte\n\t\t\t\tgotIndented, err = json.MarshalIndent(got, \"\", \" \")\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"could not marshal got to indented JSON: %s\", err)\n\t\t\t\t}\n\t\t\t\tt.Errorf(\"Analyzer.Analyze() = \\n%s\", gotIndented)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/cli.go",
    "content": "package analyzer\n\nimport (\n\t\"strings\"\n\n\t\"github.com/alecthomas/kingpin/v2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airbrake\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airtable/airtableoauth\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airtable/airtablepat\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/anthropic\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/asana\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/bitbucket\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/databricks\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/datadog\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/digitalocean\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/dockerhub\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/dropbox\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/elevenlabs\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/fastly\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/figma\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/gitlab\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/groq\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/huggingface\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/jira\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/launchdarkly\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/mailchimp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/mailgun\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/monday\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/mux\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/mysql\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/netlify\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/ngrok\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/notion\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/openai\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/opsgenie\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/plaid\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/planetscale\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/postgres\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/posthog\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/postman\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/privatekey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/sendgrid\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/shopify\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/slack\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/sourcegraph\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/square\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/stripe\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/twilio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config\"\n)\n\ntype SecretInfo struct {\n\tParts map[string]string\n\tCfg   *config.Config\n}\n\nfunc Command(app *kingpin.Application) *kingpin.CmdClause {\n\treturn app.Command(\"analyze\", \"Analyze API keys for fine-grained permissions information.\")\n}\n\nfunc Run(keyType string, secretInfo SecretInfo) {\n\tif secretInfo.Cfg == nil {\n\t\tsecretInfo.Cfg = &config.Config{}\n\t}\n\tswitch strings.ToLower(keyType) {\n\tcase \"github\":\n\t\tgithub.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"sendgrid\":\n\t\tsendgrid.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"openai\":\n\t\topenai.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"postgres\":\n\t\tpostgres.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"mysql\":\n\t\tmysql.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"slack\":\n\t\tslack.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"twilio\":\n\t\ttwilio.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"sid\"], secretInfo.Parts[\"key\"])\n\tcase \"airbrake\":\n\t\tairbrake.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"huggingface\":\n\t\thuggingface.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"stripe\":\n\t\tstripe.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"gitlab\":\n\t\tgitlab.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"mailchimp\":\n\t\tmailchimp.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"postman\":\n\t\tpostman.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"bitbucket\":\n\t\tbitbucket.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"asana\":\n\t\tasana.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"mailgun\":\n\t\tmailgun.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"square\":\n\t\tsquare.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"sourcegraph\":\n\t\tsourcegraph.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"shopify\":\n\t\tshopify.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"], secretInfo.Parts[\"url\"])\n\tcase \"opsgenie\":\n\t\topsgenie.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"privatekey\":\n\t\tprivatekey.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"notion\":\n\t\tnotion.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"dockerhub\":\n\t\tdockerhub.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"username\"], secretInfo.Parts[\"pat\"])\n\tcase \"anthropic\":\n\t\tanthropic.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"digitalocean\":\n\t\tdigitalocean.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"elevenlabs\":\n\t\televenlabs.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"planetscale\":\n\t\tplanetscale.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"id\"], secretInfo.Parts[\"token\"])\n\tcase \"airtableoauth\":\n\t\tairtableoauth.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"airtablepat\":\n\t\tairtablepat.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"groq\":\n\t\tgroq.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"launchdarkly\":\n\t\tlaunchdarkly.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"figma\":\n\t\tfigma.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"plaid\":\n\t\tplaid.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"secret\"], secretInfo.Parts[\"id\"], secretInfo.Parts[\"token\"])\n\tcase \"netlify\":\n\t\tnetlify.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"fastly\":\n\t\tfastly.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"monday\":\n\t\tmonday.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"datadog\":\n\t\tdatadog.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"api_key\"], secretInfo.Parts[\"app_key\"], secretInfo.Parts[\"endpoint\"])\n\tcase \"ngrok\":\n\t\tngrok.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"mux\":\n\t\tmux.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"], secretInfo.Parts[\"secret\"])\n\tcase \"posthog\":\n\t\tposthog.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"dropbox\":\n\t\tdropbox.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"key\"])\n\tcase \"databricks\":\n\t\tdatabricks.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"domain\"], secretInfo.Parts[\"token\"])\n\tcase \"jira\":\n\t\tjira.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts[\"domain\"], secretInfo.Parts[\"email\"], secretInfo.Parts[\"token\"])\n\t}\n}\n"
  },
  {
    "path": "pkg/analyzer/config/config.go",
    "content": "package config\n\n// TODO: separate CLI configuration from analysis configuration.\ntype Config struct {\n\tLoggingEnabled bool\n\tLogFile        string\n\tShowAll        bool\n\t// Limit API calls when enumerating permissions.\n\tShallow bool\n}\n"
  },
  {
    "path": "pkg/analyzer/generate_permissions/generate_permissions.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\t\"gopkg.in/yaml.v3\"\n)\n\ntype PermissionsData struct {\n\tPermissions []string `yaml:\"permissions\"`\n\tPackageName string   `yaml:\"package_name\"`\n}\n\nconst templateText = `// Code generated by go generate; DO NOT EDIT.\npackage {{ .PackageName }}\n\nimport \"errors\"\n\ntype Permission int\n\nconst (\n    Invalid Permission = iota\n{{- range $index, $permission := .Permissions }}\n    {{ ToCamelCase $permission }} Permission = iota\n{{- end }}\n)\n\nvar (\n    PermissionStrings = map[Permission]string{\n{{- range $index, $permission := .Permissions }}\n        {{ ToCamelCase $permission }}: \"{{ $permission }}\",\n{{- end }}\n    }\n\n    StringToPermission = map[string]Permission{\n{{- range $index, $permission := .Permissions }}\n        \"{{ $permission }}\": {{ ToCamelCase $permission }},\n{{- end }}\n    }\n\n    PermissionIDs = map[Permission]int{\n{{- range $index, $permission := .Permissions }}\n        {{ ToCamelCase $permission }}: {{ inc $index }},\n{{- end }}\n    }\n\n    IdToPermission = map[int]Permission{\n{{- range $index, $permission := .Permissions }}\n        {{ inc $index }}: {{ ToCamelCase $permission }},\n{{- end }}\n    }\n)\n\n// ToString converts a Permission enum to its string representation\nfunc (p Permission) ToString() (string, error) {\n    if str, ok := PermissionStrings[p]; ok {\n        return str, nil\n    }\n    return \"\", errors.New(\"invalid permission\")\n}\n\n// ToID converts a Permission enum to its ID\nfunc (p Permission) ToID() (int, error) {\n    if id, ok := PermissionIDs[p]; ok {\n        return id, nil\n    }\n    return 0, errors.New(\"invalid permission\")\n}\n\n// PermissionFromString converts a string representation to its Permission enum\nfunc PermissionFromString(s string) (Permission, error) {\n    if p, ok := StringToPermission[s]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission string\")\n}\n\n// PermissionFromID converts an ID to its Permission enum\nfunc PermissionFromID(id int) (Permission, error) {\n    if p, ok := IdToPermission[id]; ok {\n        return p, nil\n    }\n    return 0, errors.New(\"invalid permission ID\")\n}\n`\n\n// ToCamelCase converts a string to CamelCase\nfunc ToCamelCase(s string) string {\n\tparts := strings.Split(s, \":\")\n\tcaser := cases.Title(language.English)\n\tfor i := range parts {\n\t\tsubParts := regexp.MustCompile(`[\\_\\.\\-]+`).Split(parts[i], -1)\n\t\tfor j := range subParts {\n\t\t\tsubParts[j] = caser.String(subParts[j])\n\t\t}\n\t\tparts[i] = strings.Join(subParts, \"\")\n\t}\n\treturn strings.Join(parts, \"\")\n}\n\nfunc main() {\n\t// Read the YAML file from first argument\n\tfile, err := os.Open(os.Args[1])\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to open YAML file: %v\", err)\n\t}\n\tdefer file.Close()\n\n\tvar data PermissionsData\n\tdecoder := yaml.NewDecoder(file)\n\terr = decoder.Decode(&data)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to decode YAML file: %v\", err)\n\t}\n\tdata.PackageName = os.Args[3]\n\n\t// Parse the template\n\ttmpl, err := template.New(\"permissions\").Funcs(template.FuncMap{\n\t\t\"ToCamelCase\": ToCamelCase,\n\t\t\"inc\":         func(i int) int { return i + 1 },\n\t}).Parse(templateText)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to parse template: %v\", err)\n\t}\n\n\t// Generate the code\n\toutputFile, err := os.Create(os.Args[2])\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create output file: %v\", err)\n\t}\n\tdefer outputFile.Close()\n\n\terr = tmpl.Execute(outputFile, data)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to execute template: %v\", err)\n\t}\n\n\tfmt.Println(\"Permissions code generated successfully.\")\n}\n"
  },
  {
    "path": "pkg/buffers/buffer/buffer.go",
    "content": "// Package buffer provides a custom buffer type that includes metrics for tracking buffer usage.\n// It also provides a pool for managing buffer reusability.\npackage buffer\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"time\"\n)\n\n// Buffer is a wrapper around bytes.Buffer that includes a timestamp for tracking Buffer checkout duration.\ntype Buffer struct {\n\t*bytes.Buffer\n\tcheckedOutAt time.Time\n}\n\nconst defaultBufferSize = 1 << 12 // 4KB\n// NewBuffer creates a new instance of Buffer.\nfunc NewBuffer() *Buffer { return &Buffer{Buffer: bytes.NewBuffer(make([]byte, 0, defaultBufferSize))} }\n\nfunc (b *Buffer) Grow(size int) {\n\tb.Buffer.Grow(size)\n\tb.recordGrowth(size)\n}\n\nfunc (b *Buffer) ResetMetric() { b.checkedOutAt = time.Now() }\n\nfunc (b *Buffer) RecordMetric() {\n\tdur := time.Since(b.checkedOutAt)\n\tcheckoutDuration.Observe(float64(dur.Microseconds()))\n\tcheckoutDurationTotal.Add(float64(dur.Microseconds()))\n\ttotalBufferSize.Add(float64(b.Cap()))\n\ttotalBufferLength.Add(float64(b.Len()))\n}\n\nfunc (b *Buffer) recordGrowth(size int) {\n\tgrowCount.Inc()\n\tgrowAmount.Add(float64(size))\n}\n\n// Write date to the buffer.\nfunc (b *Buffer) Write(data []byte) (int, error) {\n\tif b.Buffer == nil {\n\t\t// This case should ideally never occur if buffers are properly managed.\n\t\tb.Buffer = bytes.NewBuffer(make([]byte, 0, defaultBufferSize))\n\t\tb.ResetMetric()\n\t}\n\n\tsize := len(data)\n\tbufferLength := b.Buffer.Len()\n\ttotalSizeNeeded := bufferLength + size\n\n\t// If the total size is within the threshold, write to the buffer.\n\tavailableSpace := b.Buffer.Cap() - bufferLength\n\tgrowSize := totalSizeNeeded - bufferLength\n\tif growSize > availableSpace {\n\t\t// We are manually growing the buffer so we can track the growth via metrics.\n\t\t// Knowing the exact data size, we directly resize to fit it, rather than exponential growth\n\t\t// which may require multiple allocations and copies if the size required is much larger\n\t\t// than double the capacity. Our approach aligns with default behavior when growth sizes\n\t\t// happen to match current capacity, retaining asymptotic efficiency benefits.\n\t\tb.Grow(growSize)\n\t}\n\n\treturn b.Buffer.Write(data)\n}\n\n// Compile time check to make sure readCloser implements io.ReadSeekCloser.\nvar _ io.ReadSeekCloser = (*readCloser)(nil)\n\n// readCloser is a custom implementation of io.ReadCloser. It wraps a bytes.Reader\n// for reading data from an in-memory buffer and includes an onClose callback.\n// The onClose callback is used to return the buffer to the pool, ensuring buffer re-usability.\ntype readCloser struct {\n\t*bytes.Reader\n\tonClose func()\n}\n\n// ReadCloser creates a new instance of readCloser.\nfunc ReadCloser(data []byte, onClose func()) *readCloser {\n\treturn &readCloser{Reader: bytes.NewReader(data), onClose: onClose}\n}\n\n// Close implements the io.Closer interface. It calls the onClose callback to return the buffer\n// to the pool, enabling buffer reuse. This method should be called by the consumers of ReadCloser\n// once they have finished reading the data to ensure proper resource management.\nfunc (brc *readCloser) Close() error {\n\tif brc.onClose == nil {\n\t\treturn nil\n\t}\n\n\tbrc.onClose() // Return the buffer to the pool\n\tbrc.Reader = nil\n\treturn nil\n}\n\n// Read reads up to len(p) bytes into p from the underlying reader.\n// It returns the number of bytes read and any error encountered.\n// On reaching the end of the available data, it returns 0 and io.EOF.\n// Calling Read on a closed reader will also return 0 and io.EOF.\nfunc (brc *readCloser) Read(p []byte) (int, error) {\n\tif brc.Reader == nil {\n\t\treturn 0, io.EOF\n\t}\n\n\treturn brc.Reader.Read(p)\n}\n"
  },
  {
    "path": "pkg/buffers/buffer/buffer_test.go",
    "content": "package buffer\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestBufferWrite(t *testing.T) {\n\tt.Parallel()\n\ttests := []struct {\n\t\tname              string\n\t\tinitialCapacity   int\n\t\twriteDataSequence [][]byte // Sequence of writes to simulate multiple writes\n\t\texpectedSize      int\n\t\texpectedCap       int\n\t}{\n\t\t{\n\t\t\tname:            \"Write to empty buffer\",\n\t\t\tinitialCapacity: defaultBufferSize,\n\t\t\twriteDataSequence: [][]byte{\n\t\t\t\t[]byte(\"hello\"),\n\t\t\t},\n\t\t\texpectedSize: 5,\n\t\t\texpectedCap:  defaultBufferSize, // No growth for small data\n\t\t},\n\t\t{\n\t\t\tname:            \"Write causing growth\",\n\t\t\tinitialCapacity: 10, // Small initial capacity to force growth\n\t\t\twriteDataSequence: [][]byte{\n\t\t\t\t[]byte(\"this is a longer string exceeding initial capacity\"),\n\t\t\t},\n\t\t\texpectedSize: 50,\n\t\t\texpectedCap:  50,\n\t\t},\n\t\t{\n\t\t\tname:              \"Write nil data\",\n\t\t\tinitialCapacity:   defaultBufferSize,\n\t\t\twriteDataSequence: [][]byte{nil},\n\t\t\texpectedCap:       defaultBufferSize,\n\t\t},\n\t\t{\n\t\t\tname:            \"Repeated writes, cumulative growth\",\n\t\t\tinitialCapacity: 20, // Set an initial capacity to test growth over multiple writes\n\t\t\twriteDataSequence: [][]byte{\n\t\t\t\t[]byte(\"first write, \"),\n\t\t\t\t[]byte(\"second write, \"),\n\t\t\t\t[]byte(\"third write exceeding the initial capacity.\"),\n\t\t\t},\n\t\t\texpectedSize: 70,\n\t\t\texpectedCap:  70, // Expect capacity to grow to accommodate all writes\n\t\t},\n\t\t{\n\t\t\tname:            \"Write large single data to test significant growth\",\n\t\t\tinitialCapacity: 50, // Set an initial capacity smaller than the data to be written\n\t\t\twriteDataSequence: [][]byte{\n\t\t\t\tbytes.Repeat([]byte(\"a\"), 1024), // 1KB data to significantly exceed initial capacity\n\t\t\t},\n\t\t\texpectedSize: 1024,\n\t\t\texpectedCap:  1024,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tbuf := &Buffer{Buffer: bytes.NewBuffer(make([]byte, 0, tc.initialCapacity))}\n\t\t\ttotalWritten := 0\n\t\t\tfor _, data := range tc.writeDataSequence {\n\t\t\t\tn, err := buf.Write(data)\n\t\t\t\tassert.NoError(t, err)\n\n\t\t\t\ttotalWritten += n\n\t\t\t}\n\t\t\tassert.Equal(t, tc.expectedSize, totalWritten)\n\t\t\tassert.Equal(t, tc.expectedSize, buf.Len())\n\t\t\tassert.GreaterOrEqual(t, buf.Cap(), tc.expectedCap)\n\t\t})\n\t}\n}\n\nfunc TestReadCloserClose(t *testing.T) {\n\tt.Parallel()\n\tonCloseCalled := false\n\trc := ReadCloser([]byte(\"data\"), func() { onCloseCalled = true })\n\n\terr := rc.Close()\n\tassert.NoError(t, err)\n\tassert.True(t, onCloseCalled, \"onClose callback should be called upon Close\")\n}\n"
  },
  {
    "path": "pkg/buffers/buffer/metrics.go",
    "content": "package buffer\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\nvar (\n\tgrowCount = promauto.NewCounter(prometheus.CounterOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"grow_count\",\n\t\tHelp:      \"Total number of times buffers in the pool have grown.\",\n\t})\n\n\tgrowAmount = promauto.NewCounter(prometheus.CounterOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"grow_amount\",\n\t\tHelp:      \"Total amount of bytes buffers in the pool have grown by.\",\n\t})\n\n\tcheckoutDurationTotal = promauto.NewCounter(prometheus.CounterOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"checkout_duration_total_us\",\n\t\tHelp:      \"Total duration in microseconds of Buffer checkouts.\",\n\t})\n\n\tcheckoutDuration = promauto.NewHistogram(prometheus.HistogramOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"checkout_duration_us\",\n\t\tHelp:      \"Duration in microseconds of Buffer checkouts.\",\n\t\tBuckets:   prometheus.ExponentialBuckets(10, 10, 7),\n\t})\n\n\ttotalBufferLength = promauto.NewGauge(prometheus.GaugeOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"total_buffer_length\",\n\t\tHelp:      \"Total length of all buffers combined.\",\n\t})\n\n\ttotalBufferSize = promauto.NewGauge(prometheus.GaugeOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"total_buffer_size\",\n\t\tHelp:      \"Total size of all buffers combined.\",\n\t})\n)\n"
  },
  {
    "path": "pkg/buffers/pool/metrics.go",
    "content": "package pool\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\nvar (\n\tactiveBufferCount = promauto.NewGauge(prometheus.GaugeOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"active_buffer_count\",\n\t\tHelp:      \"Current number of active buffers.\",\n\t})\n\n\tbufferCount = promauto.NewGauge(prometheus.GaugeOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"buffer_count\",\n\t\tHelp:      \"Total number of buffers managed by the pool.\",\n\t})\n\n\tshrinkCount = promauto.NewCounter(prometheus.CounterOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"shrink_count\",\n\t\tHelp:      \"Total number of times buffers in the pool have shrunk.\",\n\t})\n\n\tshrinkAmount = promauto.NewCounter(prometheus.CounterOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"shrink_amount\",\n\t\tHelp:      \"Total amount of bytes buffers in the pool have shrunk by.\",\n\t})\n\n\tcheckoutCount = promauto.NewCounter(prometheus.CounterOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"checkout_count\",\n\t\tHelp:      \"Total number of Buffer checkouts.\",\n\t})\n)\n"
  },
  {
    "path": "pkg/buffers/pool/pool.go",
    "content": "package pool\n\nimport (\n\t\"bytes\"\n\t\"sync\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/buffers/buffer\"\n)\n\ntype poolMetrics struct{}\n\nfunc (poolMetrics) recordShrink(amount int) {\n\tshrinkCount.Inc()\n\tshrinkAmount.Add(float64(amount))\n}\n\nfunc (poolMetrics) recordBufferRetrival() {\n\tactiveBufferCount.Inc()\n\tcheckoutCount.Inc()\n\tbufferCount.Inc()\n}\n\nfunc (poolMetrics) recordBufferReturn(buf *buffer.Buffer) {\n\tactiveBufferCount.Dec()\n\tbuf.RecordMetric()\n}\n\n// Pool of buffers.\ntype Pool struct {\n\t*sync.Pool\n\tbufferSize int\n\n\tmetrics poolMetrics\n}\n\nconst defaultBufferSize = 1 << 12 // 4KB\n// NewBufferPool creates a new instance of BufferPool.\nfunc NewBufferPool(size int) *Pool {\n\tpool := &Pool{bufferSize: size}\n\n\tpool.Pool = &sync.Pool{\n\t\tNew: func() any {\n\t\t\treturn &buffer.Buffer{Buffer: bytes.NewBuffer(make([]byte, 0, pool.bufferSize))}\n\t\t},\n\t}\n\n\treturn pool\n}\n\n// Get returns a Buffer from the pool.\nfunc (p *Pool) Get() *buffer.Buffer {\n\tbuf, ok := p.Pool.Get().(*buffer.Buffer)\n\tif !ok {\n\t\tbuf = &buffer.Buffer{Buffer: bytes.NewBuffer(make([]byte, 0, p.bufferSize))}\n\t}\n\tp.metrics.recordBufferRetrival()\n\tbuf.ResetMetric()\n\n\treturn buf\n}\n\n// Put returns a Buffer to the pool.\nfunc (p *Pool) Put(buf *buffer.Buffer) {\n\tp.metrics.recordBufferReturn(buf)\n\n\t// If the Buffer is more than twice the default size, replace it with a new Buffer.\n\t// This prevents us from returning very large buffers to the pool.\n\tconst maxAllowedCapacity = 2 * defaultBufferSize\n\tif buf.Cap() > int(maxAllowedCapacity) {\n\t\tp.metrics.recordShrink(buf.Cap() - defaultBufferSize)\n\t\tbuf = &buffer.Buffer{Buffer: bytes.NewBuffer(make([]byte, 0, p.bufferSize))}\n\t} else {\n\t\t// Reset the Buffer to clear any existing data.\n\t\tbuf.Reset()\n\t}\n\n\tp.Pool.Put(buf)\n}\n"
  },
  {
    "path": "pkg/buffers/pool/pool_test.go",
    "content": "package pool\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/buffers/buffer\"\n)\n\nfunc TestNewBufferPool(t *testing.T) {\n\tt.Parallel()\n\ttests := []struct {\n\t\tname             string\n\t\tsize             int\n\t\texpectedBuffSize int\n\t}{\n\t\t{name: \"Default pool size\", size: defaultBufferSize, expectedBuffSize: defaultBufferSize},\n\t\t{\n\t\t\tname:             \"Custom pool size\",\n\t\t\tsize:             8 * 1024,\n\t\t\texpectedBuffSize: 8 * 1024,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tpool := NewBufferPool(tc.size)\n\t\t\tassert.Equal(t, tc.expectedBuffSize, pool.bufferSize)\n\t\t})\n\t}\n}\n\nfunc TestBufferPoolGetPut(t *testing.T) {\n\tt.Parallel()\n\ttests := []struct {\n\t\tname              string\n\t\tpreparePool       func(p *Pool) *buffer.Buffer // Prepare the pool and return an initial buffer to put if needed\n\t\texpectedCapBefore int                          // Expected capacity before putting it back\n\t\texpectedCapAfter  int                          // Expected capacity after retrieving it again\n\t}{\n\t\t{\n\t\t\tname: \"Get new buffer and put back without modification\",\n\t\t\tpreparePool: func(_ *Pool) *buffer.Buffer {\n\t\t\t\treturn nil // No initial buffer to put\n\t\t\t},\n\t\t\texpectedCapBefore: defaultBufferSize,\n\t\t\texpectedCapAfter:  defaultBufferSize,\n\t\t},\n\t\t{\n\t\t\tname: \"Put oversized buffer, expect shrink\",\n\t\t\tpreparePool: func(p *Pool) *buffer.Buffer {\n\t\t\t\tbuf := &buffer.Buffer{Buffer: bytes.NewBuffer(make([]byte, 0, 3*defaultBufferSize))}\n\t\t\t\treturn buf\n\t\t\t},\n\t\t\texpectedCapBefore: defaultBufferSize,\n\t\t\texpectedCapAfter:  defaultBufferSize, // Should shrink back to default\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tpool := NewBufferPool(defaultBufferSize)\n\t\t\tinitialBuf := tc.preparePool(pool)\n\t\t\tif initialBuf != nil {\n\t\t\t\tpool.Put(initialBuf)\n\t\t\t}\n\n\t\t\tbuf := pool.Get()\n\t\t\tassert.Equal(t, tc.expectedCapBefore, buf.Cap())\n\n\t\t\tpool.Put(buf)\n\n\t\t\tbufAfter := pool.Get()\n\t\t\tassert.Equal(t, tc.expectedCapAfter, bufAfter.Cap())\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/cache/cache.go",
    "content": "// Package cache provides an interface which can be implemented by different cache types.\npackage cache\n\n// Cache is used to store key/value pairs.\ntype Cache[T any] interface {\n\t// Set stores the given key/value pair.\n\tSet(key string, val T)\n\t// Get returns the value for the given key and a boolean indicating if the key was found.\n\tGet(key string) (T, bool)\n\t// Exists returns true if the given key exists in the cache.\n\tExists(key string) bool\n\t// Delete the given key from the cache.\n\tDelete(key string)\n\t// Clear all key/value pairs from the cache.\n\tClear()\n\t// Count the number of key/value pairs in the cache.\n\tCount() int\n\t// Keys returns all keys in the cache.\n\tKeys() []string\n\t// Values returns all values in the cache.\n\tValues() []T\n\t// Contents returns all keys in the cache encoded as a string.\n\tContents() string\n}\n"
  },
  {
    "path": "pkg/cache/decorator.go",
    "content": "package cache\n\n// WithMetrics is a decorator that adds metrics collection to any Cache implementation.\ntype WithMetrics[T any] struct {\n\twrapped   Cache[T]\n\tmetrics   BaseMetricsCollector\n\tcacheName string\n}\n\n// NewCacheWithMetrics creates a new WithMetrics decorator that wraps the provided Cache\n// and collects metrics using the provided BaseMetricsCollector.\n// The cacheName parameter is used to identify the cache in the collected metrics.\nfunc NewCacheWithMetrics[T any](wrapped Cache[T], metrics BaseMetricsCollector, cacheName string) *WithMetrics[T] {\n\treturn &WithMetrics[T]{\n\t\twrapped:   wrapped,\n\t\tmetrics:   metrics,\n\t\tcacheName: cacheName,\n\t}\n}\n\n// Set sets the value for the given key in the cache. It also records a set metric\n// for the cache using the provided metrics collector and cache name.\nfunc (c *WithMetrics[T]) Set(key string, val T) {\n\tc.metrics.RecordSet(c.cacheName)\n\tc.wrapped.Set(key, val)\n}\n\n// Get retrieves the value for the given key from the underlying cache. It also records\n// a hit or miss metric for the cache using the provided metrics collector and cache name.\nfunc (c *WithMetrics[T]) Get(key string) (T, bool) {\n\tval, found := c.wrapped.Get(key)\n\tif found {\n\t\tc.metrics.RecordHit(c.cacheName)\n\t} else {\n\t\tc.metrics.RecordMiss(c.cacheName)\n\t}\n\treturn val, found\n}\n\n// Exists checks if the given key exists in the cache. It records a hit or miss metric\n// for the cache using the provided metrics collector and cache name.\nfunc (c *WithMetrics[T]) Exists(key string) bool {\n\tfound := c.wrapped.Exists(key)\n\tif found {\n\t\tc.metrics.RecordHit(c.cacheName)\n\t} else {\n\t\tc.metrics.RecordMiss(c.cacheName)\n\t}\n\treturn found\n}\n\n// Delete removes the value for the given key from the cache. It also records a delete metric\n// for the cache using the provided metrics collector and cache name.\nfunc (c *WithMetrics[T]) Delete(key string) {\n\tc.wrapped.Delete(key)\n\tc.metrics.RecordDelete(c.cacheName)\n}\n\n// Clear removes all entries from the cache. It also records a clear metric\n// for the cache using the provided metrics collector and cache name.\nfunc (c *WithMetrics[T]) Clear() {\n\tc.wrapped.Clear()\n\tc.metrics.RecordClear(c.cacheName)\n}\n\n// Count returns the number of entries in the cache. It also records a count metric\n// for the cache using the provided metrics collector and cache name.\nfunc (c *WithMetrics[T]) Count() int {\n\tcount := c.wrapped.Count()\n\treturn count\n}\n\n// Keys returns all keys in the cache. It also records a keys metric\n// for the cache using the provided metrics collector and cache name.\nfunc (c *WithMetrics[T]) Keys() []string { return c.wrapped.Keys() }\n\n// Values returns all values in the cache. It also records a values metric\n// for the cache using the provided metrics collector and cache name.\nfunc (c *WithMetrics[T]) Values() []T { return c.wrapped.Values() }\n\n// Contents returns all keys in the cache as a string. It also records a contents metric\n// for the cache using the provided metrics collector and cache name.\nfunc (c *WithMetrics[T]) Contents() string { return c.wrapped.Contents() }\n"
  },
  {
    "path": "pkg/cache/decorator_test.go",
    "content": "package cache\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n)\n\ntype mockCollector struct{ mock.Mock }\n\nfunc (m *mockCollector) RecordHits(cacheName string, hits uint64)     { m.Called(cacheName, hits) }\nfunc (m *mockCollector) RecordMisses(cacheName string, misses uint64) { m.Called(cacheName, misses) }\n\nfunc (m *mockCollector) RecordSet(cacheName string)    { m.Called(cacheName) }\nfunc (m *mockCollector) RecordHit(cacheName string)    { m.Called(cacheName) }\nfunc (m *mockCollector) RecordMiss(cacheName string)   { m.Called(cacheName) }\nfunc (m *mockCollector) RecordDelete(cacheName string) { m.Called(cacheName) }\nfunc (m *mockCollector) RecordClear(cacheName string)  { m.Called(cacheName) }\n\ntype mockCache[T any] struct{ mock.Mock }\n\nfunc (m *mockCache[T]) Set(key string, val T) { m.Called(key, val) }\n\nfunc (m *mockCache[T]) Get(key string) (T, bool) {\n\targs := m.Called(key)\n\tvar zero T\n\tif args.Get(0) != nil {\n\t\treturn args.Get(0).(T), args.Bool(1)\n\t}\n\treturn zero, args.Bool(1)\n}\n\nfunc (m *mockCache[T]) Exists(key string) bool {\n\targs := m.Called(key)\n\treturn args.Bool(0)\n}\n\nfunc (m *mockCache[T]) Delete(key string) { m.Called(key) }\n\nfunc (m *mockCache[T]) Clear() { m.Called() }\n\nfunc (m *mockCache[T]) Count() int {\n\targs := m.Called()\n\treturn args.Int(0)\n}\n\nfunc (m *mockCache[T]) Keys() []string {\n\targs := m.Called()\n\treturn args.Get(0).([]string)\n}\n\nfunc (m *mockCache[T]) Values() []T {\n\targs := m.Called()\n\treturn args.Get(0).([]T)\n}\n\nfunc (m *mockCache[T]) Contents() string {\n\targs := m.Called()\n\treturn args.String(0)\n}\n\n// setupCache initializes the mock cache and metrics collector, then wraps them with the WithMetrics decorator.\nfunc setupCache[T any](t *testing.T) (*WithMetrics[T], *mockCache[T], *mockCollector) {\n\tt.Helper()\n\n\tcollector := new(mockCollector)\n\tcache := new(mockCache[T])\n\twrappedCache := NewCacheWithMetrics[T](cache, collector, \"test_cache\")\n\tassert.NotNil(t, wrappedCache, \"WithMetrics cache should not be nil\")\n\n\treturn wrappedCache, cache, collector\n}\n\nfunc TestNewLRUCache(t *testing.T) {\n\tc, _, _ := setupCache[int](t)\n\tassert.Equal(t, \"test_cache\", c.cacheName)\n}\n\nfunc TestCacheSet(t *testing.T) {\n\tc, cacheMock, collectorMock := setupCache[string](t)\n\n\tcollectorMock.On(\"RecordSet\", \"test_cache\").Once()\n\tcacheMock.On(\"Set\", \"key\", \"value\").Once()\n\n\tc.Set(\"key\", \"value\")\n\n\tcollectorMock.AssertCalled(t, \"RecordSet\", \"test_cache\")\n\tcacheMock.AssertCalled(t, \"Set\", \"key\", \"value\")\n}\n\nfunc TestCacheGet(t *testing.T) {\n\tc, cacheMock, collectorMock := setupCache[string](t)\n\n\tcollectorMock.On(\"RecordSet\", \"test_cache\").Once()\n\tcacheMock.On(\"Set\", \"key\", \"value\").Once()\n\n\tcollectorMock.On(\"RecordHit\", \"test_cache\").Once()\n\tcacheMock.On(\"Get\", \"key\").Return(\"value\", true).Once()\n\n\tcollectorMock.On(\"RecordMiss\", \"test_cache\").Once()\n\tcacheMock.On(\"Get\", \"non_existent\").Return(\"\", false).Once()\n\n\tc.Set(\"key\", \"value\")\n\tcollectorMock.AssertCalled(t, \"RecordSet\", \"test_cache\")\n\tcacheMock.AssertCalled(t, \"Set\", \"key\", \"value\")\n\n\tvalue, found := c.Get(\"key\")\n\tassert.True(t, found, \"Expected to find the key\")\n\tassert.Equal(t, \"value\", value, \"Expected value to match\")\n\tcollectorMock.AssertCalled(t, \"RecordHit\", \"test_cache\")\n\tcacheMock.AssertCalled(t, \"Get\", \"key\")\n\n\t_, found = c.Get(\"non_existent\")\n\tassert.False(t, found, \"Expected not to find the key\")\n\tcollectorMock.AssertCalled(t, \"RecordMiss\", \"test_cache\")\n\tcacheMock.AssertCalled(t, \"Get\", \"non_existent\")\n\n\tcollectorMock.AssertExpectations(t)\n\tcacheMock.AssertExpectations(t)\n}\n\nfunc TestCacheExists(t *testing.T) {\n\tc, cacheMock, collectorMock := setupCache[string](t)\n\n\tcollectorMock.On(\"RecordSet\", \"test_cache\").Once()\n\tcacheMock.On(\"Set\", \"key\", \"value\").Once()\n\n\tcollectorMock.On(\"RecordHit\", \"test_cache\").Once()\n\tcacheMock.On(\"Exists\", \"key\").Return(true).Once()\n\n\tcollectorMock.On(\"RecordMiss\", \"test_cache\").Once()\n\tcacheMock.On(\"Exists\", \"non_existent\").Return(false).Once()\n\n\tc.Set(\"key\", \"value\")\n\tcollectorMock.AssertCalled(t, \"RecordSet\", \"test_cache\")\n\tcacheMock.AssertCalled(t, \"Set\", \"key\", \"value\")\n\n\texists := c.Exists(\"key\")\n\tassert.True(t, exists, \"Expected the key to exist\")\n\tcollectorMock.AssertCalled(t, \"RecordHit\", \"test_cache\")\n\tcacheMock.AssertCalled(t, \"Exists\", \"key\")\n\n\texists = c.Exists(\"non_existent\")\n\tassert.False(t, exists, \"Expected the key not to exist\")\n\tcollectorMock.AssertCalled(t, \"RecordMiss\", \"test_cache\")\n\tcacheMock.AssertCalled(t, \"Exists\", \"non_existent\")\n\n\tcollectorMock.AssertExpectations(t)\n\tcacheMock.AssertExpectations(t)\n}\n\nfunc TestCacheDelete(t *testing.T) {\n\tc, cacheMock, collectorMock := setupCache[string](t)\n\n\tcollectorMock.On(\"RecordSet\", \"test_cache\").Once()\n\tcacheMock.On(\"Set\", \"key\", \"value\").Once()\n\n\tcollectorMock.On(\"RecordDelete\", \"test_cache\").Once()\n\tcacheMock.On(\"Delete\", \"key\").Once()\n\n\tcacheMock.On(\"Get\", \"key\").Return(\"\", false).Once()\n\n\tcollectorMock.On(\"RecordMiss\", \"test_cache\").Once()\n\n\tc.Set(\"key\", \"value\")\n\tcollectorMock.AssertCalled(t, \"RecordSet\", \"test_cache\")\n\tcacheMock.AssertCalled(t, \"Set\", \"key\", \"value\")\n\n\tc.Delete(\"key\")\n\tcollectorMock.AssertCalled(t, \"RecordDelete\", \"test_cache\")\n\tcacheMock.AssertCalled(t, \"Delete\", \"key\")\n\n\t_, found := c.Get(\"key\")\n\tassert.False(t, found, \"Expected not to find the deleted key\")\n\tcollectorMock.AssertCalled(t, \"RecordMiss\", \"test_cache\")\n\tcacheMock.AssertCalled(t, \"Get\", \"key\")\n\n\tcollectorMock.AssertExpectations(t)\n\tcacheMock.AssertExpectations(t)\n}\n\nfunc TestCacheClear(t *testing.T) {\n\tc, cacheMock, collectorMock := setupCache[string](t)\n\n\tcollectorMock.On(\"RecordSet\", \"test_cache\").Twice()\n\tcacheMock.On(\"Set\", \"key1\", \"value1\").Once()\n\tcacheMock.On(\"Set\", \"key2\", \"value2\").Once()\n\n\tcollectorMock.On(\"RecordClear\", \"test_cache\").Once()\n\tcacheMock.On(\"Clear\").Once()\n\n\tcacheMock.On(\"Get\", \"key1\").Return(\"\", false).Once()\n\tcacheMock.On(\"Get\", \"key2\").Return(\"\", false).Once()\n\n\tc.Set(\"key1\", \"value1\")\n\tc.Set(\"key2\", \"value2\")\n\tcollectorMock.AssertNumberOfCalls(t, \"RecordSet\", 2)\n\tcacheMock.AssertCalled(t, \"Set\", \"key1\", \"value1\")\n\tcacheMock.AssertCalled(t, \"Set\", \"key2\", \"value2\")\n\n\tc.Clear()\n\tcollectorMock.AssertCalled(t, \"RecordClear\", \"test_cache\")\n\tcacheMock.AssertCalled(t, \"Clear\")\n\n\tcollectorMock.On(\"RecordMiss\", \"test_cache\").Twice()\n\t_, found1 := c.Get(\"key1\")\n\t_, found2 := c.Get(\"key2\")\n\tassert.False(t, found1, \"Expected not to find key1 after clear\")\n\tassert.False(t, found2, \"Expected not to find key2 after clear\")\n\tcollectorMock.AssertNumberOfCalls(t, \"RecordMiss\", 2)\n\tcacheMock.AssertCalled(t, \"Get\", \"key1\")\n\tcacheMock.AssertCalled(t, \"Get\", \"key2\")\n\n\tcollectorMock.AssertExpectations(t)\n\tcacheMock.AssertExpectations(t)\n}\n\nfunc TestCacheCount(t *testing.T) {\n\tc, cacheMock, collectorMock := setupCache[string](t)\n\n\tcollectorMock.On(\"RecordSet\", \"test_cache\").Times(3)\n\tcacheMock.On(\"Set\", mock.Anything, mock.Anything).Times(3)\n\n\tcacheMock.On(\"Count\").Return(3).Once()\n\n\tcollectorMock.On(\"RecordDelete\", \"test_cache\").Once()\n\tcacheMock.On(\"Delete\", \"key2\").Once()\n\tcacheMock.On(\"Count\").Return(2).Once()\n\n\tcollectorMock.On(\"RecordClear\", \"test_cache\").Once()\n\tcacheMock.On(\"Clear\").Once()\n\tcacheMock.On(\"Count\").Return(0).Once()\n\n\tc.Set(\"key1\", \"value1\")\n\tc.Set(\"key2\", \"value2\")\n\tc.Set(\"key3\", \"value3\")\n\tassert.Equal(t, 3, c.Count(), \"Expected count to be 3\")\n\tcollectorMock.AssertNumberOfCalls(t, \"RecordSet\", 3)\n\tcacheMock.AssertNumberOfCalls(t, \"Set\", 3)\n\tcacheMock.AssertCalled(t, \"Count\")\n\n\tc.Delete(\"key2\")\n\tassert.Equal(t, 2, c.Count(), \"Expected count to be 2 after deletion\")\n\tcollectorMock.AssertCalled(t, \"RecordDelete\", \"test_cache\")\n\tcacheMock.AssertCalled(t, \"Delete\", \"key2\")\n\tcacheMock.AssertCalled(t, \"Count\")\n\n\tc.Clear()\n\tassert.Equal(t, 0, c.Count(), \"Expected count to be 0 after clear\")\n\tcollectorMock.AssertCalled(t, \"RecordClear\", \"test_cache\")\n\tcacheMock.AssertCalled(t, \"Clear\")\n\tcacheMock.AssertCalled(t, \"Count\")\n\n\tcollectorMock.AssertExpectations(t)\n\tcacheMock.AssertExpectations(t)\n}\n\nfunc TestCacheKeys(t *testing.T) {\n\tc, cacheMock, collectorMock := setupCache[string](t)\n\n\tcollectorMock.On(\"RecordSet\", \"test_cache\").Times(3)\n\tcacheMock.On(\"Set\", mock.Anything, mock.Anything).Times(3)\n\n\tcollectorMock.On(\"RecordDelete\", \"test_cache\").Once()\n\tcacheMock.On(\"Delete\", \"key2\").Once()\n\tcacheMock.On(\"Clear\").Once()\n\tcollectorMock.On(\"RecordClear\", \"test_cache\").Once()\n\n\tcacheMock.On(\"Keys\").Return([]string{\"key1\", \"key2\", \"key3\"}).Once()\n\tcacheMock.On(\"Keys\").Return([]string{\"key1\", \"key3\"}).Once()\n\tcacheMock.On(\"Keys\").Return([]string{}).Once()\n\n\tc.Set(\"key1\", \"value1\")\n\tc.Set(\"key2\", \"value2\")\n\tc.Set(\"key3\", \"value3\")\n\tcollectorMock.AssertNumberOfCalls(t, \"RecordSet\", 3)\n\tcacheMock.AssertNumberOfCalls(t, \"Set\", 3)\n\n\tkeys := c.Keys()\n\tassert.Len(t, keys, 3, \"Expected 3 keys\")\n\tassert.ElementsMatch(t, []string{\"key1\", \"key2\", \"key3\"}, keys, \"Keys do not match expected values\")\n\n\tc.Delete(\"key2\")\n\tkeys = c.Keys()\n\tassert.Len(t, keys, 2, \"Expected 2 keys after deletion\")\n\tassert.ElementsMatch(t, []string{\"key1\", \"key3\"}, keys, \"Keys do not match expected values after deletion\")\n\tcollectorMock.AssertCalled(t, \"RecordDelete\", \"test_cache\")\n\n\tc.Clear()\n\tkeys = c.Keys()\n\tassert.Len(t, keys, 0, \"Expected no keys after clear\")\n\tcollectorMock.AssertCalled(t, \"RecordClear\", \"test_cache\")\n\n\tcollectorMock.AssertExpectations(t)\n\tcacheMock.AssertExpectations(t)\n}\n\nfunc TestCacheValues(t *testing.T) {\n\tc, cacheMock, collectorMock := setupCache[string](t)\n\n\tcollectorMock.On(\"RecordSet\", \"test_cache\").Times(3)\n\tcacheMock.On(\"Set\", mock.Anything, mock.Anything).Times(3)\n\n\tcollectorMock.On(\"RecordDelete\", \"test_cache\").Once()\n\tcacheMock.On(\"Delete\", \"key2\").Once()\n\tcollectorMock.On(\"RecordClear\", \"test_cache\").Once()\n\tcacheMock.On(\"Clear\").Once()\n\n\tcacheMock.On(\"Values\").Return([]string{\"value1\", \"value2\", \"value3\"}).Once()\n\tcacheMock.On(\"Values\").Return([]string{\"value1\", \"value3\"}).Once()\n\tcacheMock.On(\"Values\").Return([]string{}).Once()\n\n\tc.Set(\"key1\", \"value1\")\n\tc.Set(\"key2\", \"value2\")\n\tc.Set(\"key3\", \"value3\")\n\tcollectorMock.AssertNumberOfCalls(t, \"RecordSet\", 3)\n\tcacheMock.AssertNumberOfCalls(t, \"Set\", 3)\n\n\tvalues := c.Values()\n\tassert.Len(t, values, 3, \"Expected 3 values\")\n\tassert.ElementsMatch(t, []string{\"value1\", \"value2\", \"value3\"}, values, \"Values do not match expected values\")\n\n\tc.Delete(\"key2\")\n\tvalues = c.Values()\n\tassert.Len(t, values, 2, \"Expected 2 values after deletion\")\n\tassert.ElementsMatch(t, []string{\"value1\", \"value3\"}, values, \"Values do not match expected values after deletion\")\n\tcollectorMock.AssertCalled(t, \"RecordDelete\", \"test_cache\")\n\n\tc.Clear()\n\tvalues = c.Values()\n\tassert.Len(t, values, 0, \"Expected no values after clear\")\n\tcollectorMock.AssertCalled(t, \"RecordClear\", \"test_cache\")\n\n\tcollectorMock.AssertExpectations(t)\n\tcacheMock.AssertExpectations(t)\n}\n\nfunc TestCacheContents(t *testing.T) {\n\tc, cacheMock, collectorMock := setupCache[string](t)\n\n\tcollectorMock.On(\"RecordSet\", \"test_cache\").Times(3)\n\tcacheMock.On(\"Set\", mock.Anything, mock.Anything).Times(3)\n\n\tcollectorMock.On(\"RecordDelete\", \"test_cache\").Once()\n\tcacheMock.On(\"Delete\", \"key2\").Once()\n\tcollectorMock.On(\"RecordClear\", \"test_cache\").Once()\n\tcacheMock.On(\"Clear\").Once()\n\n\tcacheMock.On(\"Contents\").Return(\"key1, key2, key3\").Once()\n\tcacheMock.On(\"Contents\").Return(\"key1, key3\").Once()\n\tcacheMock.On(\"Contents\").Return(\"[]\").Once()\n\n\tc.Set(\"key1\", \"value1\")\n\tc.Set(\"key2\", \"value2\")\n\tc.Set(\"key3\", \"value3\")\n\tcollectorMock.AssertNumberOfCalls(t, \"RecordSet\", 3)\n\tcacheMock.AssertNumberOfCalls(t, \"Set\", 3)\n\n\tcontents := c.Contents()\n\tassert.Contains(t, contents, \"key1\", \"Contents should contain key1\")\n\tassert.Contains(t, contents, \"key2\", \"Contents should contain key2\")\n\tassert.Contains(t, contents, \"key3\", \"Contents should contain key3\")\n\n\tc.Delete(\"key2\")\n\tcontents = c.Contents()\n\tassert.Contains(t, contents, \"key1\", \"Contents should contain key1\")\n\tassert.NotContains(t, contents, \"key2\", \"Contents should not contain key2\")\n\tassert.Contains(t, contents, \"key3\", \"Contents should contain key3\")\n\tcollectorMock.AssertCalled(t, \"RecordDelete\", \"test_cache\")\n\n\tc.Clear()\n\tcontents = c.Contents()\n\tassert.Equal(t, \"[]\", contents, \"Contents should be empty after clear\")\n\tcollectorMock.AssertCalled(t, \"RecordClear\", \"test_cache\")\n\n\tcollectorMock.AssertExpectations(t)\n\tcacheMock.AssertExpectations(t)\n}\n"
  },
  {
    "path": "pkg/cache/lru/lru.go",
    "content": "// Package lru provides a generic, size-limited, LRU (Least Recently Used) cache with optional\n// metrics collection and reporting. It wraps the golang-lru/v2 caching library, adding support for custom\n// metrics tracking cache hits, misses, evictions, and other cache operations.\n//\n// This package supports configuring key aspects of cache behavior, including maximum cache size,\n// and custom metrics collection.\npackage lru\n\nimport (\n\t\"fmt\"\n\n\tlru \"github.com/hashicorp/golang-lru/v2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache\"\n)\n\n// Cache is a generic LRU-sized cache that stores key-value pairs with a maximum size limit.\n// It wraps the lru.Cache library and adds support for custom metrics collection.\ntype Cache[T any] struct {\n\tcache *lru.Cache[string, T]\n\n\tcacheName    string\n\tcapacity     int\n\tevictMetrics cache.EvictionMetricsCollector\n}\n\n// Option defines a functional option for configuring the Cache.\ntype Option[T any] func(*Cache[T])\n\n// WithCapacity is a functional option to set the maximum number of items the cache can hold.\n// If the capacity is not set, the default value (128_000) is used.\nfunc WithCapacity[T any](capacity int) Option[T] {\n\treturn func(lc *Cache[T]) { lc.capacity = capacity }\n}\n\n// WithMetricsCollector is a functional option to set a custom metrics collector.\nfunc WithMetricsCollector[T any](collector cache.EvictionMetricsCollector) Option[T] {\n\treturn func(lc *Cache[T]) { lc.evictMetrics = collector }\n}\n\n// NewCache creates a new Cache with optional configuration parameters.\n// It takes a cache name and a variadic list of options.\nfunc NewCache[T any](cacheName string, opts ...Option[T]) (*Cache[T], error) {\n\t// Default values for cache configuration.\n\tconst defaultSize = 128_000\n\n\tsizedLRU := &Cache[T]{\n\t\tcacheName: cacheName,\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(sizedLRU)\n\t}\n\n\tvar onEvicted func(string, T)\n\t// Provide a evict callback function to record evictions if a custom metrics collector is provided.\n\tif sizedLRU.evictMetrics != nil {\n\t\tonEvicted = func(string, T) {\n\t\t\tsizedLRU.evictMetrics.RecordEviction(sizedLRU.cacheName)\n\t\t}\n\t}\n\n\tlcache, err := lru.NewWithEvict[string, T](defaultSize, onEvicted)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create lrusized cache: %w\", err)\n\t}\n\n\tsizedLRU.cache = lcache\n\n\treturn sizedLRU, nil\n}\n\n// Set adds a key-value pair to the cache.\nfunc (lc *Cache[T]) Set(key string, val T) { lc.cache.Add(key, val) }\n\n// Get retrieves a value from the cache by key.\nfunc (lc *Cache[T]) Get(key string) (T, bool) {\n\tvalue, found := lc.cache.Get(key)\n\tif found {\n\t\treturn value, true\n\t}\n\tvar zero T\n\treturn zero, false\n}\n\n// Exists checks if a key exists in the cache.\nfunc (lc *Cache[T]) Exists(key string) bool {\n\t_, found := lc.cache.Get(key)\n\treturn found\n}\n\n// Delete removes a key from the cache.\nfunc (lc *Cache[T]) Delete(key string) {\n\tlc.cache.Remove(key)\n}\n\n// Clear removes all keys from the cache.\nfunc (lc *Cache[T]) Clear() {\n\tlc.cache.Purge()\n}\n\n// Count returns the number of key-value pairs in the cache.\nfunc (lc *Cache[T]) Count() int { return lc.cache.Len() }\n\n// Keys returns all keys in the cache.\nfunc (lc *Cache[T]) Keys() []string { return lc.cache.Keys() }\n\n// Values returns all values in the cache.\nfunc (lc *Cache[T]) Values() []T {\n\titems := lc.cache.Keys()\n\tres := make([]T, 0, len(items))\n\tfor _, k := range items {\n\t\tv, _ := lc.cache.Get(k)\n\t\tres = append(res, v)\n\t}\n\treturn res\n}\n\n// Contents returns all keys in the cache encoded as a string.\nfunc (lc *Cache[T]) Contents() string {\n\treturn fmt.Sprintf(\"%v\", lc.cache.Keys())\n}\n"
  },
  {
    "path": "pkg/cache/lru/lru_test.go",
    "content": "package lru\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n)\n\ntype mockCollector struct{ mock.Mock }\n\nfunc (m *mockCollector) RecordEviction(cacheName string) { m.Called(cacheName) }\n\n// setupCache initializes the metrics and cache.\n// If withCollector is true, it sets up a cache with a custom metrics collector.\n// Otherwise, it sets up a cache without a custom metrics collector.\nfunc setupCache[T any](t *testing.T, withCollector bool) (*Cache[T], *mockCollector) {\n\tt.Helper()\n\n\tvar collector *mockCollector\n\tvar c *Cache[T]\n\tvar err error\n\n\tif withCollector {\n\t\tcollector = new(mockCollector)\n\t\tc, err = NewCache[T](\"test_cache\", WithMetricsCollector[T](collector))\n\t} else {\n\t\tc, err = NewCache[T](\"test_cache\")\n\t}\n\n\tassert.NoError(t, err, \"Failed to create cache\")\n\tassert.NotNil(t, c, \"Cache should not be nil\")\n\n\treturn c, collector\n}\n\nfunc TestNewLRUCache(t *testing.T) {\n\tt.Run(\"default configuration\", func(t *testing.T) {\n\t\tc, _ := setupCache[int](t, false)\n\t\tassert.Equal(t, \"test_cache\", c.cacheName)\n\t})\n\n\tt.Run(\"with custom max cost\", func(t *testing.T) {\n\t\tc, _ := setupCache[int](t, false)\n\t\tassert.NotNil(t, c)\n\t})\n\n\tt.Run(\"with metrics collector\", func(t *testing.T) {\n\t\tc, collector := setupCache[int](t, true)\n\t\tassert.NotNil(t, c)\n\t\tassert.Equal(t, \"test_cache\", c.cacheName)\n\t\tassert.Equal(t, collector, c.evictMetrics, \"Cache metrics should match the collector\")\n\t})\n}\n\nfunc TestCacheSet(t *testing.T) {\n\tc, _ := setupCache[string](t, true)\n\n\tc.Set(\"key\", \"value\")\n\tvalue, found := c.Get(\"key\")\n\tassert.True(t, found, \"Expected to find the key\")\n\tassert.Equal(t, \"value\", value, \"Expected value to match\")\n}\n\nfunc TestCacheGet(t *testing.T) {\n\tc, _ := setupCache[string](t, true)\n\n\tc.Set(\"key\", \"value\")\n\n\tvalue, found := c.Get(\"key\")\n\tassert.True(t, found, \"Expected to find the key\")\n\tassert.Equal(t, \"value\", value, \"Expected value to match\")\n\n\t_, found = c.Get(\"non_existent\")\n\tassert.False(t, found, \"Expected not to find the key\")\n}\n\nfunc TestCacheExists(t *testing.T) {\n\tc, _ := setupCache[string](t, true)\n\n\tc.Set(\"key\", \"value\")\n\n\texists := c.Exists(\"key\")\n\tassert.True(t, exists, \"Expected the key to exist\")\n\n\texists = c.Exists(\"non_existent\")\n\tassert.False(t, exists, \"Expected the key not to exist\")\n}\n\nfunc TestCacheDelete(t *testing.T) {\n\tc, collector := setupCache[string](t, true)\n\n\tcollector.On(\"RecordEviction\", \"test_cache\").Once()\n\n\tc.Set(\"key\", \"value\")\n\n\tc.Delete(\"key\")\n\tcollector.AssertCalled(t, \"RecordEviction\", \"test_cache\")\n\n\t_, found := c.Get(\"key\")\n\tassert.False(t, found, \"Expected not to find the deleted key\")\n}\n\nfunc TestCacheClear(t *testing.T) {\n\tc, collector := setupCache[string](t, true)\n\n\tcollector.On(\"RecordEviction\", \"test_cache\").Twice()\n\n\tc.Set(\"key1\", \"value1\")\n\tc.Set(\"key2\", \"value2\")\n\n\tc.Clear()\n\tcollector.AssertNumberOfCalls(t, \"RecordEviction\", 2)\n\n\t_, found1 := c.Get(\"key1\")\n\t_, found2 := c.Get(\"key2\")\n\tassert.False(t, found1, \"Expected not to find key1 after clear\")\n\tassert.False(t, found2, \"Expected not to find key2 after clear\")\n}\n\nfunc TestCacheCount(t *testing.T) {\n\tc, collector := setupCache[string](t, true)\n\n\tcollector.On(\"RecordEviction\", \"test_cache\").Times(3)\n\n\tc.Set(\"key1\", \"value1\")\n\tc.Set(\"key2\", \"value2\")\n\tc.Set(\"key3\", \"value3\")\n\n\tassert.Equal(t, 3, c.Count(), \"Expected count to be 3\")\n\n\tc.Delete(\"key2\")\n\tassert.Equal(t, 2, c.Count(), \"Expected count to be 2 after deletion\")\n\tcollector.AssertNumberOfCalls(t, \"RecordEviction\", 1)\n\n\tc.Clear()\n\tassert.Equal(t, 0, c.Count(), \"Expected count to be 0 after clear\")\n\tcollector.AssertNumberOfCalls(t, \"RecordEviction\", 3)\n}\n\nfunc TestCacheKeys(t *testing.T) {\n\tc, collector := setupCache[string](t, true)\n\n\tcollector.On(\"RecordEviction\", \"test_cache\").Times(3)\n\n\tc.Set(\"key1\", \"value1\")\n\tc.Set(\"key2\", \"value2\")\n\tc.Set(\"key3\", \"value3\")\n\n\tkeys := c.Keys()\n\tassert.Len(t, keys, 3, \"Expected 3 keys\")\n\tassert.ElementsMatch(t, []string{\"key1\", \"key2\", \"key3\"}, keys, \"Keys do not match expected values\")\n\n\tc.Delete(\"key2\")\n\tkeys = c.Keys()\n\tassert.Len(t, keys, 2, \"Expected 2 keys after deletion\")\n\tassert.ElementsMatch(t, []string{\"key1\", \"key3\"}, keys, \"Keys do not match expected values after deletion\")\n\tcollector.AssertNumberOfCalls(t, \"RecordEviction\", 1)\n\n\tc.Clear()\n\tkeys = c.Keys()\n\tassert.Len(t, keys, 0, \"Expected no keys after clear\")\n\tcollector.AssertNumberOfCalls(t, \"RecordEviction\", 3)\n}\n\nfunc TestCacheValues(t *testing.T) {\n\tc, collector := setupCache[string](t, true)\n\n\tcollector.On(\"RecordEviction\", \"test_cache\").Times(3)\n\n\tc.Set(\"key1\", \"value1\")\n\tc.Set(\"key2\", \"value2\")\n\tc.Set(\"key3\", \"value3\")\n\n\tvalues := c.Values()\n\tassert.Len(t, values, 3, \"Expected 3 values\")\n\tassert.ElementsMatch(t, []string{\"value1\", \"value2\", \"value3\"}, values, \"Values do not match expected values\")\n\n\tc.Delete(\"key2\")\n\tvalues = c.Values()\n\tassert.Len(t, values, 2, \"Expected 2 values after deletion\")\n\tassert.ElementsMatch(t, []string{\"value1\", \"value3\"}, values, \"Values do not match expected values after deletion\")\n\tcollector.AssertNumberOfCalls(t, \"RecordEviction\", 1)\n\n\tc.Clear()\n\tvalues = c.Values()\n\tassert.Len(t, values, 0, \"Expected no values after clear\")\n\tcollector.AssertNumberOfCalls(t, \"RecordEviction\", 3)\n}\n\nfunc TestCacheContents(t *testing.T) {\n\tc, collector := setupCache[string](t, true)\n\n\tcollector.On(\"RecordEviction\", \"test_cache\").Times(3)\n\n\tc.Set(\"key1\", \"value1\")\n\tc.Set(\"key2\", \"value2\")\n\tc.Set(\"key3\", \"value3\")\n\n\tcontents := c.Contents()\n\tassert.Contains(t, contents, \"key1\", \"Contents should contain key1\")\n\tassert.Contains(t, contents, \"key2\", \"Contents should contain key2\")\n\tassert.Contains(t, contents, \"key3\", \"Contents should contain key3\")\n\n\tc.Delete(\"key2\")\n\tcontents = c.Contents()\n\tassert.Contains(t, contents, \"key1\", \"Contents should contain key1\")\n\tassert.NotContains(t, contents, \"key2\", \"Contents should not contain key2\")\n\tassert.Contains(t, contents, \"key3\", \"Contents should contain key3\")\n\tcollector.AssertNumberOfCalls(t, \"RecordEviction\", 1)\n\n\tc.Clear()\n\tcontents = c.Contents()\n\tassert.Equal(t, \"[]\", contents, \"Contents should be empty after clear\")\n\tcollector.AssertNumberOfCalls(t, \"RecordEviction\", 3)\n}\n"
  },
  {
    "path": "pkg/cache/metrics.go",
    "content": "package cache\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\n// BaseMetricsCollector defines the interface for recording cache metrics.\n// Each method corresponds to a specific cache-related operation.\ntype BaseMetricsCollector interface {\n\tRecordHit(cacheName string)\n\tRecordMiss(cacheName string)\n\tRecordSet(cacheName string)\n\tRecordDelete(cacheName string)\n\tRecordClear(cacheName string)\n}\n\n// EvictionMetricsCollector defines the interface for recording cache-specific eviction metrics.\ntype EvictionMetricsCollector interface {\n\tRecordEviction(cacheName string)\n}\n\n// baseCollector encapsulates all Prometheus metrics with labels.\n// It holds Prometheus counters for cache operations, which help track\n// the performance and usage of the cache.\ntype baseCollector struct {\n\t// Base metrics.\n\thits    *prometheus.CounterVec\n\tmisses  *prometheus.CounterVec\n\tsets    *prometheus.CounterVec\n\tdeletes *prometheus.CounterVec\n\tclears  *prometheus.CounterVec\n}\n\nfunc init() {\n\t// Initialize the singleton baseCollector.\n\t// Set up Prometheus counters for cache operations (hits, misses, sets, deletes, clears).\n\tbaseMetricsInstance = &baseCollector{\n\t\thits: promauto.NewCounterVec(prometheus.CounterOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"hits_total\",\n\t\t\tHelp:      \"Total number of cache hits.\",\n\t\t}, []string{\"cache_name\"}),\n\n\t\tmisses: promauto.NewCounterVec(prometheus.CounterOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"misses_total\",\n\t\t\tHelp:      \"Total number of cache misses.\",\n\t\t}, []string{\"cache_name\"}),\n\n\t\tsets: promauto.NewCounterVec(prometheus.CounterOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"sets_total\",\n\t\t\tHelp:      \"Total number of cache set operations.\",\n\t\t}, []string{\"cache_name\"}),\n\n\t\tdeletes: promauto.NewCounterVec(prometheus.CounterOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"deletes_total\",\n\t\t\tHelp:      \"Total number of cache delete operations.\",\n\t\t}, []string{\"cache_name\"}),\n\n\t\tclears: promauto.NewCounterVec(prometheus.CounterOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"clears_total\",\n\t\t\tHelp:      \"Total number of cache clear operations.\",\n\t\t}, []string{\"cache_name\"}),\n\t}\n\n\t// Initialize the singleton evictionMetrics.\n\t// Set up Prometheus counters for cache evictions.\n\tevictionMetricsInstance = &evictionMetrics{\n\t\tevictions: promauto.NewCounterVec(prometheus.CounterOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"evictions_total\",\n\t\t\tHelp:      \"Total number of cache evictions.\",\n\t\t}, []string{\"cache_name\"}),\n\t}\n}\n\nvar (\n\tbaseMetricsInstance     *baseCollector\n\tevictionMetricsInstance *evictionMetrics\n)\n\n// GetBaseMetricsCollector returns the singleton baseCollector instance.\nfunc GetBaseMetricsCollector() BaseMetricsCollector { return baseMetricsInstance }\n\n// GetEvictionMetricsCollector returns the singleton evictionMetrics instance.\nfunc GetEvictionMetricsCollector() EvictionMetricsCollector { return evictionMetricsInstance }\n\n// Implement BaseMetricsCollector interface methods.\n\n// RecordHit increments the counter for cache hits, tracking how often cache lookups succeed.\nfunc (m *baseCollector) RecordHit(cacheName string) { m.hits.WithLabelValues(cacheName).Inc() }\n\n// RecordMiss increments the counter for cache misses, tracking how often cache lookups fail.\nfunc (m *baseCollector) RecordMiss(cacheName string) { m.misses.WithLabelValues(cacheName).Inc() }\n\n// RecordSet increments the counter for cache set operations, tracking how often items are added/updated.\nfunc (m *baseCollector) RecordSet(cacheName string) { m.sets.WithLabelValues(cacheName).Inc() }\n\n// RecordDelete increments the counter for cache delete operations, tracking how often items are removed.\nfunc (m *baseCollector) RecordDelete(cacheName string) { m.deletes.WithLabelValues(cacheName).Inc() }\n\n// RecordClear increments the counter for cache clear operations, tracking how often the cache is completely cleared.\nfunc (m *baseCollector) RecordClear(cacheName string) { m.clears.WithLabelValues(cacheName).Inc() }\n\n// evictionMetrics implements EvictionMetricsCollector interface.\ntype evictionMetrics struct {\n\tevictions *prometheus.CounterVec\n}\n\n// Implement EvictionMetricsCollector interface method.\n\nfunc (em *evictionMetrics) RecordEviction(cacheName string) {\n\tem.evictions.WithLabelValues(cacheName).Inc()\n}\n"
  },
  {
    "path": "pkg/cache/simple/simple.go",
    "content": "package simple\n\nimport (\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/patrickmn/go-cache\"\n)\n\nconst (\n\tdefaultExpirationInterval = 12 * time.Hour\n\tdefaultPurgeInterval      = 13 * time.Hour\n\tdefaultExpiration         = cache.DefaultExpiration\n)\n\n// Cache wraps the go-cache library to provide an in-memory key-value store.\ntype Cache[T any] struct {\n\tc             *cache.Cache\n\texpiration    time.Duration\n\tpurgeInterval time.Duration\n}\n\n// CacheOption defines a function type used for configuring a Cache.\ntype CacheOption[T any] func(*Cache[T])\n\n// WithExpirationInterval returns a CacheOption to set the expiration interval of cache items.\n// The interval determines the duration a cached item remains in the cache before it is expired.\nfunc WithExpirationInterval[T any](interval time.Duration) CacheOption[T] {\n\treturn func(c *Cache[T]) { c.expiration = interval }\n}\n\n// WithPurgeInterval returns a CacheOption to set the interval at which the cache purges expired items.\n// Regular purging helps in freeing up memory by removing stale entries.\nfunc WithPurgeInterval[T any](interval time.Duration) CacheOption[T] {\n\treturn func(c *Cache[T]) { c.purgeInterval = interval }\n}\n\n// NewCache constructs a new in-memory cache instance with optional configurations.\n// By default, it sets the expiration and purge intervals to 12 and 13 hours, respectively.\n// These defaults can be overridden using the functional options: WithExpirationInterval and WithPurgeInterval.\nfunc NewCache[T any](opts ...CacheOption[T]) *Cache[T] {\n\treturn NewCacheWithData[T](nil, opts...)\n}\n\n// CacheEntry represents a single entry in the cache, consisting of a key and its corresponding value.\ntype CacheEntry[T any] struct {\n\t// Key is the unique identifier for the entry.\n\tKey string\n\t// Value is the data stored in the entry.\n\tValue T\n}\n\n// NewCacheWithData constructs a new in-memory cache with existing data.\n// It also accepts CacheOption parameters to override default configuration values.\nfunc NewCacheWithData[T any](data []CacheEntry[T], opts ...CacheOption[T]) *Cache[T] {\n\tinstance := &Cache[T]{expiration: defaultExpirationInterval, purgeInterval: defaultPurgeInterval}\n\tfor _, opt := range opts {\n\t\topt(instance)\n\t}\n\n\t// Convert data slice to map required by go-cache.\n\titems := make(map[string]cache.Item, len(data))\n\tfor _, d := range data {\n\t\titems[d.Key] = cache.Item{Object: d.Value, Expiration: int64(defaultExpiration)}\n\t}\n\n\tinstance.c = cache.NewFrom(instance.expiration, instance.purgeInterval, items)\n\treturn instance\n}\n\n// Set adds a key-value pair to the cache.\nfunc (c *Cache[T]) Set(key string, value T) {\n\tc.c.Set(key, value, defaultExpiration)\n}\n\n// Get returns the value for the given key.\nfunc (c *Cache[T]) Get(key string) (T, bool) {\n\tvar value T\n\n\tv, ok := c.c.Get(key)\n\tif !ok {\n\t\treturn value, false\n\t}\n\n\tvalue, ok = v.(T)\n\treturn value, ok\n}\n\n// Exists returns true if the given key exists in the cache.\nfunc (c *Cache[T]) Exists(key string) bool {\n\t_, ok := c.c.Get(key)\n\treturn ok\n}\n\n// Delete removes the key-value pair from the cache.\nfunc (c *Cache[T]) Delete(key string) {\n\tc.c.Delete(key)\n}\n\n// Clear removes all key-value pairs from the cache.\nfunc (c *Cache[T]) Clear() {\n\tc.c.Flush()\n}\n\n// Count returns the number of key-value pairs in the cache.\nfunc (c *Cache[T]) Count() int {\n\treturn c.c.ItemCount()\n}\n\n// Keys returns all keys in the cache.\nfunc (c *Cache[T]) Keys() []string {\n\titems := c.c.Items()\n\tres := make([]string, 0, len(items))\n\tfor k := range items {\n\t\tres = append(res, k)\n\t}\n\treturn res\n}\n\n// Values returns all values in the cache.\nfunc (c *Cache[T]) Values() []T {\n\titems := c.c.Items()\n\tres := make([]T, 0, len(items))\n\tfor _, v := range items {\n\t\tobj, ok := v.Object.(T)\n\t\tif ok {\n\t\t\tres = append(res, obj)\n\t\t}\n\t}\n\treturn res\n}\n\n// Contents returns a comma-separated string containing all keys in the cache.\nfunc (c *Cache[T]) Contents() string {\n\titems := c.c.Items()\n\tres := make([]string, 0, len(items))\n\tfor k := range items {\n\t\tres = append(res, k)\n\t}\n\treturn strings.Join(res, \",\")\n}\n"
  },
  {
    "path": "pkg/cache/simple/simple_test.go",
    "content": "package simple\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n)\n\nfunc TestCache(t *testing.T) {\n\tc := NewCache[string]()\n\n\t// Test set and get.\n\tc.Set(\"key1\", \"key1\")\n\tv, ok := c.Get(\"key1\")\n\tif !ok || v != \"key1\" {\n\t\tt.Fatalf(\"Unexpected value for key1: %v, %v\", v, ok)\n\t}\n\n\t// Test exists.\n\tif !c.Exists(\"key1\") {\n\t\tt.Fatalf(\"Expected key1 to exist\")\n\t}\n\n\t// Test the count.\n\tif c.Count() != 1 {\n\t\tt.Fatalf(\"Unexpected count: %d\", c.Count())\n\t}\n\n\t// Test delete.\n\tc.Delete(\"key1\")\n\tv, ok = c.Get(\"key1\")\n\tif ok || v != \"\" {\n\t\tt.Fatalf(\"Unexpected value for key1 after delete: %v, %v\", v, ok)\n\t}\n\n\t// Test clear.\n\tc.Set(\"key10\", \"key10\")\n\tc.Clear()\n\tv, ok = c.Get(\"key10\")\n\tif ok || v != \"\" {\n\t\tt.Fatalf(\"Unexpected value for key10 after clear: %v, %v\", v, ok)\n\t}\n\n\t// Test getting only the keys.\n\tkeys := []string{\"key1\", \"key2\", \"key3\"}\n\tvalues := []string{\"value1\", \"value2\", \"value3\"}\n\tfor i, k := range keys {\n\t\tc.Set(k, values[i])\n\t}\n\tk := c.Keys()\n\tsort.Strings(keys)\n\tsort.Strings(k)\n\tif !cmp.Equal(keys, k) {\n\t\tt.Fatalf(\"Unexpected keys: %v\", k)\n\t}\n\n\t// Test getting only the values.\n\tvals := make([]string, 0, c.Count())\n\tvals = append(vals, c.Values()...)\n\tsort.Strings(vals)\n\tsort.Strings(values)\n\tif !cmp.Equal(values, vals) {\n\t\tt.Fatalf(\"Unexpected values: %v\", vals)\n\t}\n\n\t// Test contents.\n\titems := c.Contents()\n\tsort.Strings(keys)\n\tres := strings.Split(items, \",\")\n\tsort.Strings(res)\n\n\tif len(keys) != len(res) {\n\t\tt.Fatalf(\"Unexpected length of items: %d\", len(res))\n\t}\n\tif !cmp.Equal(keys, res) {\n\t\tt.Fatalf(\"Unexpected items: %v\", res)\n\t}\n}\n\nfunc TestCache_NewWithData(t *testing.T) {\n\tdata := []CacheEntry[string]{{\"key1\", \"value1\"}, {\"key2\", \"value2\"}, {\"key3\", \"value3\"}}\n\tc := NewCacheWithData(data)\n\n\t// Test the count.\n\tif c.Count() != 3 {\n\t\tt.Fatalf(\"Unexpected count: %d\", c.Count())\n\t}\n\n\t// Test contents.\n\tkeys := []string{\"key1\", \"key2\", \"key3\"}\n\titems := c.Contents()\n\tsort.Strings(keys)\n\tres := strings.Split(items, \",\")\n\tsort.Strings(res)\n\n\tif len(keys) != len(res) {\n\t\tt.Fatalf(\"Unexpected length of items: %d\", len(res))\n\t}\n\tif !cmp.Equal(keys, res) {\n\t\tt.Fatalf(\"Unexpected items: %v\", res)\n\t}\n}\n\nfunc setupBenchmarks(b *testing.B) *Cache[string] {\n\tb.Helper()\n\n\tc := NewCache[string]()\n\n\tfor i := 0; i < 500_000; i++ {\n\t\tkey := fmt.Sprintf(\"key%d\", i)\n\t\tc.Set(key, key)\n\t}\n\n\treturn c\n}\n\nfunc BenchmarkSet(b *testing.B) {\n\tc := NewCache[string]()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tkey := fmt.Sprintf(\"key%d\", i)\n\t\tc.Set(key, key)\n\t}\n}\n\nfunc BenchmarkGet(b *testing.B) {\n\tc := setupBenchmarks(b)\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tkey := fmt.Sprintf(\"key%d\", i)\n\t\tc.Get(key)\n\t}\n}\n\nfunc BenchmarkDelete(b *testing.B) {\n\tc := setupBenchmarks(b)\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tkey := fmt.Sprintf(\"key%d\", i)\n\t\tc.Delete(key)\n\t}\n}\n\nfunc BenchmarkCount(b *testing.B) {\n\tc := setupBenchmarks(b)\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tc.Count()\n\t}\n}\n\nfunc BenchmarkContents(b *testing.B) {\n\tc := setupBenchmarks(b)\n\tb.ResetTimer()\n\n\tvar s string\n\n\tfor i := 0; i < b.N; i++ {\n\t\ts = c.Contents()\n\t}\n\n\t_ = s\n}\n"
  },
  {
    "path": "pkg/channelmetrics/metrics_collector/prometheus/collector.go",
    "content": "package prometheus\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n)\n\n// MetricsCollector implements the |channelmetrics.MetricsCollector| interface using Prometheus.\n// It records various metrics related to channel operations.\ntype MetricsCollector struct {\n\tproduceDuration prometheus.Histogram\n\tconsumeDuration prometheus.Histogram\n\tchannelLen      prometheus.Gauge\n\tchannelCap      prometheus.Gauge\n}\n\nvar (\n\tcollectors   = make(map[string]*MetricsCollector)\n\tcollectorsMu sync.Mutex\n)\n\n// NewMetricsCollector creates a new MetricsCollector with\n// histograms for produce and consume durations, and gauges for channel length and capacity.\n// It accepts namespace, subsystem, and chanName parameters to organize metrics.\n// The function initializes and returns a pointer to a MetricsCollector struct\n// that contains the following Prometheus metrics:\n//\n//   - produceDuration: a Histogram metric that measures the duration of producing an item.\n//     It tracks the time taken to add an item to the ObservableChan.\n//     This metric helps to monitor the performance and latency of item production.\n//\n//   - consumeDuration: a Histogram metric that measures the duration of consuming an item.\n//     It tracks the time taken to retrieve an item from the ObservableChan.\n//     This metric helps to monitor the performance and latency of item consumption.\n//\n//   - channelLen: a Gauge metric that measures the current size of the channel buffer.\n//     It tracks the number of items in the channel buffer at any given time.\n//     This metric helps to monitor the utilization of the channel buffer.\n//\n//   - channelCap: a Gauge metric that measures the capacity of the channel buffer.\n//     It tracks the maximum number of items that the channel buffer can hold.\n//     This metric helps to understand the configuration and potential limits of the channel buffer.\n//\n// These metrics are useful for monitoring the performance and throughput of the ObservableChan.\n// By tracking the durations of item production and consumption, as well as the buffer size and capacity,\n// you can identify bottlenecks, optimize performance, and ensure that the ObservableChan is operating efficiently.\nfunc NewMetricsCollector(chanName, namespace, subsystem string) *MetricsCollector {\n\tkey := fmt.Sprintf(\"%s_%s_%s\", namespace, subsystem, chanName)\n\n\tcollectorsMu.Lock()\n\tdefer collectorsMu.Unlock()\n\n\tif collector, exists := collectors[key]; exists {\n\t\treturn collector\n\t}\n\n\tcollector := &MetricsCollector{\n\t\tproduceDuration: promauto.NewHistogram(prometheus.HistogramOpts{\n\t\t\tName:      metricName(chanName, \"produce_duration_microseconds\"),\n\t\t\tNamespace: namespace,\n\t\t\tSubsystem: subsystem,\n\t\t\tHelp:      \"Duration of producing an item in microseconds.\",\n\t\t\tBuckets:   prometheus.ExponentialBuckets(1, 2, 20),\n\t\t}),\n\t\tconsumeDuration: promauto.NewHistogram(prometheus.HistogramOpts{\n\t\t\tName:      metricName(chanName, \"consume_duration_microseconds\"),\n\t\t\tNamespace: namespace,\n\t\t\tSubsystem: subsystem,\n\t\t\tHelp:      \"Duration of consuming an item in microseconds.\",\n\t\t\tBuckets:   prometheus.ExponentialBuckets(1, 2, 20),\n\t\t}),\n\t\tchannelLen: promauto.NewGauge(prometheus.GaugeOpts{\n\t\t\tName:      metricName(chanName, \"channel_length\"),\n\t\t\tNamespace: namespace,\n\t\t\tSubsystem: subsystem,\n\t\t\tHelp:      \"Current size of the channel buffer.\",\n\t\t}),\n\t\tchannelCap: promauto.NewGauge(prometheus.GaugeOpts{\n\t\t\tName:      metricName(chanName, \"channel_capacity\"),\n\t\t\tNamespace: namespace,\n\t\t\tSubsystem: subsystem,\n\t\t\tHelp:      \"Capacity of the channel buffer.\",\n\t\t}),\n\t}\n\n\tcollectors[key] = collector\n\treturn collector\n}\n\n// metricName constructs a full metric name by combining the channel name with the specific metric.\nfunc metricName(chanName, metric string) string { return chanName + \"_\" + metric }\n\n// RecordProduceDuration records the duration taken to produce an item into the channel.\nfunc (c *MetricsCollector) RecordProduceDuration(duration time.Duration) {\n\tc.produceDuration.Observe(float64(duration.Microseconds()))\n}\n\n// RecordConsumeDuration records the duration taken to consume an item from the channel.\nfunc (c *MetricsCollector) RecordConsumeDuration(duration time.Duration) {\n\tc.consumeDuration.Observe(float64(duration.Microseconds()))\n}\n\n// RecordChannelLen records the current size of the channel buffer.\nfunc (c *MetricsCollector) RecordChannelLen(size int) { c.channelLen.Set(float64(size)) }\n\n// RecordChannelCap records the capacity of the channel buffer.\nfunc (c *MetricsCollector) RecordChannelCap(capacity int) { c.channelCap.Set(float64(capacity)) }\n"
  },
  {
    "path": "pkg/channelmetrics/noopcollector.go",
    "content": "package channelmetrics\n\nimport \"time\"\n\n// noopCollector is a default implementation of the MetricsCollector interface\n// for internal package use only.\ntype noopCollector struct{}\n\nfunc (noopCollector) RecordProduceDuration(duration time.Duration) {}\nfunc (noopCollector) RecordConsumeDuration(duration time.Duration) {}\nfunc (noopCollector) RecordChannelLen(size int)                    {}\nfunc (noopCollector) RecordChannelCap(capacity int)                {}\n"
  },
  {
    "path": "pkg/channelmetrics/observablechan.go",
    "content": "// Package channelmetrics provides a flexible way to wrap Go channels with\n// additional metrics collection capabilities. This allows for monitoring\n// and tracking of channel usage and performance using different metrics backends.\npackage channelmetrics\n\nimport (\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n// MetricsCollector is an interface for collecting metrics. Implementations\n// of this interface can be used to record various channel metrics.\ntype MetricsCollector interface {\n\tRecordProduceDuration(duration time.Duration)\n\tRecordConsumeDuration(duration time.Duration)\n\tRecordChannelLen(size int)\n\tRecordChannelCap(capacity int)\n}\n\n// ObservableChan wraps a Go channel and collects metrics about its usage.\n// It supports any type of channel and records metrics using a provided\n// MetricsCollector implementation.\ntype ObservableChan[T any] struct {\n\tch      chan T\n\tmetrics MetricsCollector\n}\n\n// NewObservableChan creates a new ObservableChan wrapping the provided channel.\n// It records the channel's capacity immediately and sets up metrics collection\n// using the provided MetricsCollector and channel name. The chanName is used to\n// distinguish between metrics for different channels by incorporating it into\n// the metric names.\nfunc NewObservableChan[T any](ch chan T, metrics MetricsCollector) *ObservableChan[T] {\n\tif metrics == nil {\n\t\tmetrics = noopCollector{}\n\t}\n\toChan := &ObservableChan[T]{\n\t\tch:      ch,\n\t\tmetrics: metrics,\n\t}\n\toChan.RecordChannelCapacity()\n\t// Record the current length of the channel.\n\t// Note: The channel is likely empty, but it may contain items if it\n\t// was pre-existing.\n\toChan.RecordChannelLen()\n\treturn oChan\n}\n\n// Close closes the channel and records the current size of the channel buffer.\nfunc (oc *ObservableChan[T]) Close() {\n\tclose(oc.ch)\n\toc.RecordChannelLen()\n}\n\n// Send sends an item into the channel and records the duration taken to do so.\n// It also updates the current size of the channel buffer. This method blocks\n// until the item is sent.\nfunc (oc *ObservableChan[T]) Send(item T) { _ = oc.SendCtx(context.Background(), item) }\n\n// SendCtx sends an item into the channel with context and records the duration\n// taken to do so. It also updates the current size of the channel buffer and\n// supports context cancellation.\nfunc (oc *ObservableChan[T]) SendCtx(ctx context.Context, item T) error {\n\tdefer func(start time.Time) {\n\t\toc.metrics.RecordProduceDuration(time.Since(start))\n\t\toc.RecordChannelLen()\n\t}(time.Now())\n\n\treturn common.CancellableWrite(ctx, oc.ch, item)\n}\n\n// Recv receives an item from the channel and records the duration taken to do\n// so. It also updates the current size of the channel buffer. This method\n// blocks until an item is available.\nfunc (oc *ObservableChan[T]) Recv() T {\n\tv, _ := oc.RecvCtx(context.Background())\n\treturn v\n}\n\n// RecvCtx receives an item from the channel with context and records the\n// duration taken to do so. It also updates the current size of the channel\n// buffer and supports context cancellation. If an error occurs, it logs the\n// error.\nfunc (oc *ObservableChan[T]) RecvCtx(ctx context.Context) (T, error) {\n\tdefer func(start time.Time) {\n\t\toc.metrics.RecordConsumeDuration(time.Since(start))\n\t\toc.RecordChannelLen()\n\t}(time.Now())\n\n\treturn common.CancellableRead(ctx, oc.ch)\n}\n\n// RecordChannelCapacity records the capacity of the channel buffer.\nfunc (oc *ObservableChan[T]) RecordChannelCapacity() { oc.metrics.RecordChannelCap(cap(oc.ch)) }\n\n// RecordChannelLen records the current size of the channel buffer.\nfunc (oc *ObservableChan[T]) RecordChannelLen() { oc.metrics.RecordChannelLen(len(oc.ch)) }\n"
  },
  {
    "path": "pkg/channelmetrics/observablechan_test.go",
    "content": "package channelmetrics\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\ntype MockMetricsCollector struct{ mock.Mock }\n\nfunc (m *MockMetricsCollector) RecordProduceDuration(duration time.Duration) { m.Called(duration) }\n\nfunc (m *MockMetricsCollector) RecordConsumeDuration(duration time.Duration) { m.Called(duration) }\n\nfunc (m *MockMetricsCollector) RecordChannelLen(size int) { m.Called(size) }\n\nfunc (m *MockMetricsCollector) RecordChannelCap(capacity int) { m.Called(capacity) }\n\nfunc TestObservableChanSend(t *testing.T) {\n\tt.Parallel()\n\n\tmockMetrics := new(MockMetricsCollector)\n\tbufferCap := 10\n\n\tmockMetrics.On(\"RecordProduceDuration\", mock.Anything).Once()\n\tmockMetrics.On(\"RecordChannelLen\", mock.AnythingOfType(\"int\")).Twice()\n\tmockMetrics.On(\"RecordChannelCap\", bufferCap).Once()\n\n\tch := make(chan int, bufferCap)\n\toc := NewObservableChan(ch, mockMetrics)\n\tassert.Equal(t, bufferCap, cap(oc.ch))\n\n\terr := oc.SendCtx(context.Background(), 1)\n\tassert.NoError(t, err)\n\n\tmockMetrics.AssertExpectations(t)\n}\n\nfunc TestObservableChanRecv(t *testing.T) {\n\tt.Parallel()\n\n\tmockMetrics := new(MockMetricsCollector)\n\tbufferCap := 10\n\n\tmockMetrics.On(\"RecordConsumeDuration\", mock.Anything).Once() // For the send\n\tmockMetrics.On(\"RecordProduceDuration\", mock.Anything).Once()\n\tmockMetrics.On(\"RecordChannelLen\", mock.AnythingOfType(\"int\")).Times(3) // For the send and recv\n\tmockMetrics.On(\"RecordChannelCap\", bufferCap).Once()\n\n\tch := make(chan int, bufferCap)\n\toc := NewObservableChan(ch, mockMetrics)\n\tassert.Equal(t, bufferCap, cap(oc.ch))\n\n\tgo func() {\n\t\terr := oc.SendCtx(context.Background(), 1)\n\t\tassert.NoError(t, err)\n\t}()\n\n\ttime.Sleep(100 * time.Millisecond) // Ensure Send happens before Recv\n\n\t_, err := oc.RecvCtx(context.Background())\n\tassert.NoError(t, err)\n\n\tmockMetrics.AssertExpectations(t)\n}\n\nfunc TestObservableChanRecordChannelCapacity(t *testing.T) {\n\tt.Parallel()\n\n\tmockMetrics := new(MockMetricsCollector)\n\tbufferCap := 10\n\n\tmockMetrics.On(\"RecordChannelCap\", bufferCap).Twice()\n\tmockMetrics.On(\"RecordChannelLen\", mock.AnythingOfType(\"int\")).Once()\n\n\tch := make(chan int, bufferCap)\n\toc := NewObservableChan(ch, mockMetrics)\n\n\toc.RecordChannelCapacity()\n\n\tmockMetrics.AssertExpectations(t)\n}\n\nfunc TestObservableChanRecordChannelLen(t *testing.T) {\n\tt.Parallel()\n\n\tmockMetrics := new(MockMetricsCollector)\n\tbufferCap := 10\n\n\tmockMetrics.On(\"RecordChannelLen\", mock.AnythingOfType(\"int\")).Twice()\n\tmockMetrics.On(\"RecordChannelCap\", bufferCap).Once()\n\n\tch := make(chan int, bufferCap)\n\toc := NewObservableChan(ch, mockMetrics)\n\n\toc.RecordChannelLen()\n\n\tmockMetrics.AssertExpectations(t)\n}\n\nfunc TestObservableChan_Close(t *testing.T) {\n\tt.Parallel()\n\n\tmockMetrics := new(MockMetricsCollector)\n\tbufferCap := 1\n\n\tmockMetrics.On(\"RecordChannelCap\", bufferCap).Once()\n\tmockMetrics.On(\"RecordChannelLen\", mock.AnythingOfType(\"int\")).Twice()\n\n\tch := make(chan int, bufferCap)\n\toc := NewObservableChan(ch, mockMetrics)\n\n\toc.Close()\n\n\tmockMetrics.AssertExpectations(t)\n}\n\nfunc TestObservableChanClosed(t *testing.T) {\n\tt.Parallel()\n\n\tch := make(chan int)\n\tclose(ch)\n\toc := NewObservableChan(ch, nil)\n\n\tctx, cancel := context.WithCancel(context.Background())\n\t// Closed channel should return with an error.\n\tv, err := oc.RecvCtx(ctx)\n\tassert.Error(t, err)\n\tassert.Equal(t, 0, v)\n\n\t// Cancelled context should also return with an error.\n\tcancel()\n\t_, err = oc.RecvCtx(ctx)\n\tassert.Error(t, err)\n}\n"
  },
  {
    "path": "pkg/cleantemp/cleantemp.go",
    "content": "package cleantemp\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/mitchellh/go-ps\"\n\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nconst (\n\tdefaultExecPath             = \"trufflehog\"\n\tdefaultArtifactPrefixFormat = \"%s-%d-\"\n)\n\n// MkdirTemp returns a temporary directory path formatted as:\n// trufflehog-<pid>-<randint>\nfunc MkdirTemp() (string, error) {\n\tpid := os.Getpid()\n\ttmpdir := fmt.Sprintf(defaultArtifactPrefixFormat, defaultExecPath, pid)\n\tdir, err := os.MkdirTemp(os.TempDir(), tmpdir)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn dir, nil\n}\n\n// Unlike MkdirTemp, we only want to generate the filename string.\n// The tempfile creation in trufflehog we're interested in\n// is generally handled by \"github.com/trufflesecurity/disk-buffer-reader\"\nfunc MkFilename() string {\n\tpid := os.Getpid()\n\tfilename := fmt.Sprintf(defaultArtifactPrefixFormat, defaultExecPath, pid)\n\treturn filename\n}\n\n// Only compile during startup.\nvar trufflehogRE = regexp.MustCompile(`^trufflehog-\\d+-\\d+$`)\n\n// CleanTempArtifacts deletes orphaned temp directories and files that do not contain running PID values.\nfunc CleanTempArtifacts(ctx logContext.Context) error {\n\texecutablePath, err := os.Executable()\n\tif err != nil {\n\t\texecutablePath = defaultExecPath\n\t}\n\texecName := filepath.Base(executablePath)\n\n\tvar pids []string\n\tprocs, err := ps.Processes()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting jobs PIDs: %w\", err)\n\t}\n\n\tfor _, proc := range procs {\n\t\tif proc.Executable() == execName {\n\t\t\tpids = append(pids, strconv.Itoa(proc.Pid()))\n\t\t}\n\t}\n\n\tif len(pids) == 0 {\n\t\tctx.Logger().V(5).Info(\"No trufflehog processes were found\")\n\t\treturn nil\n\t}\n\n\ttempDir := os.TempDir()\n\tdir, err := os.Open(tempDir)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error opening temp dir: %w\", err)\n\t}\n\tdefer dir.Close()\n\n\tfor {\n\t\tentries, err := dir.ReadDir(1) // read only one entry\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tentry := entries[0]\n\n\t\tif trufflehogRE.MatchString(entry.Name()) {\n\n\t\t\t// Mark these artifacts initially as ones that should be deleted.\n\t\t\tshouldDelete := true\n\t\t\t// Check if the name matches any live PIDs.\n\t\t\t// Potential race condition here if a PID is started and creates tmp data after the initial check.\n\t\t\tfor _, pidval := range pids {\n\t\t\t\tif strings.Contains(entry.Name(), fmt.Sprintf(\"-%s-\", pidval)) {\n\t\t\t\t\tshouldDelete = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif shouldDelete {\n\t\t\t\tpath := filepath.Join(tempDir, entry.Name())\n\t\t\t\tisDir := entry.IsDir()\n\t\t\t\tif isDir {\n\t\t\t\t\terr = os.RemoveAll(path)\n\t\t\t\t} else {\n\t\t\t\t\terr = os.Remove(path)\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error deleting temp artifact (dir: %v) %s: %w\", isDir, path, err)\n\t\t\t\t}\n\n\t\t\t\tctx.Logger().V(4).Info(\"Deleted orphaned temp artifact\", \"artifact\", path)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// CleanTempDirsForLegacyJSON removes all directories that start with \"trufflehog-\"\n// from either the provided clonePath (if not empty) or the OS temp directory.\nfunc CleanTempDirsForLegacyJSON(baseDir string) error {\n\t// If no custom clone path was provided, clean repos from the OS temp directory\n\t// since that's where they were cloned during the scan.\n\tif baseDir == \"\" {\n\t\tbaseDir = os.TempDir()\n\t}\n\n\tentries, err := os.ReadDir(baseDir)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() && strings.HasPrefix(entry.Name(), \"trufflehog-\") {\n\t\t\tfullPath := filepath.Join(baseDir, entry.Name())\n\t\t\tif err := os.RemoveAll(fullPath); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cleantemp/cleantemp_test.go",
    "content": "package cleantemp\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/mitchellh/go-ps\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestExecName(t *testing.T) {\n\texecutablePath, err := os.Executable()\n\tassert.Nil(t, err)\n\texecName := filepath.Base(executablePath)\n\tassert.Equal(t, \"cleantemp.test\", execName)\n\n\tprocs, err := ps.Processes()\n\tassert.Nil(t, err)\n\tassert.NotEmpty(t, procs)\n\n\tfound := false\n\tfor _, proc := range procs {\n\t\tif proc.Executable() == execName {\n\t\t\tfound = true\n\t\t}\n\t}\n\n\tassert.True(t, found)\n}\n\nfunc TestCleanTempDirsForLegacyJSON(t *testing.T) {\n\tbaseDir := t.TempDir()\n\n\t// Create dirs that should be deleted\n\tdir1 := filepath.Join(baseDir, \"trufflehog-123\")\n\tdir2 := filepath.Join(baseDir, \"trufflehog-456\")\n\tassert.NoError(t, os.Mkdir(dir1, 0o755))\n\tassert.NoError(t, os.Mkdir(dir2, 0o755))\n\n\t// Create dirs that should NOT be deleted\n\tkeepDir := filepath.Join(baseDir, \"keepme-123\")\n\tassert.NoError(t, os.Mkdir(keepDir, 0o755))\n\n\t// Create a file with trufflehog- prefix (should not be deleted because only dirs are deleted)\n\tkeepFile := filepath.Join(baseDir, \"trufflehog-file\")\n\tassert.NoError(t, os.WriteFile(keepFile, []byte(\"data\"), 0o644))\n\n\terr := CleanTempDirsForLegacyJSON(baseDir)\n\tassert.NoError(t, err)\n\n\t_, err = os.Stat(dir1)\n\tassert.True(t, os.IsNotExist(err))\n\t_, err = os.Stat(dir2)\n\tassert.True(t, os.IsNotExist(err))\n\n\t_, err = os.Stat(keepDir)\n\tassert.NoError(t, err)\n\n\t_, err = os.Stat(keepFile)\n\tassert.NoError(t, err)\n}\n"
  },
  {
    "path": "pkg/common/context.go",
    "content": "package common\n\nimport \"context\"\n\n// ChannelClosedErr indicates that a read was performed from a closed channel.\ntype ChannelClosedErr struct{}\n\nfunc (ChannelClosedErr) Error() string { return \"channel is closed\" }\n\nfunc IsDone(ctx context.Context) bool {\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// CancellableWrite blocks on writing the item to the channel but can be\n// cancelled by the context. If both the context is cancelled and the channel\n// write would succeed, either operation will be performed randomly, however\n// priority is given to context cancellation.\nfunc CancellableWrite[T any](ctx context.Context, ch chan<- T, item T) error {\n\tselect {\n\tcase <-ctx.Done(): // priority to context cancellation\n\t\treturn ctx.Err()\n\tdefault:\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\tcase ch <- item:\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\n// CancellableRead blocks on receiving an item from the channel but can be\n// cancelled by the context. If the channel is closed, a ChannelClosedErr is\n// returned. If both the context is cancelled and the channel read would\n// succeed, either operation will be performed randomly, however priority is\n// given to context cancellation.\nfunc CancellableRead[T any](ctx context.Context, ch <-chan T) (T, error) {\n\tvar zero T // zero value of type T\n\n\tselect {\n\tcase <-ctx.Done(): // priority to context cancellation\n\t\treturn zero, ctx.Err()\n\tdefault:\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn zero, ctx.Err()\n\t\tcase item, ok := <-ch:\n\t\t\tif !ok {\n\t\t\t\treturn item, ChannelClosedErr{}\n\t\t\t}\n\t\t\treturn item, nil\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/common/export_error.go",
    "content": "package common\n\n// ExportError is an implementation of error that can be JSON marshalled. It\n// must be a public exported type for this reason.\ntype ExportError string\n\nfunc (e ExportError) Error() string { return string(e) }\n\n// ExportErrors converts a list of errors into []ExportError.\nfunc ExportErrors(errs ...error) []error {\n\toutput := make([]error, 0, len(errs))\n\tfor _, err := range errs {\n\t\toutput = append(output, ExportError(err.Error()))\n\t}\n\treturn output\n}\n"
  },
  {
    "path": "pkg/common/filter.go",
    "content": "package common\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\ntype Filter struct {\n\tinclude *FilterRuleSet\n\texclude *FilterRuleSet\n}\n\ntype FilterRuleSet []regexp.Regexp\n\n// FilterEmpty returns a Filter that always passes.\nfunc FilterEmpty() *Filter {\n\tfilter, err := FilterFromFiles(\"\", \"\")\n\tif err != nil {\n\t\tcontext.Background().Logger().Error(err, \"could not create empty filter\")\n\t\tos.Exit(1)\n\t}\n\treturn filter\n}\n\n// FilterFromFiles creates a Filter using the rules in the provided include and exclude files.\nfunc FilterFromFiles(includeFilterPath, excludeFilterPath string) (*Filter, error) {\n\tincludeRules, err := FilterRulesFromFile(includeFilterPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not create include rules: %s\", err)\n\t}\n\texcludeRules, err := FilterRulesFromFile(excludeFilterPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not create exclude rules: %s\", err)\n\t}\n\n\t// If no includeFilterPath is provided, every pattern should pass the include rules.\n\tif includeFilterPath == \"\" {\n\t\tincludeRules = &FilterRuleSet{*regexp.MustCompile(\"\")}\n\t}\n\n\tfilter := &Filter{\n\t\tinclude: includeRules,\n\t\texclude: excludeRules,\n\t}\n\n\treturn filter, nil\n}\n\n// FilterRulesFromFile loads the list of regular expression filter rules in `source` and creates a FilterRuleSet.\nfunc FilterRulesFromFile(source string) (*FilterRuleSet, error) {\n\trules := FilterRuleSet{}\n\tif source == \"\" {\n\t\treturn &rules, nil\n\t}\n\n\tcommentPattern := regexp.MustCompile(`^\\s*#`)\n\temptyLinePattern := regexp.MustCompile(`^\\s*$`)\n\n\tfile, err := os.Open(source)\n\tlogger := context.Background().Logger().WithValues(\"file\", source)\n\tif err != nil {\n\t\tlogger.Error(err, \"unable to open filter file\", \"file\", source)\n\t\tos.Exit(1)\n\t}\n\tdefer func(file *os.File) {\n\t\terr := file.Close()\n\t\tif err != nil {\n\t\t\tlogger.Error(err, \"unable to close filter file\")\n\t\t\tos.Exit(1)\n\t\t}\n\t}(file)\n\n\tscanner := bufio.NewScanner(file)\n\tfor scanner.Scan() {\n\t\tline := scanner.Text()\n\t\tif commentPattern.MatchString(line) {\n\t\t\tcontinue\n\t\t}\n\t\tif emptyLinePattern.MatchString(line) {\n\t\t\tcontinue\n\t\t}\n\t\tpattern, err := regexp.Compile(line)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"can not compile regular expression: %s\", line)\n\t\t}\n\t\trules = append(rules, *pattern)\n\t}\n\treturn &rules, nil\n}\n\n// Pass returns true if the include FilterRuleSet matches the pattern and the exclude FilterRuleSet does not match.\nfunc (filter *Filter) Pass(object string) bool {\n\tif filter == nil {\n\t\treturn true\n\t}\n\n\texcluded := filter.exclude.Matches(object)\n\tincluded := filter.include.Matches(object)\n\n\treturn !excluded && included\n}\n\n// Matches will return true if any of the regular expressions in the FilterRuleSet match the pattern.\nfunc (rules *FilterRuleSet) Matches(object string) bool {\n\tif rules == nil {\n\t\treturn false\n\t}\n\tfor _, rule := range *rules {\n\t\tif rule.MatchString(object) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// ShouldExclude return true if any regular expressions in the exclude FilterRuleSet matches the path.\nfunc (filter *Filter) ShouldExclude(path string) bool {\n\treturn filter.exclude.Matches(path)\n}\n"
  },
  {
    "path": "pkg/common/filter_test.go",
    "content": "package common\n\nimport (\n\t\"os\"\n\t\"regexp\"\n\t\"testing\"\n)\n\nfunc TestFilterBasic(t *testing.T) {\n\ttype filterTest struct {\n\t\tfilter  Filter\n\t\tpattern string\n\t\tpass    bool\n\t}\n\ttests := map[string]filterTest{\n\t\t\"IncludePassed\": {\n\t\t\tfilter: Filter{\n\t\t\t\tinclude: &FilterRuleSet{*regexp.MustCompile(\"test\")},\n\t\t\t},\n\t\t\tpattern: \"teststring\",\n\t\t\tpass:    true,\n\t\t},\n\t\t\"IncludeFiltered\": {\n\t\t\tfilter: Filter{\n\t\t\t\tinclude: &FilterRuleSet{*regexp.MustCompile(\"nomatch\")},\n\t\t\t},\n\t\t\tpattern: \"teststring\",\n\t\t\tpass:    false,\n\t\t},\n\t\t\"ExcludePassed\": {\n\t\t\tfilter: Filter{\n\t\t\t\tinclude: &FilterRuleSet{*regexp.MustCompile(\"\")},\n\t\t\t\texclude: &FilterRuleSet{*regexp.MustCompile(\"nomatch\")},\n\t\t\t},\n\t\t\tpattern: \"teststring\",\n\t\t\tpass:    true,\n\t\t},\n\t\t\"ExcludeFiltered\": {\n\t\t\tfilter: Filter{\n\t\t\t\tinclude: &FilterRuleSet{*regexp.MustCompile(\"\")},\n\t\t\t\texclude: &FilterRuleSet{*regexp.MustCompile(\"test\")},\n\t\t\t},\n\t\t\tpattern: \"teststring\",\n\t\t\tpass:    false,\n\t\t},\n\t\t\"IncludeExcludeDifferentPass\": {\n\t\t\tfilter: Filter{\n\t\t\t\tinclude: &FilterRuleSet{*regexp.MustCompile(\"test\")},\n\t\t\t\texclude: &FilterRuleSet{*regexp.MustCompile(\"nomatch\")},\n\t\t\t},\n\t\t\tpattern: \"teststring\",\n\t\t\tpass:    true,\n\t\t},\n\t\t\"IncludeExcludeDifferentFiltered\": {\n\t\t\tfilter: Filter{\n\t\t\t\tinclude: &FilterRuleSet{*regexp.MustCompile(\"nomatch\")},\n\t\t\t\texclude: &FilterRuleSet{*regexp.MustCompile(\"test\")},\n\t\t\t},\n\t\t\tpattern: \"teststring\",\n\t\t\tpass:    false,\n\t\t},\n\t\t\"IncludeExcludeSameFiltered\": {\n\t\t\tfilter: Filter{\n\t\t\t\tinclude: &FilterRuleSet{*regexp.MustCompile(\"test\")},\n\t\t\t\texclude: &FilterRuleSet{*regexp.MustCompile(\"test\")},\n\t\t\t},\n\t\t\tpattern: \"teststring\",\n\t\t\tpass:    false,\n\t\t},\n\t}\n\n\tfor name, test := range tests {\n\t\tif test.filter.Pass(test.pattern) != test.pass {\n\t\t\tt.Errorf(\"%s: unexpected filter result. pattern: %q, pass: %t\", name, test.pattern, !test.pass)\n\t\t}\n\t}\n}\n\nfunc TestFilterFromFile(t *testing.T) {\n\ttype filterTest struct {\n\t\tincludeFile         bool\n\t\texcludeFile         bool\n\t\tincludeFileContents string\n\t\texcludeFileContents string\n\t\tpattern             string\n\t\tpass                bool\n\t}\n\ttests := map[string]filterTest{\n\t\t\"includeFileOnlyPass\": {\n\t\t\tincludeFile:         true,\n\t\t\texcludeFile:         false,\n\t\t\tincludeFileContents: \"test\",\n\t\t\tpattern:             \"test\",\n\t\t\tpass:                true,\n\t\t},\n\t\t\"includeFileOnlyFiltered\": {\n\t\t\tincludeFile:         true,\n\t\t\texcludeFile:         false,\n\t\t\tincludeFileContents: \"nomatch\",\n\t\t\tpattern:             \"test\",\n\t\t\tpass:                false,\n\t\t},\n\t\t\"includeFileEmptyFiltered\": {\n\t\t\tincludeFile:         true,\n\t\t\texcludeFile:         false,\n\t\t\tincludeFileContents: \"\",\n\t\t\tpattern:             \"test\",\n\t\t\tpass:                false,\n\t\t},\n\t\t\"excludeFileOnlyPass\": {\n\t\t\tincludeFile:         false,\n\t\t\texcludeFile:         true,\n\t\t\texcludeFileContents: \"nomatch\",\n\t\t\tpattern:             \"test\",\n\t\t\tpass:                true,\n\t\t},\n\t\t\"excludeFileOnlyFiltered\": {\n\t\t\tincludeFile:         false,\n\t\t\texcludeFile:         true,\n\t\t\texcludeFileContents: \"test\",\n\t\t\tpattern:             \"test\",\n\t\t\tpass:                false,\n\t\t},\n\t\t\"BothFilesEmptyExcludeFiltered\": {\n\t\t\tincludeFile:         true,\n\t\t\texcludeFile:         true,\n\t\t\texcludeFileContents: \"\",\n\t\t\tincludeFileContents: \"\",\n\t\t\tpattern:             \"test\",\n\t\t\tpass:                false,\n\t\t},\n\t\t\"EmptyLinesAreIgnored\": {\n\t\t\tincludeFile:         false,\n\t\t\texcludeFile:         true,\n\t\t\texcludeFileContents: \" \\ntest.txt\",\n\t\t\tpattern:             \"hello world.txt\",\n\t\t\tpass:                true,\n\t\t},\n\t}\n\tfor name, test := range tests {\n\t\tvar includeTestFile, excludeTestFile string\n\t\tif test.includeFile {\n\t\t\tincludeTestFile = \"/tmp/trufflehog_test_ifilter.txt\"\n\t\t\tif err := testFilterWriteFile(includeTestFile, []byte(test.includeFileContents)); err != nil {\n\t\t\t\tt.Fatalf(\"failed to create include rules file: %s\", err)\n\t\t\t}\n\t\t\tdefer os.Remove(includeTestFile)\n\t\t}\n\t\tif test.excludeFile {\n\t\t\texcludeTestFile = \"/tmp/trufflehog_test_xfilter.txt\"\n\t\t\tif err := testFilterWriteFile(excludeTestFile, []byte(test.excludeFileContents)); err != nil {\n\t\t\t\tt.Fatalf(\"failed to create include rules file: %s\", err)\n\t\t\t}\n\t\t\tdefer os.Remove(excludeTestFile)\n\t\t}\n\n\t\tfilter, err := FilterFromFiles(includeTestFile, excludeTestFile)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"failed to create filter from files: %s\", err)\n\t\t}\n\n\t\tif filter.Pass(test.pattern) != test.pass {\n\t\t\tt.Errorf(\"%s: unexpected filter result. pattern: %q, pass: %t\", name, test.pattern, !test.pass)\n\t\t}\n\t}\n}\n\nfunc testFilterWriteFile(filename string, content []byte) error {\n\tf, err := os.Create(filename)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = f.Write(content)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn f.Close()\n}\n"
  },
  {
    "path": "pkg/common/glob/glob.go",
    "content": "package glob\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gobwas/glob\"\n)\n\n// Filter is a generic filter for excluding and including globs (limited\n// regular expressions). Exclusion takes precedence if both include and exclude\n// lists are provided.\ntype Filter struct {\n\texclude []glob.Glob\n\tinclude []glob.Glob\n}\n\ntype globFilterOpt func(*Filter) error\n\n// WithExcludeGlobs adds exclude globs to the filter.\nfunc WithExcludeGlobs(excludes ...string) globFilterOpt {\n\treturn func(f *Filter) error {\n\t\tfor _, exclude := range excludes {\n\t\t\tg, err := glob.Compile(exclude)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"invalid exclude glob %q: %w\", exclude, err)\n\t\t\t}\n\t\t\tf.exclude = append(f.exclude, g)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// WithIncludeGlobs adds include globs to the filter.\nfunc WithIncludeGlobs(includes ...string) globFilterOpt {\n\treturn func(f *Filter) error {\n\t\tfor _, include := range includes {\n\t\t\tg, err := glob.Compile(include)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"invalid include glob %q: %w\", include, err)\n\t\t\t}\n\t\t\tf.include = append(f.include, g)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// NewGlobFilter creates a new Filter with the provided options.\nfunc NewGlobFilter(opts ...globFilterOpt) (*Filter, error) {\n\tfilter := &Filter{}\n\tfor _, opt := range opts {\n\t\tif err := opt(filter); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn filter, nil\n}\n\n// ShouldInclude returns whether the object is in the include list or not in\n// the exclude list (exclude taking precedence).\nfunc (f *Filter) ShouldInclude(object string) bool {\n\tif f == nil {\n\t\treturn true\n\t}\n\texclude, include := len(f.exclude), len(f.include)\n\tif exclude == 0 && include == 0 {\n\t\treturn true\n\t} else if exclude > 0 && include == 0 {\n\t\treturn f.shouldIncludeFromExclude(object)\n\t} else if exclude == 0 && include > 0 {\n\t\treturn f.shouldIncludeFromInclude(object)\n\t} else {\n\t\tif ok, err := f.shouldIncludeFromBoth(object); err == nil {\n\t\t\treturn ok\n\t\t}\n\t\t// Ambiguous case.\n\t\treturn false\n\t}\n}\n\n// shouldIncludeFromExclude checks for explicitly excluded paths. This should\n// only be called when the include list is empty.\nfunc (f *Filter) shouldIncludeFromExclude(object string) bool {\n\tfor _, g := range f.exclude {\n\t\tif g.Match(object) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// shouldIncludeFromInclude checks for explicitly included paths. This should\n// only be called when the exclude list is empty.\nfunc (f *Filter) shouldIncludeFromInclude(object string) bool {\n\tfor _, g := range f.include {\n\t\tif g.Match(object) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// shouldIncludeFromBoth checks for either excluded or included paths. Exclusion\n// takes precedence. If neither list contains the object, true is returned.\nfunc (f *Filter) shouldIncludeFromBoth(object string) (bool, error) {\n\t// Exclude takes precedence. If we find the object in the exclude list,\n\t// we should not match.\n\tfor _, g := range f.exclude {\n\t\tif g.Match(object) {\n\t\t\treturn false, nil\n\t\t}\n\t}\n\t// If we find the object in the include list, we should match.\n\tfor _, g := range f.include {\n\t\tif g.Match(object) {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\t// If we find it in neither, return an error to let the caller decide.\n\treturn false, fmt.Errorf(\"ambiguous match\")\n}\n"
  },
  {
    "path": "pkg/common/glob/glob_test.go",
    "content": "package glob\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"pgregory.net/rapid\"\n)\n\ntype globTest struct {\n\tinput         string\n\tshouldInclude bool\n}\n\nfunc testGlobs(t *testing.T, filter *Filter, tests ...globTest) {\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input, func(t *testing.T) {\n\t\t\t// Invert because mentally it's easier to say whether an\n\t\t\t// input should be included.\n\t\t\tassert.Equal(t, tt.shouldInclude, filter.ShouldInclude(tt.input))\n\t\t})\n\t}\n}\n\nfunc TestGlobFilterExclude(t *testing.T) {\n\tfilter, err := NewGlobFilter(WithExcludeGlobs(\"foo\", \"bar*\"))\n\tassert.NoError(t, err)\n\n\ttestGlobs(t, filter,\n\t\tglobTest{\"foo\", false},\n\t\tglobTest{\"bar\", false},\n\t\tglobTest{\"bara\", false},\n\t\tglobTest{\"barb\", false},\n\t\tglobTest{\"barbosa\", false},\n\t\tglobTest{\"foobar\", true},\n\t\tglobTest{\"food\", true},\n\t\tglobTest{\"anything else\", true},\n\t)\n}\n\nfunc TestGlobFilterInclude(t *testing.T) {\n\tfilter, err := NewGlobFilter(WithIncludeGlobs(\"foo\", \"bar*\"))\n\tassert.NoError(t, err)\n\n\ttestGlobs(t, filter,\n\t\tglobTest{\"foo\", true},\n\t\tglobTest{\"bar\", true},\n\t\tglobTest{\"bara\", true},\n\t\tglobTest{\"barb\", true},\n\t\tglobTest{\"barbosa\", true},\n\t\tglobTest{\"foobar\", false},\n\t\tglobTest{\"food\", false},\n\t\tglobTest{\"anything else\", false},\n\t)\n}\n\nfunc TestGlobFilterEmpty(t *testing.T) {\n\tfilter, err := NewGlobFilter()\n\tassert.NoError(t, err)\n\n\ttestGlobs(t, filter,\n\t\tglobTest{\"foo\", true},\n\t\tglobTest{\"bar\", true},\n\t\tglobTest{\"bara\", true},\n\t\tglobTest{\"barb\", true},\n\t\tglobTest{\"barbosa\", true},\n\t\tglobTest{\"foobar\", true},\n\t\tglobTest{\"food\", true},\n\t\tglobTest{\"anything else\", true},\n\t)\n}\n\nfunc TestGlobFilterExcludeInclude(t *testing.T) {\n\tfilter, err := NewGlobFilter(WithExcludeGlobs(\"/foo/bar/**\"), WithIncludeGlobs(\"/foo/**\"))\n\tassert.NoError(t, err)\n\n\ttestGlobs(t, filter,\n\t\tglobTest{\"/foo/a\", true},\n\t\tglobTest{\"/foo/b\", true},\n\t\tglobTest{\"/foo/c/d/e\", true},\n\t\tglobTest{\"/foo/bar/a\", false},\n\t\tglobTest{\"/foo/bar/b\", false},\n\t\tglobTest{\"/foo/bar/c/d/e\", false},\n\t\tglobTest{\"/any/other/path\", false},\n\t)\n}\n\nfunc TestGlobFilterExcludePrecedence(t *testing.T) {\n\tfilter, err := NewGlobFilter(WithExcludeGlobs(\"foo\"), WithIncludeGlobs(\"foo*\"))\n\tassert.NoError(t, err)\n\n\ttestGlobs(t, filter,\n\t\tglobTest{\"foo\", false},\n\t\tglobTest{\"foobar\", true},\n\t)\n}\n\nfunc TestGlobErrorContainsGlob(t *testing.T) {\n\tinvalidGlob := \"[this is invalid because it doesn't close the capture group\"\n\t_, err := NewGlobFilter(WithExcludeGlobs(invalidGlob))\n\tassert.Error(t, err)\n\tassert.Contains(t, err.Error(), invalidGlob)\n}\n\n// The filters in this test should be mutually exclusive because one includes\n// and the other excludes the same glob.\nfunc TestGlobInverse(t *testing.T) {\n\tfor _, glob := range []string{\n\t\t\"a\",\n\t\t\"a*\",\n\t\t\"a**\",\n\t\t\"*a\",\n\t\t\"**a\",\n\t\t\"*\",\n\t} {\n\t\tinclude, err := NewGlobFilter(WithIncludeGlobs(glob))\n\t\tassert.NoError(t, err)\n\t\texclude, err := NewGlobFilter(WithExcludeGlobs(glob))\n\t\tassert.NoError(t, err)\n\t\trapid.Check(t, func(t *rapid.T) {\n\t\t\tinput := rapid.String().Draw(t, \"input\")\n\t\t\ta, b := include.ShouldInclude(input), exclude.ShouldInclude(input)\n\t\t\tif a == b {\n\t\t\t\tt.Fatalf(\"Filter(Include(%q)) == Filter(Exclude(%q)) == %v for input %q\", glob, glob, a, input)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGlobDefaultFilters(t *testing.T) {\n\tfor _, filter := range []*Filter{nil, {}} {\n\t\trapid.Check(t, func(t *rapid.T) {\n\t\t\tif !filter.ShouldInclude(rapid.String().Draw(t, \"input\")) {\n\t\t\t\tt.Fatalf(\"filter %#v did not include input\", filter)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/common/http.go",
    "content": "package common\n\nimport (\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/hashicorp/go-retryablehttp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/feature\"\n)\n\nvar caCerts = []string{\n\t// \tCN = ISRG Root X1\n\t// TODO: Expires Monday, June 4, 2035 at 4:04:38 AM Pacific\n\t`\n-----BEGIN CERTIFICATE-----\nMIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\nTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\ncmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\nWhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\nZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\nMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\nh77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\nA5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\nT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\nB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\nB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\nKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\nOlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\njh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\nqHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\nrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\nHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\nhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\nubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\nNFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\nORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\nTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\njNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\noyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\nmRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\nemyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n-----END CERTIFICATE-----\n`,\n\t// \tCN = ISRG Root X2\n\t// TODO: Expires September 17, 2040 at 9:00:00 AM Pacific Daylight Time\n\t`\n-----BEGIN CERTIFICATE-----\nMIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw\nCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg\nR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00\nMDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT\nZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw\nEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW\n+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9\nItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T\nAQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI\nzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW\ntL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1\n/q4AaOeMSQ+2b1tbFfLn\n-----END CERTIFICATE-----\n`,\n}\n\nfunc PinnedCertPool() *x509.CertPool {\n\ttrustedCerts := x509.NewCertPool()\n\tfor _, cert := range caCerts {\n\t\ttrustedCerts.AppendCertsFromPEM([]byte(strings.TrimSpace(cert)))\n\t}\n\treturn trustedCerts\n}\n\ntype FakeTransport struct {\n\tCreateResponse func(req *http.Request) (*http.Response, error)\n}\n\nfunc (t FakeTransport) RoundTrip(req *http.Request) (*http.Response, error) {\n\treturn t.CreateResponse(req)\n}\n\ntype CustomTransport struct {\n\tT http.RoundTripper\n}\n\nfunc UserAgent() string {\n\tif len(feature.UserAgentSuffix.Load()) > 0 {\n\t\treturn \"TruffleHog \" + feature.UserAgentSuffix.Load()\n\t}\n\treturn \"TruffleHog\"\n}\n\nfunc (t *CustomTransport) RoundTrip(req *http.Request) (*http.Response, error) {\n\treq.Header.Add(\"User-Agent\", UserAgent())\n\treturn t.T.RoundTrip(req)\n}\n\nfunc NewCustomTransport(T http.RoundTripper) *CustomTransport {\n\tif T == nil {\n\t\tT = http.DefaultTransport\n\t}\n\treturn &CustomTransport{T}\n}\n\ntype InstrumentedTransport struct {\n\tT http.RoundTripper\n}\n\nfunc (t *InstrumentedTransport) RoundTrip(req *http.Request) (*http.Response, error) {\n\n\tsanitizedURL := sanitizeURL(req.URL.String())\n\n\t// increment counter for the URL\n\trecordHTTPRequest(sanitizedURL)\n\n\t// Record start time for latency measurement\n\tstart := time.Now()\n\n\tresp, err := t.T.RoundTrip(req)\n\n\t// Time the latency\n\tduration := time.Since(start)\n\n\tif err != nil {\n\t\trecordNetworkError(sanitizedURL)\n\t\treturn nil, err\n\t}\n\n\tif resp != nil {\n\t\t// record latency, response size and increment counter for non-200 status code\n\t\trecordHTTPResponse(sanitizedURL, resp.StatusCode, duration.Seconds(), resp.ContentLength)\n\t}\n\n\treturn resp, err\n}\n\nfunc NewInstrumentedTransport(T http.RoundTripper) *InstrumentedTransport {\n\tif T == nil {\n\t\tT = http.DefaultTransport\n\t}\n\treturn &InstrumentedTransport{T}\n}\n\nfunc ConstantResponseHttpClient(statusCode int, body string) *http.Client {\n\treturn &http.Client{\n\t\tTimeout: DefaultResponseTimeout,\n\t\tTransport: FakeTransport{\n\t\t\tCreateResponse: func(req *http.Request) (*http.Response, error) {\n\t\t\t\treturn &http.Response{\n\t\t\t\t\tRequest:    req,\n\t\t\t\t\tBody:       io.NopCloser(strings.NewReader(body)),\n\t\t\t\t\tStatusCode: statusCode,\n\t\t\t\t}, nil\n\t\t\t},\n\t\t},\n\t}\n}\n\n// ClientOption configures how we set up the client.\ntype ClientOption func(*retryablehttp.Client)\n\n// WithCheckRetry allows setting a custom CheckRetry policy.\nfunc WithCheckRetry(cr retryablehttp.CheckRetry) ClientOption {\n\treturn func(c *retryablehttp.Client) { c.CheckRetry = cr }\n}\n\n// WithBackoff allows setting a custom backoff policy.\nfunc WithBackoff(b retryablehttp.Backoff) ClientOption {\n\treturn func(c *retryablehttp.Client) { c.Backoff = b }\n}\n\n// WithTimeout allows setting a custom timeout.\nfunc WithTimeout(timeout time.Duration) ClientOption {\n\treturn func(c *retryablehttp.Client) { c.HTTPClient.Timeout = timeout }\n}\n\n// WithMaxRetries allows setting a custom maximum number of retries.\nfunc WithMaxRetries(retries int) ClientOption {\n\treturn func(c *retryablehttp.Client) { c.RetryMax = retries }\n}\n\n// WithRetryWaitMin allows setting a custom minimum retry wait.\nfunc WithRetryWaitMin(wait time.Duration) ClientOption {\n\treturn func(c *retryablehttp.Client) { c.RetryWaitMin = wait }\n}\n\n// WithRetryWaitMax allows setting a custom maximum retry wait.\nfunc WithRetryWaitMax(wait time.Duration) ClientOption {\n\treturn func(c *retryablehttp.Client) { c.RetryWaitMax = wait }\n}\n\nfunc PinnedRetryableHttpClient() *http.Client {\n\thttpClient := retryablehttp.NewClient()\n\thttpClient.Logger = nil\n\thttpClient.HTTPClient.Transport = NewInstrumentedTransport(NewCustomTransport(&http.Transport{\n\t\tTLSClientConfig: &tls.Config{\n\t\t\tRootCAs: PinnedCertPool(),\n\t\t},\n\t\tProxy: http.ProxyFromEnvironment,\n\t\tDialContext: (&net.Dialer{\n\t\t\tTimeout:   30 * time.Second,\n\t\t\tKeepAlive: 30 * time.Second,\n\t\t}).DialContext,\n\t\tForceAttemptHTTP2:     true,\n\t\tMaxIdleConns:          100,\n\t\tIdleConnTimeout:       90 * time.Second,\n\t\tTLSHandshakeTimeout:   10 * time.Second,\n\t\tExpectContinueTimeout: 1 * time.Second,\n\t}))\n\treturn httpClient.StandardClient()\n}\n\nfunc RetryableHTTPClient(opts ...ClientOption) *http.Client {\n\thttpClient := retryablehttp.NewClient()\n\thttpClient.RetryMax = 3\n\thttpClient.Logger = nil\n\thttpClient.HTTPClient.Transport = NewInstrumentedTransport(NewCustomTransport(nil))\n\n\tfor _, opt := range opts {\n\t\topt(httpClient)\n\t}\n\treturn httpClient.StandardClient()\n}\n\n// RetryableHTTPClientTimeout returns a new http client with a specified timeout and RoundTripper transport\nfunc RetryableHTTPClientTimeout(timeOutSeconds int64, opts ...ClientOption) *http.Client {\n\thttpClient := retryablehttp.NewClient()\n\thttpClient.RetryMax = 3\n\thttpClient.Logger = nil\n\thttpClient.HTTPClient.Timeout = time.Duration(timeOutSeconds) * time.Second\n\thttpClient.HTTPClient.Transport = NewInstrumentedTransport(NewCustomTransport(nil))\n\n\tfor _, opt := range opts {\n\t\topt(httpClient)\n\t}\n\n\tstandardClient := httpClient.StandardClient()\n\tstandardClient.Timeout = httpClient.HTTPClient.Timeout\n\treturn standardClient\n}\n\nconst DefaultResponseTimeout = 5 * time.Second\n\nvar saneTransport = &http.Transport{\n\tProxy: http.ProxyFromEnvironment,\n\tDialContext: (&net.Dialer{\n\t\tTimeout:   2 * time.Second,\n\t\tKeepAlive: 5 * time.Second,\n\t}).DialContext,\n\tMaxIdleConns:          5,\n\tIdleConnTimeout:       5 * time.Second,\n\tTLSHandshakeTimeout:   3 * time.Second,\n\tExpectContinueTimeout: 1 * time.Second,\n}\n\nfunc SaneHttpClient() *http.Client {\n\thttpClient := &http.Client{}\n\thttpClient.Timeout = DefaultResponseTimeout\n\thttpClient.Transport = NewInstrumentedTransport(NewCustomTransport(saneTransport))\n\treturn httpClient\n}\n\n// SaneHttpClientTimeOut adds a custom timeout for some scanners\nfunc SaneHttpClientTimeOut(timeout time.Duration) *http.Client {\n\thttpClient := &http.Client{}\n\thttpClient.Timeout = timeout\n\thttpClient.Transport = NewInstrumentedTransport(NewCustomTransport(nil))\n\treturn httpClient\n}\n"
  },
  {
    "path": "pkg/common/http_metrics.go",
    "content": "package common\n\nimport (\n\t\"net/url\"\n\t\"strconv\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n)\n\nvar (\n\thttpRequestsTotal = promauto.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: MetricsNamespace,\n\t\t\tSubsystem: \"http_client\",\n\t\t\tName:      \"requests_total\",\n\t\t\tHelp:      \"Total number of HTTP requests made, labeled by URL.\",\n\t\t},\n\t\t[]string{\"url\"},\n\t)\n\n\thttpRequestDuration = promauto.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tNamespace: MetricsNamespace,\n\t\t\tSubsystem: \"http_client\",\n\t\t\tName:      \"request_duration_seconds\",\n\t\t\tHelp:      \"HTTP request latency in seconds, labeled by URL.\",\n\t\t\tBuckets:   prometheus.DefBuckets,\n\t\t},\n\t\t[]string{\"url\"},\n\t)\n\n\thttpNon200ResponsesTotal = promauto.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: MetricsNamespace,\n\t\t\tSubsystem: \"http_client\",\n\t\t\tName:      \"non_200_responses_total\",\n\t\t\tHelp:      \"Total number of non-200 HTTP responses, labeled by URL and status code.\",\n\t\t},\n\t\t[]string{\"url\", \"status_code\"},\n\t)\n\n\thttpResponseBodySizeBytes = promauto.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tNamespace: MetricsNamespace,\n\t\t\tSubsystem: \"http_client\",\n\t\t\tName:      \"response_body_size_bytes\",\n\t\t\tHelp:      \"Size of HTTP response bodies in bytes, labeled by URL.\",\n\t\t\tBuckets:   prometheus.ExponentialBuckets(100, 10, 5), // [100B, 1KB, 10KB, 100KB, 1MB]\n\t\t},\n\t\t[]string{\"url\"},\n\t)\n)\n\n// sanitizeURL sanitizes a URL to avoid high cardinality metrics.\n// It keeps only the host and path, removing query parameters, fragments, and user info.\nfunc sanitizeURL(rawURL string) string {\n\tif rawURL == \"\" {\n\t\treturn \"unknown\"\n\t}\n\n\tparsedURL, err := url.Parse(rawURL)\n\tif err != nil {\n\t\treturn \"invalid_url\"\n\t}\n\n\t// Build sanitized URL with just scheme, host, and path\n\tsanitized := &url.URL{\n\t\tScheme: parsedURL.Scheme,\n\t\tHost:   parsedURL.Host,\n\t\tPath:   parsedURL.Path,\n\t}\n\n\t// If host is empty, try to extract from the raw URL\n\tif sanitized.Host == \"\" {\n\t\t// For relative URLs or malformed URLs, just use a placeholder\n\t\treturn \"relative_or_invalid\"\n\t}\n\n\t// Normalize path\n\tif sanitized.Path == \"\" {\n\t\tsanitized.Path = \"/\"\n\t}\n\n\t// Limit path length to avoid extremely long paths creating high cardinality\n\tif len(sanitized.Path) > 100 {\n\t\tsanitized.Path = sanitized.Path[:100] + \"...\"\n\t}\n\n\tresult := sanitized.String()\n\n\t// Final fallback to avoid empty strings\n\tif result == \"\" {\n\t\treturn \"unknown\"\n\t}\n\n\treturn result\n}\n\n// recordHTTPRequest records metrics for an HTTP request.\nfunc recordHTTPRequest(sanitizedURL string) {\n\thttpRequestsTotal.WithLabelValues(sanitizedURL).Inc()\n}\n\n// recordHTTPResponse records metrics for an HTTP response.\nfunc recordHTTPResponse(sanitizedURL string, statusCode int, durationSeconds float64, contentLength int64) {\n\t// Record latency\n\thttpRequestDuration.WithLabelValues(sanitizedURL).Observe(durationSeconds)\n\n\t// Record non-200 responses\n\tif statusCode != 200 {\n\t\thttpNon200ResponsesTotal.WithLabelValues(sanitizedURL, strconv.Itoa(statusCode)).Inc()\n\t}\n\n\t// Record response body size if known\n\tif contentLength >= 0 {\n\t\thttpResponseBodySizeBytes.WithLabelValues(sanitizedURL).Observe(float64(contentLength))\n\t}\n}\n\n// recordNetworkError records metrics for failed HTTP response\nfunc recordNetworkError(sanitizedURL string) {\n\thttpNon200ResponsesTotal.WithLabelValues(sanitizedURL, \"network_error\").Inc()\n}\n"
  },
  {
    "path": "pkg/common/http_test.go",
    "content": "package common\n\nimport (\n\t\"context\"\n\t\"math\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"slices\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/hashicorp/go-retryablehttp\"\n\t\"github.com/prometheus/client_golang/prometheus/testutil\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRetryableHTTPClientCheckRetry(t *testing.T) {\n\ttestCases := []struct {\n\t\tname            string\n\t\tresponseStatus  int\n\t\tcheckRetry      retryablehttp.CheckRetry\n\t\texpectedRetries int\n\t}{\n\t\t{\n\t\t\tname:           \"Retry on 500 status, give up after 3 retries\",\n\t\t\tresponseStatus: http.StatusInternalServerError, // Server error status\n\t\t\tcheckRetry: func(ctx context.Context, resp *http.Response, err error) (bool, error) {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"expected response with 500 status, got error: %v\", err)\n\t\t\t\t\treturn false, err\n\t\t\t\t}\n\t\t\t\t// The underlying transport will retry on 500 status.\n\t\t\t\tif resp.StatusCode == http.StatusInternalServerError {\n\t\t\t\t\treturn true, nil\n\t\t\t\t}\n\t\t\t\treturn false, nil\n\t\t\t},\n\t\t\texpectedRetries: 3,\n\t\t},\n\t\t{\n\t\t\tname:           \"No retry on 400 status\",\n\t\t\tresponseStatus: http.StatusBadRequest, // Client error status\n\t\t\tcheckRetry: func(ctx context.Context, resp *http.Response, err error) (bool, error) {\n\t\t\t\t// Do not retry on client errors.\n\t\t\t\treturn false, nil\n\t\t\t},\n\t\t\texpectedRetries: 0,\n\t\t},\n\t\t{\n\t\t\tname:           \"Retry on 429 status, give up after 3 retries\",\n\t\t\tresponseStatus: http.StatusTooManyRequests,\n\t\t\tcheckRetry: func(ctx context.Context, resp *http.Response, err error) (bool, error) {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"expected response with 429 status, got error: %v\", err)\n\t\t\t\t\treturn false, err\n\t\t\t\t}\n\t\t\t\t// The underlying transport will retry on 429 status.\n\t\t\t\tif resp.StatusCode == http.StatusTooManyRequests {\n\t\t\t\t\treturn true, nil\n\t\t\t\t}\n\t\t\t\treturn false, nil\n\t\t\t},\n\t\t\texpectedRetries: 3,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tvar retryCount int\n\n\t\t\t// Do not count the initial request as a retry.\n\t\t\ti := 0\n\t\t\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tif i != 0 {\n\t\t\t\t\tretryCount++\n\t\t\t\t}\n\t\t\t\ti++\n\t\t\t\tw.WriteHeader(tc.responseStatus)\n\t\t\t}))\n\t\t\tdefer server.Close()\n\n\t\t\tctx := context.Background()\n\t\t\tclient := RetryableHTTPClient(WithCheckRetry(tc.checkRetry), WithTimeout(10*time.Millisecond), WithRetryWaitMin(1*time.Millisecond))\n\t\t\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, server.URL, nil)\n\t\t\tassert.NoError(t, err)\n\n\t\t\t// Bad linter, there is no body to close.\n\t\t\t_, err = client.Do(req) //nolint:bodyclose\n\t\t\tif slices.Contains([]int{http.StatusInternalServerError, http.StatusTooManyRequests}, tc.responseStatus) {\n\t\t\t\t// The underlying transport will retry on 500 and 429 status.\n\t\t\t\tassert.Error(t, err)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.expectedRetries, retryCount, \"Retry count does not match expected\")\n\t\t})\n\t}\n}\n\nfunc TestRetryableHTTPClientMaxRetry(t *testing.T) {\n\ttestCases := []struct {\n\t\tname            string\n\t\tresponseStatus  int\n\t\tmaxRetries      int\n\t\texpectedRetries int\n\t}{\n\t\t{\n\t\t\tname:            \"Max retries with 500 status\",\n\t\t\tresponseStatus:  http.StatusInternalServerError,\n\t\t\tmaxRetries:      2,\n\t\t\texpectedRetries: 2,\n\t\t},\n\t\t{\n\t\t\tname:            \"Max retries with 429 status\",\n\t\t\tresponseStatus:  http.StatusTooManyRequests,\n\t\t\tmaxRetries:      1,\n\t\t\texpectedRetries: 1,\n\t\t},\n\t\t{\n\t\t\tname:            \"Max retries with 200 status\",\n\t\t\tresponseStatus:  http.StatusOK,\n\t\t\tmaxRetries:      3,\n\t\t\texpectedRetries: 0,\n\t\t},\n\t\t{\n\t\t\tname:            \"Max retries with 400 status\",\n\t\t\tresponseStatus:  http.StatusBadRequest,\n\t\t\tmaxRetries:      3,\n\t\t\texpectedRetries: 0,\n\t\t},\n\t\t{\n\t\t\tname:            \"Max retries with 401 status\",\n\t\t\tresponseStatus:  http.StatusUnauthorized,\n\t\t\tmaxRetries:      3,\n\t\t\texpectedRetries: 0,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tvar retryCount int\n\n\t\t\ti := 0\n\t\t\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tif i != 0 {\n\t\t\t\t\tretryCount++\n\t\t\t\t}\n\t\t\t\ti++\n\t\t\t\tw.WriteHeader(tc.responseStatus)\n\t\t\t}))\n\t\t\tdefer server.Close()\n\n\t\t\tclient := RetryableHTTPClient(\n\t\t\t\tWithMaxRetries(tc.maxRetries),\n\t\t\t\tWithTimeout(10*time.Millisecond),\n\t\t\t\tWithRetryWaitMin(1*time.Millisecond),\n\t\t\t)\n\n\t\t\tctx := context.Background()\n\t\t\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, server.URL, nil)\n\t\t\tassert.NoError(t, err)\n\n\t\t\t// Bad linter, there is no body to close.\n\t\t\t_, err = client.Do(req) //nolint:bodyclose\n\t\t\tif err != nil && tc.responseStatus == http.StatusOK {\n\t\t\t\tassert.Error(t, err)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.expectedRetries, retryCount, \"Retry count does not match expected\")\n\t\t})\n\t}\n}\n\nfunc TestRetryableHTTPClientBackoff(t *testing.T) {\n\ttestCases := []struct {\n\t\tname             string\n\t\tresponseStatus   int\n\t\texpectedRetries  int\n\t\tbackoffPolicy    retryablehttp.Backoff\n\t\texpectedBackoffs []time.Duration\n\t}{\n\t\t{\n\t\t\tname:            \"Custom backoff on 500 status\",\n\t\t\tresponseStatus:  http.StatusInternalServerError,\n\t\t\texpectedRetries: 3,\n\t\t\tbackoffPolicy: func(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {\n\t\t\t\tswitch attemptNum {\n\t\t\t\tcase 1:\n\t\t\t\t\treturn 1 * time.Millisecond\n\t\t\t\tcase 2:\n\t\t\t\t\treturn 2 * time.Millisecond\n\t\t\t\tcase 3:\n\t\t\t\t\treturn 4 * time.Millisecond\n\t\t\t\tdefault:\n\t\t\t\t\treturn max\n\t\t\t\t}\n\t\t\t},\n\t\t\texpectedBackoffs: []time.Duration{1 * time.Millisecond, 2 * time.Millisecond, 4 * time.Millisecond},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tvar actualBackoffs []time.Duration\n\t\t\tvar lastTime time.Time\n\n\t\t\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tnow := time.Now()\n\t\t\t\tif !lastTime.IsZero() {\n\t\t\t\t\tactualBackoffs = append(actualBackoffs, now.Sub(lastTime))\n\t\t\t\t}\n\t\t\t\tlastTime = now\n\t\t\t\tw.WriteHeader(tc.responseStatus)\n\t\t\t}))\n\t\t\tdefer server.Close()\n\n\t\t\tctx := context.Background()\n\t\t\tclient := RetryableHTTPClient(\n\t\t\t\tWithBackoff(tc.backoffPolicy),\n\t\t\t\tWithTimeout(10*time.Millisecond),\n\t\t\t\tWithRetryWaitMin(1*time.Millisecond),\n\t\t\t\tWithRetryWaitMax(10*time.Millisecond),\n\t\t\t)\n\t\t\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, server.URL, nil)\n\t\t\tassert.NoError(t, err)\n\n\t\t\t_, err = client.Do(req) //nolint:bodyclose\n\t\t\tassert.Error(t, err, \"Expected error due to 500 status\")\n\n\t\t\tassert.Len(t, actualBackoffs, tc.expectedRetries, \"Unexpected number of backoffs\")\n\n\t\t\tfor i, expectedBackoff := range tc.expectedBackoffs {\n\t\t\t\tif i < len(actualBackoffs) {\n\t\t\t\t\t// Allow some deviation in timing due to processing delays.\n\t\t\t\t\tassert.Less(t, math.Abs(float64(actualBackoffs[i]-expectedBackoff)), float64(15*time.Millisecond), \"Unexpected backoff duration\")\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRetryableHTTPClientTimeout(t *testing.T) {\n\ttestCases := []struct {\n\t\tname            string\n\t\ttimeoutSeconds  int64\n\t\texpectedTimeout time.Duration\n\t}{\n\t\t{\n\t\t\tname:            \"5 second timeout\",\n\t\t\ttimeoutSeconds:  5,\n\t\t\texpectedTimeout: 5 * time.Second,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\n\t\t\t// Call the function with the test timeout value\n\t\t\tclient := RetryableHTTPClientTimeout(tc.timeoutSeconds)\n\n\t\t\t// Verify that the timeout is set correctly\n\t\t\tassert.Equal(t, tc.expectedTimeout, client.Timeout, \"HTTP client timeout does not match expected value\")\n\n\t\t\t// Verify that the transport is a custom transport\n\t\t\t_, isRoundTripperTransport := client.Transport.(*retryablehttp.RoundTripper)\n\t\t\tassert.True(t, isRoundTripperTransport, \"HTTP client transport is not a retryablehttp.RoundTripper\")\n\t\t})\n\t}\n}\n\nfunc TestSanitizeURL(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\tinput    string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname:     \"valid https URL\",\n\t\t\tinput:    \"https://api.example.com/v1/users\",\n\t\t\texpected: \"https://api.example.com/v1/users\",\n\t\t},\n\t\t{\n\t\t\tname:     \"URL with query parameters\",\n\t\t\tinput:    \"https://api.example.com/search?q=secret&limit=10\",\n\t\t\texpected: \"https://api.example.com/search\",\n\t\t},\n\t\t{\n\t\t\tname:     \"URL with fragment\",\n\t\t\tinput:    \"https://example.com/page#section\",\n\t\t\texpected: \"https://example.com/page\",\n\t\t},\n\t\t{\n\t\t\tname:     \"URL with user info\",\n\t\t\tinput:    \"https://user:pass@api.example.com/path\",\n\t\t\texpected: \"https://api.example.com/path\",\n\t\t},\n\t\t{\n\t\t\tname:     \"empty URL\",\n\t\t\tinput:    \"\",\n\t\t\texpected: \"unknown\",\n\t\t},\n\t\t{\n\t\t\tname:     \"invalid URL\",\n\t\t\tinput:    \"not-a-url\",\n\t\t\texpected: \"relative_or_invalid\",\n\t\t},\n\t\t{\n\t\t\tname:     \"very long path\",\n\t\t\tinput:    \"https://example.com/\" + strings.Repeat(\"a\", 150),\n\t\t\texpected: \"https://example.com/\" + strings.Repeat(\"a\", 99) + \"...\", // 99 + 1 (\"/\") = 100 chars\n\t\t},\n\t\t{\n\t\t\tname:     \"root path\",\n\t\t\tinput:    \"https://example.com\",\n\t\t\texpected: \"https://example.com/\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tresult := sanitizeURL(tc.input)\n\t\t\tassert.Equal(t, tc.expected, result)\n\t\t})\n\t}\n}\n\nfunc TestSaneHttpClientMetrics(t *testing.T) {\n\t// Create a test server that returns different status codes\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tswitch r.URL.Path {\n\t\tcase \"/success\":\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\t_, _ = w.Write([]byte(\"success\"))\n\t\tcase \"/error\":\n\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t_, _ = w.Write([]byte(\"error\"))\n\t\tcase \"/notfound\":\n\t\t\tw.WriteHeader(http.StatusNotFound)\n\t\t\t_, _ = w.Write([]byte(\"not found\"))\n\t\tdefault:\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\t_, _ = w.Write([]byte(\"default\"))\n\t\t}\n\t}))\n\tdefer server.Close()\n\n\t// Create a SaneHttpClient\n\tclient := SaneHttpClient()\n\n\ttestCases := []struct {\n\t\tname               string\n\t\tpath               string\n\t\texpectedStatusCode int\n\t\texpectsNon200      bool\n\t}{\n\t\t{\n\t\t\tname:               \"successful request\",\n\t\t\tpath:               \"/success\",\n\t\t\texpectedStatusCode: 200,\n\t\t\texpectsNon200:      false,\n\t\t},\n\t\t{\n\t\t\tname:               \"server error request\",\n\t\t\tpath:               \"/error\",\n\t\t\texpectedStatusCode: 500,\n\t\t\texpectsNon200:      true,\n\t\t},\n\t\t{\n\t\t\tname:               \"not found request\",\n\t\t\tpath:               \"/notfound\",\n\t\t\texpectedStatusCode: 404,\n\t\t\texpectsNon200:      true,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tvar requestURL string\n\t\t\tif strings.HasPrefix(tc.path, \"http\") {\n\t\t\t\trequestURL = tc.path\n\t\t\t} else {\n\t\t\t\trequestURL = server.URL + tc.path\n\t\t\t}\n\n\t\t\t// Get initial metric values\n\t\t\tsanitizedURL := sanitizeURL(requestURL)\n\t\t\tinitialRequestsTotal := testutil.ToFloat64(httpRequestsTotal.WithLabelValues(sanitizedURL))\n\n\t\t\t// Make the request\n\t\t\tresp, err := client.Get(requestURL)\n\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer resp.Body.Close()\n\t\t\tassert.Equal(t, tc.expectedStatusCode, resp.StatusCode)\n\n\t\t\t// Check that request counter was incremented\n\t\t\trequestsTotal := testutil.ToFloat64(httpRequestsTotal.WithLabelValues(sanitizedURL))\n\t\t\tassert.Equal(t, initialRequestsTotal+1, requestsTotal)\n\t\t})\n\t}\n}\n\nfunc TestRetryableHttpClientMetrics(t *testing.T) {\n\t// Create a test server that returns different status codes\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tswitch r.URL.Path {\n\t\tcase \"/success\":\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\t_, _ = w.Write([]byte(\"success\"))\n\t\tcase \"/error\":\n\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t_, _ = w.Write([]byte(\"error\"))\n\t\tcase \"/notfound\":\n\t\t\tw.WriteHeader(http.StatusNotFound)\n\t\t\t_, _ = w.Write([]byte(\"not found\"))\n\t\tdefault:\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\t_, _ = w.Write([]byte(\"default\"))\n\t\t}\n\t}))\n\tdefer server.Close()\n\n\t// Create a RetryableHttpClient\n\tclient := RetryableHTTPClient()\n\n\ttestCases := []struct {\n\t\tname               string\n\t\tpath               string\n\t\texpectedStatusCode int\n\t}{\n\t\t{\n\t\t\tname:               \"successful request\",\n\t\t\tpath:               \"/success\",\n\t\t\texpectedStatusCode: 200,\n\t\t},\n\t\t{\n\t\t\tname:               \"not found request\",\n\t\t\tpath:               \"/notfound\",\n\t\t\texpectedStatusCode: 404,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tvar requestURL string\n\t\t\tif strings.HasPrefix(tc.path, \"http\") {\n\t\t\t\trequestURL = tc.path\n\t\t\t} else {\n\t\t\t\trequestURL = server.URL + tc.path\n\t\t\t}\n\n\t\t\t// Get initial metric values\n\t\t\tsanitizedURL := sanitizeURL(requestURL)\n\t\t\tinitialRequestsTotal := testutil.ToFloat64(httpRequestsTotal.WithLabelValues(sanitizedURL))\n\n\t\t\t// Make the request\n\t\t\tresp, err := client.Get(requestURL)\n\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer resp.Body.Close()\n\t\t\tassert.Equal(t, tc.expectedStatusCode, resp.StatusCode)\n\n\t\t\t// Check that request counter was incremented\n\t\t\trequestsTotal := testutil.ToFloat64(httpRequestsTotal.WithLabelValues(sanitizedURL))\n\t\t\tassert.Equal(t, initialRequestsTotal+1, requestsTotal)\n\t\t})\n\t}\n}\n\nfunc TestInstrumentedTransport(t *testing.T) {\n\t// Create a mock transport that we can control\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\t_, _ = w.Write([]byte(\"test response\"))\n\t}))\n\tdefer server.Close()\n\n\t// Create instrumented transport\n\ttransport := NewInstrumentedTransport(nil)\n\tclient := &http.Client{\n\t\tTransport: transport,\n\t\tTimeout:   5 * time.Second,\n\t}\n\n\t// Get initial metric value\n\tsanitizedURL := sanitizeURL(server.URL)\n\tinitialCount := testutil.ToFloat64(httpRequestsTotal.WithLabelValues(sanitizedURL))\n\n\t// Make a request\n\tresp, err := client.Get(server.URL)\n\trequire.NoError(t, err)\n\tdefer resp.Body.Close()\n\n\t// Verify the request was successful\n\tassert.Equal(t, http.StatusOK, resp.StatusCode)\n\n\t// Verify metrics were recorded\n\tfinalCount := testutil.ToFloat64(httpRequestsTotal.WithLabelValues(sanitizedURL))\n\tassert.Equal(t, initialCount+1, finalCount)\n\n\t// Note: Testing histogram metrics is complex due to the way Prometheus handles them\n\t// The main thing is that the request completed successfully and counters were incremented\n}\n"
  },
  {
    "path": "pkg/common/metrics.go",
    "content": "package common\n\nconst (\n\t// MetricsNamespace is the namespace for all metrics.\n\tMetricsNamespace = \"trufflehog\"\n\t// MetricsSubsystem is the subsystem for all metrics.\n\tMetricsSubsystem = \"scanner\"\n)\n"
  },
  {
    "path": "pkg/common/patterns.go",
    "content": "package common\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nconst 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`\nconst SubDomainPattern = `\\b([A-Za-z0-9](?:[A-Za-z0-9\\-]{0,61}[A-Za-z0-9])?)\\b`\nconst UUIDPattern = `\\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\b`\nconst UUIDPatternUpperCase = `\\b([0-9A-Z]{8}-[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{12})\\b`\n\nconst RegexPattern = \"0-9a-z\"\nconst AlphaNumPattern = \"0-9a-zA-Z\"\nconst HexPattern = \"0-9a-f\"\n\ntype RegexState struct {\n\tcompiledRegex *regexp.Regexp\n}\n\n// Custom Regex functions\nfunc BuildRegex(pattern string, specialChar string, length int) string {\n\treturn fmt.Sprintf(`\\b([%s%s]{%s})\\b`, pattern, specialChar, strconv.Itoa(length))\n}\n\nfunc BuildRegexJWT(firstRange, secondRange, thirdRange string) string {\n\tif RangeValidation(firstRange) || RangeValidation(secondRange) || RangeValidation(thirdRange) {\n\t\tpanic(\"Min value should not be greater than or equal to max\")\n\t}\n\treturn fmt.Sprintf(`\\b(ey[%s]{%s}.ey[%s-\\/_]{%s}.[%s-\\/_]{%s})\\b`, AlphaNumPattern, firstRange, AlphaNumPattern, secondRange, AlphaNumPattern, thirdRange)\n}\n\nfunc RangeValidation(rangeInput string) bool {\n\trange_split := strings.Split(rangeInput, \",\")\n\trange_min, _ := strconv.ParseInt(strings.TrimSpace(range_split[0]), 10, 0)\n\trange_max, _ := strconv.ParseInt(strings.TrimSpace(range_split[1]), 10, 0)\n\treturn range_min >= range_max\n}\n\nfunc ToUpperCase(input string) string {\n\treturn strings.ToUpper(input)\n}\n\nfunc (r RegexState) Matches(data []byte) []string {\n\tmatches := r.compiledRegex.FindAllStringSubmatch(string(data), -1)\n\n\tres := make([]string, 0, len(matches))\n\n\t// trim off all white spaces and different quote types ('\"\") & some special characters (,;).\n\tfor i := range matches {\n\t\tres = append(res, strings.Trim(strings.TrimSpace(matches[i][1]), `\"' ),;`))\n\t}\n\n\treturn res\n}\n\n// UsernameRegexCheck constructs an username usernameRegex pattern from a given pattern of excluded characters.\nfunc UsernameRegexCheck(pattern string) RegexState {\n\traw := fmt.Sprintf(`(?im)(?:user|usr)\\S{0,40}?[:=\\s]{1,3}[ '\"=]{0,1}([^:%+v]{4,40})\\b`, pattern)\n\n\treturn RegexState{regexp.MustCompile(raw)}\n}\n\n// PasswordRegexCheck constructs an username usernameRegex pattern from a given pattern of excluded characters.\nfunc PasswordRegexCheck(pattern string) RegexState {\n\traw := fmt.Sprintf(`(?im)(?:pass|password)\\S{0,40}?[:=\\s]{1,3}[ '\"=]{0,1}([^:%+v]{4,40})`, pattern)\n\n\treturn RegexState{regexp.MustCompile(raw)}\n}\n"
  },
  {
    "path": "pkg/common/patterns_test.go",
    "content": "package common\n\nimport (\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nconst (\n\tusernamePattern = `?()/\\+=\\s\\n`\n\tpasswordPattern = `^<>;.*&|£\\n\\s`\n\tusernameRegex   = `(?im)(?:user|usr)\\S{0,40}?[:=\\s]{1,3}[ '\"=]{0,1}([^:?()/\\+=\\s\\n]{4,40})\\b`\n\tpasswordRegex   = `(?im)(?:pass|password)\\S{0,40}?[:=\\s]{1,3}[ '\"=]{0,1}([^:^<>;.*&|£\\n\\s]{4,40})`\n)\n\nfunc TestEmailRegexCheck(t *testing.T) {\n\ttestEmails := `\n\t\t// positive cases\n\t\tstandard email     = john.doe@example.com\n\t\tsubdomain email    = jane_doe123@sub.domain.co.us\n\t\torganization email = alice.smith@test.org\n\t\ttest email         = bob@test.name\n\t\twith tag email     = user.name+tag@domain.com\n\t\thyphen domain      = info@my-site.net\n\t\tservice email      = contact@web-service.io\n\t\tunderscore email   = example_user@domain.info\n\t\tdepartment email   = first.last@department.company.edu\n\t\talphanumeric email = user1234@domain.co\n\t\tlocal server email = admin@local-server.local\n\t\tdot email          = test.email@my-email-service.xyz\n\t\tspecial char email = special@characters.com\n\t\tsupport email      = support@customer-service.org\n\t\tinsenstive email   = ADMIN@example.com\n\t\tinsenstive domain  = ADMIN@COMPANY.COM\n\t\tmix email          = USER123xyz@local-Server.local\n\n\t\t// negative cases\n\t\tnot an email       = abc.123@z\n\t\tlooks like email   = test@user <- no domain\n\t\trandom text        = here's some information about local-user@edu user\n\t`\n\n\texpectedStr := []string{\n\t\t\"john.doe@example.com\", \"jane_doe123@sub.domain.co.us\",\n\t\t\"alice.smith@test.org\", \"bob@test.name\", \"user.name+tag@domain.com\",\n\t\t\"info@my-site.net\", \"contact@web-service.io\", \"example_user@domain.info\",\n\t\t\"first.last@department.company.edu\", \"user1234@domain.co\", \"admin@local-server.local\",\n\t\t\"test.email@my-email-service.xyz\", \"special@characters.com\", \"support@customer-service.org\",\n\t\t\"ADMIN@example.com\", \"ADMIN@COMPANY.COM\", \"USER123xyz@local-Server.local\",\n\t}\n\n\temailRegex := regexp.MustCompile(EmailPattern)\n\n\temailMatches := emailRegex.FindAllString(testEmails, -1)\n\n\tassert.Exactly(t, emailMatches, expectedStr)\n\n}\n\nfunc TestUsernameRegexCheck(t *testing.T) {\n\tusernameRegexPat := UsernameRegexCheck(usernamePattern)\n\n\texpectedRegexPattern := regexp.MustCompile(usernameRegex)\n\n\tif usernameRegexPat.compiledRegex.String() != expectedRegexPattern.String() {\n\t\tt.Errorf(\"\\n got %v \\n want %v\", usernameRegexPat.compiledRegex, expectedRegexPattern)\n\t}\n\n\ttestString := `username = \"johnsmith123\"\n                   username='johnsmith123'\n\t\t\t\t   username:=\"johnsmith123\"\n\t\t\t\t   username:=\"johnsmith123\",\n\t\t\t\t   username:=\"johnsmith123\";\n                   username = johnsmith123\n                   username=johnsmith123`\n\n\texpectedStr := []string{\n\t\t\"johnsmith123\",\n\t\t\"johnsmith123\",\n\t\t\"johnsmith123\",\n\t\t\"johnsmith123\",\n\t\t\"johnsmith123\",\n\t\t\"johnsmith123\",\n\t\t\"johnsmith123\",\n\t}\n\n\tusernameRegexMatches := usernameRegexPat.Matches([]byte(testString))\n\n\tassert.Exactly(t, usernameRegexMatches, expectedStr)\n\n}\n\nfunc TestPasswordRegexCheck(t *testing.T) {\n\tpasswordRegexPat := PasswordRegexCheck(passwordPattern)\n\n\texpectedRegexPattern := regexp.MustCompile(passwordRegex)\n\tassert.Equal(t, passwordRegexPat.compiledRegex, expectedRegexPattern)\n\n\ttestString := `password = \"johnsmith123$!\"\n                   password='johnsmith123$!'\n\t\t\t\t   password:=\"johnsmith123$!\"\n\t\t\t\t   password:=\"johnsmith123$!\",\n\t\t\t\t   password:=\"johnsmith123$!\";\n\t\t\t\t   password:=\"johnsmi',th123$!\";\n                   password = johnsmith123$!\n                   password=johnsmith123$!\n\t\t\t\t   PasswordAuthenticator(username, \"johnsmith123$!\")`\n\n\texpectedStr := []string{\n\t\t\"johnsmith123$!\",\n\t\t\"johnsmith123$!\",\n\t\t\"johnsmith123$!\",\n\t\t\"johnsmith123$!\",\n\t\t\"johnsmith123$!\",\n\t\t\"johnsmi',th123$!\",\n\t\t\"johnsmith123$!\",\n\t\t\"johnsmith123$!\",\n\t\t\"johnsmith123$!\",\n\t}\n\n\tpasswordRegexMatches := passwordRegexPat.Matches([]byte(testString))\n\n\tassert.Exactly(t, passwordRegexMatches, expectedStr)\n\n}\n"
  },
  {
    "path": "pkg/common/recover.go",
    "content": "package common\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"runtime/debug\"\n\t\"time\"\n\n\t\"github.com/getsentry/sentry-go\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n// Recover handles panics and reports to Sentry.\nfunc Recover(ctx context.Context) {\n\tif err := recover(); err != nil {\n\t\tpanicStack := string(debug.Stack())\n\t\tif eventID := sentry.CurrentHub().Recover(err); eventID != nil {\n\t\t\tctx.Logger().Info(\"panic captured\", \"event_id\", *eventID)\n\t\t}\n\t\tctx.Logger().Error(fmt.Errorf(\"panic\"), panicStack,\n\t\t\t\"recover\", err,\n\t\t)\n\t\tif !sentry.Flush(time.Second * 5) {\n\t\t\tctx.Logger().Info(\"sentry flush failed\")\n\t\t}\n\t}\n}\n\n// RecoverWithHandler handles panics and reports to Sentry, then turns control\n// over to a provided function.  This permits extra reporting in the same scope\n// without re-panicking, as recover() clears the state after it's called.  Does\n// NOT block to flush sentry report.\nfunc RecoverWithHandler(ctx context.Context, callback func(error)) {\n\tif err := recover(); err != nil {\n\t\tpanicStack := string(debug.Stack())\n\t\tif eventID := sentry.CurrentHub().Recover(err); eventID != nil {\n\t\t\tctx.Logger().Info(\"panic captured\", \"event_id\", *eventID)\n\t\t}\n\t\tctx.Logger().Error(fmt.Errorf(\"panic\"), panicStack,\n\t\t\t\"recover\", err,\n\t\t)\n\n\t\tswitch v := err.(type) {\n\t\tcase error:\n\t\t\tcallback(fmt.Errorf(\"panic: %w\", v))\n\t\tdefault:\n\t\t\tcallback(fmt.Errorf(\"panic: %v\", v))\n\t\t}\n\t}\n}\n\n// RecoverWithExit handles panics and reports to Sentry before exiting.\nfunc RecoverWithExit(ctx context.Context) {\n\tif err := recover(); err != nil {\n\t\tpanicStack := string(debug.Stack())\n\t\tif eventID := sentry.CurrentHub().Recover(err); eventID != nil {\n\t\t\tctx.Logger().Info(\"panic captured\", \"event_id\", *eventID)\n\t\t}\n\t\tctx.Logger().Error(fmt.Errorf(\"panic\"), \"recovered from panic before exiting\",\n\t\t\t\"stack-trace\", panicStack,\n\t\t\t\"recover\", err,\n\t\t)\n\t\tif !sentry.Flush(time.Second * 5) {\n\t\t\tctx.Logger().Info(\"sentry flush failed\")\n\t\t}\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "pkg/common/secrets.go",
    "content": "package common\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\tsecretmanager \"cloud.google.com/go/secretmanager/apiv1\"\n\t\"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb\"\n\t\"github.com/joho/godotenv\"\n\t\"github.com/pkg/errors\"\n)\n\ntype Secret struct{ kv map[string]string }\n\nfunc (s *Secret) MustGetField(name string) string {\n\tval, ok := s.kv[name]\n\tif !ok {\n\t\tpanic(errors.Errorf(\"field %s not found\", name))\n\t}\n\treturn val\n}\n\nfunc GetSecretFromEnv(filename string) (secret *Secret, err error) {\n\tdata, err := godotenv.Read(filename)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Secret{kv: data}, nil\n}\n\nfunc GetTestSecret(ctx context.Context) (secret *Secret, err error) {\n\tfilename := os.Getenv(\"TEST_SECRET_FILE\")\n\tif len(filename) > 0 {\n\t\treturn GetSecretFromEnv(filename)\n\t}\n\n\treturn GetSecret(ctx, \"trufflehog-testing\", \"test\")\n}\n\nfunc GetSecret(ctx context.Context, gcpProject, name string) (secret *Secret, err error) {\n\tctx, cancel := context.WithTimeout(ctx, time.Second*10)\n\tdefer cancel()\n\n\tfilename := os.Getenv(\"TEST_SECRET_FILE\")\n\tif len(filename) > 0 {\n\t\treturn GetSecretFromEnv(filename)\n\t}\n\n\tparent := fmt.Sprintf(\"projects/%s/secrets/%s/versions/latest\", gcpProject, name)\n\n\tclient, err := secretmanager.NewClient(ctx)\n\tif err != nil {\n\t\treturn nil, errors.Errorf(\"failed to create secretmanager client: %v\", err)\n\t}\n\tdefer client.Close()\n\n\treq := &secretmanagerpb.AccessSecretVersionRequest{\n\t\tName: parent,\n\t}\n\n\tresult, err := client.AccessSecretVersion(ctx, req)\n\tif err != nil {\n\t\treturn nil, errors.Errorf(\"failed to access secret version: %v\", err)\n\t}\n\n\tdata, err := godotenv.Unmarshal(string(result.Payload.Data))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Secret{kv: data}, nil\n}\n"
  },
  {
    "path": "pkg/common/utils.go",
    "content": "package common\n\nimport (\n\t\"bufio\"\n\t\"crypto/rand\"\n\t\"io\"\n\t\"math/big\"\n\tmrand \"math/rand\"\n\t\"strings\"\n)\n\nfunc AddStringSliceItem(item string, slice *[]string) {\n\tfor _, i := range *slice {\n\t\tif i == item {\n\t\t\treturn\n\t\t}\n\t}\n\t*slice = append(*slice, item)\n}\n\nfunc RemoveStringSliceItem(item string, slice *[]string) {\n\tfor i, listItem := range *slice {\n\t\tif item == listItem {\n\t\t\t(*slice)[i] = (*slice)[len(*slice)-1]\n\t\t\t*slice = (*slice)[:len(*slice)-1]\n\t\t}\n\t}\n}\n\nfunc ResponseContainsSubstring(reader io.ReadCloser, target string) (bool, error) {\n\tscanner := bufio.NewScanner(reader)\n\tfor scanner.Scan() {\n\t\tif strings.Contains(scanner.Text(), target) {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\tif err := scanner.Err(); err != nil {\n\t\treturn false, err\n\t}\n\treturn false, nil\n}\n\nvar letters = []rune(\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\")\n\n// RandomID returns a random string of the given length.\nfunc RandomID(length int) string {\n\tb := make([]rune, length)\n\tfor i := range b {\n\t\trandInt, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))\n\t\tb[i] = letters[randInt.Int64()]\n\t}\n\n\treturn string(b)\n}\n\n// SliceContainsString searches a slice to determine if it contains a specified string.\n// Returns the index of the first match in the slice.\nfunc SliceContainsString(origTargetString string, stringSlice []string, ignoreCase bool) (bool, string, int) {\n\ttargetString := origTargetString\n\tif ignoreCase {\n\t\ttargetString = strings.ToLower(origTargetString)\n\t}\n\tfor i, origStringFromSlice := range stringSlice {\n\t\tstringFromSlice := origStringFromSlice\n\t\tif ignoreCase {\n\t\t\tstringFromSlice = strings.ToLower(origStringFromSlice)\n\t\t}\n\t\tif targetString == stringFromSlice {\n\t\t\treturn true, targetString, i\n\t\t}\n\t}\n\treturn false, \"\", 0\n}\n\n// GoFakeIt Password generator does not guarantee inclusion of characters.\n// Using a custom random password generator with guaranteed inclusions (atleast) of lower, upper, numeric and special characters\nfunc GenerateRandomPassword(lower, upper, numeric, special bool, length int) string {\n\tif length < 1 {\n\t\treturn \"\"\n\t}\n\n\tvar password []rune\n\tvar required []rune\n\tvar allowed []rune\n\n\tlowerChars := []rune(\"abcdefghijklmnopqrstuvwxyz\")\n\tupperChars := []rune(\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\")\n\tspecialChars := []rune(\"!@#$%^&*()-_=+[]{}|;:',.<>?/\")\n\tnumberChars := []rune(\"0123456789\")\n\n\t// Ensure inclusion from each requested category\n\tif lower {\n\t\trand, _ := rand.Int(rand.Reader, big.NewInt(int64(len(lowerChars))))\n\t\tch := lowerChars[rand.Int64()]\n\t\trequired = append(required, ch)\n\t\tallowed = append(allowed, lowerChars...)\n\t}\n\tif upper {\n\t\trand, _ := rand.Int(rand.Reader, big.NewInt(int64(len(upperChars))))\n\t\tch := upperChars[rand.Int64()]\n\t\trequired = append(required, ch)\n\t\tallowed = append(allowed, upperChars...)\n\t}\n\tif numeric {\n\t\trand, _ := rand.Int(rand.Reader, big.NewInt(int64(len(numberChars))))\n\t\tch := numberChars[rand.Int64()]\n\t\trequired = append(required, ch)\n\t\tallowed = append(allowed, numberChars...)\n\t}\n\tif special {\n\t\trand, _ := rand.Int(rand.Reader, big.NewInt(int64(len(specialChars))))\n\t\tch := specialChars[rand.Int64()]\n\t\trequired = append(required, ch)\n\t\tallowed = append(allowed, specialChars...)\n\t}\n\n\tif len(allowed) == 0 {\n\t\treturn \"\" // No character sets enabled\n\t}\n\n\t// Fill the rest of the password\n\tfor i := 0; i < length-len(required); i++ {\n\t\trand, _ := rand.Int(rand.Reader, big.NewInt(int64(len(allowed))))\n\t\tch := allowed[rand.Int64()]\n\t\tpassword = append(password, ch)\n\t}\n\n\t// Combine required and random characters, then shuffle\n\tpassword = append(password, required...)\n\tmrand.Shuffle(len(password), func(i, j int) {\n\t\tpassword[i], password[j] = password[j], password[i]\n\t})\n\n\treturn string(password)\n}\n"
  },
  {
    "path": "pkg/common/utils_test.go",
    "content": "package common\n\nimport (\n\t\"io\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\t\"unicode\"\n)\n\nfunc TestAddItem(t *testing.T) {\n\ttype Case struct {\n\t\tSlice    []string\n\t\tModifier []string\n\t\tExpected []string\n\t}\n\ttests := map[string]Case{\n\t\t\"newItem\": {\n\t\t\tSlice:    []string{\"a\", \"b\", \"c\"},\n\t\t\tModifier: []string{\"d\"},\n\t\t\tExpected: []string{\"a\", \"b\", \"c\", \"d\"},\n\t\t},\n\t\t\"newDuplicate\": {\n\t\t\tSlice:    []string{\"a\", \"b\", \"c\"},\n\t\t\tModifier: []string{\"c\"},\n\t\t\tExpected: []string{\"a\", \"b\", \"c\"},\n\t\t},\n\t}\n\n\tfor name, test := range tests {\n\t\tfor _, item := range test.Modifier {\n\t\t\tAddStringSliceItem(item, &test.Slice)\n\t\t}\n\n\t\tif !reflect.DeepEqual(test.Slice, test.Expected) {\n\t\t\tt.Errorf(\"%s: expected:%v, got:%v\", name, test.Expected, test.Slice)\n\t\t}\n\t}\n}\n\nfunc TestRemoveItem(t *testing.T) {\n\ttype Case struct {\n\t\tSlice    []string\n\t\tModifier []string\n\t\tExpected []string\n\t}\n\ttests := map[string]Case{\n\t\t\"existingItemEnd\": {\n\t\t\tSlice:    []string{\"a\", \"b\", \"c\"},\n\t\t\tModifier: []string{\"c\"},\n\t\t\tExpected: []string{\"a\", \"b\"},\n\t\t},\n\t\t\"existingItemMiddle\": {\n\t\t\tSlice:    []string{\"a\", \"b\", \"c\"},\n\t\t\tModifier: []string{\"b\"},\n\t\t\tExpected: []string{\"a\", \"c\"},\n\t\t},\n\t\t\"existingItemBeginning\": {\n\t\t\tSlice:    []string{\"a\", \"b\", \"c\"},\n\t\t\tModifier: []string{\"a\"},\n\t\t\tExpected: []string{\"c\", \"b\"},\n\t\t},\n\t\t\"nonExistingItem\": {\n\t\t\tSlice:    []string{\"a\", \"b\", \"c\"},\n\t\t\tModifier: []string{\"d\"},\n\t\t\tExpected: []string{\"a\", \"b\", \"c\"},\n\t\t},\n\t}\n\n\tfor name, test := range tests {\n\t\tfor _, item := range test.Modifier {\n\t\t\tRemoveStringSliceItem(item, &test.Slice)\n\t\t}\n\n\t\tif !reflect.DeepEqual(test.Slice, test.Expected) {\n\t\t\tt.Errorf(\"%s: expected:%v, got:%v\", name, test.Expected, test.Slice)\n\t\t}\n\t}\n}\n\n// Test ParseResponseForKeywords with a reader that contains the keyword and a reader that doesn't.\nfunc TestParseResponseForKeywords(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\tinput    string\n\t\tkeyword  string\n\t\texpected bool\n\t}{\n\t\t{\n\t\t\tname:     \"Should find keyword\",\n\t\t\tinput:    \"ey: abc\",\n\t\t\tkeyword:  \"ey\",\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tname:     \"Should not find keyword\",\n\t\t\tinput:    \"fake response\",\n\t\t\tkeyword:  \"ey\",\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tname:     \"Empty string\",\n\t\t\tinput:    \"\",\n\t\t\tkeyword:  \"ey\",\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tname:     \"Keyword at end\",\n\t\t\tinput:    \"abc ey\",\n\t\t\tkeyword:  \"ey\",\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tname:     \"Keyword at start\",\n\t\t\tinput:    \"ey abc\",\n\t\t\tkeyword:  \"ey\",\n\t\t\texpected: true,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttestReader := strings.NewReader(tc.input)\n\t\t\ttestReadCloser := io.NopCloser(testReader)\n\t\t\tfound, err := ResponseContainsSubstring(testReadCloser, tc.keyword)\n\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Error: %v\", err)\n\t\t\t}\n\n\t\t\tif found != tc.expected {\n\t\t\t\tt.Errorf(\"Expected %v, got %v\", tc.expected, found)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSliceContainsString(t *testing.T) {\n\ttestCases := []struct {\n\t\tname           string\n\t\tslice          []string\n\t\ttarget         string\n\t\texpectedBool   bool\n\t\texpectedString string\n\t\texpectedIndex  int\n\t\tignoreCase     bool\n\t}{\n\t\t{\n\t\t\tname:           \"matching case, target exists\",\n\t\t\tslice:          []string{\"one\", \"two\", \"three\"},\n\t\t\ttarget:         \"two\",\n\t\t\texpectedBool:   true,\n\t\t\texpectedString: \"two\",\n\t\t\texpectedIndex:  1,\n\t\t\tignoreCase:     false,\n\t\t},\n\t\t{\n\t\t\tname:           \"non-matching case, target exists, ignore case\",\n\t\t\tslice:          []string{\"one\", \"two\", \"three\"},\n\t\t\ttarget:         \"Two\",\n\t\t\texpectedBool:   true,\n\t\t\texpectedString: \"two\",\n\t\t\texpectedIndex:  1,\n\t\t\tignoreCase:     true,\n\t\t},\n\t\t{\n\t\t\tname:           \"non-matching case, target in wrong case, case respected\",\n\t\t\tslice:          []string{\"one\", \"two\", \"three\"},\n\t\t\ttarget:         \"Two\",\n\t\t\texpectedBool:   false,\n\t\t\texpectedString: \"\",\n\t\t\texpectedIndex:  0,\n\t\t\tignoreCase:     false,\n\t\t},\n\t\t{\n\t\t\tname:           \"target not in slice\",\n\t\t\tslice:          []string{\"one\", \"two\", \"three\"},\n\t\t\ttarget:         \"four\",\n\t\t\texpectedBool:   false,\n\t\t\texpectedString: \"\",\n\t\t\texpectedIndex:  0,\n\t\t\tignoreCase:     false,\n\t\t},\n\t}\n\tfor _, testCase := range testCases {\n\t\tresultBool, resultString, resultIndex := SliceContainsString(testCase.target, testCase.slice, testCase.ignoreCase)\n\t\tif resultBool != testCase.expectedBool {\n\t\t\tt.Errorf(\"%s: bool values do not match. Got: %t, expected: %t\", testCase.name, resultBool, testCase.expectedBool)\n\t\t}\n\t\tif resultString != testCase.expectedString {\n\t\t\tt.Errorf(\"%s: string values do not match. Got: %s, expected: %s\", testCase.name, resultString, testCase.expectedString)\n\t\t}\n\t\tif resultIndex != testCase.expectedIndex {\n\t\t\tt.Errorf(\"%s: index values do not match. Got: %d, expected: %d\", testCase.name, resultIndex, testCase.expectedIndex)\n\t\t}\n\t}\n}\n\nfunc TestGenerateRandomPassword_Length(t *testing.T) {\n\tpass := GenerateRandomPassword(true, true, true, true, 16)\n\tif len(pass) != 16 {\n\t\tt.Errorf(\"expected length 16, got %d\", len(pass))\n\t}\n}\n\nfunc TestGenerateRandomPassword_Empty(t *testing.T) {\n\tpass := GenerateRandomPassword(false, false, false, false, 10)\n\tif pass != \"\" {\n\t\tt.Errorf(\"expected empty string, got %q\", pass)\n\t}\n}\n\nfunc TestGenerateRandomPassword_RequiredSets(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tlower   bool\n\t\tupper   bool\n\t\tnumeric bool\n\t\tspecial bool\n\t}{\n\t\t{\"lower only\", true, false, false, false},\n\t\t{\"upper only\", false, true, false, false},\n\t\t{\"numeric only\", false, false, true, false},\n\t\t{\"special only\", false, false, false, true},\n\t\t{\"all\", true, true, true, true},\n\t\t{\"lower+upper\", true, true, false, false},\n\t\t{\"lower+numeric\", true, false, true, false},\n\t\t{\"upper+special\", false, true, false, true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tpass := GenerateRandomPassword(tc.lower, tc.upper, tc.numeric, tc.special, 12)\n\t\t\tif len(pass) != 12 {\n\t\t\t\tt.Errorf(\"expected length 12, got %d\", len(pass))\n\t\t\t}\n\t\t\tif tc.lower && !contains(pass, unicode.IsLower) {\n\t\t\t\tt.Errorf(\"expected at least one lowercase letter\")\n\t\t\t}\n\t\t\tif tc.upper && !contains(pass, unicode.IsUpper) {\n\t\t\t\tt.Errorf(\"expected at least one uppercase letter\")\n\t\t\t}\n\t\t\tif tc.numeric && !contains(pass, unicode.IsDigit) {\n\t\t\t\tt.Errorf(\"expected at least one digit\")\n\t\t\t}\n\t\t\tif tc.special && !containsSpecial(pass) {\n\t\t\t\tt.Errorf(\"expected at least one special character\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGenerateRandomPassword_ShortLength(t *testing.T) {\n\tpass := GenerateRandomPassword(true, true, true, true, 0)\n\tif pass != \"\" {\n\t\tt.Errorf(\"expected empty string for length 0, got %q\", pass)\n\t}\n}\n\nfunc contains(s string, fn func(rune) bool) bool {\n\tfor _, r := range s {\n\t\tif fn(r) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc containsSpecial(s string) bool {\n\tspecials := \"!@#$%^&*()-_=+[]{}|;:',.<>?/\"\n\tfor _, r := range s {\n\t\tfor _, sr := range specials {\n\t\t\tif r == sr {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/common/vars.go",
    "content": "package common\n\nimport (\n\t\"path/filepath\"\n\t\"strings\"\n)\n\nvar (\n\tKB, MB, GB, TB, PB = 1e3, 1e6, 1e9, 1e12, 1e15\n\tignoredExtensions  = map[string]struct{}{\n\t\t// images\n\t\t\"apng\":  {},\n\t\t\"avif\":  {},\n\t\t\"avifs\": {},\n\t\t\"bmp\":   {},\n\t\t\"dia\":   {}, // Open-source Visio clone\n\t\t\"gif\":   {},\n\t\t\"icns\":  {}, // Apple icon image file\n\t\t\"ico\":   {}, // Icon file\n\t\t\"jpg\":   {},\n\t\t\"jpeg\":  {},\n\t\t\"jxl\":   {},\n\t\t\"png\":   {},\n\t\t\"svg\":   {},\n\t\t\"svgz\":  {}, // Compressed Scalable Vector Graphics file\n\t\t\"tga\":   {},\n\t\t\"tif\":   {},\n\t\t\"tiff\":  {},\n\t\t\"vsdx\":  {}, // Microsoft Visio drawing file\n\t\t\"vsix\":  {}, // Visual Studio extension file\n\n\t\t// audio\n\t\t\"fev\":  {}, // video game audio\n\t\t\"fsb\":  {},\n\t\t\"m2a\":  {},\n\t\t\"m4a\":  {},\n\t\t\"mp2\":  {},\n\t\t\"mp3\":  {},\n\t\t\"snag\": {},\n\n\t\t// video\n\t\t\"264\":  {},\n\t\t\"3gp\":  {},\n\t\t\"avi\":  {},\n\t\t\"flac\": {},\n\t\t\"flv\":  {},\n\t\t\"hdv\":  {},\n\t\t\"m4p\":  {},\n\t\t\"mov\":  {},\n\t\t\"mp4\":  {},\n\t\t\"mpg\":  {},\n\t\t\"mpeg\": {},\n\t\t\"ogg\":  {},\n\t\t\"qt\":   {},\n\t\t\"swf\":  {},\n\t\t\"vob\":  {},\n\t\t\"wav\":  {},\n\t\t\"webm\": {},\n\t\t\"webp\": {},\n\t\t\"wmv\":  {},\n\n\t\t// documents\n\t\t\"pdf\": {},\n\t\t\"psd\": {},\n\n\t\t// fonts\n\t\t\"eot\":   {}, // Embedded OpenType font\n\t\t\"fnt\":   {}, // Windows font file\n\t\t\"fon\":   {}, // Generic font file\n\t\t\"otf\":   {}, // OpenType font\n\t\t\"ttf\":   {}, // TrueType font\n\t\t\"woff\":  {}, // Web Open Font Format\n\t\t\"woff2\": {}, // Web Open Font Format 2\n\n\t\t// misc\n\t\t\"glb\":  {}, // 3d models (binary)\n\t\t\"gltf\": {}, // 3d models (JSON/ASCII)\n\t}\n\n\tbinaryExtensions = map[string]struct{}{\n\t\t// binaries\n\t\t// These can theoretically contain secrets, but need decoding for users to make sense of them, and we don't have\n\t\t// any such decoders right now.\n\t\t\"class\":  {}, // Java bytecode class file\n\t\t\"dll\":    {}, // Dynamic Link Library, Windows\n\t\t\"jdo\":    {}, // Java Data Object, Java serialization format\n\t\t\"jks\":    {}, // Java Key Store, Java keystore format\n\t\t\"ser\":    {}, // Java serialization format\n\t\t\"idx\":    {}, // Index file, often binary\n\t\t\"hprof\":  {}, // Java heap dump format\n\t\t\"exe\":    {}, // Executable, Windows\n\t\t\"bin\":    {}, // Binary, often used for compiled source code\n\t\t\"so\":     {}, // Shared object, Unix/Linux\n\t\t\"o\":      {}, // Object file from compilation/ intermediate object file\n\t\t\"a\":      {}, // Static library, Unix/Linux\n\t\t\"dylib\":  {}, // Dynamic library, macOS\n\t\t\"lib\":    {}, // Library, Unix/Linux\n\t\t\"obj\":    {}, // Object file, typically from compiled source code\n\t\t\"pdb\":    {}, // Program Database, Microsoft Visual Studio debugging format\n\t\t\"dat\":    {}, // Generic data file, often binary but not always\n\t\t\"elf\":    {}, // Executable and Linkable Format, common in Unix/Linux\n\t\t\"dmg\":    {}, // Disk Image for macOS\n\t\t\"iso\":    {}, // ISO image (optical disk image)\n\t\t\"img\":    {}, // Disk image files\n\t\t\"out\":    {}, // Common output file from compiled executable in Unix/Linux\n\t\t\"com\":    {}, // DOS command file, executable\n\t\t\"sys\":    {}, // Windows system file, often a driver\n\t\t\"vxd\":    {}, // Virtual device driver in Windows\n\t\t\"sfx\":    {}, // Self-extracting archive\n\t\t\"bundle\": {}, // Mac OS X application bundle\n\t\t\"pyo\":    {}, // Compiled Python file\n\t\t\"pyc\":    {}, // Compiled Python file\n\t\t\"sym\":    {}, // Symbolic link, Unix/Linux\n\t\t\"rlib\":   {}, // Rust library\n\t\t\"pth\":    {}, // Pytorch serialized model\n\t\t\"pbix\":   {}, // Power BI report file\n\t\t\"pbit\":   {}, // Power BI template file\n\t}\n)\n\n// SkipFile returns true if the file extension is in the ignoredExtensions list.\nfunc SkipFile(filename string) bool {\n\text := strings.ToLower(strings.TrimPrefix(filepath.Ext(filename), \".\"))\n\t_, ok := ignoredExtensions[ext]\n\treturn ok\n}\n\n// IsBinary returns true if the file extension is in the binaryExtensions list.\nfunc IsBinary(filename string) bool {\n\t_, ok := binaryExtensions[strings.ToLower(strings.TrimPrefix(filepath.Ext(filename), \".\"))]\n\treturn ok\n}\n"
  },
  {
    "path": "pkg/common/vars_test.go",
    "content": "package common\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestSkipFile(t *testing.T) {\n\ttype testCase struct {\n\t\tfile string\n\t\twant bool\n\t}\n\n\t// Add a test case for each ignored extension.\n\ttestCases := make([]testCase, 0, (len(ignoredExtensions)+1)*2)\n\tfor ext := range ignoredExtensions {\n\t\ttestCases = append(testCases, testCase{\n\t\t\tfile: \"test.\" + ext,\n\t\t\twant: true,\n\t\t})\n\n\t\ttestCases = append(testCases, testCase{\n\t\t\tfile: \"test.\" + strings.ToUpper(ext),\n\t\t\twant: true,\n\t\t})\n\t}\n\n\t// Add a test case for a file that should not be skipped.\n\ttestCases = append(testCases, testCase{file: \"test.txt\", want: false})\n\n\tfor _, tt := range testCases {\n\t\tt.Run(tt.file, func(t *testing.T) {\n\t\t\tif got := SkipFile(tt.file); got != tt.want {\n\t\t\t\tt.Errorf(\"SkipFile(%v) got %v, want %v\", tt.file, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkSkipFile(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\tSkipFile(\"test.mp4\")\n\t}\n}\n\nfunc TestIsBinary(t *testing.T) {\n\ttype testCase struct {\n\t\tfile string\n\t\twant bool\n\t}\n\n\t// Add a test case for each binary extension.\n\ttestCases := make([]testCase, 0, len(binaryExtensions)+1)\n\tfor ext := range binaryExtensions {\n\t\ttestCases = append(testCases, testCase{\n\t\t\tfile: \"test.\" + ext,\n\t\t\twant: true,\n\t\t})\n\t}\n\n\t// Add a test case for a file that should not be skipped.\n\ttestCases = append(testCases, testCase{file: \"test.txt\", want: false})\n\n\tfor _, tt := range testCases {\n\t\tt.Run(tt.file, func(t *testing.T) {\n\t\t\tif got := IsBinary(tt.file); got != tt.want {\n\t\t\t\tt.Errorf(\"IsBinary(%v) got %v, want %v\", tt.file, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkIsBinary(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\tIsBinary(\"test.exe\")\n\t}\n}\n"
  },
  {
    "path": "pkg/config/config.go",
    "content": "package config\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/custom_detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/configpb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/protoyaml\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/docker\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/filesystem\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/gcs\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/github\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/gitlab\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/jenkins\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/postman\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/s3\"\n)\n\n// Config holds user supplied configuration.\ntype Config struct {\n\tSources   []sources.ConfiguredSource\n\tDetectors []detectors.Detector\n}\n\n// Read parses a given filename into a Config.\nfunc Read(filename string) (*Config, error) {\n\tinput, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewYAML(input)\n}\n\n// NewYAML parses the given YAML data into a Config.\nfunc NewYAML(input []byte) (*Config, error) {\n\tvar inputYAML configpb.Config\n\t// Parse the raw YAML into a structure.\n\tif err := protoyaml.UnmarshalStrict(input, &inputYAML); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Convert to detectors.\n\tvar detectorConfigs []detectors.Detector\n\tfor _, detectorConfig := range inputYAML.Detectors {\n\t\tdetector, err := custom_detectors.NewWebhookCustomRegex(detectorConfig)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdetectorConfigs = append(detectorConfigs, detector)\n\t}\n\n\t// Convert to configured sources.\n\tvar sourceConfigs []sources.ConfiguredSource\n\tfor _, pbSource := range inputYAML.Sources {\n\t\ts, err := instantiateSourceFromType(pbSource.GetType())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tsrc := sources.NewConfiguredSource(s, pbSource)\n\n\t\tsourceConfigs = append(sourceConfigs, src)\n\t}\n\n\treturn &Config{\n\t\tDetectors: detectorConfigs,\n\t\tSources:   sourceConfigs,\n\t}, nil\n}\n\n// instantiateSourceFromType creates a concrete implementation of\n// sources.Source for the provided type.\nfunc instantiateSourceFromType(sourceType string) (sources.Source, error) {\n\tvar source sources.Source\n\tswitch sourceType {\n\tcase sourcespb.SourceType_SOURCE_TYPE_GIT.String():\n\t\tsource = new(git.Source)\n\tcase sourcespb.SourceType_SOURCE_TYPE_GITHUB.String():\n\t\tsource = new(github.Source)\n\tcase sourcespb.SourceType_SOURCE_TYPE_GITHUB_UNAUTHENTICATED_ORG.String():\n\t\tsource = new(github.Source)\n\tcase sourcespb.SourceType_SOURCE_TYPE_PUBLIC_GIT.String():\n\t\tsource = new(git.Source)\n\tcase sourcespb.SourceType_SOURCE_TYPE_GITLAB.String():\n\t\tsource = new(gitlab.Source)\n\tcase sourcespb.SourceType_SOURCE_TYPE_POSTMAN.String():\n\t\tsource = new(postman.Source)\n\tcase sourcespb.SourceType_SOURCE_TYPE_S3.String():\n\t\tsource = new(s3.Source)\n\tcase sourcespb.SourceType_SOURCE_TYPE_S3_UNAUTHED.String():\n\t\tsource = new(s3.Source)\n\tcase sourcespb.SourceType_SOURCE_TYPE_FILESYSTEM.String():\n\t\tsource = new(filesystem.Source)\n\tcase sourcespb.SourceType_SOURCE_TYPE_JENKINS.String():\n\t\tsource = new(jenkins.Source)\n\tcase sourcespb.SourceType_SOURCE_TYPE_GCS.String():\n\t\tsource = new(gcs.Source)\n\tcase sourcespb.SourceType_SOURCE_TYPE_GCS_UNAUTHED.String():\n\t\tsource = new(gcs.Source)\n\tcase sourcespb.SourceType_SOURCE_TYPE_DOCKER.String():\n\t\tsource = new(docker.Source)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"got unexpected source type: %q\", sourceType)\n\t}\n\n\treturn source, nil\n}\n"
  },
  {
    "path": "pkg/config/detectors.go",
    "content": "package config\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\tdpb \"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nvar (\n\tspecialGroups = map[string][]DetectorID{\n\t\t\"all\": allDetectors(),\n\t}\n\n\tdetectorTypeValue = make(map[string]dpb.DetectorType, len(dpb.DetectorType_value))\n\tvalidDetectors    = make(map[dpb.DetectorType]struct{}, len(dpb.DetectorType_value))\n\tmaxDetectorType   dpb.DetectorType\n)\n\n// Setup package local global variables.\nfunc init() {\n\tfor k, v := range dpb.DetectorType_value {\n\t\tdt := dpb.DetectorType(v)\n\t\tdetectorTypeValue[strings.ToLower(k)] = dt\n\t\tvalidDetectors[dt] = struct{}{}\n\t\tif dt > maxDetectorType {\n\t\t\tmaxDetectorType = dt\n\t\t}\n\t}\n}\n\n// DetectorID identifies a detector type and version. This struct is used as a\n// way for users to identify detectors, whether unique or not. A DetectorID\n// with Version = 0 indicates all possible versions of a detector.\ntype DetectorID struct {\n\tID      dpb.DetectorType\n\tVersion int\n}\n\n// GetDetectorID extracts the DetectorID from a Detector.\nfunc GetDetectorID(d detectors.Detector) DetectorID {\n\tvar version int\n\tif v, ok := d.(detectors.Versioner); ok {\n\t\tversion = v.Version()\n\t}\n\treturn DetectorID{\n\t\tID:      d.Type(),\n\t\tVersion: version,\n\t}\n}\n\n// ParseDetectors parses user supplied string into a list of detectors types.\n// \"all\" will return the list of all available detectors. The input is comma\n// separated and may use the case-insensitive detector name defined in the\n// protobuf, or the protobuf enum number. A range may be used as well in the\n// form \"start-end\". Order is preserved and duplicates are ignored.\nfunc ParseDetectors(input string) ([]DetectorID, error) {\n\tvar output []DetectorID\n\tseenDetector := map[DetectorID]struct{}{}\n\tfor _, item := range strings.Split(input, \",\") {\n\t\titem = strings.TrimSpace(item)\n\t\tif item == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tallDetectors, ok := specialGroups[strings.ToLower(item)]\n\t\tif !ok {\n\t\t\tvar err error\n\t\t\tallDetectors, err = asRange(item)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tfor _, d := range allDetectors {\n\t\t\tif _, ok := seenDetector[d]; ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tseenDetector[d] = struct{}{}\n\t\t\toutput = append(output, d)\n\t\t}\n\t}\n\treturn output, nil\n}\n\n// ParseDetector parses a user supplied string into a single DetectorID. Input\n// is case-insensitive and either the detector name or ID may be used.\nfunc ParseDetector(input string) (DetectorID, error) {\n\treturn asDetectorID(strings.TrimSpace(input))\n}\n\n// ParseVerifierEndpoints parses a map of user supplied verifier URLs. The\n// input keys are detector IDs and the values are a comma separated list of\n// URLs. The URLs are validated as HTTPS endpoints.\nfunc ParseVerifierEndpoints(verifierURLs map[string]string) (map[DetectorID][]string, error) {\n\tverifiers := make(map[DetectorID][]string, len(verifierURLs))\n\tfor detectorID, urls := range verifierURLs {\n\t\tkey, err := ParseDetector(detectorID)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid detector ID for verifier: %w\", err)\n\t\t}\n\t\tverifierURLs := strings.Split(urls, \",\")\n\t\tfor i, rawEndpoint := range verifierURLs {\n\t\t\trawEndpoint := strings.TrimSpace(rawEndpoint)\n\t\t\tverifierURLs[i] = rawEndpoint\n\t\t\tif endpoint, err := url.Parse(rawEndpoint); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid verifier url %q: %w\", rawEndpoint, err)\n\t\t\t} else if endpoint.Scheme != \"https\" {\n\t\t\t\treturn nil, fmt.Errorf(\"verifier url must be https: %q\", rawEndpoint)\n\t\t\t}\n\t\t}\n\t\tverifiers[key] = append(verifiers[key], verifierURLs...)\n\t}\n\treturn verifiers, nil\n}\n\nfunc (id DetectorID) String() string {\n\tname := dpb.DetectorType_name[int32(id.ID)]\n\tif name == \"\" {\n\t\tname = \"<invalid ID>\"\n\t}\n\tif id.Version == 0 {\n\t\treturn name\n\t}\n\treturn fmt.Sprintf(\"%s.v%d\", name, id.Version)\n}\n\n// allDetectors returns an ordered slice of all detector types.\nfunc allDetectors() []DetectorID {\n\tall := make([]DetectorID, 0, len(dpb.DetectorType_name))\n\tfor id := range dpb.DetectorType_name {\n\t\tall = append(all, DetectorID{ID: dpb.DetectorType(id)})\n\t}\n\tsort.Slice(all, func(i, j int) bool { return all[i].ID < all[j].ID })\n\treturn all\n}\n\n// asRange converts a single input into a slice of detector types. If the input\n// is not in range format, a slice of length 1 is returned. Unbounded ranges\n// are allowed.\nfunc asRange(input string) ([]DetectorID, error) {\n\t// Check if it's a single detector type.\n\tdt, err := asDetectorID(input)\n\tif err == nil {\n\t\treturn []DetectorID{dt}, nil\n\t}\n\n\t// Check if it's a range; if not return the error from above.\n\tstart, end, found := strings.Cut(input, \"-\")\n\tif !found {\n\t\treturn nil, err\n\t}\n\tstart, end = strings.TrimSpace(start), strings.TrimSpace(end)\n\n\t// Convert the range start and end to a DetectorType.\n\tdtStart, err := asDetectorID(start)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdtEnd, err := asDetectorID(end)\n\t// If end is empty it's an unbounded range.\n\tif err != nil && end != \"\" {\n\t\treturn nil, err\n\t}\n\tif end == \"\" {\n\t\tdtEnd.ID = maxDetectorType\n\t}\n\n\t// Ensure these ranges don't have versions.\n\tif dtEnd.Version != 0 || dtStart.Version != 0 {\n\t\treturn nil, fmt.Errorf(\"versions within ranges are not supported: %s\", input)\n\t}\n\n\tstep := dpb.DetectorType(1)\n\tif dtStart.ID > dtEnd.ID {\n\t\tstep = -1\n\t}\n\tvar output []DetectorID\n\tfor dt := dtStart.ID; dt != dtEnd.ID; dt += step {\n\t\tif _, ok := validDetectors[dt]; !ok {\n\t\t\tcontinue\n\t\t}\n\t\toutput = append(output, DetectorID{ID: dt})\n\t}\n\treturn append(output, dtEnd), nil\n}\n\n// asDetectorID converts the case-insensitive input into a DetectorID. Name or\n// ID may be used. Input is expected to be already trimmed of whitespace.\nfunc asDetectorID(input string) (DetectorID, error) {\n\tif input == \"\" {\n\t\treturn DetectorID{}, fmt.Errorf(\"empty detector\")\n\t}\n\tvar detectorID DetectorID\n\t// Separate the version if there is one.\n\tif detector, version, hasVersion := strings.Cut(input, \".\"); hasVersion {\n\t\tparsedVersion, err := parseVersion(version)\n\t\tif err != nil {\n\t\t\treturn DetectorID{}, fmt.Errorf(\"invalid version for input: %q error: %w\", input, err)\n\t\t}\n\t\tdetectorID.Version = parsedVersion\n\t\t// Because there was a version, the detector type input is the part before the '.'\n\t\tinput = detector\n\t}\n\n\t// Check if it's a named detector.\n\tif dt, ok := detectorTypeValue[strings.ToLower(input)]; ok {\n\t\tdetectorID.ID = dt\n\t\treturn detectorID, nil\n\t}\n\t// Check if it's a detector ID.\n\tif i, err := strconv.ParseInt(input, 10, 32); err == nil {\n\t\tdt := dpb.DetectorType(i)\n\t\tif _, ok := validDetectors[dt]; !ok {\n\t\t\treturn DetectorID{}, fmt.Errorf(\"invalid detector ID: %s\", input)\n\t\t}\n\t\tdetectorID.ID = dt\n\t\treturn detectorID, nil\n\t}\n\treturn DetectorID{}, fmt.Errorf(\"unrecognized detector type: %s\", input)\n}\n\nfunc parseVersion(v string) (int, error) {\n\tif !strings.HasPrefix(strings.ToLower(v), \"v\") {\n\t\treturn 0, fmt.Errorf(\"version must start with 'v'\")\n\t}\n\tversion := strings.TrimLeft(v, \"vV\")\n\treturn strconv.Atoi(version)\n}\n"
  },
  {
    "path": "pkg/config/detectors_test.go",
    "content": "package config\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\tdpb \"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDetectorParsing(t *testing.T) {\n\ttests := map[string]struct {\n\t\tinput    string\n\t\texpected []DetectorID\n\t}{\n\t\t\"all\":                       {\"AlL\", allDetectors()},\n\t\t\"trailing range\":            {\"0-\", allDetectors()},\n\t\t\"all after 1\":               {\"1-\", allDetectors()[1:]},\n\t\t\"named and valid range\":     {\"aWs,8-9\", []DetectorID{{ID: dpb.DetectorType_AWS}, {ID: dpb.DetectorType_Github}, {ID: dpb.DetectorType_Gitlab}}},\n\t\t\"duplicate order preserved\": {\"9, 8, 9\", []DetectorID{{ID: 9}, {ID: 8}}},\n\t\t\"named range\":               {\"github - gitlab\", []DetectorID{{ID: dpb.DetectorType_Github}, {ID: dpb.DetectorType_Gitlab}}},\n\t\t\"range preserved\":           {\"8-9, 7-10\", []DetectorID{{ID: 8}, {ID: 9}, {ID: 7}, {ID: 10}}},\n\t\t\"reverse range\":             {\"9-8\", []DetectorID{{ID: 9}, {ID: 8}}},\n\t\t\"range preserved with all\":  {\"10-,all\", append(allDetectors()[10:], allDetectors()[:10]...)},\n\t\t\"empty list item\":           {\"8, ,9\", []DetectorID{{ID: 8}, {ID: 9}}},\n\t\t\"invalid end range\":         {\"0-1337\", nil},\n\t\t\"invalid name\":              {\"foo\", nil},\n\t\t\"negative\":                  {\"-1\", nil},\n\t\t\"github.v1\":                 {\"github.v1\", []DetectorID{{ID: dpb.DetectorType_Github, Version: 1}}},\n\t\t\"gitlab.v100\":               {\"gitlab.v100\", []DetectorID{{ID: dpb.DetectorType_Gitlab, Version: 100}}},\n\t\t\"range with versions\":       {\"github.v2 - gitlab.v1\", nil},\n\t\t\"invalid version no v\":      {\"gitlab.2\", nil},\n\t\t\"invalid version no number\": {\"gitlab.github\", nil},\n\t\t\"capital V is fine\":         {\"GiTlAb.V2\", []DetectorID{{ID: dpb.DetectorType_Gitlab, Version: 2}}},\n\t\t\"id number with version\":    {\"8.v2\", []DetectorID{{ID: 8, Version: 2}}},\n\t}\n\n\tfor name, tt := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tgot, gotErr := ParseDetectors(tt.input)\n\t\t\tif tt.expected == nil {\n\t\t\t\tassert.Error(t, gotErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tassert.Equal(t, tt.expected, got)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/context/context.go",
    "content": "package context\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/log\"\n)\n\nvar (\n\t// defaultLogger can be set via SetDefaultLogger.\n\t// It is initialized to write to stderr. To disable, you can call\n\t// SetDefaultLogger with logr.Discard().\n\tdefaultLogger logr.Logger\n)\n\n// logEntryKey is used to store a reference to the logger in the\n// context.Context key/value bag. This is used for regaining the logger in case\n// the context is converted into a different type.\nconst logEntryKey logEntryKeyT = 0\n\ntype logEntryKeyT int\n\nfunc init() {\n\tdefaultLogger, _ = log.New(\"context\", log.WithConsoleSink(os.Stderr))\n}\n\n// Context wraps context.Context and includes an additional Logger() method.\ntype Context interface {\n\tcontext.Context\n\tLogger() logr.Logger\n}\n\n// CancelFunc and CancelCauseFunc are type aliases to allow use as if they are\n// the same types as the standard library variants.\ntype CancelFunc = context.CancelFunc\ntype CancelCauseFunc = context.CancelCauseFunc\n\n// logCtx implements Context.\ntype logCtx struct {\n\t// Embed context.Context to get all methods for free.\n\tcontext.Context\n\tlog logr.Logger\n}\n\n// Logger returns a structured logger.\nfunc (l logCtx) Logger() logr.Logger {\n\treturn l.log\n}\n\n// Background returns context.Background with a default logger.\nfunc Background() Context {\n\treturn logCtx{\n\t\tlog:     defaultLogger,\n\t\tContext: context.Background(),\n\t}\n}\n\n// TODO returns context.TODO with a default logger.\nfunc TODO() Context {\n\treturn logCtx{\n\t\tlog:     defaultLogger,\n\t\tContext: context.TODO(),\n\t}\n}\n\n// WithCancel returns context.WithCancel with the log object propagated.\nfunc WithCancel(parent Context) (Context, context.CancelFunc) {\n\tctx, cancel := context.WithCancel(parent)\n\tlCtx := logCtx{\n\t\tlog:     parent.Logger(),\n\t\tContext: ctx,\n\t}\n\treturn lCtx, cancel\n}\n\n// WithCancelCause returns context.WithCancelCause with the log object propagated.\nfunc WithCancelCause(parent Context) (Context, context.CancelCauseFunc) {\n\tctx, cancel := context.WithCancelCause(parent)\n\tlCtx := logCtx{\n\t\tlog:     parent.Logger(),\n\t\tContext: ctx,\n\t}\n\treturn lCtx, cancel\n}\n\n// WithDeadline returns context.WithDeadline with the log object propagated and\n// the deadline added to the structured log values.\nfunc WithDeadline(parent Context, d time.Time) (Context, context.CancelFunc) {\n\tctx, cancel := context.WithDeadline(parent, d)\n\tlCtx := logCtx{\n\t\tlog:     parent.Logger().WithValues(\"deadline\", d),\n\t\tContext: ctx,\n\t}\n\treturn lCtx, cancel\n}\n\n// WithDeadlineCause returns context.WithDeadlineCause with the log object\n// propagated and the deadline added to the structured log values.\nfunc WithDeadlineCause(parent Context, d time.Time, cause error) (Context, context.CancelFunc) {\n\tctx, cancel := context.WithDeadlineCause(parent, d, cause)\n\tlCtx := logCtx{\n\t\tlog:     parent.Logger().WithValues(\"deadline\", d),\n\t\tContext: ctx,\n\t}\n\treturn lCtx, cancel\n}\n\n// WithTimeout returns context.WithTimeout with the log object propagated and\n// the timeout added to the structured log values.\nfunc WithTimeout(parent Context, timeout time.Duration) (Context, context.CancelFunc) {\n\tctx, cancel := context.WithTimeout(parent, timeout)\n\tlCtx := logCtx{\n\t\tlog:     parent.Logger().WithValues(\"timeout\", timeout),\n\t\tContext: ctx,\n\t}\n\treturn lCtx, cancel\n}\n\n// WithTimeoutCause returns context.WithTimeoutCause with the log object\n// propagated and the timeout added to the structured log values.\nfunc WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, context.CancelFunc) {\n\tctx, cancel := context.WithTimeoutCause(parent, timeout, cause)\n\tlCtx := logCtx{\n\t\tlog:     parent.Logger().WithValues(\"timeout\", timeout),\n\t\tContext: ctx,\n\t}\n\treturn lCtx, cancel\n}\n\n// Cause returns the context.Cause of the context.\nfunc Cause(ctx context.Context) error {\n\treturn context.Cause(ctx)\n}\n\n// WithValue returns context.WithValue with the log object propagated and\n// the value added to the structured log values (if the key is a string).\nfunc WithValue(parent Context, key, val any) Context {\n\tlogger := parent.Logger()\n\tparentCtx := context.WithValue(parent, key, val)\n\tif k, ok := key.(string); ok {\n\t\tlogger = logger.WithValues(k, val)\n\t\tparentCtx = context.WithValue(parentCtx, logEntryKey, logger)\n\t}\n\treturn logCtx{\n\t\tlog:     logger,\n\t\tContext: parentCtx,\n\t}\n}\n\n// WithValues returns context.WithValue with the log object propagated and\n// the values added to the structured log values (if the key is a string).\nfunc WithValues(parent Context, keyAndVals ...any) Context {\n\tctx := parent\n\tfor i := 0; i < len(keyAndVals)-1; i += 2 {\n\t\tctx = WithValue(ctx, keyAndVals[i], keyAndVals[i+1])\n\t}\n\treturn ctx\n}\n\n// WithLogger converts a context.Context into a Context by adding a logger.\nfunc WithLogger(parent context.Context, logger logr.Logger) Context {\n\treturn logCtx{\n\t\tlog:     logger,\n\t\tContext: context.WithValue(parent, logEntryKey, logger),\n\t}\n}\n\n// AddLogger converts a context.Context into a Context. If the underlying type\n// is already a Context, that will be returned, otherwise a default logger will\n// be added.\nfunc AddLogger(parent context.Context) Context {\n\t// If the context.Context is already a Context, return that.\n\tif loggerCtx, ok := parent.(Context); ok {\n\t\treturn loggerCtx\n\t}\n\t// If the logger exists in the grab bag (and is the correct type),\n\t// return that.\n\tif logEntryVal := parent.Value(logEntryKey); logEntryVal != nil {\n\t\tif logger, ok := logEntryVal.(logr.Logger); ok {\n\t\t\treturn WithLogger(parent, logger)\n\t\t}\n\t}\n\t// Otherwise, add the default logger.\n\treturn WithLogger(parent, defaultLogger)\n}\n\n// SetDefaultLogger sets the package-level global default logger that will be\n// used for Background and TODO contexts. On startup, the default logger will\n// be configured to output logs to stderr. Use logr.Discard() to disable all\n// logs from Contexts.\nfunc SetDefaultLogger(l logr.Logger) {\n\tdefaultLogger = l\n}\n"
  },
  {
    "path": "pkg/context/context_test.go",
    "content": "package context\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/go-logr/zapr\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/log\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n\t\"go.uber.org/zap/zaptest\"\n)\n\n// testLogger is a helper function to create a logger with a closure callback.\nfunc testLogger(t *testing.T, f func(zapcore.Entry)) logr.Logger {\n\treturn zapr.NewLogger(zaptest.NewLogger(t,\n\t\tzaptest.WrapOptions(zap.Hooks(func(e zapcore.Entry) error {\n\t\t\tf(e)\n\t\t\treturn nil\n\t\t}))))\n}\n\n// infoCounterContext is a helper function to create a Context that will count\n// the number of Info messages logged.\nfunc infoCounterContext(t *testing.T) (Context, *int) {\n\tvar infoCount int\n\tlogger := testLogger(t, func(e zapcore.Entry) {\n\t\tif e.Level == zap.InfoLevel {\n\t\t\tinfoCount++\n\t\t}\n\t})\n\treturn WithLogger(context.Background(), logger), &infoCount\n}\n\nfunc TestWithCancel(t *testing.T) {\n\tparentCtx, infoCount := infoCounterContext(t)\n\tctx, cancel := WithCancel(parentCtx)\n\tcancel()\n\tassert.Equal(t, 0, *infoCount)\n\tselect {\n\tcase <-ctx.Done():\n\t\tctx.Logger().Info(\"yay\")\n\tcase <-time.After(1 * time.Second):\n\t\tassert.Fail(t, \"context should be done\")\n\t}\n\tassert.Equal(t, 1, *infoCount)\n}\n\nfunc TestWithTimeout(t *testing.T) {\n\tparentCtx, infoCount := infoCounterContext(t)\n\tctx, cancel := WithTimeout(parentCtx, 10*time.Millisecond)\n\tdefer cancel()\n\n\tassert.Equal(t, 0, *infoCount)\n\tselect {\n\tcase <-ctx.Done():\n\t\tctx.Logger().Info(\"yay\")\n\tcase <-time.After(1 * time.Second):\n\t\tassert.Fail(t, \"context should be done\")\n\t}\n\tassert.Equal(t, 1, *infoCount)\n\n\tctx, cancel = WithTimeout(parentCtx, 1*time.Second)\n\tdefer cancel()\n\tselect {\n\tcase <-ctx.Done():\n\t\tassert.Fail(t, \"context should not be done\")\n\tcase <-time.After(10 * time.Millisecond):\n\t\tctx.Logger().Info(\"yay\")\n\t}\n\tassert.Equal(t, 2, *infoCount)\n}\n\nfunc TestWithLogger(t *testing.T) {\n\tvar infoCount int\n\tlogger := testLogger(t, func(e zapcore.Entry) {\n\t\tif e.Level == zap.InfoLevel {\n\t\t\tinfoCount++\n\t\t}\n\t})\n\n\tctx := WithLogger(context.Background(), logger)\n\tassert.Equal(t, logger, ctx.Logger())\n\n\tassert.Equal(t, 0, infoCount)\n\tctx.Logger().Info(\"yay\")\n\tassert.Equal(t, 1, infoCount)\n}\n\nfunc TestAsContext(t *testing.T) {\n\tvar gotValue any\n\tnormalFuncThatTakesContext := func(ctx context.Context) {\n\t\tif logCtx, ok := ctx.(Context); ok {\n\t\t\tlogCtx.Logger().Info(\"yay\")\n\t\t}\n\t\tgotValue = ctx.Value(\"key\")\n\t}\n\tparentCtx, infoCount := infoCounterContext(t)\n\tctx := WithValue(parentCtx, \"key\", \"value\")\n\n\tassert.Equal(t, 0, *infoCount)\n\tnormalFuncThatTakesContext(ctx)\n\tassert.Equal(t, 1, *infoCount)\n\tassert.Equal(t, \"value\", gotValue)\n}\n\nfunc TestWithValues(t *testing.T) {\n\tvar buffer bytes.Buffer\n\tlogger, sync := log.New(\"test\",\n\t\tlog.WithConsoleSink(&buffer),\n\t)\n\tdefer func(prevLogger logr.Logger) {\n\t\tdefaultLogger = prevLogger\n\t}(defaultLogger)\n\tSetDefaultLogger(logger)\n\n\t{\n\t\tctx1 := Background()\n\t\tctx1.Logger().Info(\"only a\", \"a\", 0)\n\n\t\tctx2 := WithValue(ctx1, \"b\", 1)\n\t\tctx2.Logger().Info(\"only b\")\n\t\tassert.Equal(t, 1, ctx2.Value(\"b\"))\n\n\t\tctx3 := WithLogger(ctx2, ctx2.Logger().WithValues(\"c\", 2, \"d\", 3))\n\t\tctx3.Logger().Info(\"bcd\")\n\n\t\tctx2.Logger().Info(\"only b again\")\n\n\t\ttype customKey string\n\t\tctx4 := WithValue(Background(), customKey(\"foo\"), \"bar\")\n\t\t// foo:bar shouldn't be added to the logger because the key isn't a string\n\t\tctx4.Logger().Info(\"foo\")\n\n\t\tctx5 := WithValues(ctx2, \"e\", 4, \"f\", 5, 6, \"six\")\n\t\tctx5.Logger().Info(\"bef\")\n\t\tassert.Equal(t, \"six\", ctx5.Value(6))\n\n\t\tctx6 := WithValues(ctx2, \"what does this do?\")\n\t\tctx6.Logger().Info(\"silently fail I suppose\")\n\t}\n\tassert.Nil(t, sync())\n\tlogs := strings.Split(strings.TrimSpace(buffer.String()), \"\\n\")\n\n\tassert.Equal(t, 7, len(logs))\n\tassert.Contains(t, logs[0], `{\"a\": 0}`)\n\tassert.Contains(t, logs[1], `{\"b\": 1}`)\n\tassert.Contains(t, logs[2], `{\"b\": 1, \"c\": 2, \"d\": 3}`)\n\tassert.Contains(t, logs[3], `{\"b\": 1}`)\n\tassert.NotContains(t, logs[4], `{\"foo\": \"bar\"}`)\n\tassert.Contains(t, logs[5], `{\"b\": 1, \"e\": 4, \"f\": 5}`)\n\tassert.Contains(t, logs[6], `silently fail`)\n\tassert.NotContains(t, logs[6], `what does this do?`)\n}\n\nfunc TestDefaultLogger(t *testing.T) {\n\tvar panicked bool\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tpanicked = true\n\t\t}\n\t\tassert.False(t, panicked)\n\t}()\n\tctx := Background()\n\tctx.Logger().Info(\"this shouldn't panic\")\n}\n\nfunc TestRace(t *testing.T) {\n\tctx, cancel := WithCancel(Background())\n\tgo cancel()\n\tgo func() { _ = ctx.Err() }()\n\tcancel()\n\t_ = ctx.Err()\n}\n\nfunc TestCause(t *testing.T) {\n\tctx, cancel := WithCancelCause(Background())\n\terr := fmt.Errorf(\"oh no\")\n\tcancel(err)\n\tassert.Equal(t, err, Cause(ctx))\n}\n\n// TestBuriedLogger tests when a logging context is wrapped by a non-logging\n// implementation that we can still regain the original logger.\nfunc TestBuriedLogger(t *testing.T) {\n\tvar buffer bytes.Buffer\n\tlogger, sync := log.New(\"test\",\n\t\tlog.WithConsoleSink(&buffer),\n\t)\n\tdefer func(prevLogger logr.Logger) {\n\t\tdefaultLogger = prevLogger\n\t}(defaultLogger)\n\tSetDefaultLogger(logger)\n\n\t// Create a context with a key/value log entry.\n\tctx := WithValue(Background(), \"log\", \"entry\")\n\t// Convert it to a stdlib context.\n\tstdCtx := context.WithValue(ctx, \"std\", \"entry\") //nolint:staticcheck\n\t// Try to get the logger back again.\n\tctx = AddLogger(stdCtx)\n\tctx.Logger().Info(\"test\")\n\n\tassert.Nil(t, sync())\n\tlogs := strings.Split(strings.TrimSpace(buffer.String()), \"\\n\")\n\n\t// Logger has the key/value log entry.\n\tassert.Equal(t, 1, len(logs))\n\tassert.Contains(t, logs[0], `{\"log\": \"entry\"}`)\n\t// Grab bag still has both values.\n\tassert.Equal(t, \"entry\", ctx.Value(\"log\"))\n\tassert.Equal(t, \"entry\", ctx.Value(\"std\"))\n}\n"
  },
  {
    "path": "pkg/custom_detectors/CUSTOM_DETECTORS.md",
    "content": "# TruffleHog Custom Detector Setup Guide\n\nThis 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.\n\n## Steps to Set Up a Custom Detector\n\n1. **Create a Configuration File**:\n   - TruffleHog uses a configuration file, typically named `config.yaml`, to manage custom detector configuration.\n   - If this file doesn't exist, create it in your system.\n\n2. **Define the Custom Detector**:\n   - Open `config.yaml` with a text editor.\n   - Add a new detector under the `detectors` section.\n\n   Here's a template for a custom detector:\n\n   ```yaml\n   # config.yaml\n   detectors:\n     - name: HogTokenDetector\n       keywords:\n         - hog\n       regex:\n         token: '[^A-Za-z0-9+\\/]{0,1}([A-Za-z0-9+\\/]{40})[^A-Za-z0-9+\\/]{0,1}'\n       verify:\n         - endpoint: http://localhost:8000/\n           # 'unsafe' must be set to true if the endpoint uses HTTP\n           unsafe: true\n           headers:\n             - \"Authorization: super secret authorization header\"\n   ```\n\n   **Explanation**:\n   - **`name`**: A unique identifier for your custom detector.\n   - **`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.\n   - **`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.\n\n   - **`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)\n\n   **Other allowed parameters:**\n   - **`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. If not provided, the first regex name in sorted order will be used as the primary regex by default.\n   - **`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.\n   - **`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.\n   - **`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.\n   - **`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.\n   - **`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.\n   Available validation options:\n     - **`contains_digit`**: Ensures the match contains at least one numeric digit (0-9). Useful for API keys or tokens that must include numbers.\n     - **`contains_lowercase`**: Ensures the match contains at least one lowercase letter (a-z). Common requirement for passwords and mixed-case tokens.\n     - **`contains_uppercase`**: Ensures the match contains at least one uppercase letter (A-Z). Helps validate tokens that follow mixed-case conventions.\n     - **`contains_special_char`**: Ensures the match contains at least one special character from the set `!@#$%^&*()_+-=[]{}|;:,.<>?`. Useful for complex passwords or encoded tokens.\n    \n\n    [Here](/examples/generic_with_filters.yml) is an example of a custom detector using these parameters. \n\n3. **Run TruffleHog with the Custom Detector**:\n   - Execute TruffleHog, specifying your configuration file:\n\n     ```bash\n     trufflehog filesystem <path_to_folder_or_file> --config=<path_to_file>/config.yaml\n     ```\n\n   - Replace `<path_to_folder_or_file>` with the path to the directory or file you want to scan, and `<path_to_file>` with the path to your `config.yaml`.\n   - TruffleHog will scan the specified file or folder using the custom detector you've defined.\n\n4. **Example**:\n\n   Let's use the template config provided above to search a file.\n\n   Assume you have a file `/tmp/data.txt` with the following content:\n\n   ```text\n   // this is a custom example\n   this file has some random text and maybe a secret\n   hog token: pOIAj9x47WT5qElx5JrI3e7O714HgaAIz2ck9sVn\n   // end of file\n   ```\n\n   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.\n\n   Run the following command:\n\n   ```bash\n   trufflehog filesystem /tmp --config=config.yaml\n   ```\n\n   The output should be similar to:\n\n   ```\n   🐷🔑🐷  TruffleHog. Unearth your secrets. 🐷🔑🐷\n\n   Found verified result 🐷🔑\n   Detector Type: CustomRegex\n   Decoder Type: PLAIN\n   Raw result: pOIAj9x47WT5qElx5JrI3e7O714HgaAIz2ck9sVn\n   File: /tmp/data.txt\n   Line: 3\n   ```\n\n   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.\n\n\n## Verification Server Examples\nUnless 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.\n\n### Python:\n\n```python\nimport json\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\n\nAUTH_HEADER = 'super secret authorization header'\n\nclass Verifier(BaseHTTPRequestHandler):\n    def do_GET(self):\n        self.send_response(405)\n        self.end_headers()\n\n    def do_POST(self):\n        try:\n            if self.headers['Authorization'] != AUTH_HEADER:\n                self.send_response(401)\n                self.end_headers()\n                return\n\n            length = int(self.headers['Content-Length'])\n            request = json.loads(self.rfile.read(length))\n            self.log_message(\"%s\", request)\n\n            if not validateTokens(request['HogTokenDetector']['token']):\n                self.send_response(200)\n                self.end_headers()\n            else:\n                self.send_response(403)\n                self.end_headers()\n        except Exception:\n            self.send_response(400)\n            self.end_headers()\n\ndef validateTokens(token):\n    return False  # Implement actual validation logic\n\nwith HTTPServer(('', 8000), Verifier) as server:\n    try:\n        server.serve_forever()\n    except KeyboardInterrupt:\n        pass\n```\n\n### Go\n```go\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n)\n\nconst authHeader = \"super secret authorization header\"\n\ntype HogTokenDetector struct {\n\tToken string `json:\"token\"`\n}\n\ntype RequestBody struct {\n\tHogTokenDetector HogTokenDetector `json:\"HogTokenDetector\"`\n}\n\nfunc validateTokens(token string) bool {\n\treturn false // Implement actual validation logic\n}\n\nfunc verifierHandler(w http.ResponseWriter, r *http.Request) {\n\tif r.Method != http.MethodPost {\n\t\thttp.Error(w, \"Method Not Allowed\", http.StatusMethodNotAllowed)\n\t\treturn\n\t}\n\n\tif r.Header.Get(\"Authorization\") != authHeader {\n\t\thttp.Error(w, \"Unauthorized\", http.StatusUnauthorized)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\thttp.Error(w, \"Bad Request\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tdefer r.Body.Close()\n\n\tvar requestBody RequestBody\n\tif err := json.Unmarshal(body, &requestBody); err != nil {\n\t\thttp.Error(w, \"Bad Request\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tlog.Printf(\"Received Request: %+v\", requestBody)\n\n\tif validateTokens(requestBody.HogTokenDetector.Token) {\n\t\thttp.Error(w, \"Forbidden\", http.StatusForbidden)\n\t} else {\n\t\tw.WriteHeader(http.StatusOK)\n\t}\n}\n\nfunc main() {\n\thttp.HandleFunc(\"/\", verifierHandler)\n\tserverAddr := \":8000\"\n\tfmt.Printf(\"Starting server on %s...\\n\", serverAddr)\n\tif err := http.ListenAndServe(serverAddr, nil); err != nil {\n\t\tlog.Fatalf(\"Server failed: %s\", err)\n\t}\n}\n```\n"
  },
  {
    "path": "pkg/custom_detectors/custom_detectors.go",
    "content": "package custom_detectors\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"maps\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"golang.org/x/sync/errgroup\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/custom_detectorspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\n// The maximum number of matches from one chunk. This const is used when\n// permutating each regex match to protect the scanner from doing too much work\n// for poorly defined regexps.\nconst maxTotalMatches = 100\n\n// CustomRegexWebhook is a CustomRegex with webhook validation that is\n// guaranteed to be valid (assuming the data is not changed after\n// initialization).\ntype CustomRegexWebhook struct {\n\t*custom_detectorspb.CustomRegex\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*CustomRegexWebhook)(nil)\nvar _ detectors.CustomFalsePositiveChecker = (*CustomRegexWebhook)(nil)\nvar _ detectors.MaxSecretSizeProvider = (*CustomRegexWebhook)(nil)\n\n// NewWebhookCustomRegex initializes and validates a CustomRegexWebhook. An\n// unexported type is intentionally returned here to ensure the values have\n// been validated.\nfunc NewWebhookCustomRegex(pb *custom_detectorspb.CustomRegex) (*CustomRegexWebhook, error) {\n\t// TODO: Return all validation errors.\n\tif err := ValidateKeywords(pb.Keywords); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := ValidateRegex(pb.Regex); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := ValidateRegexSlice(pb.ExcludeRegexesCapture); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := ValidateRegexSlice(pb.ExcludeRegexesMatch); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := ValidatePrimaryRegexName(pb.PrimaryRegexName, pb.Regex); err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, verify := range pb.Verify {\n\t\tif err := ValidateVerifyEndpoint(verify.Endpoint, verify.Unsafe); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif err := ValidateVerifyHeaders(verify.Headers); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// Ensure primary regex name is set.\n\tensurePrimaryRegexNameSet(pb)\n\n\t// TODO: Copy only necessary data out of pb.\n\treturn &CustomRegexWebhook{pb}, nil\n}\n\nvar httpClient = common.SaneHttpClient()\n\nfunc (c *CustomRegexWebhook) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tregexMatches := make(map[string][][]string, len(c.GetRegex()))\n\n\t// Compile exclude regexes targeting the capture group\n\texcludeRegexesCapture := make([]*regexp.Regexp, 0, len(c.GetExcludeRegexesCapture()))\n\tfor _, exclude := range c.GetExcludeRegexesCapture() {\n\t\tregex, err := regexp.Compile(exclude)\n\t\tif err != nil {\n\t\t\t// This will only happen if the regex is invalid.\n\t\t\treturn nil, err\n\t\t}\n\t\texcludeRegexesCapture = append(excludeRegexesCapture, regex)\n\t}\n\n\t// Compile exclude regexes targeting the entire match\n\texcludeRegexes := make([]*regexp.Regexp, 0, len(c.GetExcludeRegexesMatch()))\n\tfor _, exclude := range c.GetExcludeRegexesMatch() {\n\t\tregex, err := regexp.Compile(exclude)\n\t\tif err != nil {\n\t\t\t// This will only happen if the regex is invalid.\n\t\t\treturn nil, err\n\t\t}\n\t\texcludeRegexes = append(excludeRegexes, regex)\n\t}\n\n\t// Find all submatches for each regex.\n\tfor name, regex := range c.GetRegex() {\n\t\tregex, err := regexp.Compile(regex)\n\t\tif err != nil {\n\t\t\t// This will only happen if the regex is invalid.\n\t\t\treturn nil, err\n\t\t}\n\t\tregexMatches[name] = regex.FindAllStringSubmatch(dataStr, -1)\n\t}\n\n\t// Permutate each individual match.\n\t// {\n\t//    \"foo\": [[\"match1\"]]\n\t//    \"bar\": [[\"match2\"], [\"match3\"]]\n\t// }\n\t// becomes\n\t// [\n\t//    {\"foo\": [\"match1\"], \"bar\": [\"match2\"]},\n\t//    {\"foo\": [\"match1\"], \"bar\": [\"match3\"]},\n\t// ]\n\tmatches := permutateMatches(regexMatches)\n\n\tg := new(errgroup.Group)\n\n\t// Create result object and test for verification.\n\tresultsCh := make(chan detectors.Result, maxTotalMatches)\n\nMatchLoop:\n\tfor _, match := range matches {\n\t\tfor key, values := range match {\n\t\t\t// attempt to use capture group\n\t\t\tsecret := values[0]\n\t\t\tif len(values) > 1 {\n\t\t\t\tsecret = values[1]\n\t\t\t}\n\n\t\t\t// check entropy\n\t\t\tentropy := c.GetEntropy()\n\t\t\tif entropy > 0.0 && detectors.StringShannonEntropy(secret) < float64(entropy) {\n\t\t\t\tcontinue MatchLoop\n\t\t\t}\n\n\t\t\t// check for exclude words\n\t\t\tfor _, excludeWord := range c.GetExcludeWords() {\n\t\t\t\tif strings.Contains(strings.ToLower(secret), excludeWord) {\n\t\t\t\t\tcontinue MatchLoop\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// exclude checks\n\t\t\tfor _, excludeMatch := range excludeRegexes {\n\t\t\t\tif excludeMatch.MatchString(values[0]) {\n\t\t\t\t\tcontinue MatchLoop\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// exclude secret (capture group), or if no capture group is set,\n\t\t\t// check against entire match.\n\t\t\tfor _, excludeSecret := range excludeRegexesCapture {\n\t\t\t\tif excludeSecret.MatchString(secret) {\n\t\t\t\t\tcontinue MatchLoop\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif validations := c.GetValidations(); validations != nil {\n\t\t\t\tvalidationRules := []struct {\n\t\t\t\t\tenabled   bool\n\t\t\t\t\tvalidator func(string) bool\n\t\t\t\t}{\n\t\t\t\t\t{validations[key].GetContainsDigit(), ContainsDigit},\n\t\t\t\t\t{validations[key].GetContainsLowercase(), ContainsLowercase},\n\t\t\t\t\t{validations[key].GetContainsUppercase(), ContainsUppercase},\n\t\t\t\t\t{validations[key].GetContainsSpecialChar(), ContainsSpecialChar},\n\t\t\t\t}\n\n\t\t\t\tfor _, rule := range validationRules {\n\t\t\t\t\tif rule.enabled && !rule.validator(secret) {\n\t\t\t\t\t\t// skip this match if a validation rule is enabled but missing from the secret\n\t\t\t\t\t\tcontinue MatchLoop\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\tg.Go(func() error {\n\t\t\treturn c.createResults(ctx, match, verify, resultsCh)\n\t\t})\n\t}\n\n\t// Ignore any errors and collect as many of the results as we can.\n\t_ = g.Wait()\n\tclose(resultsCh)\n\n\tfor result := range resultsCh {\n\t\tif result.ExtraData != nil {\n\t\t\tresult.ExtraData[\"name\"] = c.GetName()\n\t\t}\n\n\t\tresults = append(results, result)\n\t}\n\n\treturn results, nil\n}\n\nfunc (c *CustomRegexWebhook) IsFalsePositive(_ detectors.Result) (bool, string) {\n\treturn false, \"\"\n}\n\n// custom max size for custom detector\nfunc (c *CustomRegexWebhook) MaxSecretSize() int64 {\n\treturn 1000\n}\n\nfunc (c *CustomRegexWebhook) createResults(ctx context.Context, match map[string][]string, verify bool, results chan<- detectors.Result) error {\n\tif common.IsDone(ctx) {\n\t\t// TODO: Log we're possibly leaving out results.\n\t\treturn ctx.Err()\n\t}\n\n\tresult := detectors.Result{\n\t\tDetectorType: detectorspb.DetectorType_CustomRegex,\n\t\tDetectorName: c.GetName(),\n\t\tExtraData:    map[string]string{},\n\t}\n\n\tvar raw string\n\tfor _, key := range slices.Sorted(maps.Keys(match)) {\n\t\tvalues := match[key]\n\t\t// values[0] contains the entire regex match.\n\t\tsecret := values[0]\n\t\tfullMatch := values[0]\n\t\tif len(values) > 1 {\n\t\t\tsecret = values[1]\n\t\t}\n\t\traw += secret\n\n\t\t// We set the full regex match as the primary secret value.\n\t\t// Reasoning:\n\t\t// The engine calculates the line number using the match. When a primary secret is set, it uses that value instead of the raw secret.\n\t\t// While the secret match itself is sufficient to calculate the line number, the same group match could appear elsewhere in the data.\n\t\t// To avoid ambiguity, we store the full regex match as the primary secret value.\n\t\t// This primary secret value is used only for identifying the exact line number and is not used anywhere else.\n\n\t\t// Example:\n\t\t// Full regex match: secret = ABC123\n\t\t// Secret (raw): ABC123\n\n\t\t// In this case, the primary secret value stores the full string `secret = ABC123`,\n\t\t// allowing the engine to pinpoint the exact location and avoid matching redundant occurrences of `ABC123` in the data.\n\t\tif c.PrimaryRegexName == key {\n\t\t\tresult.SetPrimarySecretValue(fullMatch)\n\t\t}\n\t}\n\n\tresult.Raw = []byte(raw)\n\n\tif !verify {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\tcase results <- result:\n\t\t\treturn nil\n\t\t}\n\t}\n\t// Verify via webhook.\n\tjsonBody, err := json.Marshal(map[string]map[string][]string{\n\t\tc.GetName(): match,\n\t})\n\tif err != nil {\n\t\t// This should never happen, but if it does, return nil to not\n\t\t// disrupt other verification.\n\t\treturn nil\n\t}\n\t// Try each config until we successfully verify.\n\tfor _, verifyConfig := range c.GetVerify() {\n\t\tif common.IsDone(ctx) {\n\t\t\t// TODO: Log we're possibly leaving out results.\n\t\t\treturn ctx.Err()\n\t\t}\n\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", verifyConfig.GetEndpoint(), bytes.NewReader(jsonBody))\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, header := range verifyConfig.GetHeaders() {\n\t\t\tkey, value, found := strings.Cut(header, \":\")\n\t\t\tif !found {\n\t\t\t\t// Should be unreachable due to validation.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(key, strings.TrimLeft(value, \"\\t\\n\\v\\f\\r \"))\n\t\t}\n\t\tresp, err := httpClient.Do(req)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tdefer func() {\n\t\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t\t_ = resp.Body.Close()\n\t\t}()\n\n\t\tif resp.StatusCode == http.StatusOK {\n\t\t\t// mark the result as verified\n\t\t\tresult.Verified = true\n\n\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// TODO: handle different content-type responses seperatly when implement custom detector configurations\n\t\t\tresponseStr := string(body)\n\t\t\t// truncate to 200 characters if response length exceeds 200\n\t\t\tif len(responseStr) > 200 {\n\t\t\t\tresponseStr = responseStr[:200]\n\t\t\t}\n\n\t\t\t// store the processed response in ExtraData\n\t\t\tresult.ExtraData[\"response\"] = responseStr\n\n\t\t\tbreak\n\t\t}\n\t}\n\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn ctx.Err()\n\tcase results <- result:\n\t\treturn nil\n\t}\n}\n\nfunc (c *CustomRegexWebhook) Keywords() []string {\n\treturn c.GetKeywords()\n}\n\n// productIndices produces a permutation of indices for each length. Example:\n// productIndices(3, 2) -> [[0 0] [1 0] [2 0] [0 1] [1 1] [2 1]]. It returns\n// a slice of length no larger than maxTotalMatches.\nfunc productIndices(lengths ...int) [][]int {\n\tcount := 1\n\tfor _, l := range lengths {\n\t\tcount *= l\n\t}\n\tif count == 0 {\n\t\treturn nil\n\t}\n\tif count > maxTotalMatches {\n\t\tcount = maxTotalMatches\n\t}\n\n\tresults := make([][]int, count)\n\tfor i := 0; i < count; i++ {\n\t\tj := 1\n\t\tresult := make([]int, 0, len(lengths))\n\t\tfor _, l := range lengths {\n\t\t\tresult = append(result, (i/j)%l)\n\t\t\tj *= l\n\t\t}\n\t\tresults[i] = result\n\t}\n\treturn results\n}\n\n// permutateMatches converts the list of all regex matches into all possible\n// permutations selecting one from each named entry in the map. For example:\n// {\"foo\": [matchA, matchB], \"bar\": [matchC]} becomes\n//\n// [{\"foo\": matchA, \"bar\": matchC}, {\"foo\": matchB, \"bar\": matchC}]\nfunc permutateMatches(regexMatches map[string][][]string) []map[string][]string {\n\t// Get a consistent order for names and their matching lengths.\n\t// The lengths are used in calculating the permutation so order matters.\n\tnames := make([]string, 0, len(regexMatches))\n\tlengths := make([]int, 0, len(regexMatches))\n\tfor key, value := range regexMatches {\n\t\tnames = append(names, key)\n\t\tlengths = append(lengths, len(value))\n\t}\n\n\t// Permutate all the indices for each match. For example, if \"foo\" has\n\t// [matchA, matchB] and \"bar\" has [matchC], we will get indices [0 0] [1 0].\n\tpermutationIndices := productIndices(lengths...)\n\n\t// Build {\"foo\": matchA, \"bar\": matchC} and {\"foo\": matchB, \"bar\": matchC}\n\t// from the indices.\n\tvar matches []map[string][]string\n\tfor _, permutation := range permutationIndices {\n\t\tcandidate := make(map[string][]string, len(permutationIndices))\n\t\tfor i, name := range names {\n\t\t\tcandidate[name] = regexMatches[name][permutation[i]]\n\t\t}\n\t\tmatches = append(matches, candidate)\n\t}\n\n\treturn matches\n}\n\nfunc (c *CustomRegexWebhook) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CustomRegex\n}\n\nconst defaultDescription = \"This is a user-defined detector with no description provided.\"\n\nfunc (c *CustomRegexWebhook) Description() string {\n\tif c.GetDescription() == \"\" {\n\t\treturn defaultDescription\n\t}\n\treturn c.GetDescription()\n}\n\n// ensurePrimaryRegexNameSet sets the PrimaryRegexName field to the\n// first regex name in sorted order if it is not already set.\n// We're sorting to ensure deterministic behavior.\nfunc ensurePrimaryRegexNameSet(pb *custom_detectorspb.CustomRegex) {\n\tif pb.PrimaryRegexName == \"\" {\n\t\tfor _, name := range slices.Sorted(maps.Keys(pb.Regex)) {\n\t\t\tpb.PrimaryRegexName = name\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/custom_detectors/custom_detectors_test.go",
    "content": "package custom_detectors\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/custom_detectorspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/protoyaml\"\n)\n\nfunc TestCustomRegexTemplateParsing(t *testing.T) {\n\ttestCustomRegexTemplateYaml := `name: Internal bi tool\nkeywords:\n- secret_v1_\n- pat_v2_\nregex:\n  id_pat_example: ([a-zA-Z0-9]{32})\n  secret_pat_example: ([a-zA-Z0-9]{32})\nverify:\n- endpoint: http://localhost:8000/{id_pat_example}\n  unsafe: true\n  headers:\n  - 'Authorization: Bearer {secret_pat_example.0}'\n  successRanges:\n  - 200-250\n  - '288'`\n\n\tvar got custom_detectorspb.CustomRegex\n\tassert.NoError(t, protoyaml.UnmarshalStrict([]byte(testCustomRegexTemplateYaml), &got))\n\tassert.Equal(t, \"Internal bi tool\", got.Name)\n\tassert.Equal(t, []string{\"secret_v1_\", \"pat_v2_\"}, got.Keywords)\n\tassert.Equal(t, map[string]string{\n\t\t\"id_pat_example\":     \"([a-zA-Z0-9]{32})\",\n\t\t\"secret_pat_example\": \"([a-zA-Z0-9]{32})\",\n\t}, got.Regex)\n\tassert.Equal(t, 1, len(got.Verify))\n\tassert.Equal(t, \"http://localhost:8000/{id_pat_example}\", got.Verify[0].Endpoint)\n\tassert.Equal(t, true, got.Verify[0].Unsafe)\n\tassert.Equal(t, []string{\"Authorization: Bearer {secret_pat_example.0}\"}, got.Verify[0].Headers)\n\tassert.Equal(t, []string{\"200-250\", \"288\"}, got.Verify[0].SuccessRanges)\n}\n\nfunc TestCustomRegexWebhookParsing(t *testing.T) {\n\ttestCustomRegexWebhookYaml := `name: Internal bi tool\nkeywords:\n- secret_v1_\n- pat_v2_\nregex:\n  id_pat_example: ([a-zA-Z0-9]{32})\n  secret_pat_example: ([a-zA-Z0-9]{32})\nverify:\n- endpoint: http://localhost:8000/\n  unsafe: true\n  headers:\n  - 'Authorization: Bearer token'`\n\n\tvar got custom_detectorspb.CustomRegex\n\tassert.NoError(t, protoyaml.UnmarshalStrict([]byte(testCustomRegexWebhookYaml), &got))\n\tassert.Equal(t, \"Internal bi tool\", got.Name)\n\tassert.Equal(t, []string{\"secret_v1_\", \"pat_v2_\"}, got.Keywords)\n\tassert.Equal(t, map[string]string{\n\t\t\"id_pat_example\":     \"([a-zA-Z0-9]{32})\",\n\t\t\"secret_pat_example\": \"([a-zA-Z0-9]{32})\",\n\t}, got.Regex)\n\tassert.Equal(t, 1, len(got.Verify))\n\tassert.Equal(t, \"http://localhost:8000/\", got.Verify[0].Endpoint)\n\tassert.Equal(t, true, got.Verify[0].Unsafe)\n\tassert.Equal(t, []string{\"Authorization: Bearer token\"}, got.Verify[0].Headers)\n}\n\n// TestCustomDetectorsParsing tests the full `detectors` configuration.\nfunc TestCustomDetectorsParsing(t *testing.T) {\n\t// TODO: Support both template and webhook.\n\ttestYamlConfig := `detectors:\n- name: Internal bi tool\n  keywords:\n  - secret_v1_\n  - pat_v2_\n  regex:\n    id_pat_example: ([a-zA-Z0-9]{32})\n    secret_pat_example: ([a-zA-Z0-9]{32})\n  verify:\n  - endpoint: http://localhost:8000/\n    unsafe: true\n    headers:\n    - 'Authorization: Bearer token'`\n\n\tvar messages custom_detectorspb.CustomDetectors\n\tassert.NoError(t, protoyaml.UnmarshalStrict([]byte(testYamlConfig), &messages))\n\tassert.Equal(t, 1, len(messages.Detectors))\n\n\tgot := messages.Detectors[0]\n\tassert.Equal(t, \"Internal bi tool\", got.Name)\n\tassert.Equal(t, []string{\"secret_v1_\", \"pat_v2_\"}, got.Keywords)\n\tassert.Equal(t, map[string]string{\n\t\t\"id_pat_example\":     \"([a-zA-Z0-9]{32})\",\n\t\t\"secret_pat_example\": \"([a-zA-Z0-9]{32})\",\n\t}, got.Regex)\n\tassert.Equal(t, 1, len(got.Verify))\n\tassert.Equal(t, \"http://localhost:8000/\", got.Verify[0].Endpoint)\n\tassert.Equal(t, true, got.Verify[0].Unsafe)\n\tassert.Equal(t, []string{\"Authorization: Bearer token\"}, got.Verify[0].Headers)\n}\n\nfunc TestFromData_InvalidRegEx(t *testing.T) {\n\tc := &CustomRegexWebhook{\n\t\t&custom_detectorspb.CustomRegex{\n\t\t\tName:     \"Internal bi tool\",\n\t\t\tKeywords: []string{\"secret_v1_\", \"pat_v2_\"},\n\t\t\tRegex: map[string]string{\n\t\t\t\t\"test\": \"!!?(?:?)[a-zA-Z0-9]{32}\", // invalid regex\n\t\t\t},\n\t\t},\n\t}\n\n\t_, err := c.FromData(context.Background(), false, []byte(\"test\"))\n\tassert.Error(t, err)\n}\n\nfunc TestProductIndices(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tinput []int\n\t\twant  [][]int\n\t}{\n\t\t{\n\t\t\tname:  \"zero\",\n\t\t\tinput: []int{3, 0},\n\t\t\twant:  nil,\n\t\t},\n\t\t{\n\t\t\tname:  \"one input\",\n\t\t\tinput: []int{3},\n\t\t\twant:  [][]int{{0}, {1}, {2}},\n\t\t},\n\t\t{\n\t\t\tname:  \"two inputs\",\n\t\t\tinput: []int{3, 2},\n\t\t\twant: [][]int{\n\t\t\t\t{0, 0}, {1, 0}, {2, 0},\n\t\t\t\t{0, 1}, {1, 1}, {2, 1},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"three inputs\",\n\t\t\tinput: []int{3, 2, 3},\n\t\t\twant: [][]int{\n\t\t\t\t{0, 0, 0}, {1, 0, 0}, {2, 0, 0},\n\t\t\t\t{0, 1, 0}, {1, 1, 0}, {2, 1, 0},\n\t\t\t\t{0, 0, 1}, {1, 0, 1}, {2, 0, 1},\n\t\t\t\t{0, 1, 1}, {1, 1, 1}, {2, 1, 1},\n\t\t\t\t{0, 0, 2}, {1, 0, 2}, {2, 0, 2},\n\t\t\t\t{0, 1, 2}, {1, 1, 2}, {2, 1, 2},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := productIndices(tt.input...)\n\t\t\tassert.Equal(t, tt.want, got)\n\t\t})\n\t}\n}\n\nfunc TestProductIndicesMax(t *testing.T) {\n\tgot := productIndices(2, 3, 4, 5, 6)\n\tassert.GreaterOrEqual(t, 2*3*4*5*6, maxTotalMatches)\n\tassert.Equal(t, maxTotalMatches, len(got))\n}\n\nfunc TestPermutateMatches(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tinput map[string][][]string\n\t\twant  []map[string][]string\n\t}{\n\t\t{\n\t\t\tname:  \"two matches\",\n\t\t\tinput: map[string][][]string{\"foo\": {{\"matchA\"}, {\"matchB\"}}, \"bar\": {{\"matchC\"}}},\n\t\t\twant: []map[string][]string{\n\t\t\t\t{\"foo\": {\"matchA\"}, \"bar\": {\"matchC\"}},\n\t\t\t\t{\"foo\": {\"matchB\"}, \"bar\": {\"matchC\"}},\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := permutateMatches(tt.input)\n\t\t\tassert.Equal(t, tt.want, got)\n\t\t})\n\t}\n}\n\nfunc TestDetector(t *testing.T) {\n\tdetector, err := NewWebhookCustomRegex(&custom_detectorspb.CustomRegex{\n\t\tName: \"test\",\n\t\t// \"password\" is normally flagged as a false positive, but CustomRegex\n\t\t// should allow the user to decide and report it as a result.\n\t\tKeywords: []string{\"password\"},\n\t\tRegex:    map[string]string{\"regex\": \"password=\\\"(.*)\\\"\"},\n\t})\n\tassert.NoError(t, err)\n\tresults, err := detector.FromData(context.Background(), false, []byte(`password=\"123456\"`))\n\tassert.NoError(t, err)\n\tassert.Equal(t, 1, len(results))\n\tassert.Equal(t, results[0].Raw, []byte(`123456`))\n}\n\nfunc TestDetectorPrimarySecret(t *testing.T) {\n\tdetector, err := NewWebhookCustomRegex(&custom_detectorspb.CustomRegex{\n\t\tName:             \"test\",\n\t\tKeywords:         []string{\"secret\"},\n\t\tRegex:            map[string]string{\"id\": \"id_[A-Z0-9]{10}_yy\", \"secret\": \"secret_[A-Z0-9]{10}_yy\"},\n\t\tPrimaryRegexName: \"secret\",\n\t})\n\tassert.NoError(t, err)\n\tresults, err := detector.FromData(context.Background(), false, []byte(`\n\t// getData returns id and secret\n\tfunc getData()(string, string){\n    \treturn \"id_ALPHA10100_yy\", \"secret_YI7C90ACY1_yy\"\n\t}\n\t`))\n\tassert.NoError(t, err)\n\tassert.Equal(t, 1, len(results))\n\tassert.Equal(t, \"secret_YI7C90ACY1_yy\", results[0].GetPrimarySecretValue())\n}\n\nfunc TestDetectorPrimarySecretFullMatch(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tinput *custom_detectorspb.CustomRegex\n\t\tchunk []byte\n\t\twant  string\n\t}{\n\t\t{\n\t\t\tname: \"primary regex full match\",\n\t\t\tinput: &custom_detectorspb.CustomRegex{\n\t\t\t\tName:             \"test\",\n\t\t\t\tKeywords:         []string{\"secret\"},\n\t\t\t\tRegex:            map[string]string{\"secret\": `secret *= *\"([^\"\\r\\n]+)\"`},\n\t\t\t\tPrimaryRegexName: \"secret\",\n\t\t\t},\n\t\t\tchunk: []byte(`\n\t\t\t// some code\n\t\t\tsecret=\"mysecret\"\n\t\t\t// some code\n\t\t\t`),\n\t\t\twant: `secret=\"mysecret\"`,\n\t\t},\n\t\t{\n\t\t\tname: \"primary regex full match multiline\",\n\t\t\tinput: &custom_detectorspb.CustomRegex{\n\t\t\t\tName:             \"test\",\n\t\t\t\tKeywords:         []string{\"secret\"},\n\t\t\t\tRegex:            map[string]string{\"secret\": `secret *= *\"([^\"]+)\"`},\n\t\t\t\tPrimaryRegexName: \"secret\",\n\t\t\t},\n\t\t\tchunk: []byte(`\n\t\t\t// some code\n\t\t\tsecret=\"mysecret\n\t\t\tthatspansmultiplelines\"\n\t\t\t// some code\n\t\t\t`),\n\t\t\twant: `secret=\"mysecret\n\t\t\tthatspansmultiplelines\"`,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdetector, err := NewWebhookCustomRegex(tt.input)\n\t\t\tassert.NoError(t, err)\n\t\t\tresults, err := detector.FromData(context.Background(), false, tt.chunk)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Equal(t, 1, len(results))\n\t\t\tassert.Equal(t, tt.want, results[0].GetPrimarySecretValue())\n\t\t})\n\t}\n\n}\n\nfunc TestDetectorValidations(t *testing.T) {\n\ttype args struct {\n\t\tCustomRegex *custom_detectorspb.CustomRegex\n\t\tData        string\n\t}\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput args\n\t\twant  []detectors.Result\n\t}{\n\t\t{\n\t\t\tname: \"custom validation - contains digit\",\n\t\t\tinput: args{\n\t\t\t\tCustomRegex: &custom_detectorspb.CustomRegex{\n\t\t\t\t\tName:     \"test\",\n\t\t\t\t\tKeywords: []string{\"password\"},\n\t\t\t\t\tRegex:    map[string]string{\"password\": `([A-Za-z0-9!@#$%^&*()_+=\\-]{12,})`},\n\t\t\t\t\tValidations: map[string]*custom_detectorspb.ValidationConfig{\n\t\t\t\t\t\t\"password\": {\n\t\t\t\t\t\t\tContainsDigit: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: `This is custom example\n\t\t\t\t\t\tThis file has a random text and maybe a secret\n\t\t\t\t\t\tPassword: MyStr0ngP@ssword!\n\t\t\t\t\t\tEnd of file`,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CustomRegex,\n\t\t\t\t\tDetectorName: \"test\",\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(\"MyStr0ngP@ssword!\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"custom validation - does not contains digit\",\n\t\t\tinput: args{\n\t\t\t\tCustomRegex: &custom_detectorspb.CustomRegex{\n\t\t\t\t\tName:     \"test\",\n\t\t\t\t\tKeywords: []string{\"password\"},\n\t\t\t\t\tRegex:    map[string]string{\"password\": `([A-Za-z0-9!@#$%^&*()_+=\\-]{12,})`},\n\t\t\t\t\tValidations: map[string]*custom_detectorspb.ValidationConfig{\n\t\t\t\t\t\t\"password\": {\n\t\t\t\t\t\t\tContainsDigit: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: `This is custom example\n\t\t\t\t\t\tThis file has a random text and maybe a secret\n\t\t\t\t\t\tPassword: MyStrongPassword!\n\t\t\t\t\t\tEnd of file`,\n\t\t\t},\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"custom validation - contains lowercase\",\n\t\t\tinput: args{\n\t\t\t\tCustomRegex: &custom_detectorspb.CustomRegex{\n\t\t\t\t\tName:     \"test\",\n\t\t\t\t\tKeywords: []string{\"password\"},\n\t\t\t\t\tRegex:    map[string]string{\"password\": `([A-Za-z0-9!@#$%^&*()_+=\\-]{12,})`},\n\t\t\t\t\tValidations: map[string]*custom_detectorspb.ValidationConfig{\n\t\t\t\t\t\t\"password\": {\n\t\t\t\t\t\t\tContainsLowercase: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: `This is custom example\n\t\t\t\t\t\tThis file has a random text and maybe a secret\n\t\t\t\t\t\tPassword: MyStrongPassword!\n\t\t\t\t\t\tEnd of file`,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CustomRegex,\n\t\t\t\t\tDetectorName: \"test\",\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(\"MyStrongPassword!\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"custom validation - does not contains lowercase\",\n\t\t\tinput: args{\n\t\t\t\tCustomRegex: &custom_detectorspb.CustomRegex{\n\t\t\t\t\tName:     \"test\",\n\t\t\t\t\tKeywords: []string{\"password\"},\n\t\t\t\t\tRegex:    map[string]string{\"password\": `([A-Za-z0-9!@#$%^&*()_+=\\-]{12,})`},\n\t\t\t\t\tValidations: map[string]*custom_detectorspb.ValidationConfig{\n\t\t\t\t\t\t\"password\": {\n\t\t\t\t\t\t\tContainsLowercase: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: `This is custom example\n\t\t\t\t\t\tThis file has a random text and maybe a secret\n\t\t\t\t\t\tPassword: MYSTRONGPASSWORD!\n\t\t\t\t\t\tEnd of file`,\n\t\t\t},\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"custom validation - contains uppercase\",\n\t\t\tinput: args{\n\t\t\t\tCustomRegex: &custom_detectorspb.CustomRegex{\n\t\t\t\t\tName:     \"test\",\n\t\t\t\t\tKeywords: []string{\"password\"},\n\t\t\t\t\tRegex:    map[string]string{\"password\": `([A-Za-z0-9!@#$%^&*()_+=\\-]{12,})`},\n\t\t\t\t\tValidations: map[string]*custom_detectorspb.ValidationConfig{\n\t\t\t\t\t\t\"password\": {\n\t\t\t\t\t\t\tContainsUppercase: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: `This is custom example\n\t\t\t\t\t\tThis file has a random text and maybe a secret\n\t\t\t\t\t\tPassword: MyStrongPassword!\n\t\t\t\t\t\tEnd of file`,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CustomRegex,\n\t\t\t\t\tDetectorName: \"test\",\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(\"MyStrongPassword!\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"custom validation - does not contains uppercase\",\n\t\t\tinput: args{\n\t\t\t\tCustomRegex: &custom_detectorspb.CustomRegex{\n\t\t\t\t\tName:     \"test\",\n\t\t\t\t\tKeywords: []string{\"password\"},\n\t\t\t\t\tRegex:    map[string]string{\"password\": `([A-Za-z0-9!@#$%^&*()_+=\\-]{12,})`},\n\t\t\t\t\tValidations: map[string]*custom_detectorspb.ValidationConfig{\n\t\t\t\t\t\t\"password\": {\n\t\t\t\t\t\t\tContainsUppercase: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: `This is custom example\n\t\t\t\t\t\tThis file has a random text and maybe a secret\n\t\t\t\t\t\tPassword: mystrongpassword!\n\t\t\t\t\t\tEnd of file`,\n\t\t\t},\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"custom validation - contains special character\",\n\t\t\tinput: args{\n\t\t\t\tCustomRegex: &custom_detectorspb.CustomRegex{\n\t\t\t\t\tName:     \"test\",\n\t\t\t\t\tKeywords: []string{\"password\"},\n\t\t\t\t\tRegex:    map[string]string{\"password\": `([A-Za-z0-9!@#$%^&*()_+=\\-]{12,})`},\n\t\t\t\t\tValidations: map[string]*custom_detectorspb.ValidationConfig{\n\t\t\t\t\t\t\"password\": {\n\t\t\t\t\t\t\tContainsSpecialChar: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: `This is custom example\n\t\t\t\t\t\tThis file has a random text and maybe a secret\n\t\t\t\t\t\tPassword: MyStr@ngP@ssword!\n\t\t\t\t\t\tEnd of file`,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CustomRegex,\n\t\t\t\t\tDetectorName: \"test\",\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(\"MyStr@ngP@ssword!\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"custom validation - does not contains special character\",\n\t\t\tinput: args{\n\t\t\t\tCustomRegex: &custom_detectorspb.CustomRegex{\n\t\t\t\t\tName:     \"test\",\n\t\t\t\t\tKeywords: []string{\"password\"},\n\t\t\t\t\tRegex:    map[string]string{\"password\": `([A-Za-z0-9!@#$%^&*()_+=\\-]{12,})`},\n\t\t\t\t\tValidations: map[string]*custom_detectorspb.ValidationConfig{\n\t\t\t\t\t\t\"password\": {\n\t\t\t\t\t\t\tContainsSpecialChar: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: `This is custom example\n\t\t\t\t\t\tThis file has a random text and maybe a secret\n\t\t\t\t\t\tPassword: MyStrongPassword\n\t\t\t\t\t\tEnd of file`,\n\t\t\t},\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"custom validation - contains uppercase and special characters\",\n\t\t\tinput: args{\n\t\t\t\tCustomRegex: &custom_detectorspb.CustomRegex{\n\t\t\t\t\tName:     \"test\",\n\t\t\t\t\tKeywords: []string{\"password\"},\n\t\t\t\t\tRegex:    map[string]string{\"password\": `([A-Za-z0-9!@#$%^&*()_+=\\-]{12,})`},\n\t\t\t\t\tValidations: map[string]*custom_detectorspb.ValidationConfig{\n\t\t\t\t\t\t\"password\": {\n\t\t\t\t\t\t\tContainsUppercase:   true,\n\t\t\t\t\t\t\tContainsSpecialChar: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: `This is custom example\n\t\t\t\t\t\tThis file has a random text and maybe a secret\n\t\t\t\t\t\tPassword: MyStrongP@ssword\n\t\t\t\t\t\tEnd of file`,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CustomRegex,\n\t\t\t\t\tDetectorName: \"test\",\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(\"MyStrongP@ssword\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"custom validation - contains uppercase but does not contain special characters\",\n\t\t\tinput: args{\n\t\t\t\tCustomRegex: &custom_detectorspb.CustomRegex{\n\t\t\t\t\tName:     \"test\",\n\t\t\t\t\tKeywords: []string{\"password\"},\n\t\t\t\t\tRegex:    map[string]string{\"password\": `([A-Za-z0-9!@#$%^&*()_+=\\-]{12,})`},\n\t\t\t\t\tValidations: map[string]*custom_detectorspb.ValidationConfig{\n\t\t\t\t\t\t\"password\": {\n\t\t\t\t\t\t\tContainsUppercase:   true,\n\t\t\t\t\t\t\tContainsSpecialChar: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: `This is custom example\n\t\t\t\t\t\tThis file has a random text and maybe a secret\n\t\t\t\t\t\tPassword: MyStrongPassword\n\t\t\t\t\t\tEnd of file`,\n\t\t\t},\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"custom validation - wrong regex name in validations\",\n\t\t\tinput: args{\n\t\t\t\tCustomRegex: &custom_detectorspb.CustomRegex{\n\t\t\t\t\tName:     \"test\",\n\t\t\t\t\tKeywords: []string{\"password\"},\n\t\t\t\t\tRegex:    map[string]string{\"password\": `([A-Za-z0-9!@#$%^&*()_+=\\-]{12,})`},\n\t\t\t\t\tValidations: map[string]*custom_detectorspb.ValidationConfig{\n\t\t\t\t\t\t\"wrong\": {\n\t\t\t\t\t\t\tContainsUppercase: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: `This is custom example\n\t\t\t\t\t\tThis file has a random text and maybe a secret\n\t\t\t\t\t\tPassword: mystrongp@ssword\n\t\t\t\t\t\tEnd of file`,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CustomRegex,\n\t\t\t\t\tDetectorName: \"test\",\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(\"mystrongp@ssword\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"custom validation - multiple regex validations\",\n\t\t\tinput: args{\n\t\t\t\tCustomRegex: &custom_detectorspb.CustomRegex{\n\t\t\t\t\tName:     \"test\",\n\t\t\t\t\tKeywords: []string{\"password\", \"api_key\"},\n\t\t\t\t\tRegex: map[string]string{\n\t\t\t\t\t\t\"password\": `([A-Za-z0-9!@#$%^&*()_+=\\-]{12,})`,\n\t\t\t\t\t\t\"api_key\":  `([a-f0-9_-]{32})`,\n\t\t\t\t\t},\n\t\t\t\t\tValidations: map[string]*custom_detectorspb.ValidationConfig{\n\t\t\t\t\t\t\"password\": {\n\t\t\t\t\t\t\tContainsUppercase:   true,\n\t\t\t\t\t\t\tContainsSpecialChar: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"api_key\": {\n\t\t\t\t\t\t\tContainsSpecialChar: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tData: `This is custom example\n\t\t\t\t\t\tThis file has a random text and maybe a secret\n\t\t\t\t\t\tPassword: MyStrongP@ssword\n\t\t\t\t\t\tAPI_Key: c392c9837d69b44c764cbf260b-e6184 // should be detected\n\t\t\t\t\t\tAPI_Key: c392c9837d69b44c764cbf260be6184 // should be filtered by validation\n\t\t\t\t\t\tEnd of file`,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CustomRegex,\n\t\t\t\t\tDetectorName: \"test\",\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(\"c392c9837d69b44c764cbf260b-e6184MyStrongP@ssword\"),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdetector, err := NewWebhookCustomRegex(tt.input.CustomRegex)\n\t\t\tassert.NoError(t, err)\n\t\t\tresults, err := detector.FromData(context.Background(), false, []byte(tt.input.Data))\n\t\t\tassert.NoError(t, err)\n\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"ExtraData\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(results, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"CustomDetector.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNewWebhookCustomRegex_Validation(t *testing.T) {\n\tt.Parallel()\n\n\t// A known-good baseline; each test case mutates exactly one thing to trigger a specific validator.\n\tbase := func() *custom_detectorspb.CustomRegex {\n\t\treturn &custom_detectorspb.CustomRegex{\n\t\t\tName:     \"ok\",\n\t\t\tKeywords: []string{\"kw\"},\n\t\t\tRegex: map[string]string{\n\t\t\t\t\"main\": `\\btoken_[a-z]+\\b`,\n\t\t\t},\n\t\t\tPrimaryRegexName: \"main\",\n\t\t\tExcludeRegexesCapture: []string{\n\t\t\t\t`^skip_.*$`,\n\t\t\t},\n\t\t\tExcludeRegexesMatch: []string{\n\t\t\t\t`^ignore_.*$`,\n\t\t\t},\n\t\t\tVerify: []*custom_detectorspb.VerifierConfig{\n\t\t\t\t{\n\t\t\t\t\tEndpoint: \"https://example.com/verify\",\n\t\t\t\t\tUnsafe:   false,\n\t\t\t\t\tHeaders:  []string{\"Authorization: Bearer x\"},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t}\n\n\ttests := []struct {\n\t\tname          string\n\t\tmutate        func(*custom_detectorspb.CustomRegex)\n\t\twantErr       bool\n\t\twantErrSubstr string // substring expected in error\n\t}{\n\t\t{\n\t\t\tname:   \"Validate everything ok\",\n\t\t\tmutate: func(pb *custom_detectorspb.CustomRegex) {},\n\t\t},\n\t\t{\n\t\t\tname: \"ValidateKeywords: no keywords\",\n\t\t\tmutate: func(pb *custom_detectorspb.CustomRegex) {\n\t\t\t\tpb.Keywords = nil\n\t\t\t},\n\t\t\twantErr:       true,\n\t\t\twantErrSubstr: \"no keywords\",\n\t\t},\n\t\t{\n\t\t\tname: \"ValidateKeywords: empty keyword\",\n\t\t\tmutate: func(pb *custom_detectorspb.CustomRegex) {\n\t\t\t\tpb.Keywords = []string{\"\"}\n\t\t\t},\n\t\t\twantErr:       true,\n\t\t\twantErrSubstr: \"empty keyword\",\n\t\t},\n\t\t{\n\t\t\tname: \"ValidateRegex: no regex\",\n\t\t\tmutate: func(pb *custom_detectorspb.CustomRegex) {\n\t\t\t\tpb.Regex = nil\n\t\t\t},\n\t\t\twantErr:       true,\n\t\t\twantErrSubstr: \"no regex\",\n\t\t},\n\t\t{\n\t\t\tname: \"ValidateRegex: invalid regex in map\",\n\t\t\tmutate: func(pb *custom_detectorspb.CustomRegex) {\n\t\t\t\tpb.Regex = map[string]string{\"main\": \"(\"} // invalid\n\t\t\t},\n\t\t\twantErr:       true,\n\t\t\twantErrSubstr: \"regex 'main':\",\n\t\t},\n\t\t{\n\t\t\tname: \"ValidateRegexSlice: invalid exclude_regexes_capture\",\n\t\t\tmutate: func(pb *custom_detectorspb.CustomRegex) {\n\t\t\t\tpb.ExcludeRegexesCapture = []string{\"(\"} // invalid\n\t\t\t},\n\t\t\twantErr:       true,\n\t\t\twantErrSubstr: \"regex '1':\",\n\t\t},\n\t\t{\n\t\t\tname: \"ValidateRegexSlice: invalid exclude_regexes_match\",\n\t\t\tmutate: func(pb *custom_detectorspb.CustomRegex) {\n\t\t\t\tpb.ExcludeRegexesMatch = []string{\"(\"} // invalid\n\t\t\t},\n\t\t\twantErr:       true,\n\t\t\twantErrSubstr: \"regex '1':\",\n\t\t},\n\t\t{\n\t\t\tname: \"ValidatePrimaryRegexName: unknown primary regex name\",\n\t\t\tmutate: func(pb *custom_detectorspb.CustomRegex) {\n\t\t\t\tpb.PrimaryRegexName = \"does-not-exist\"\n\t\t\t},\n\t\t\twantErr:       true,\n\t\t\twantErrSubstr: `unknown primary regex name: \"does-not-exist\"`,\n\t\t},\n\t\t{\n\t\t\tname: \"ValidateVerifyEndpoint: empty endpoint\",\n\t\t\tmutate: func(pb *custom_detectorspb.CustomRegex) {\n\t\t\t\tpb.Verify = []*custom_detectorspb.VerifierConfig{\n\t\t\t\t\t{Endpoint: \"\", Unsafe: false, Headers: []string{\"A: b\"}},\n\t\t\t\t}\n\t\t\t},\n\t\t\twantErr:       true,\n\t\t\twantErrSubstr: \"no endpoint\",\n\t\t},\n\t\t{\n\t\t\tname: \"ValidateVerifyEndpoint: http endpoint without unsafe=true\",\n\t\t\tmutate: func(pb *custom_detectorspb.CustomRegex) {\n\t\t\t\tpb.Verify = []*custom_detectorspb.VerifierConfig{\n\t\t\t\t\t{Endpoint: \"http://example.com/verify\", Unsafe: false, Headers: []string{\"A: b\"}},\n\t\t\t\t}\n\t\t\t},\n\t\t\twantErr:       true,\n\t\t\twantErrSubstr: \"http endpoint must have unsafe=true\",\n\t\t},\n\t\t{\n\t\t\tname: \"ValidateVerifyHeaders: header missing colon\",\n\t\t\tmutate: func(pb *custom_detectorspb.CustomRegex) {\n\t\t\t\tpb.Verify = []*custom_detectorspb.VerifierConfig{\n\t\t\t\t\t{Endpoint: \"https://example.com/verify\", Unsafe: false, Headers: []string{\"Authorization Bearer x\"}},\n\t\t\t\t}\n\t\t\t},\n\t\t\twantErr:       true,\n\t\t\twantErrSubstr: `must contain a colon`,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tpb := base()\n\t\t\ttt.mutate(pb)\n\n\t\t\tgot, err := NewWebhookCustomRegex(pb)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Fatalf(\"expected error=%v, got error=%v (result=%#v)\", tt.wantErr, err != nil, got)\n\t\t\t}\n\t\t\tif tt.wantErr && got != nil {\n\t\t\t\tt.Fatalf(\"expected nil result on error, got=%#v\", got)\n\t\t\t}\n\t\t\tif tt.wantErr && !strings.Contains(err.Error(), tt.wantErrSubstr) {\n\t\t\t\tt.Fatalf(\"error mismatch:\\n  got:  %q\\n  want substring: %q\", err.Error(), tt.wantErrSubstr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNewWebhookCustomRegex_EnsurePrimaryRegexNameSet(t *testing.T) {\n\tt.Parallel()\n\n\tpb := &custom_detectorspb.CustomRegex{\n\t\tName:     \"test\",\n\t\tKeywords: []string{\"kw\"},\n\t\tRegex: map[string]string{\n\t\t\t\"regex_a\": `regex_a`,\n\t\t\t\"regex_b\": `regex_b`,\n\t\t},\n\t\t// PrimaryRegexName is not set.\n\t}\n\n\tdetector, err := NewWebhookCustomRegex(pb)\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"regex_a\", detector.GetPrimaryRegexName(), \"expected PrimaryRegexName to be set to regex_a\")\n}\n\nfunc BenchmarkProductIndices(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = productIndices(3, 2, 6)\n\t}\n}\n"
  },
  {
    "path": "pkg/custom_detectors/regex_varstring.go",
    "content": "package custom_detectors\n\nimport (\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// nameGroupRegex matches `{ name . group }` ignoring any whitespace.\nvar nameGroupRegex = regexp.MustCompile(`{\\s*([a-zA-Z0-9-_]+)\\s*(\\.\\s*[0-9]*)?\\s*}`)\n\n// RegexVarString is a string with embedded {name.group} variables. A name may\n// only contain alphanumeric, hyphen, and underscore characters. Group is\n// optional but if provided it must be a non-negative integer. If the group is\n// omitted it defaults to 0.\ntype RegexVarString struct {\n\toriginal string\n\t// map from name to group\n\tvariables map[string]int\n}\n\nfunc NewRegexVarString(original string) RegexVarString {\n\tvariables := make(map[string]int)\n\n\tmatches := nameGroupRegex.FindAllStringSubmatch(original, -1)\n\tfor _, match := range matches {\n\t\tname, group := match[1], 0\n\t\t// The second match will start with a period followed by any number\n\t\t// of whitespace.\n\t\tif len(match[2]) > 1 {\n\t\t\tg, err := strconv.Atoi(strings.TrimSpace(match[2][1:]))\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tgroup = g\n\t\t}\n\t\tvariables[name] = group\n\t}\n\n\treturn RegexVarString{\n\t\toriginal:  original,\n\t\tvariables: variables,\n\t}\n}\n"
  },
  {
    "path": "pkg/custom_detectors/regex_varstring_test.go",
    "content": "package custom_detectors\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestVarString(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinput    string\n\t\twantVars map[string]int\n\t}{\n\t\t{\n\t\t\tname:     \"empty\",\n\t\t\tinput:    \"{}\",\n\t\t\twantVars: map[string]int{},\n\t\t},\n\t\t{\n\t\t\tname:  \"no subgroup\",\n\t\t\tinput: \"{hello}\",\n\t\t\twantVars: map[string]int{\n\t\t\t\t\"hello\": 0,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"with subgroup\",\n\t\t\tinput: \"{hello.123}\",\n\t\t\twantVars: map[string]int{\n\t\t\t\t\"hello\": 123,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"subgroup with spaces\",\n\t\t\tinput: \"{\\thell0  . 123  }\",\n\t\t\twantVars: map[string]int{\n\t\t\t\t\"hell0\": 123,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"multiple groups\",\n\t\t\tinput: \"foo {bar} {bazz.buzz} {buzz.2}\",\n\t\t\twantVars: map[string]int{\n\t\t\t\t\"bar\":  0,\n\t\t\t\t\"buzz\": 2,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"nested groups\",\n\t\t\tinput: \"{foo {bar}}\",\n\t\t\twantVars: map[string]int{\n\t\t\t\t\"bar\": 0,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"decimal without number\",\n\t\t\tinput: \"{foo.}\",\n\t\t\twantVars: map[string]int{\n\t\t\t\t\"foo\": 0,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"negative number\",\n\t\t\tinput:    \"{foo.-1}\",\n\t\t\twantVars: map[string]int{},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := NewRegexVarString(tt.input)\n\t\t\tassert.Equal(t, tt.input, got.original)\n\t\t\tassert.Equal(t, tt.wantVars, got.variables)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/custom_detectors/validation.go",
    "content": "package custom_detectors\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nfunc ValidateKeywords(keywords []string) error {\n\tif len(keywords) == 0 {\n\t\treturn fmt.Errorf(\"no keywords\")\n\t}\n\n\tfor _, keyword := range keywords {\n\t\tif len(keyword) == 0 {\n\t\t\treturn fmt.Errorf(\"empty keyword\")\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc ValidateRegex(regex map[string]string) error {\n\tif len(regex) == 0 {\n\t\treturn fmt.Errorf(\"no regex\")\n\t}\n\tfor name, reg := range regex {\n\t\tif _, err := regexp.Compile(reg); err != nil {\n\t\t\treturn fmt.Errorf(\"regex '%s': %w\", name, err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc ValidateRegexSlice(regex []string) error {\n\tfor i, reg := range regex {\n\t\tif _, err := regexp.Compile(reg); err != nil {\n\t\t\treturn fmt.Errorf(\"regex '%d': %w\", i+1, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// validates if a provided non-empty primary regex name exists in the map of regexes\nfunc ValidatePrimaryRegexName(primaryRegexName string, regexes map[string]string) error {\n\tif primaryRegexName == \"\" {\n\t\treturn nil\n\t}\n\tif _, ok := regexes[primaryRegexName]; !ok {\n\t\treturn fmt.Errorf(\"unknown primary regex name: %q\", primaryRegexName)\n\t}\n\treturn nil\n}\n\nfunc ValidateVerifyEndpoint(endpoint string, unsafe bool) error {\n\tif len(endpoint) == 0 {\n\t\treturn fmt.Errorf(\"no endpoint\")\n\t}\n\n\tif strings.HasPrefix(endpoint, \"http://\") && !unsafe {\n\t\treturn fmt.Errorf(\"http endpoint must have unsafe=true\")\n\t}\n\treturn nil\n}\n\nfunc ValidateVerifyHeaders(headers []string) error {\n\tfor _, header := range headers {\n\t\tif !strings.Contains(header, \":\") {\n\t\t\treturn fmt.Errorf(\"header %q must contain a colon\", header)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc ValidateVerifyRanges(ranges []string) error {\n\tconst httpLowerRange = 100\n\tconst httpUpperRange = 599\n\n\tfor _, successRange := range ranges {\n\t\tif !strings.Contains(successRange, \"-\") {\n\t\t\thttpCode, err := strconv.Atoi(successRange)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"unable to convert http code to int %q\", successRange)\n\t\t\t}\n\n\t\t\tif httpCode < httpLowerRange || httpCode > httpUpperRange {\n\t\t\t\treturn fmt.Errorf(\"invalid http status code %q\", successRange)\n\t\t\t}\n\n\t\t\tcontinue\n\t\t}\n\n\t\thttpRange := strings.Split(successRange, \"-\")\n\t\tif len(httpRange) != 2 {\n\t\t\treturn fmt.Errorf(\"invalid range format %q\", successRange)\n\t\t}\n\n\t\tlowerBound, err := strconv.Atoi(httpRange[0])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to convert lower bound to int %q\", successRange)\n\t\t}\n\n\t\tupperBound, err := strconv.Atoi(httpRange[1])\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to convert upper bound to int %q\", successRange)\n\t\t}\n\n\t\tif lowerBound > upperBound {\n\t\t\treturn fmt.Errorf(\"lower bound greater than upper bound on range %q\", successRange)\n\t\t}\n\n\t\tif lowerBound < httpLowerRange || upperBound > httpUpperRange {\n\t\t\treturn fmt.Errorf(\"invalid http status code range %q\", successRange)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc ValidateRegexVars(regex map[string]string, body ...string) error {\n\tfor _, b := range body {\n\t\tmatches := NewRegexVarString(b).variables\n\t\tfor match := range matches {\n\t\t\tif _, ok := regex[match]; !ok {\n\t\t\t\treturn fmt.Errorf(\"body %q contains an unknown variable\", b)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// === Custom Validations ===\n\n// ContainsDigit checks if string contains at least one digit\nfunc ContainsDigit(s string) bool {\n\tfor i := 0; i < len(s); i++ {\n\t\tchar := s[i]\n\t\tif char >= '0' && char <= '9' {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// ContainsLowercase checks if string contains at least one lowercase letter\nfunc ContainsLowercase(s string) bool {\n\tfor i := 0; i < len(s); i++ {\n\t\tchar := s[i]\n\t\tif char >= 'a' && char <= 'z' {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// ContainsUppercase checks if string contains at least one uppercase letter\nfunc ContainsUppercase(s string) bool {\n\tfor i := 0; i < len(s); i++ {\n\t\tchar := s[i]\n\t\tif char >= 'A' && char <= 'Z' {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// ContainsSpecialChar checks if string contains at least one special character\nfunc ContainsSpecialChar(s string) bool {\n\tspecialChars := \"!@#$%^&*()_+-=[]{}|;:,.<>?\"\n\treturn strings.ContainsAny(s, specialChars)\n}\n"
  },
  {
    "path": "pkg/custom_detectors/validation_test.go",
    "content": "package custom_detectors\n\nimport (\n\t\"testing\"\n)\n\nfunc TestCustomDetectorsKeywordValidation(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tinput   []string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"Test empty list of keywords\",\n\t\t\tinput:   []string{},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"Test empty keyword\",\n\t\t\tinput:   []string{\"\"},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"Test valid keywords\",\n\t\t\tinput:   []string{\"hello\", \"world\"},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := ValidateKeywords(tt.input)\n\n\t\t\tif (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {\n\t\t\t\tt.Errorf(\"ValidateKeywords() error = %v, wantErr %v\", got, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCustomDetectorsRegexValidation(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tinput   map[string]string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Test list of keywords\",\n\t\t\tinput: map[string]string{\n\t\t\t\t\"id_pat_example\": \"([a-zA-Z0-9]{32})\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"Test empty list of keywords\",\n\t\t\tinput:   map[string]string{},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Test invalid regex\",\n\t\t\tinput: map[string]string{\n\t\t\t\t\"test\": \"!!?(?:?)[a-zA-Z0-9]{32}\",\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := ValidateRegex(tt.input)\n\n\t\t\tif (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {\n\t\t\t\tt.Errorf(\"ValidateRegex() error = %v, wantErr %v\", got, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCustomDetectorsVerifyEndpointValidation(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tendpoint string\n\t\tunsafe   bool\n\t\twantErr  bool\n\t}{\n\t\t{\n\t\t\tname:     \"Test http endpoint with unsafe flag\",\n\t\t\tendpoint: \"http://localhost:8000/{id_pat_example}\",\n\t\t\tunsafe:   true,\n\t\t\twantErr:  false,\n\t\t},\n\t\t{\n\t\t\tname:     \"Test http endpoint without unsafe flag\",\n\t\t\tendpoint: \"http://localhost:8000/{id_pat_example}\",\n\t\t\tunsafe:   false,\n\t\t\twantErr:  true,\n\t\t},\n\t\t{\n\t\t\tname:     \"Test https endpoint with unsafe flag\",\n\t\t\tendpoint: \"https://localhost:8000/{id_pat_example}\",\n\t\t\tunsafe:   true,\n\t\t\twantErr:  false,\n\t\t},\n\t\t{\n\t\t\tname:     \"Test https endpoint without unsafe flag\",\n\t\t\tendpoint: \"https://localhost:8000/{id_pat_example}\",\n\t\t\tunsafe:   false,\n\t\t\twantErr:  false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := ValidateVerifyEndpoint(tt.endpoint, tt.unsafe)\n\n\t\t\tif (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {\n\t\t\t\tt.Errorf(\"ValidateVerifyEndpoint() error = %v, wantErr %v\", got, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCustomDetectorsVerifyHeadersValidation(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\theaders []string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"Test single header\",\n\t\t\theaders: []string{\"Authorization: Bearer {secret_pat_example.0}\"},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"Test invalid header\",\n\t\t\theaders: []string{\"Hello world\"},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"Test ugly header\",\n\t\t\theaders: []string{\"Hello:::::::world::hi:\"},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"Test empty header\",\n\t\t\theaders: []string{},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := ValidateVerifyHeaders(tt.headers)\n\n\t\t\tif (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {\n\t\t\t\tt.Errorf(\"ValidateVerifyHeaders() error = %v, wantErr %v\", got, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCustomDetectorsVerifyRangeValidation(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tranges  []string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"Test multiple mixed ranges\",\n\t\t\tranges:  []string{\"200\", \"300-350\"},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"Test invalid non-number range\",\n\t\t\tranges:  []string{\"hi\"},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"Test invalid lower to upper range\",\n\t\t\tranges:  []string{\"200-100\"},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"Test invalid http range\",\n\t\t\tranges:  []string{\"400-1000\"},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"Test multiple ranges with invalid inputs\",\n\t\t\tranges:  []string{\"322\", \"hello-world\", \"100-200\"},\n\t\t\twantErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := ValidateVerifyRanges(tt.ranges)\n\n\t\t\tif (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {\n\t\t\t\tt.Errorf(\"ValidateVerifyRanges() error = %v, wantErr %v\", got, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCustomDetectorsVerifyRegexVarsValidation(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tregex   map[string]string\n\t\tbody    string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"Regex defined but not used in body\",\n\t\t\tregex:   map[string]string{\"id\": \"[0-9]{1,10}\", \"id_pat_example\": \"([a-zA-Z0-9]{32})\"},\n\t\t\tbody:    \"hello world\",\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"Regex defined and is used in body\",\n\t\t\tregex:   map[string]string{\"id\": \"[0-9]{1,10}\", \"id_pat_example\": \"([a-zA-Z0-9]{32})\"},\n\t\t\tbody:    \"hello world {id}\",\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"Regex var in body but not defined\",\n\t\t\tregex:   map[string]string{\"id\": \"[0-9]{1,10}\", \"id_pat_example\": \"([a-zA-Z0-9]{32})\"},\n\t\t\tbody:    \"hello world {hello}\",\n\t\t\twantErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := ValidateRegexVars(tt.regex, tt.body)\n\n\t\t\tif (got != nil && !tt.wantErr) || (got == nil && tt.wantErr) {\n\t\t\t\tt.Errorf(\"ValidateRegexVars() error = %v, wantErr %v\", got, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestContainsDigit(t *testing.T) {\n\ttype args struct {\n\t\ts string\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"contains digit\",\n\t\t\targs: args{s: \"lzscqf&60M\"},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"does not contains digit\",\n\t\t\targs: args{s: \"ZlDQOdaM*vsT\"},\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := ContainsDigit(tt.args.s); got != tt.want {\n\t\t\t\tt.Errorf(\"ContainsDigit() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestContainsLowercase(t *testing.T) {\n\ttype args struct {\n\t\ts string\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"contains lower case\",\n\t\t\targs: args{s: \"g0AJBHdnhRG2\"},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"does not contains lower case\",\n\t\t\targs: args{s: \"V7T#MEA6@+TN\"},\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := ContainsLowercase(tt.args.s); got != tt.want {\n\t\t\t\tt.Errorf(\"ContainsDigit() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestContainsUppercase(t *testing.T) {\n\ttype args struct {\n\t\ts string\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"contains upper case\",\n\t\t\targs: args{s: \"G1sKkJeKlSQf\"},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"does not contains upper case\",\n\t\t\targs: args{s: \"pq6-14ydz1@d\"},\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := ContainsUppercase(tt.args.s); got != tt.want {\n\t\t\t\tt.Errorf(\"ContainsDigit() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestContainsSpecialChar(t *testing.T) {\n\ttype args struct {\n\t\ts string\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"contains upper case\",\n\t\t\targs: args{s: \"HP$gE7s=do0B\"},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"does not contains upper case\",\n\t\t\targs: args{s: \"w9gvBYctrSjB\"},\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := ContainsSpecialChar(tt.args.s); got != tt.want {\n\t\t\t\tt.Errorf(\"ContainsDigit() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/decoders/base64.go",
    "content": "package decoders\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"unicode\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\ntype (\n\tBase64 struct{}\n)\n\nvar (\n\tb64Charset  = []byte(\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/-_=\")\n\tb64EndChars = \"+/-_=\"\n\t// Given characters are mostly ASCII, we can use a simple array to map.\n\tb64CharsetMapping [128]bool\n)\n\nfunc init() {\n\t// Build an array of all the characters in the base64 charset.\n\tfor _, char := range b64Charset {\n\t\tb64CharsetMapping[char] = true\n\t}\n}\n\nfunc (d *Base64) Type() detectorspb.DecoderType {\n\treturn detectorspb.DecoderType_BASE64\n}\n\nfunc (d *Base64) FromChunk(chunk *sources.Chunk) *DecodableChunk {\n\tdecodableChunk := &DecodableChunk{Chunk: chunk, DecoderType: d.Type()}\n\tencodedSubstrings := getSubstringsOfCharacterSet(chunk.Data, 20, b64CharsetMapping, b64EndChars)\n\tdecodedSubstrings := make(map[string][]byte)\n\n\tfor _, str := range encodedSubstrings {\n\t\tdec, err := base64.StdEncoding.DecodeString(str)\n\t\tif err == nil && len(dec) > 0 && isASCII(dec) {\n\t\t\tdecodedSubstrings[str] = dec\n\t\t}\n\n\t\tdec, err = base64.RawURLEncoding.DecodeString(str)\n\t\tif err == nil && len(dec) > 0 && isASCII(dec) {\n\t\t\tdecodedSubstrings[str] = dec\n\t\t}\n\t}\n\n\tif len(decodedSubstrings) > 0 {\n\t\tvar result bytes.Buffer\n\t\tresult.Grow(len(chunk.Data))\n\n\t\tstart := 0\n\t\tfor _, encoded := range encodedSubstrings {\n\t\t\tif decoded, ok := decodedSubstrings[encoded]; ok {\n\t\t\t\tend := bytes.Index(chunk.Data[start:], []byte(encoded))\n\t\t\t\tif end != -1 {\n\t\t\t\t\tresult.Write(chunk.Data[start : start+end])\n\t\t\t\t\tresult.Write(decoded)\n\t\t\t\t\tstart += end + len(encoded)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tresult.Write(chunk.Data[start:])\n\t\tchunk.Data = result.Bytes()\n\t\treturn decodableChunk\n\t}\n\n\treturn nil\n}\n\nfunc isASCII(b []byte) bool {\n\tfor i := 0; i < len(b); i++ {\n\t\tif b[i] > unicode.MaxASCII {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc getSubstringsOfCharacterSet(data []byte, threshold int, charsetMapping [128]bool, endChars string) []string {\n\tif len(data) == 0 {\n\t\treturn nil\n\t}\n\n\tcount := 0\n\tsubstringsCount := 0\n\n\t// Determine the number of substrings that will be returned.\n\t// Pre-allocate the slice to avoid reallocations.\n\tfor _, char := range data {\n\t\tif char < 128 && charsetMapping[char] {\n\t\t\tcount++\n\t\t} else {\n\t\t\tif count > threshold {\n\t\t\t\tsubstringsCount++\n\t\t\t}\n\t\t\tcount = 0\n\t\t}\n\t}\n\tif count > threshold {\n\t\tsubstringsCount++\n\t}\n\n\tcount = 0\n\tstart := 0\n\tsubstrings := make([]string, 0, substringsCount)\n\n\tfor i, char := range data {\n\t\tif char < 128 && charsetMapping[char] {\n\t\t\tif count == 0 {\n\t\t\t\tstart = i\n\t\t\t}\n\t\t\tcount++\n\t\t} else {\n\t\t\tif count > threshold {\n\t\t\t\tsubstrings = appendB64Substring(data, start, count, substrings, endChars)\n\t\t\t}\n\t\t\tcount = 0\n\t\t}\n\t}\n\n\tif count > threshold {\n\t\tsubstrings = appendB64Substring(data, start, count, substrings, endChars)\n\t}\n\n\treturn substrings\n}\n\nfunc appendB64Substring(data []byte, start, count int, substrings []string, endChars string) []string {\n\tsubstring := bytes.TrimLeft(data[start:start+count], endChars)\n\tif idx := bytes.IndexByte(bytes.TrimRight(substring, endChars), '='); idx != -1 {\n\t\tsubstrings = append(substrings, string(substring[idx+1:]))\n\t} else {\n\t\tsubstrings = append(substrings, string(substring))\n\t}\n\treturn substrings\n}\n"
  },
  {
    "path": "pkg/decoders/base64_test.go",
    "content": "package decoders\n\nimport (\n\t\"testing\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nfunc TestBase64_FromChunk(t *testing.T) {\n\ttests := []struct {\n\t\tchunk *sources.Chunk\n\t\twant  *sources.Chunk\n\t\tname  string\n\t}{\n\t\t{\n\t\t\tname: \"only b64 chunk\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(`bG9uZ2VyLWVuY29kZWQtc2VjcmV0LXRlc3Q=`),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(`longer-encoded-secret-test`),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed content\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(`token: bG9uZ2VyLWVuY29kZWQtc2VjcmV0LXRlc3Q=`),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(`token: longer-encoded-secret-test`),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"no chunk\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(``),\n\t\t\t},\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"env var (looks like all b64 decodable but has `=` in the middle)\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(`some-encoded-secret=dGVzdHNlY3JldA==`),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(`some-encoded-secret=testsecret`),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"has longer b64 inside\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(`some-encoded-secret=\"bG9uZ2VyLWVuY29kZWQtc2VjcmV0LXRlc3Q=\"`),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(`some-encoded-secret=\"longer-encoded-secret-test\"`),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"many possible substrings\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(`Many substrings in this slack message could be base64 decoded\n\t\t\t\tbut only dGhpcyBlbmNhcHN1bGF0ZWQgc2VjcmV0 should be decoded.`),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(`Many substrings in this slack message could be base64 decoded\n\t\t\t\tbut only this encapsulated secret should be decoded.`),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"b64-url-safe: only b64 chunk\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(`bG9uZ2VyLWVuY29kZWQtc2VjcmV0LXRlc3Q`),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(`longer-encoded-secret-test`),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"b64-url-safe: mixed content\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(`token: bG9uZ2VyLWVuY29kZWQtc2VjcmV0LXRlc3Q`),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(`token: longer-encoded-secret-test`),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"b64-url-safe: env var (looks like all b64 decodable but has `=` in the middle)\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(`some-encoded-secret=dGVzdHNlY3JldA`),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(`some-encoded-secret=testsecret`),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"b64-url-safe: has longer b64 inside\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(`some-encoded-secret=\"bG9uZ2VyLWVuY29kZWQtc2VjcmV0LXRlc3Q\"`),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(`some-encoded-secret=\"longer-encoded-secret-test\"`),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"b64-url-safe: hyphen url b64\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(`dHJ1ZmZsZWhvZz4-ZmluZHMtc2VjcmV0cw`),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(`trufflehog>>finds-secrets`),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"b64-url-safe: underscore url b64\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(`YjY0dXJsc2FmZS10ZXN0LXNlY3JldC11bmRlcnNjb3Jlcz8_`),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(`b64urlsafe-test-secret-underscores??`),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid base64 string\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(`a3d3fa7c2bb99e469ba55e5834ce79ee4853a8a3`),\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\td := &Base64{}\n\t\t\tgot := d.FromChunk(tt.chunk)\n\t\t\tif tt.want != nil {\n\t\t\t\tif got == nil {\n\t\t\t\t\tt.Fatal(\"got nil, did not want nil\")\n\t\t\t\t}\n\t\t\t\tif diff := pretty.Compare(string(got.Data), string(tt.want.Data)); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"Base64FromChunk() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif got != nil {\n\t\t\t\t\tt.Error(\"Expected nil chunk\")\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromChunkSmall(b *testing.B) {\n\td := Base64{}\n\tdata := detectors.MustGetBenchmarkData()[\"small\"]\n\n\tfor b.Loop() {\n\t\td.FromChunk(&sources.Chunk{Data: data})\n\t}\n}\n\nfunc BenchmarkFromChunkMedium(b *testing.B) {\n\td := Base64{}\n\tdata := detectors.MustGetBenchmarkData()[\"medium\"]\n\n\tfor b.Loop() {\n\t\td.FromChunk(&sources.Chunk{Data: data})\n\t}\n}\n\nfunc BenchmarkFromChunkLarge(b *testing.B) {\n\td := Base64{}\n\tdata := detectors.MustGetBenchmarkData()[\"big\"]\n\n\tfor b.Loop() {\n\t\td.FromChunk(&sources.Chunk{Data: data})\n\t}\n}\n"
  },
  {
    "path": "pkg/decoders/decoders.go",
    "content": "package decoders\n\nimport (\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nfunc DefaultDecoders() []Decoder {\n\treturn []Decoder{\n\t\t// UTF8 must be first for duplicate detection\n\t\t&UTF8{},\n\t\t&Base64{},\n\t\t&UTF16{},\n\t\t&EscapedUnicode{},\n\t}\n}\n\n// DecodableChunk is a chunk that includes the type of decoder used.\n// This allows us to avoid a type assertion on each decoder.\ntype DecodableChunk struct {\n\t*sources.Chunk\n\tDecoderType detectorspb.DecoderType\n}\n\ntype Decoder interface {\n\tFromChunk(chunk *sources.Chunk) *DecodableChunk\n\tType() detectorspb.DecoderType\n}\n\n// Fuzz is an entrypoint for go-fuzz, which is an AFL-style fuzzing tool.\n// This one attempts to uncover any panics during decoding.\nfunc Fuzz(data []byte) int {\n\tdecoded := false\n\tfor i, decoder := range DefaultDecoders() {\n\t\t// Skip the first decoder (plain), because it will always decode and give\n\t\t// priority to the input (return 1).\n\t\tif i == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tchunk := decoder.FromChunk(&sources.Chunk{Data: data})\n\t\tif chunk != nil {\n\t\t\tdecoded = true\n\t\t}\n\t}\n\tif decoded {\n\t\treturn 1 // prioritize the input\n\t}\n\treturn -1 // Don't add input to the corpus.\n}\n"
  },
  {
    "path": "pkg/decoders/escaped_unicode.go",
    "content": "package decoders\n\nimport (\n\t\"bytes\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"unicode/utf8\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\ntype EscapedUnicode struct{}\n\nvar _ Decoder = (*EscapedUnicode)(nil)\n\n// It might be advantageous to limit these to a subset of acceptable characters, similar to base64.\n// https://dencode.com/en/string/unicode-escape\nvar (\n\t// Standard Unicode notation.\n\t//https://unicode.org/standard/principles.html\n\tcodePointPat = regexp.MustCompile(`\\bU\\+([a-fA-F0-9]{4}).?`)\n\n\t// Common escape sequence used in programming languages.\n\tescapePat = regexp.MustCompile(`(?i:\\\\{1,2}u)([a-fA-F0-9]{4})`)\n\n\t// Additional Unicode escape formats from dencode.com\n\n\t// \\u{X} format - Rust, Swift, some JS, etc. (variable length hex in braces)\n\tbraceEscapePat = regexp.MustCompile(`\\\\u\\{([a-fA-F0-9]{1,6})\\}`)\n\n\t// \\U00XXXXXX format - Python, etc. (8-digit format for non-BMP characters)\n\tlongEscapePat = regexp.MustCompile(`\\\\U([a-fA-F0-9]{8})`)\n\n\t// \\x{X} format - Perl (variable length hex in braces)\n\tperlEscapePat = regexp.MustCompile(`\\\\x\\{([a-fA-F0-9]{1,6})\\}`)\n\n\t// \\X format - CSS (hex without padding). Go's regexp (RE2) has no look-ahead, so we\n\t// include the delimiter (whitespace, another backslash, or end-of-string) in the\n\t// match using a non-capturing group. The delimiter is later re-inserted by the\n\t// decoder when necessary.\n\tcssEscapePat = regexp.MustCompile(`\\\\([a-fA-F0-9]{1,6})(?:\\s|\\\\|$)`)\n\n\t// &#xX; format - HTML/XML (hex with semicolon)\n\thtmlEscapePat = regexp.MustCompile(`&#x([a-fA-F0-9]{1,6});`)\n\n\t// %uXXXX format - Percent-encoding (non-standard)\n\tpercentEscapePat = regexp.MustCompile(`%u([a-fA-F0-9]{4})`)\n\n\t// // 0xX format - Hexadecimal notation with space separation\n\t// Note: Commenting out for now due to high memory overhead. Review ways to handle this.\n\t// hexEscapePat = regexp.MustCompile(`0x([a-fA-F0-9]{1,6})(?:\\s|$)`)\n)\n\nfunc (d *EscapedUnicode) Type() detectorspb.DecoderType {\n\treturn detectorspb.DecoderType_ESCAPED_UNICODE\n}\n\nfunc (d *EscapedUnicode) FromChunk(chunk *sources.Chunk) *DecodableChunk {\n\tif chunk == nil || len(chunk.Data) == 0 {\n\t\treturn nil\n\t}\n\n\tvar (\n\t\t// Necessary to avoid data races.\n\t\tchunkData = bytes.Clone(chunk.Data)\n\t\tmatched   = false\n\t)\n\n\t// Process patterns in priority order - more specific patterns first\n\t// This prevents conflicts where multiple patterns match the same input\n\n\t// Long escape format (8 hex digits) - highest priority\n\tif longEscapePat.Match(chunkData) {\n\t\tmatched = true\n\t\tchunkData = decodeLongEscape(chunkData)\n\t} else if braceEscapePat.Match(chunkData) {\n\t\tmatched = true\n\t\tchunkData = decodeBraceEscape(chunkData)\n\t} else if perlEscapePat.Match(chunkData) {\n\t\tmatched = true\n\t\tchunkData = decodePerlEscape(chunkData)\n\t} else if htmlEscapePat.Match(chunkData) {\n\t\tmatched = true\n\t\tchunkData = decodeHtmlEscape(chunkData)\n\t} else if percentEscapePat.Match(chunkData) {\n\t\tmatched = true\n\t\tchunkData = decodePercentEscape(chunkData)\n\t} else if escapePat.Match(chunkData) {\n\t\tmatched = true\n\t\tchunkData = decodeEscaped(chunkData)\n\t} else if codePointPat.Match(chunkData) {\n\t\tmatched = true\n\t\tchunkData = decodeCodePoint(chunkData)\n\t} else if cssEscapePat.Match(chunkData) {\n\t\tmatched = true\n\t\tchunkData = decodeCssEscape(chunkData)\n\t\t// } else if hexEscapePat.Match(chunkData) {\n\t\t// \tmatched = true\n\t\t// \tchunkData = decodeHexEscape(chunkData)\n\t}\n\n\tif matched {\n\t\treturn &DecodableChunk{\n\t\t\tDecoderType: d.Type(),\n\t\t\tChunk: &sources.Chunk{\n\t\t\t\tData:           chunkData,\n\t\t\t\tOriginalData:   chunk.OriginalData,\n\t\t\t\tSourceName:     chunk.SourceName,\n\t\t\t\tSourceID:       chunk.SourceID,\n\t\t\t\tJobID:          chunk.JobID,\n\t\t\t\tSecretID:       chunk.SecretID,\n\t\t\t\tSourceMetadata: chunk.SourceMetadata,\n\t\t\t\tSourceType:     chunk.SourceType,\n\t\t\t\tSourceVerify:   chunk.SourceVerify,\n\t\t\t},\n\t\t}\n\t} else {\n\t\treturn nil\n\t}\n}\n\n// Unicode characters are encoded as 1 to 4 bytes per rune.\nconst maxBytesPerRune = 4\nconst spaceChar = byte(' ')\n\n// decodeWithPattern replaces escape sequences matched by re with their UTF-8\n// equivalents. The regex *must* have the first capturing group contain the\n// hexadecimal code-point digits. Any invalid value (> 0x10FFFF or parse error)\n// is skipped. The replacement walks matches in reverse order to avoid index\n// shifts.\nfunc decodeWithPattern(input []byte, re *regexp.Regexp) []byte {\n\tindices := re.FindAllSubmatchIndex(input, -1)\n\tif len(indices) == 0 {\n\t\treturn input\n\t}\n\n\tutf8Bytes := make([]byte, maxBytesPerRune)\n\tfor i := len(indices) - 1; i >= 0; i-- {\n\t\tm := indices[i]\n\t\tstart, end := m[0], m[1]\n\t\thexStart, hexEnd := m[2], m[3]\n\n\t\tcp, err := strconv.ParseUint(string(input[hexStart:hexEnd]), 16, 32)\n\t\tif err != nil || cp > 0x10FFFF {\n\t\t\tcontinue\n\t\t}\n\n\t\tutf8Len := utf8.EncodeRune(utf8Bytes, rune(cp))\n\t\tinput = append(input[:start], append(utf8Bytes[:utf8Len], input[end:]...)...)\n\t}\n\treturn input\n}\n\nfunc decodeCodePoint(input []byte) []byte {\n\t// Find all Unicode escape sequences in the input byte slice\n\tindices := codePointPat.FindAllSubmatchIndex(input, -1)\n\n\t// Iterate over found indices in reverse order to avoid modifying the slice length\n\tutf8Bytes := make([]byte, maxBytesPerRune)\n\tfor i := len(indices) - 1; i >= 0; i-- {\n\t\tmatches := indices[i]\n\n\t\tstartIndex := matches[0]\n\t\tendIndex := matches[1]\n\t\thexStartIndex := matches[2]\n\t\thexEndIndex := matches[3]\n\n\t\t// If the input is like `U+1234 U+5678` we should replace `U+1234 `.\n\t\t// Otherwise, we should only replace `U+1234`.\n\t\tif endIndex != hexEndIndex && input[endIndex-1] != spaceChar {\n\t\t\tendIndex = endIndex - 1\n\t\t}\n\n\t\t// Extract the hexadecimal value from the escape sequence\n\t\thexValue := string(input[hexStartIndex:hexEndIndex])\n\n\t\t// Parse the hexadecimal value to an integer\n\t\tunicodeInt, err := strconv.ParseInt(hexValue, 16, 32)\n\t\tif err != nil {\n\t\t\t// If there's an error, continue to the next escape sequence\n\t\t\tcontinue\n\t\t}\n\n\t\t// Convert the Unicode code point to a UTF-8 representation\n\t\tutf8Len := utf8.EncodeRune(utf8Bytes, rune(unicodeInt))\n\n\t\t// Replace the escape sequence with the UTF-8 representation\n\t\tinput = append(input[:startIndex], append(utf8Bytes[:utf8Len], input[endIndex:]...)...)\n\t}\n\n\treturn input\n}\n\nfunc decodeEscaped(input []byte) []byte {\n\treturn decodeWithPattern(input, escapePat)\n}\n\n// decodeBraceEscape handles \\u{X} format - Rust, Swift, some JS, etc.\nfunc decodeBraceEscape(input []byte) []byte {\n\treturn decodeWithPattern(input, braceEscapePat)\n}\n\n// decodeLongEscape handles \\U00XXXXXX format - Python, etc.\nfunc decodeLongEscape(input []byte) []byte {\n\treturn decodeWithPattern(input, longEscapePat)\n}\n\n// decodePerlEscape handles \\x{X} format - Perl\nfunc decodePerlEscape(input []byte) []byte {\n\treturn decodeWithPattern(input, perlEscapePat)\n}\n\n// decodeCssEscape handles \\X format - CSS (hex without padding, with space delimiter or end of string or next hex sequence)\nfunc decodeCssEscape(input []byte) []byte {\n\treturn decodeWithPattern(input, cssEscapePat)\n}\n\n// decodeHtmlEscape handles &#xX; format - HTML/XML\nfunc decodeHtmlEscape(input []byte) []byte {\n\treturn decodeWithPattern(input, htmlEscapePat)\n}\n\n// decodePercentEscape handles %uXXXX format - Percent-encoding (non-standard)\nfunc decodePercentEscape(input []byte) []byte {\n\treturn decodeWithPattern(input, percentEscapePat)\n}\n\n// decodeHexEscape handles 0xX format - Hexadecimal notation with space separation\n// func decodeHexEscape(input []byte) []byte {\n// \t// This format requires consecutive 0xNN sequences to be considered for decoding\n// \t// We'll look for patterns of multiple consecutive hex values\n// \thexPattern := regexp.MustCompile(`(?:0x[a-fA-F0-9]{1,2}(?:\\s+|$))+`)\n\n// \tmatches := hexPattern.FindAll(input, -1)\n// \tif len(matches) == 0 {\n// \t\treturn input\n// \t}\n\n// \tresult := input\n// \tfor _, match := range matches {\n// \t\t// Extract individual hex values\n// \t\tindividualHex := regexp.MustCompile(`0x([a-fA-F0-9]{1,2})`)\n// \t\thexMatches := individualHex.FindAllSubmatch(match, -1)\n\n// \t\t// Only decode if we have multiple consecutive hex values (likely to be a Unicode string)\n// \t\tif len(hexMatches) < 3 {\n// \t\t\tcontinue\n// \t\t}\n\n// \t\tvar decoded []byte\n// \t\tfor _, hexMatch := range hexMatches {\n// \t\t\thexValue := string(hexMatch[1])\n// \t\t\tif len(hexValue) == 1 {\n// \t\t\t\thexValue = \"0\" + hexValue // Pad single digit hex values\n// \t\t\t}\n\n// \t\t\tunicodeInt, err := strconv.ParseUint(hexValue, 16, 32)\n// \t\t\tif err != nil || unicodeInt > 0x10FFFF {\n// \t\t\t\tbreak\n// \t\t\t}\n\n// \t\t\tif unicodeInt <= 0x7F {\n// \t\t\t\t// ASCII character\n// \t\t\t\tdecoded = append(decoded, byte(unicodeInt))\n// \t\t\t} else {\n// \t\t\t\t// Unicode character\n// \t\t\t\tutf8Bytes := make([]byte, maxBytesPerRune)\n// \t\t\t\tutf8Len := utf8.EncodeRune(utf8Bytes, rune(unicodeInt))\n// \t\t\t\tdecoded = append(decoded, utf8Bytes[:utf8Len]...)\n// \t\t\t}\n// \t\t}\n\n// \t\t// Replace the original sequence with decoded bytes\n// \t\tresult = bytes.Replace(result, match, decoded, 1)\n// \t}\n\n// \treturn result\n// }\n"
  },
  {
    "path": "pkg/decoders/escaped_unicode_bench_test.go",
    "content": "package decoders\n\nimport (\n\t\"testing\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\n// Benchmark data for testing\nvar (\n\t// Original formats\n\toriginalUnicodeData = []byte(\"\\\\u0041\\\\u004b\\\\u0049\\\\u0041\\\\u0055\\\\u004d\\\\u0034\\\\u0047\\\\u0036\\\\u004f\\\\u0036\\\\u004e\\\\u0041\\\\u004b\\\\u0045\\\\u0037\\\\u004c\\\\u0043\\\\u0044\\\\u004a\")\n\tcodePointData       = []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\")\n\n\t// New formats\n\tbraceEscapeData   = []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}\")\n\tlongEscapeData    = []byte(\"\\\\U00000041\\\\U0000004b\\\\U00000049\\\\U00000041\\\\U00000055\\\\U0000004d\\\\U00000034\\\\U00000047\\\\U00000036\\\\U0000004f\\\\U00000036\\\\U0000004e\\\\U00000041\\\\U0000004b\\\\U00000045\\\\U00000037\\\\U0000004c\\\\U00000043\\\\U00000044\\\\U0000004a\")\n\tperlEscapeData    = []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}\")\n\tcssEscapeData     = []byte(\"\\\\41 \\\\4b \\\\49 \\\\41 \\\\55 \\\\4d \\\\34 \\\\47 \\\\36 \\\\4f \\\\36 \\\\4e \\\\41 \\\\4b \\\\45 \\\\37 \\\\4c \\\\43 \\\\44 \\\\4a \")\n\thtmlEscapeData    = []byte(\"&#x41;&#x4b;&#x49;&#x41;&#x55;&#x4d;&#x34;&#x47;&#x36;&#x4f;&#x36;&#x4e;&#x41;&#x4b;&#x45;&#x37;&#x4c;&#x43;&#x44;&#x4a;\")\n\tpercentEscapeData = []byte(\"%u0041%u004b%u0049%u0041%u0055%u004d%u0034%u0047%u0036%u004f%u0036%u004e%u0041%u004b%u0045%u0037%u004c%u0043%u0044%u004a\")\n\t//hexEscapeData     = []byte(\"0x41 0x4b 0x49 0x41 0x55 0x4d 0x34 0x47 0x36 0x4f 0x36 0x4e 0x41 0x4b 0x45 0x37 0x4c 0x43 0x44 0x4a \")\n\n\t// Mixed content (more realistic scenario)\n\tmixedContentData = []byte(`\n\t\tconst config = {\n\t\t\tapiKey: \"\\\\u0041\\\\u004b\\\\u0049\\\\u0041\\\\u0055\\\\u004d\\\\u0034\\\\u0047\\\\u0036\\\\u004f\\\\u0036\\\\u004e\\\\u0041\\\\u004b\\\\u0045\\\\u0037\\\\u004c\\\\u0043\\\\u0044\\\\u004a\",\n\t\t\tsecretKey: \"\\\\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}\",\n\t\t\thtmlToken: \"&#x41;&#x4b;&#x49;&#x41;&#x55;&#x4d;&#x34;&#x47;&#x36;&#x4f;&#x36;&#x4e;&#x41;&#x4b;&#x45;&#x37;&#x4c;&#x43;&#x44;&#x4a;\",\n\t\t\tnormalText: \"This is normal text that should not be processed\"\n\t\t}\n\t`)\n\n\t// Large data for stress testing\n\tlargeData = func() []byte {\n\t\tdata := make([]byte, 0, 10000)\n\t\tfor i := 0; i < 100; i++ {\n\t\t\tdata = append(data, originalUnicodeData...)\n\t\t\tdata = append(data, braceEscapeData...)\n\t\t\tdata = append(data, longEscapeData...)\n\t\t\tdata = append(data, htmlEscapeData...)\n\t\t\tdata = append(data, []byte(\" normal text \")...)\n\t\t}\n\t\treturn data\n\t}()\n\n\t// No Unicode data (worst case for performance)\n\tnoUnicodeData = []byte(`\n\t\tThis is a large block of text with no Unicode escape sequences.\n\t\tIt contains various programming constructs like:\n\t\t- Variable declarations: var x = 123;\n\t\t- Function calls: doSomething(param1, param2);\n\t\t- Comments: /* this is a comment */\n\t\t- Strings: \"hello world\"\n\t\t- Numbers: 42, 3.14159, 0xFF\n\t\t- But no Unicode escapes that would trigger our decoders.\n\t\tThis simulates the common case where files don't contain Unicode escapes.\n\t`)\n)\n\n// Benchmark individual decoder functions\nfunc BenchmarkDecodeOriginalEscape(b *testing.B) {\n\tfor b.Loop() {\n\t\t_ = decodeEscaped(originalUnicodeData)\n\t}\n}\n\nfunc BenchmarkDecodeCodePoint(b *testing.B) {\n\tfor b.Loop() {\n\t\t_ = decodeCodePoint(codePointData)\n\t}\n}\n\nfunc BenchmarkDecodeBraceEscape(b *testing.B) {\n\tfor b.Loop() {\n\t\t_ = decodeBraceEscape(braceEscapeData)\n\t}\n}\n\nfunc BenchmarkDecodeLongEscape(b *testing.B) {\n\tfor b.Loop() {\n\t\t_ = decodeLongEscape(longEscapeData)\n\t}\n}\n\nfunc BenchmarkDecodePerlEscape(b *testing.B) {\n\tfor b.Loop() {\n\t\t_ = decodePerlEscape(perlEscapeData)\n\t}\n}\n\nfunc BenchmarkDecodeCssEscape(b *testing.B) {\n\tfor b.Loop() {\n\t\t_ = decodeCssEscape(cssEscapeData)\n\t}\n}\n\nfunc BenchmarkDecodeHtmlEscape(b *testing.B) {\n\tfor b.Loop() {\n\t\t_ = decodeHtmlEscape(htmlEscapeData)\n\t}\n}\n\nfunc BenchmarkDecodePercentEscape(b *testing.B) {\n\tfor b.Loop() {\n\t\t_ = decodePercentEscape(percentEscapeData)\n\t}\n}\n\n// func BenchmarkDecodeHexEscape(b *testing.B) {\n// \tfor i := 0; i < b.N; i++ {\n// \t\t_ = decodeHexEscape(hexEscapeData)\n// \t}\n// }\n\n// Benchmark the full FromChunk method with different data types\nfunc BenchmarkFromChunk_OriginalFormat(b *testing.B) {\n\tdecoder := &EscapedUnicode{}\n\tchunk := &sources.Chunk{Data: originalUnicodeData}\n\n\tfor b.Loop() {\n\t\t_ = decoder.FromChunk(chunk)\n\t}\n}\n\nfunc BenchmarkFromChunk_BraceFormat(b *testing.B) {\n\tdecoder := &EscapedUnicode{}\n\tchunk := &sources.Chunk{Data: braceEscapeData}\n\n\tfor b.Loop() {\n\t\t_ = decoder.FromChunk(chunk)\n\t}\n}\n\nfunc BenchmarkFromChunk_LongFormat(b *testing.B) {\n\tdecoder := &EscapedUnicode{}\n\tchunk := &sources.Chunk{Data: longEscapeData}\n\n\tfor b.Loop() {\n\t\t_ = decoder.FromChunk(chunk)\n\t}\n}\n\nfunc BenchmarkFromChunk_HtmlFormat(b *testing.B) {\n\tdecoder := &EscapedUnicode{}\n\tchunk := &sources.Chunk{Data: htmlEscapeData}\n\n\tfor b.Loop() {\n\t\t_ = decoder.FromChunk(chunk)\n\t}\n}\n\nfunc BenchmarkFromChunk_MixedContent(b *testing.B) {\n\tdecoder := &EscapedUnicode{}\n\tchunk := &sources.Chunk{Data: mixedContentData}\n\n\tfor b.Loop() {\n\t\t_ = decoder.FromChunk(chunk)\n\t}\n}\n\nfunc BenchmarkFromChunk_NoUnicode(b *testing.B) {\n\tdecoder := &EscapedUnicode{}\n\tchunk := &sources.Chunk{Data: noUnicodeData}\n\n\tfor b.Loop() {\n\t\t_ = decoder.FromChunk(chunk)\n\t}\n}\n\nfunc BenchmarkFromChunk_LargeData(b *testing.B) {\n\tdecoder := &EscapedUnicode{}\n\tchunk := &sources.Chunk{Data: largeData}\n\n\tfor b.Loop() {\n\t\t_ = decoder.FromChunk(chunk)\n\t}\n}\n\n// Benchmark regex matching performance (most expensive operation)\nfunc BenchmarkRegexMatching_AllPatterns(b *testing.B) {\n\ttestData := mixedContentData\n\n\tfor b.Loop() {\n\t\t// Simulate the pattern matching in FromChunk\n\t\t_ = longEscapePat.Match(testData)\n\t\t_ = braceEscapePat.Match(testData)\n\t\t_ = perlEscapePat.Match(testData)\n\t\t_ = htmlEscapePat.Match(testData)\n\t\t_ = percentEscapePat.Match(testData)\n\t\t_ = escapePat.Match(testData)\n\t\t_ = codePointPat.Match(testData)\n\t\t_ = cssEscapePat.Match(testData)\n\t\t//_ = hexEscapePat.Match(testData)\n\t}\n}\n\nfunc BenchmarkRegexMatching_NoMatch(b *testing.B) {\n\ttestData := noUnicodeData\n\n\tfor b.Loop() {\n\t\t// Simulate the pattern matching in FromChunk on data with no matches\n\t\t_ = longEscapePat.Match(testData)\n\t\t_ = braceEscapePat.Match(testData)\n\t\t_ = perlEscapePat.Match(testData)\n\t\t_ = htmlEscapePat.Match(testData)\n\t\t_ = percentEscapePat.Match(testData)\n\t\t_ = escapePat.Match(testData)\n\t\t_ = codePointPat.Match(testData)\n\t\t_ = cssEscapePat.Match(testData)\n\t\t//_ = hexEscapePat.Match(testData)\n\t}\n}\n\n// Memory allocation benchmarks\nfunc BenchmarkFromChunk_MemoryAllocation(b *testing.B) {\n\tdecoder := &EscapedUnicode{}\n\tchunk := &sources.Chunk{Data: mixedContentData}\n\n\tb.ReportAllocs()\n\tfor b.Loop() {\n\t\tresult := decoder.FromChunk(chunk)\n\t\tif result != nil {\n\t\t\t// Prevent compiler optimization\n\t\t\t_ = result.Data\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/decoders/escaped_unicode_test.go",
    "content": "package decoders\n\nimport (\n\t\"testing\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nfunc TestUnicodeEscape_FromChunk(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tchunk   *sources.Chunk\n\t\twant    *sources.Chunk\n\t\twantErr bool\n\t}{\n\t\t// U+1234\n\t\t{\n\t\t\tname: \"[notation] all escaped\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []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\"),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(\"token: \\\"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\\\"\"),\n\t\t\t},\n\t\t},\n\t\t// \\u1234\n\t\t{\n\t\t\tname: \"[slash] all escaped\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []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\"),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(\"token: \\\"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\\\"\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"[slash] mixed content\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(\"npm config set @trufflesec:registry=https://npm.pkg.github.com\\nnpm config set //npm.pkg.github.com:_authToken=$'\\\\u0067hp_9ovSHEBCq0drG42yjoam76iNybtqLN25CgSf'\"),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(\"npm config set @trufflesec:registry=https://npm.pkg.github.com\\nnpm config set //npm.pkg.github.com:_authToken=$'ghp_9ovSHEBCq0drG42yjoam76iNybtqLN25CgSf'\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"[slash] multiple slashes\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(`SameValue(\"hello\",\"\\\\u0068el\\\\u006co\");          // true`),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(`SameValue(\"hello\",\"hello\");          // true`),\n\t\t\t},\n\t\t},\n\n\t\t// New test cases for additional Unicode escape formats\n\n\t\t// \\u{X} format - Rust, Swift, some JS, etc.\n\t\t{\n\t\t\tname: \"[brace] \\\\u{X} format - Rust/Swift style\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []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}\"),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(\"token: \\\"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\\\"\"),\n\t\t\t},\n\t\t},\n\n\t\t// \\U00XXXXXX format - Python, etc.\n\t\t{\n\t\t\tname: \"[long] \\\\U00XXXXXX format - Python style\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []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\"),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(\"token: \\\"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\\\"\"),\n\t\t\t},\n\t\t},\n\n\t\t// \\x{X} format - Perl\n\t\t{\n\t\t\tname: \"[perl] \\\\x{X} format - Perl style\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []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}\"),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(\"token: \\\"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\\\"\"),\n\t\t\t},\n\t\t},\n\n\t\t// \\X format - CSS (space delimited)\n\t\t// ToDo: Look into supporting CSS where there is no whitespace ex: \\013322\\013171\\013001. Currently not supported by this implementation.\n\t\t{\n\t\t\tname: \"[css] \\\\X format - CSS style\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []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 \"),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(\"token: \\\"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\\\"\"),\n\t\t\t},\n\t\t},\n\n\t\t// &#xX; format - HTML/XML\n\t\t{\n\t\t\tname: \"[html] &#xX; format - HTML/XML style\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(\"&#x74;&#x6f;&#x6b;&#x65;&#x6e;&#x3a;&#x20;&#x22;&#x67;&#x68;&#x70;&#x5f;&#x49;&#x77;&#x64;&#x4d;&#x78;&#x39;&#x57;&#x46;&#x57;&#x52;&#x52;&#x66;&#x4d;&#x68;&#x54;&#x59;&#x69;&#x61;&#x56;&#x6a;&#x5a;&#x37;&#x38;&#x4a;&#x66;&#x75;&#x61;&#x6d;&#x76;&#x6e;&#x30;&#x59;&#x57;&#x52;&#x4d;&#x30;&#x22;\"),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(\"token: \\\"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\\\"\"),\n\t\t\t},\n\t\t},\n\n\t\t// %uXXXX format - Percent-encoding (non-standard)\n\t\t{\n\t\t\tname: \"[percent] %uXXXX format - Percent encoding\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []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\"),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(\"token: \\\"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\\\"\"),\n\t\t\t},\n\t\t},\n\n\t\t// // 0xX format - Hexadecimal notation with space separation\n\t\t// {\n\t\t// \tname: \"[hex] 0xX format - Hex with spaces\",\n\t\t// \tchunk: &sources.Chunk{\n\t\t// \t\tData: []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 \"),\n\t\t// \t},\n\t\t// \twant: &sources.Chunk{\n\t\t// \t\tData: []byte(\"token: \\\"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\\\"\"),\n\t\t// \t},\n\t\t// },\n\n\t\t// // 0xX format - Hexadecimal notation with comma separation\n\t\t// {\n\t\t// \tname: \"[hex] 0xX format - Hex with commas\",\n\t\t// \tchunk: &sources.Chunk{\n\t\t// \t\tData: []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\"),\n\t\t// \t},\n\t\t// \twant: &sources.Chunk{\n\t\t// \t\tData: []byte(\"token: \\\"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\\\"\"),\n\t\t// \t},\n\t\t// },\n\n\t\t// Test cases for mixed content with new formats\n\t\t{\n\t\t\tname: \"[mixed] \\\\u{X} in code context\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []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}\\\";\"),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(\"const secret = \\\"AKIAUM4G6O6NAKE7LCDJ\\\";\"),\n\t\t\t},\n\t\t},\n\n\t\t{\n\t\t\tname: \"[mixed] HTML entity in web context\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(\"<span>AWS Key: &#x41;&#x4b;&#x49;&#x41;&#x55;&#x4d;&#x34;&#x47;&#x36;&#x4f;&#x36;&#x4e;&#x41;&#x4b;&#x45;&#x37;&#x4c;&#x43;&#x44;&#x4a;</span>\"),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(\"<span>AWS Key: AKIAUM4G6O6NAKE7LCDJ</span>\"),\n\t\t\t},\n\t\t},\n\n\t\t// Test cases for higher Unicode values (non-BMP)\n\t\t{\n\t\t\tname: \"[emoji] \\\\u{X} with emoji\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(\"\\\\u{1f600} Happy face emoji\"),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(\"😀 Happy face emoji\"),\n\t\t\t},\n\t\t},\n\n\t\t{\n\t\t\tname: \"[emoji] \\\\U00XXXXXX with emoji\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(\"\\\\U0001f600 Happy face emoji\"),\n\t\t\t},\n\t\t\twant: &sources.Chunk{\n\t\t\t\tData: []byte(\"😀 Happy face emoji\"),\n\t\t\t},\n\t\t},\n\n\t\t// nothing\n\t\t{\n\t\t\tname: \"no escaped\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(`-//npm.fontawesome.com/:_authToken=12345678-2323-1111-1111-12345670B312\n+//npm.fontawesome.com/:_authToken=REMOVED_TOKEN`),\n\t\t\t},\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\td := &EscapedUnicode{}\n\t\t\tgot := d.FromChunk(tt.chunk)\n\t\t\tif tt.want != nil {\n\t\t\t\tif got == nil {\n\t\t\t\t\tt.Fatal(\"got nil, did not want nil\")\n\t\t\t\t}\n\t\t\t\tif diff := pretty.Compare(string(tt.want.Data), string(got.Data)); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"UnicodeEscape.FromChunk() %s diff: (-want +got)\\n%s\", tt.name, diff)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif got != nil {\n\t\t\t\t\tt.Error(\"Expected nil chunk\")\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/decoders/utf16.go",
    "content": "package decoders\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"unicode/utf8\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\ntype UTF16 struct{}\n\nfunc (d *UTF16) Type() detectorspb.DecoderType {\n\treturn detectorspb.DecoderType_UTF16\n}\n\nfunc (d *UTF16) FromChunk(chunk *sources.Chunk) *DecodableChunk {\n\tif chunk == nil || len(chunk.Data) == 0 {\n\t\treturn nil\n\t}\n\n\tdecodableChunk := &DecodableChunk{Chunk: chunk, DecoderType: d.Type()}\n\tif utf16Data, err := utf16ToUTF8(chunk.Data); err == nil {\n\t\tif len(utf16Data) == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tchunk.Data = utf16Data\n\t\treturn decodableChunk\n\t}\n\n\treturn nil\n}\n\n// utf16ToUTF8 converts a byte slice containing UTF-16 encoded data to a UTF-8 encoded byte slice.\nfunc utf16ToUTF8(b []byte) ([]byte, error) {\n\tvar bufBE, bufLE bytes.Buffer\n\tfor i := 0; i < len(b)-1; i += 2 {\n\t\tif r := rune(binary.BigEndian.Uint16(b[i:])); b[i] == 0 && utf8.ValidRune(r) {\n\t\t\tif isPrintableByte(byte(r)) {\n\t\t\t\tbufBE.WriteRune(r)\n\t\t\t}\n\t\t}\n\t\tif r := rune(binary.LittleEndian.Uint16(b[i:])); b[i+1] == 0 && utf8.ValidRune(r) {\n\t\t\tif isPrintableByte(byte(r)) {\n\t\t\t\tbufLE.WriteRune(r)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn append(bufLE.Bytes(), bufBE.Bytes()...), nil\n}\n"
  },
  {
    "path": "pkg/decoders/utf16_test.go",
    "content": "package decoders\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nfunc TestUTF16Decoder(t *testing.T) {\n\ttestCases := []struct {\n\t\tname      string\n\t\tinput     []byte\n\t\texpected  []byte\n\t\texpectNil bool\n\t}{\n\t\t{\n\t\t\tname:      \"Valid UTF-16LE input\",\n\t\t\tinput:     []byte{72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0},\n\t\t\texpected:  []byte(\"Hello World\"),\n\t\t\texpectNil: false,\n\t\t},\n\t\t{\n\t\t\tname:      \"Valid UTF-16BE input\",\n\t\t\tinput:     []byte{0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100},\n\t\t\texpected:  []byte(\"Hello World\"),\n\t\t\texpectNil: false,\n\t\t},\n\t\t{\n\t\t\tname:      \"Valid UTF-16LE input with BOM (FF FE)\",\n\t\t\tinput:     []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},\n\t\t\texpected:  []byte(\"Hello World\"),\n\t\t\texpectNil: false,\n\t\t},\n\t\t{\n\t\t\tname:      \"Valid UTF-16BE input with BOM (FE FF)\",\n\t\t\tinput:     []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},\n\t\t\texpected:  []byte(\"Hello World\"),\n\t\t\texpectNil: false,\n\t\t},\n\t\t{\n\t\t\tname:      \"Invalid UTF-16 input (it's UTF-8)\",\n\t\t\tinput:     []byte(\"Hello World!\"),\n\t\t\texpected:  nil,\n\t\t\texpectNil: true,\n\t\t},\n\t\t{\n\t\t\tname:      \"Invalid UTF-16 input (odd length)\",\n\t\t\tinput:     []byte{72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 0},\n\t\t\texpected:  []byte(\"Hello Worl\"),\n\t\t\texpectNil: false,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tchunk := &sources.Chunk{Data: tc.input}\n\t\t\tdecoder := &UTF16{}\n\t\t\tdecodedChunk := decoder.FromChunk(chunk)\n\n\t\t\tif tc.expectNil {\n\t\t\t\tif decodedChunk != nil {\n\t\t\t\t\tt.Errorf(\"Expected nil, got chunk with data: %v\", decodedChunk.Data)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif decodedChunk == nil {\n\t\t\t\tt.Errorf(\"Expected chunk with data, got nil\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !bytes.Equal(decodedChunk.Data, tc.expected) {\n\t\t\t\tt.Errorf(\"Expected decoded data: %s, got: %s\", tc.expected, decodedChunk.Data)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDLL(t *testing.T) {\n\tdata, err := os.ReadFile(\"utf16_test.dll\")\n\tif err != nil {\n\t\tt.Errorf(\"Failed to read test data: %v\", err)\n\t\treturn\n\t}\n\n\tchunk := &sources.Chunk{Data: data}\n\tdecoder := &UTF16{}\n\tdecodedChunk := decoder.FromChunk(chunk)\n\tif decodedChunk == nil {\n\t\tt.Errorf(\"Expected chunk with data, got nil\")\n\t\treturn\n\t}\n\tif !bytes.Contains(decodedChunk.Data, []byte(\"aws_secret_access_key\")) {\n\t\tt.Errorf(\"Expected chunk to have aws_secret_access_key\")\n\t\treturn\n\t}\n}\n\nfunc BenchmarkUtf16ToUtf8(b *testing.B) {\n\t// Example UTF-16LE encoded data\n\tdata := []byte{72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0}\n\n\tfor b.Loop() {\n\t\t_, _ = utf16ToUTF8(data)\n\t}\n}\n"
  },
  {
    "path": "pkg/decoders/utf8.go",
    "content": "package decoders\n\nimport (\n\t\"unicode/utf8\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\ntype UTF8 struct{}\n\nfunc (d *UTF8) Type() detectorspb.DecoderType {\n\treturn detectorspb.DecoderType_PLAIN\n}\n\nfunc (d *UTF8) FromChunk(chunk *sources.Chunk) *DecodableChunk {\n\tif chunk == nil || len(chunk.Data) == 0 {\n\t\treturn nil\n\t}\n\n\tdecodableChunk := &DecodableChunk{Chunk: chunk, DecoderType: d.Type()}\n\n\tif !utf8.Valid(chunk.Data) {\n\t\tchunk.Data = extractSubstrings(chunk.Data)\n\t\treturn decodableChunk\n\t}\n\n\treturn decodableChunk\n}\n\n// utf8ReplacementBytes holds the UTF-8 encoded form of the Unicode replacement character (U+FFFD).\n// This is pre-computed since it's used frequently when replacing invalid UTF-8 sequences\n// and control characters.\nvar utf8ReplacementBytes = []byte(string(utf8.RuneError))\n\n// extractSubstrings sanitizes byte sequences to ensure consistent handling of malformed input\n// while maintaining readable content. It handles ASCII and UTF-8 data as follows:\n//\n// For ASCII range (0-127): preserves printable characters (32-126) while replacing\n// control characters with the UTF-8 replacement character.\n// https://cs.opensource.google/go/go/+/refs/tags/go1.23.3:src/unicode/utf8/utf8.go;l=16\n//\n// For multi-byte sequences: preserves valid UTF-8 as-is, while invalid sequences\n// are replaced with a single UTF-8 replacement character.\nfunc extractSubstrings(b []byte) []byte {\n\tdataLen := len(b)\n\tbuf := make([]byte, 0, dataLen)\n\tfor idx := 0; idx < dataLen; {\n\t\t// If it's ASCII, handle separately.\n\t\t// This is faster than decoding for common cases.\n\t\tif b[idx] < utf8.RuneSelf {\n\t\t\tif isPrintableByte(b[idx]) {\n\t\t\t\tbuf = append(buf, b[idx])\n\t\t\t} else {\n\t\t\t\tbuf = append(buf, utf8ReplacementBytes...)\n\t\t\t}\n\t\t\tidx++\n\t\t\tcontinue\n\t\t}\n\n\t\tr, size := utf8.DecodeRune(b[idx:])\n\t\tif r == utf8.RuneError {\n\t\t\t// Collapse any malformed sequence into a single replacement character\n\t\t\t// rather than replacing each byte individually.\n\t\t\tbuf = append(buf, utf8ReplacementBytes...)\n\t\t\tidx++\n\t\t} else {\n\t\t\t// Keep valid multi-byte UTF-8 sequences intact to preserve unicode characters.\n\t\t\tbuf = append(buf, b[idx:idx+size]...)\n\t\t\tidx += size\n\t\t}\n\t}\n\n\treturn buf\n}\n\n// isPrintableByte reports whether a byte represents a printable ASCII character\n// using a fast byte-range check. This avoids the overhead of utf8.DecodeRune\n// for the common case of ASCII characters (0-127), since we know any byte < 128\n// represents a complete ASCII character and doesn't need UTF-8 decoding.\n// This includes letters, digits, punctuation, and symbols, but excludes control characters.\n// The upper bound is 127 (not 128) because 127 is the DEL control character.\n//\n// https://www.rapidtables.com/code/text/ascii-table.html\nfunc isPrintableByte(c byte) bool { return c > 31 && c < 127 }\n"
  },
  {
    "path": "pkg/decoders/utf8_test.go",
    "content": "package decoders\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nfunc TestUTF8_FromChunk_ValidUTF8(t *testing.T) {\n\ttype args struct {\n\t\tchunk *sources.Chunk\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\td       *UTF8\n\t\targs    args\n\t\twant    *sources.Chunk\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"successful UTF8 decode\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte(\"plain 'ol chunk that should decode successfully\")},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"plain 'ol chunk that should decode successfully\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"empty chunk\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: nil,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"valid UTF8 with control characters\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte(\"FIRST_KEY_123456\\x00SECOND_KEY_789012\")},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"FIRST_KEY_123456\\x00SECOND_KEY_789012\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"valid UTF8 with all ASCII control characters\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte{\n\t\t\t\t\t'S', 'T', 'A', 'R', 'T',\n\t\t\t\t\t0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,\n\t\t\t\t\t0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,\n\t\t\t\t\t0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,\n\t\t\t\t\t'E', 'N', 'D',\n\t\t\t\t}},\n\t\t\t},\n\t\t\twant:    &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\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"aws key in binary data - valid utf8\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte(\"AWS_ACCESS_KEY_ID\\x00\\x00\\x00AKIAEXAMPLEKEY123\\x00\")},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"AWS_ACCESS_KEY_ID\\x00\\x00\\x00AKIAEXAMPLEKEY123\\x00\")},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\td := &UTF8{}\n\t\t\tgot := d.FromChunk(tt.args.chunk)\n\t\t\tif got != nil && tt.want != nil {\n\t\t\t\tif diff := pretty.Compare(string(got.Data), string(tt.want.Data)); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"%s: UTF8.FromChunk() diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"%s: UTF8.FromChunk() diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUTF8_FromChunk_InvalidUTF8(t *testing.T) {\n\ttype args struct {\n\t\tchunk *sources.Chunk\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\td       *UTF8\n\t\targs    args\n\t\twant    *sources.Chunk\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"basic invalid utf8\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte(\"\\xF0\\x28\\x8C\\x28\")},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"�(�(\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid utf8 between words\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte(\"START\\xF0\\x28\\x8C\\x28MIDDLE\\xC0\\x80END\")},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"START�(�(MIDDLE��END\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"binary data with embedded text\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte{\n\t\t\t\t\t0xF0, 'S', 'E', 'C', 'R', 'E', 'T', // Invalid UTF-8 before text\n\t\t\t\t\t0xC0, 0x80, // Invalid UTF-8 sequence\n\t\t\t\t\t'V', 'A', 'L', 'U', 'E',\n\t\t\t\t\t0xFF, 0x8C, // More invalid UTF-8\n\t\t\t\t}},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"�SECRET��VALUE��\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"binary protocol with length fields\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte{\n\t\t\t\t\t0x02,                   // frame type\n\t\t\t\t\t0x00, 0x00, 0x00, 0x0A, // length field\n\t\t\t\t\t'P', 'A', 'S', 'S', 'W', 'O', 'R', 'D', '1', '2',\n\t\t\t\t\t0xFE, 0xFF, // checksum\n\t\t\t\t}},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"�����PASSWORD12��\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"truncated utf8 sequence\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte(\"PREFIX\\xF0\\x28SUFFIX\")},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"PREFIX�(SUFFIX\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"multiple invalid sequences\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte{\n\t\t\t\t\t0xF0, 'A', // Invalid + ASCII\n\t\t\t\t\t0xC0, 0x80, // Invalid sequence\n\t\t\t\t\t'B',\n\t\t\t\t\t0xFF, // Single invalid byte\n\t\t\t\t\t'C',\n\t\t\t\t\t0xF0, 0x28, 0x8C, 0x28, // Invalid sequence\n\t\t\t\t\t'D',\n\t\t\t\t}},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"�A��B�C�(�(D\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid utf8 header with embedded secret\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte{\n\t\t\t\t\t0xF0, 0x28, 0x8C, // Invalid UTF-8 sequence\n\t\t\t\t\t'S', 'E', 'C', 'R', 'E', 'T', '=',\n\t\t\t\t\t0xC0, 0x80, // Another invalid UTF-8 sequence\n\t\t\t\t\t'A', 'K', 'I', 'A', '1', '2', '3', '4', '5', '6',\n\t\t\t\t\t0xF8, 0x88, // More invalid UTF-8\n\t\t\t\t}},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"�(�SECRET=��AKIA123456��\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"key value pairs with length prefixes\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte{\n\t\t\t\t\t0x00, 0x01, // header\n\t\t\t\t\t'A', 'P', 'I', '_', 'K', 'E', 'Y', '=',\n\t\t\t\t\t0x00, 0x00, 0x00, 0x05, // length\n\t\t\t\t\t'A', 'K', 'I', 'A', '5',\n\t\t\t\t\t0xFF, // separator\n\t\t\t\t\t'S', 'E', 'C', 'R', 'E', 'T', '=',\n\t\t\t\t\t0x00, 0x00, 0x00, 0x06,\n\t\t\t\t\t'S', 'E', 'C', 'R', 'E', 'T',\n\t\t\t\t}},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"��API_KEY=����AKIA5�SECRET=����SECRET\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"mixed binary and invalid utf8\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte{\n\t\t\t\t\t0x00, 0x01, // valid binary\n\t\t\t\t\t0xF0, 0x28, // invalid UTF-8\n\t\t\t\t\t'K', 'E', 'Y', '=',\n\t\t\t\t\t0xC0, 0x80, // more invalid UTF-8\n\t\t\t\t\t'V', 'A', 'L', 'U', 'E',\n\t\t\t\t}},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"���(KEY=��VALUE\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"very large utf8 sequence\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte(strings.Repeat(\"世界\", 1000))},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(strings.Repeat(\"世界\", 1000))},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"single byte chunk\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte{0x41}}, // Single 'A'\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"A\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"chunk with zero bytes between valid utf8\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte(\"hello\\x00world\\x00!\")},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"hello\\x00world\\x00!\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"multi-byte unicode characters\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte(\"🌍🌎🌏\")},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"🌍🌎🌏\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"mixed ascii and multi-byte unicode with invalid sequences\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte(\"Hello 世界\\xF0\\x28\\x8C\\x28Testing🌍\")},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"Hello 世界�(�(Testing🌍\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"chunk ending with partial utf8 sequence\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte(\"Hello\\xE2\\x80\")}, // Incomplete UTF-8 sequence\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"Hello��\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"chunk with all printable ascii chars\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte(\" !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\")},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\" !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"alternating valid and invalid utf8\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte(\"A\\xF0B\\xF0C\\xF0D\")},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"A�B�C�D\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"overlong utf8 encoding\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte{0xF0, 0x82, 0x82, 0xAC}}, // Overlong encoding of €\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"����\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"utf8 boundary conditions\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte{\n\t\t\t\t\t0xFF,       // Invalid single byte -> �\n\t\t\t\t\t0xC2, 0x80, // Minimum valid 2-byte UTF-8 sequence (U+0080) -> \\u0080\n\t\t\t\t\t0xDF, 0xBF, // Maximum valid 2-byte UTF-8 sequence (U+07FF) -> ߿\n\t\t\t\t\t0xE0, 0x80, 0x80, // Invalid 3-byte (overlong encoding) -> �\n\t\t\t\t\t0xEF, 0xBF, 0xBF, // Valid 3-byte sequence for U+FFFF -> \\uffff\n\t\t\t\t\t0xF0, 0x28, 0x8C, 0x28, // Invalid UTF-8 mixed with ASCII -> �(�(\n\t\t\t\t\t0xF4, 0x8F, 0xBF, 0xBF, // Valid 4-byte sequence for U+10FFFF -> \\U0010ffff\n\t\t\t\t}},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"�\\u0080߿���\\uffff�(�(\\U0010ffff\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"chunk with byte order mark (BOM)\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\tchunk: &sources.Chunk{Data: []byte{0xEF, 0xBB, 0xBF, 'h', 'e', 'l', 'l', 'o'}},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"\\uFEFFhello\")},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"chunk with surrogate pairs\",\n\t\t\td:    &UTF8{},\n\t\t\targs: args{\n\t\t\t\t// Invalid UTF-8 encoding of surrogate pairs\n\t\t\t\tchunk: &sources.Chunk{Data: []byte{0xED, 0xA0, 0x80, 0xED, 0xB0, 0x80}},\n\t\t\t},\n\t\t\twant:    &sources.Chunk{Data: []byte(\"������\")},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\td := &UTF8{}\n\t\t\tgot := d.FromChunk(tt.args.chunk)\n\t\t\tif got != nil && tt.want != nil {\n\t\t\t\tif diff := pretty.Compare(string(got.Data), string(tt.want.Data)); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"%s: UTF8.FromChunk() diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"%s: UTF8.FromChunk() diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nvar testBytes = []byte(`some words   with random spaces and\n\t\nnewlines with           \narbitrary length           \nof\n\n\they\n\nthe lines themselves.\n\nand\nshort\nwords\nthat\ngo\naway.`)\n\nfunc Benchmark_extractSubstrings(b *testing.B) {\n\tfor b.Loop() {\n\t\textractSubstrings(testBytes)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/abstract/abstract.go",
    "content": "package abstract\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nconst abstractURL = \"https://exchange-rates.abstractapi.com\"\n\nvar (\n\t// Ensure the Scanner satisfies the interface at compile time.\n\t_ detectors.Detector = (*Scanner)(nil)\n\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"abstract\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"abstract\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Abstract secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Abstract,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.getClient()\n\t\t\tisVerified, verificationErr := verifyAbstract(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAbstract(ctx context.Context, client *http.Client, resMatch string) (bool, error) {\n\t// https://docs.abstractapi.com/exchange-rates#response-and-error-codes\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, abstractURL+fmt.Sprintf(\"/v1/live/?api_key=%s&base=USD\", resMatch), nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\t// https://docs.abstractapi.com/exchange-rates#response-and-error-codes\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Abstract\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Abstract API provides various services including exchange rates. The API keys can be used to access these services and retrieve data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/abstract/abstract_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage abstract\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAbstract_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ABSTRACT\")\n\tinactiveSecret := testSecrets.MustGetField(\"ABSTRACT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a abstract secret %s within but verified\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Abstract,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a abstract secret %s within but verified\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Abstract,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(context.DeadlineExceeded)\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"{}\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a abstract secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Abstract,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"unexpected HTTP response status 500\"))\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a abstract secret %s within but not valid\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Abstract,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttime.Sleep(900 * time.Millisecond) // avoid rate limiting\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Abstract.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Abstract.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/abstract/abstract_test.go",
    "content": "package abstract\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAbstract_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to abstract API\n\t\t\t\t[DEBUG] Using API_KEY=oxpf4a93fjovt0v1z6lltcbcizlrml98\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"oxpf4a93fjovt0v1z6lltcbcizlrml98\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{abstract}</id>\n  \t\t\t\t\t<secret>{abstract AQAAABAAA 5422358j60yxo9nc0dbpxby602tsxd6j}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"5422358j60yxo9nc0dbpxby602tsxd6j\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - two keys\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to abstract API\n\t\t\t\t[DEBUG] Using API_KEY=oxpf4a93fjovt0v1z6lltcbcizlrml98\n\t\t\t\t[Error] Response received: 401 UnAuthorized\n\t\t\t\t[INFO] Sending request to abstract API\n\t\t\t\t[DEBUG] Using API_KEY=muytrs09876iugt67s7a7sa0akhsxz82\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"oxpf4a93fjovt0v1z6lltcbcizlrml98\", \"muytrs09876iugt67s7a7sa0akhsxz82\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to abstract API\n\t\t\t\t[INFO] Processing request\n\t\t\t\t[Info] Response received: 200 OK\n\t\t\t\t[DEBUG] Used API_KEY=oxpf4a93fjovt0v1z6lltcbcizlrml98\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to abstract API\n\t\t\t\t[DEBUG] Using API_KEY=zxcvbr12345iugt67s7a7sa0akhsXz820\n\t\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/abuseipdb/abuseipdb.go",
    "content": "package abuseipdb\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nconst abuseipdbURL = \"https://api.abuseipdb.com\"\n\nvar (\n\t// Ensure the Scanner satisfies the interface at compile time.\n\t_ detectors.Detector = (*Scanner)(nil)\n\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"abuseipdb\"}) + `\\b([a-z0-9]{80})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"abuseipdb\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify AbuseIPDB secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_AbuseIPDB,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.getClient()\n\t\t\tisVerified, verificationErr := verifyAbuseIPDB(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAbuseIPDB(ctx context.Context, client *http.Client, resMatch string) (bool, error) {\n\t// https://docs.abuseipdb.com/#check-endpoint\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, abuseipdbURL+\"/api/v2/check?ipAddress=8.8.8.8\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Key\", resMatch)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tvalidResponse := bytes.Contains(bodyBytes, []byte(\"ipAddress\"))\n\t\tif validResponse {\n\t\t\treturn true, nil\n\t\t} else {\n\t\t\treturn false, nil\n\t\t}\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AbuseIPDB\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/abuseipdb/abuseipdb_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage abuseipdb\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAbuseIPDB_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ABUSEIPDB\")\n\tinactiveSecret := testSecrets.MustGetField(\"ABUSEIPDB_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a abuseipdb secret %s within but verified\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AbuseIPDB,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a abuseipdb secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AbuseIPDB,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(context.DeadlineExceeded)\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"{}\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a abuseipdb secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AbuseIPDB,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"unexpected HTTP response status 500\"))\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a abuseipdb secret %s within\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AbuseIPDB,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AbuseIPDB.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"AbuseIPDB.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/abuseipdb/abuseipdb_test.go",
    "content": "package abuseipdb\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAbuseipdb_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to abuseipdb API\n\t\t\t\t[DEBUG] Using API_KEY=o8oqti3tghu2xic76ii4t7jb9bxuzd4200j1yrkdjl6s8834hx4dgz1wwo90diqraakjd13sljcjkfnf\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"o8oqti3tghu2xic76ii4t7jb9bxuzd4200j1yrkdjl6s8834hx4dgz1wwo90diqraakjd13sljcjkfnf\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{abuseipdb}</id>\n  \t\t\t\t\t<secret>{abuseipdb AQAAABAAA zgtj0q3v38u4pthc6nmy02n60bj244u5o9j47ln1jlue5mxzaasfi29x4dzcbxroawvkm26thtr61066}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"zgtj0q3v38u4pthc6nmy02n60bj244u5o9j47ln1jlue5mxzaasfi29x4dzcbxroawvkm26thtr61066\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to abuseipdb API\n\t\t\t\t[INFO] Processing request\n\t\t\t\t[Info] Response received: 200 OK\n\t\t\t\t[DEBUG] Used API_KEY=o8oqti3tghu2xic76ii4t7jb9bxuzd4200j1yrkdjl6s8834hx4dgz1wwo90diqraakjd13sljcjkfnf\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to abuseipdb API\n\t\t\t\t[DEBUG] Using API_KEY=7e4abcdef456Ghijkl789mnopqr012stuvwx3455123abcdef456ghijkl789mnopqr012stuvwX\n\t\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/abyssale/abyssale.go",
    "content": "package abyssale\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nconst abyssaleURL = \"https://api.abyssale.com\"\n\nvar (\n\t// Ensure the Scanner satisfies the interface at compile time.\n\t_ detectors.Detector = (*Scanner)(nil)\n\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"abyssale\"}) + `\\b([a-z0-9A-Z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"abyssale\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Abyssale secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Abyssale,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.getClient()\n\t\t\tisVerified, verificationErr := verifyAbyssale(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAbyssale(ctx context.Context, client *http.Client, resMatch string) (bool, error) {\n\t// https://developers.abyssale.com/rest-api/authentication\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, abyssaleURL+\"/ready\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"x-api-key\", resMatch)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Abyssale\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/abyssale/abyssale_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage abyssale\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n)\n\nfunc TestAbyssale_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ABYSSALE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"ABYSSALE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a abyssale secret %s within but verified\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Abyssale,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a abyssale secret %s within but verified\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Abyssale,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(context.DeadlineExceeded)\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"{}\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a abyssale secret %s within but verified\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Abyssale,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"unexpected HTTP response status 500\"))\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a abyssale secret %s within but verified\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Abyssale,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Abyssale.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Abyssale.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/abyssale/abyssale_test.go",
    "content": "package abyssale\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAbyssale_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to abyssale API\n\t\t\t\t[DEBUG] Using API_KEY=rWE8I0axy6Fvw40RE8tsNS3L7zBU5vAhEnW4hq9G\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"rWE8I0axy6Fvw40RE8tsNS3L7zBU5vAhEnW4hq9G\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{abyssale}</id>\n  \t\t\t\t\t<secret>{abyssale AQAAABAAA xTiPNSDg6JjzG8fWoLb8JlE8SBcMKkCx2fZLZD91}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"xTiPNSDg6JjzG8fWoLb8JlE8SBcMKkCx2fZLZD91\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to abyssale API\n\t\t\t\t[INFO] Processing request\n\t\t\t\t[Info] Response received: 200 OK\n\t\t\t\t[DEBUG] Used API_KEY=rWE8I0axy6Fvw40RE8tsNS3L7zBU5vAhEnW4hq9G\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to abyssale API\n\t\t\t\t[DEBUG] Using API_KEY=rWE8_0axy6Fvw40RE8tsNS3L7zBU5vAhEnW4hq9G\n\t\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/account_filter.go",
    "content": "package detectors\n\n// AccountFilter implements account-based filtering functionality that detectors can embed\n// to gain allow and deny list capabilities for account IDs.\ntype AccountFilter struct {\n\tallowedAccounts map[string]struct{}\n\tdeniedAccounts  map[string]struct{}\n}\n\n// SetAllowedAccounts configures the allowed account IDs.\n// If set, only accounts in this list will be verified.\nfunc (a *AccountFilter) SetAllowedAccounts(accountIDs []string) {\n\tif len(accountIDs) == 0 {\n\t\ta.allowedAccounts = nil\n\t\treturn\n\t}\n\n\taccounts := make(map[string]struct{}, len(accountIDs))\n\tfor _, accountID := range accountIDs {\n\t\taccounts[accountID] = struct{}{}\n\t}\n\ta.allowedAccounts = accounts\n}\n\n// SetDeniedAccounts configures the denied account IDs.\n// Accounts in this list will never be verified.\nfunc (a *AccountFilter) SetDeniedAccounts(accountIDs []string) {\n\tif len(accountIDs) == 0 {\n\t\ta.deniedAccounts = nil\n\t\treturn\n\t}\n\n\taccounts := make(map[string]struct{}, len(accountIDs))\n\tfor _, accountID := range accountIDs {\n\t\taccounts[accountID] = struct{}{}\n\t}\n\ta.deniedAccounts = accounts\n}\n\n// ShouldSkipAccount checks if an account ID should be skipped for verification\n// based on allow and deny lists.\n//\n// Precedence: deny list > allow list (if account is in both, it's denied)\nfunc (a *AccountFilter) ShouldSkipAccount(accountID string) bool {\n\t// Check deny list first - takes precedence\n\tif len(a.deniedAccounts) > 0 {\n\t\tif _, isDenied := a.deniedAccounts[accountID]; isDenied {\n\t\t\treturn true\n\t\t}\n\t}\n\n\t// Check allow list - if populated, account must be in it\n\tif len(a.allowedAccounts) > 0 {\n\t\tif _, isAllowed := a.allowedAccounts[accountID]; !isAllowed {\n\t\t\treturn true\n\t\t}\n\t}\n\n\t// Account is allowed for verification\n\treturn false\n}\n\n// IsInDenyList checks if an account ID is in the deny list\nfunc (a *AccountFilter) IsInDenyList(accountID string) bool {\n\tif len(a.deniedAccounts) == 0 {\n\t\treturn false\n\t}\n\t_, isDenied := a.deniedAccounts[accountID]\n\treturn isDenied\n}\n\n// IsInAllowList checks if an account ID is in the allow list\nfunc (a *AccountFilter) IsInAllowList(accountID string) bool {\n\tif len(a.allowedAccounts) == 0 {\n\t\treturn false\n\t}\n\t_, isAllowed := a.allowedAccounts[accountID]\n\treturn isAllowed\n}\n"
  },
  {
    "path": "pkg/detectors/account_filter_test.go",
    "content": "package detectors\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestEmbeddedAccountFilter(t *testing.T) {\n\ttype Scanner struct{ AccountFilter }\n\n\tt.Run(\"no filtering configured - should not skip\", func(t *testing.T) {\n\t\tvar s Scanner // Fresh instance for this test\n\n\t\tshouldSkip := s.ShouldSkipAccount(\"test-account\")\n\t\tassert.False(t, shouldSkip)\n\t\tassert.False(t, s.IsInDenyList(\"test-account\"))\n\t\tassert.False(t, s.IsInAllowList(\"test-account\"))\n\t})\n\n\tt.Run(\"allowed accounts only\", func(t *testing.T) {\n\t\tvar s Scanner // Fresh instance for this test\n\t\ts.SetAllowedAccounts([]string{\"allowed-account-1\", \"allowed-account-2\"})\n\n\t\t// Account in allow list - should not skip\n\t\tshouldSkip := s.ShouldSkipAccount(\"allowed-account-1\")\n\t\tassert.False(t, shouldSkip)\n\t\tassert.True(t, s.IsInAllowList(\"allowed-account-1\"))\n\n\t\t// Account not in allow list - should skip\n\t\tshouldSkip = s.ShouldSkipAccount(\"other-account\")\n\t\tassert.True(t, shouldSkip)\n\t\tassert.False(t, s.IsInAllowList(\"other-account\"))\n\t})\n\n\tt.Run(\"denied accounts only\", func(t *testing.T) {\n\t\tvar s Scanner // Fresh instance for this test\n\t\ts.SetDeniedAccounts([]string{\"denied-account-1\", \"denied-account-2\"})\n\n\t\t// Account in deny list - should skip\n\t\tshouldSkip := s.ShouldSkipAccount(\"denied-account-1\")\n\t\tassert.True(t, shouldSkip)\n\t\tassert.True(t, s.IsInDenyList(\"denied-account-1\"))\n\n\t\t// Account not in deny list - should not skip (no allow list restrictions)\n\t\tshouldSkip = s.ShouldSkipAccount(\"other-account\")\n\t\tassert.False(t, shouldSkip)\n\t\tassert.False(t, s.IsInDenyList(\"other-account\"))\n\t})\n\n\tt.Run(\"deny list takes precedence over allow list\", func(t *testing.T) {\n\t\tvar s Scanner // Fresh instance for this test\n\t\ts.SetAllowedAccounts([]string{\"conflicted-account\", \"allowed-only-account\"})\n\t\ts.SetDeniedAccounts([]string{\"conflicted-account\"}) // Same account in both lists\n\n\t\t// Account is in both allow and deny lists - deny takes precedence\n\t\tshouldSkip := s.ShouldSkipAccount(\"conflicted-account\")\n\t\tassert.True(t, shouldSkip)\n\t\tassert.True(t, s.IsInDenyList(\"conflicted-account\"))\n\t\tassert.True(t, s.IsInAllowList(\"conflicted-account\"))\n\n\t\t// Account only in allow list - should not skip\n\t\tshouldSkip = s.ShouldSkipAccount(\"allowed-only-account\")\n\t\tassert.False(t, shouldSkip)\n\t\tassert.False(t, s.IsInDenyList(\"allowed-only-account\"))\n\t\tassert.True(t, s.IsInAllowList(\"allowed-only-account\"))\n\t})\n\n\tt.Run(\"allow list with denied account not in allow list\", func(t *testing.T) {\n\t\tvar s Scanner                                     // Fresh instance for this test\n\t\ts.SetAllowedAccounts([]string{\"trusted-account\"}) // Allow one account\n\t\ts.SetDeniedAccounts([]string{\"blocked-account\"})  // Deny different account\n\n\t\t// Account in deny list (not in allow list) - should skip due to deny list\n\t\tshouldSkip := s.ShouldSkipAccount(\"blocked-account\")\n\t\tassert.True(t, shouldSkip)\n\t\tassert.True(t, s.IsInDenyList(\"blocked-account\"))\n\t\tassert.False(t, s.IsInAllowList(\"blocked-account\"))\n\n\t\t// Account in allow list (not in deny list) - should not skip\n\t\tshouldSkip = s.ShouldSkipAccount(\"trusted-account\")\n\t\tassert.False(t, shouldSkip)\n\t\tassert.False(t, s.IsInDenyList(\"trusted-account\"))\n\t\tassert.True(t, s.IsInAllowList(\"trusted-account\"))\n\n\t\t// Account in neither list - should skip due to allow list restriction\n\t\tshouldSkip = s.ShouldSkipAccount(\"unknown-account\")\n\t\tassert.True(t, shouldSkip)\n\t\tassert.False(t, s.IsInDenyList(\"unknown-account\"))\n\t\tassert.False(t, s.IsInAllowList(\"unknown-account\"))\n\t})\n\n\tt.Run(\"clearing lists\", func(t *testing.T) {\n\t\tvar s Scanner // Fresh instance for this test\n\t\ts.SetAllowedAccounts([]string{\"initial-allowed\"})\n\t\ts.SetDeniedAccounts([]string{\"initial-denied\"})\n\n\t\t// Verify initial state\n\t\tassert.True(t, s.ShouldSkipAccount(\"random-account\")) // Not in allow list\n\t\tassert.True(t, s.ShouldSkipAccount(\"initial-denied\")) // In deny list\n\n\t\t// Clear allowed accounts with nil\n\t\ts.SetAllowedAccounts(nil)\n\t\tassert.False(t, s.ShouldSkipAccount(\"random-account\")) // No allow list restriction\n\t\tassert.True(t, s.ShouldSkipAccount(\"initial-denied\"))  // Still in deny list\n\n\t\t// Clear denied accounts with empty slice\n\t\ts.SetDeniedAccounts([]string{})\n\t\tassert.False(t, s.ShouldSkipAccount(\"initial-denied\"))  // No longer denied\n\t\tassert.False(t, s.ShouldSkipAccount(\"initial-allowed\")) // No restrictions\n\t})\n}\n"
  },
  {
    "path": "pkg/detectors/accuweather/v1/accuweather.go",
    "content": "package accuweather\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tClient *http.Client\n}\n\nconst accuweatherURL = \"https://dataservice.accuweather.com\"\nconst requiredShannonEntropy = 4\n\nvar (\n\t// Ensure the Scanner satisfies the interface at compile time.\n\t_ detectors.Detector = (*Scanner)(nil)\n\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"accuweather\"}) + `([a-z0-9A-Z\\%]{35})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"accuweather\"}\n}\n\nfunc (s Scanner) Version() int { return 1 }\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.Client != nil {\n\t\treturn s.Client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Accuweather secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tmatches := keyPat.FindAllStringSubmatch(string(data), -1)\n\treturn s.ProcessMatches(ctx, matches, verify)\n}\n\nfunc (s Scanner) ProcessMatches(ctx context.Context, matches [][]string, verify bool) (results []detectors.Result, err error) {\n\tuniqueMatches := getUniqueMatches(matches)\n\tfor key := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Accuweather,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.getClient()\n\t\t\tisVerified, verificationErr := verifyAccuweather(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, key)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc getUniqueMatches(allMatches [][]string) map[string]struct{} {\n\tuniqueMatches := map[string]struct{}{}\n\tfor _, match := range allMatches {\n\t\tk := match[1]\n\t\tif detectors.StringShannonEntropy(k) < requiredShannonEntropy {\n\t\t\tcontinue\n\t\t}\n\t\tuniqueMatches[k] = struct{}{}\n\t}\n\treturn uniqueMatches\n}\n\nfunc verifyAccuweather(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, accuweatherURL+\"/locations/v1/cities/autocomplete?apikey=\"+key+\"&q=----&language=en-us\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\t// https://developer.accuweather.com/accuweather-locations-api/apis/get/locations/v1/cities/autocomplete\n\tswitch res.StatusCode {\n\tcase http.StatusOK, http.StatusForbidden:\n\t\t// 403 indicates lack of permission, but valid token\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Accuweather\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"AccuWeather is a weather forecasting service. AccuWeather API keys can be used to access weather data and forecasts.\"\n}\n"
  },
  {
    "path": "pkg/detectors/accuweather/v1/accuweather_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage accuweather\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAccuweather_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ACCUWEATHER\")\n\tinactiveSecret := testSecrets.MustGetField(\"ACCUWEATHER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a accuweather secret %s within but verified\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Accuweather,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to timeout\",\n\t\t\ts:    Scanner{Client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a accuweather secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Accuweather,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(context.DeadlineExceeded)\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to unexpected api surface\",\n\t\t\ts:    Scanner{Client: common.ConstantResponseHttpClient(500, \"{}\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a accuweather secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Accuweather,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"unexpected HTTP response status 500\"))\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a accuweather secret %s within but verified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Accuweather,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Accuweather.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Accuweather.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/accuweather/v1/accuweather_test.go",
    "content": "package accuweather\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAccuWeather_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to accuweather API\n\t\t\t\t[DEBUG] Using API_KEY=WAgP6m4gYc1qe%HnjWAAF5HBKL%i6kwrsbD\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"WAgP6m4gYc1qe%HnjWAAF5HBKL%i6kwrsbD\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{accuweather}</id>\n  \t\t\t\t\t<secret>{accuweather AQAAABAAA ErOAU9rTSuX6IfHFGsJbpK3bCC1jIEX%gtj}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"ErOAU9rTSuX6IfHFGsJbpK3bCC1jIEX%gtj\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to accuweather API\n\t\t\t\t[INFO] Processing request\n\t\t\t\t[Info] Response received: 200 OK\n\t\t\t\t[DEBUG] Used API_KEY=WAgP6m4gYc1qe%HnjWAAF5HBKL%i6kwrsbD\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to accuweather API\n\t\t\t\t[DEBUG] Using API_KEY=WAgP6m4gYc1qe$HnjWAAF5HBKL%i6kwrsbD\n\t\t\t\t[Error] Response received: 400 BadRequest\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - Shannon entropy below threshold\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to accuweather API\n\t\t\t\t[DEBUG] Using API_KEY=WAAP6A4gYA1qA%HAaWAAFAHBAL%a6kwwwbD\n\t\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/accuweather/v2/accuweather.go",
    "content": "package accuweather\n\nimport (\n\t\"context\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\tv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/accuweather/v1\"\n)\n\ntype Scanner struct {\n\tv1.Scanner\n}\n\nfunc (s Scanner) Version() int { return 2 }\n\nvar (\n\t// Ensure the Scanner satisfies the interface at compile time.\n\t_ detectors.Detector = (*Scanner)(nil)\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"accuweather\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n)\n\n// FromData will find and optionally verify Accuweather secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tmatches := keyPat.FindAllStringSubmatch(string(data), -1)\n\treturn s.ProcessMatches(ctx, matches, verify)\n}\n"
  },
  {
    "path": "pkg/detectors/accuweather/v2/accuweather_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage accuweather\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\tv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/accuweather/v1\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAccuweather_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ACCUWEATHER\")\n\tinactiveSecret := testSecrets.MustGetField(\"ACCUWEATHER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a accuweather secret %s within but verified\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Accuweather,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to timeout\",\n\t\t\ts:    Scanner{Scanner: v1.Scanner{Client: common.SaneHttpClientTimeOut(1 * time.Microsecond)}},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a accuweather secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Accuweather,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(context.DeadlineExceeded)\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to unexpected api surface\",\n\t\t\ts:    Scanner{Scanner: v1.Scanner{Client: common.ConstantResponseHttpClient(500, \"{}\")}},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a accuweather secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Accuweather,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"unexpected HTTP response status 500\"))\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a accuweather secret %s within but verified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Accuweather,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Accuweather.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Accuweather.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/accuweather/v2/accuweather_test.go",
    "content": "package accuweather\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAccuWeather_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to accuweather API\n\t\t\t\t[DEBUG] Using API_KEY=Qh6DP6Zf7vHtmnDDsbS219qcz4d883Y9\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"Qh6DP6Zf7vHtmnDDsbS219qcz4d883Y9\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{accuweather}</id>\n  \t\t\t\t\t<secret>{accuweather AQAAABAAA BJDD9bYh8bR586Wcw3F1lvkUYy3RZZbD}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"BJDD9bYh8bR586Wcw3F1lvkUYy3RZZbD\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to accuweather API\n\t\t\t\t[INFO] Processing request\n\t\t\t\t[Info] Response received: 200 OK\n\t\t\t\t[DEBUG] Used API_KEY=Qh6DP6Zf7vHtmnDDsbS219qcz4d883Y9\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to accuweather API\n\t\t\t\t[DEBUG] Using API_KEY=Qh6DP6Zf7vHtm@DDsbS219qcz4d883Y9\n\t\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/adafruitio/adafruitio.go",
    "content": "package adafruitio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nconst adafruitioURL = \"https://io.adafruit.com\"\n\nvar (\n\t// Ensure the Scanner satisfies the interface at compile time.\n\t_ detectors.Detector = (*Scanner)(nil)\n\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(aio\\_[a-zA-Z0-9]{28})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"aio_\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify AdafruitIO secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_AdafruitIO,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.getClient()\n\t\t\tisVerified, verificationErr := verifyAdafruitIO(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAdafruitIO(ctx context.Context, client *http.Client, resMatch string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, adafruitioURL+\"/api/v2/ladybugtest/feeds/?x-aio-key=\"+resMatch, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\t// https://learn.adafruit.com/adafruit-io/http-status-codes\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AdafruitIO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/adafruitio/adafruitio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage adafruitio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAdafruitIO_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ADAFRUITIO\")\n\tinactiveSecret := testSecrets.MustGetField(\"ADAFRUITIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a adafruitio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AdafruitIO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(10 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a adafruitio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AdafruitIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(context.DeadlineExceeded)\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"{}\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a adafruitio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AdafruitIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"unexpected HTTP response status 500\"))\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AdafruitIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AdafruitIO.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"AdafruitIO.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/adafruitio/adafruitio_test.go",
    "content": "package adafruitio\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAdafruitio_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using API_KEY=aio_VxEqGaqgMgZej3DceezbBy03eWyW\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"aio_VxEqGaqgMgZej3DceezbBy03eWyW\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{adafruitio}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA aio_cQD77DF9SgsYbgWcxJbpLOlR5emX}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"aio_cQD77DF9SgsYbgWcxJbpLOlR5emX\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using API_KEY=aio_VxEqGaqgMgZej3DceezbBy03eWyWa\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/adobeio/adobeio.go",
    "content": "package adobeio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"adobe\"}) + `\\b([a-z0-9]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"adobe\"}) + `\\b([a-zA-Z0-9.]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"adobe\"}\n}\n\n// FromData will find and optionally verify AdobeIO secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys, uniqueIds = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor _, matches := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueIds[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\tfor id := range uniqueIds {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AdobeIO,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tRawV2:        []byte(key + id),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, verificationErr := verifyAdobeIOSecret(ctx, client, key, id)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAdobeIOSecret(ctx context.Context, client *http.Client, key string, id string) (bool, error) {\n\turl := \"https://stock.adobe.io/Rest/Media/1/Search/Files?locale=en_US%2526search_parameters%255Bwords%255D=kittens\"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"x-api-key\", key)\n\treq.Header.Add(\"x-product\", id)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AdobeIO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"AdobeIO provides APIs for integrating with Adobe services. These credentials can be used to access Adobe services and data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/adobeio/adobeio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage adobeio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAdobeIO_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ADOBEIO_TOKEN\")\n\tid := testSecrets.MustGetField(\"ADOBEIO_PRODUCT\")\n\tinactiveSecret := testSecrets.MustGetField(\"ADOBEIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a adobeio secret %s within adobeio %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AdobeIO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AdobeIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AdobeIO.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"AdobeIO.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/adobeio/adobeio_test.go",
    "content": "package adobeio\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAdobeIO_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the adobe API\n\t\t\t\t[DEBUG] Using adobe KEY=zoaw0c0m50m0hz2h1fm21y4tqfyl7ifi\n\t\t\t\t[DEBUG] Using adobe ID=qCRbiIy1NJaW\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"zoaw0c0m50m0hz2h1fm21y4tqfyl7ifiqCRbiIy1NJaW\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{adobe ftd7hkeafk0q}</id>\n  \t\t\t\t\t<secret>{adobe AQAAABAAA siybmtkgho9nsgjhng5yhp92wnir2a9t}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"siybmtkgho9nsgjhng5yhp92wnir2a9tftd7hkeafk0q\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the adobe API\n\t\t\t\t[DEBUG] Using KEY=zoaw0c0m50m0hz2h1fm21y4tqfyl7ifi\n\t\t\t\t[DEBUG] Using ID=qCRbiIy1NJaW\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the adobe API\n\t\t\t\t[DEBUG] Using adobe KEY=Rzxc#0987$%bv1234poiu6749gtnrfv54\n\t\t\t\t[DEBUG] Using adobe ID=qCRbiIy1NJaW\n\t\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/adzuna/adzuna.go",
    "content": "package adzuna\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\nconst adzunaURL = \"https://api.adzuna.com\"\n\nvar (\n\t// Ensure the Scanner satisfies the interface at compile time.\n\t_ detectors.Detector = (*Scanner)(nil)\n\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"adzuna\"}) + `\\b([a-z0-9]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"adzuna\"}) + `\\b([a-z0-9]{8})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"adzuna\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Adzuna secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Adzuna,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resIdMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.getClient()\n\t\t\t\tisVerified, verificationErr := verifyAdzuna(ctx, client, resMatch, resIdMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAdzuna(ctx context.Context, client *http.Client, resMatch, resIdMatch string) (bool, error) {\n\t// https://developer.adzuna.com/activedocs#!/adzuna/search\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, adzunaURL+fmt.Sprintf(\"/v1/api/jobs/us/search/1?app_id=%s&app_key=%s\", resIdMatch, resMatch), nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\t// https://developer.adzuna.com/overview\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Adzuna\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Adzuna is a job search engine used to find job listings. Adzuna API keys can be used to access job listing data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/adzuna/adzuna_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage adzuna\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAdzuna_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ADZUNA\")\n\tid := testSecrets.MustGetField(\"ADZUNA_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"ADZUNA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a adzuna secret %s within adzuna %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Adzuna,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a adzuna secret %s within adzuna %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Adzuna,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(context.DeadlineExceeded)\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"{}\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a adzuna secret %s within adzuna %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Adzuna,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"unexpected HTTP response status 500\"))\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a adzuna secret %s within adzuna %s but not valid\", inactiveSecret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Adzuna,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Adzuna.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Adzuna.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/adzuna/adzuna_test.go",
    "content": "package adzuna\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAdzuna_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the adzuna API\n\t\t\t\t[DEBUG] Using adzuna KEY=smcud4y6elxx7u6q58ewwv8rq01hpi3f\n\t\t\t\t[DEBUG] Using adzuna ID=cxu9w2g6\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"smcud4y6elxx7u6q58ewwv8rq01hpi3fcxu9w2g6\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{adzuna svkit0wx}</id>\n  \t\t\t\t\t<secret>{adzuna AQAAABAAA atubvgvpd6jjo0ac1wjianofnpgr24ac}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"atubvgvpd6jjo0ac1wjianofnpgr24acsvkit0wx\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the adzuna API\n\t\t\t\t[DEBUG] Using KEY=smcud4y6elxx7u6q58ewwv8rq01hpi3f\n\t\t\t\t[DEBUG] Using ID=cxu9w2g6\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - only key\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the adzuna API\n\t\t\t\t[DEBUG] Using KEY=smcud4y6elxx7u6q58ewwv8rq01hpi3f\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - only id\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the adzuna API\n\t\t\t\t[DEBUG] Using ID=cxu9w2g6\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the adzuna API\n\t\t\t\t[DEBUG] Using KEY=sxojb6ygb2wsx0o\n\t\t\t\t[DEBUG] Using ID=cxu9w2g6\n\t\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/aeroworkflow/aeroworkflow.go",
    "content": "package aeroworkflow\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\nconst aeroworkflowURL = \"https://api.aeroworkflow.com\"\n\nvar (\n\t// Ensure the Scanner satisfies the interface at compile time.\n\t_ detectors.Detector = (*Scanner)(nil)\n\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"aeroworkflow\"}) + `\\b([a-zA-Z0-9^!?#:*;]{20})`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"aeroworkflow\"}) + `\\b([0-9]{1,})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"aeroworkflow\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Aeroworkflow secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Aeroworkflow,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resIdMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.getClient()\n\t\t\t\tisVerified, verificationErr := verifyAeroworkflow(ctx, client, resMatch, resIdMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAeroworkflow(ctx context.Context, client *http.Client, resMatch, resIdMatch string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, aeroworkflowURL+\"/api/\"+resIdMatch+\"/me\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Accept\", \"application/json\")\n\treq.Header.Add(\"apikey\", resMatch)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\t// https://api.aeroworkflow.com/swagger/index.html\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\t// 401 for invalid API key\n\t\t// 403 for invalid Account ID\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Aeroworkflow\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Aeroworkflow is a service for managing workflows. Aeroworkflow API keys and Account IDs can be used to access and manage workflows.\"\n}\n"
  },
  {
    "path": "pkg/detectors/aeroworkflow/aeroworkflow_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage aeroworkflow\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAeroworkflow_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AEROWORKFLOW_SECRET\")\n\tid := testSecrets.MustGetField(\"AEROWORKFLOW_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"AEROWORKFLOW_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a aeroworkflow secret %s within aeroworkflow %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Aeroworkflow,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a aeroworkflow secret %s within aeroworkflow %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Aeroworkflow,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(context.DeadlineExceeded)\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"{}\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a aeroworkflow secret %s within aeroworkflow %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Aeroworkflow,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"unexpected HTTP response status 500\"))\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Aeroworkflow,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Aeroworkflow.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Aeroworkflow.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/aeroworkflow/aeroworkflow_test.go",
    "content": "package aeroworkflow\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAeroWorkflow_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the aeroworkflow API\n\t\t\t\t[DEBUG] Using aeroworkflow KEY=VmFYK7WG3CkgVmTl:c*X\n\t\t\t\t[DEBUG] Using aeroworkflow ID=678436\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"VmFYK7WG3CkgVmTl:c*X678436\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{aeroworkflow 6}</id>\n  \t\t\t\t\t<secret>{aeroworkflow AQAAABAAA XjPSUOhREIN:4HX2#akH}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"XjPSUOhREIN:4HX2#akH6\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the aeroworkflow API\n\t\t\t\t[DEBUG] Using KEY=VmFYK7WG3CkgVmTl:c*X\n\t\t\t\t[DEBUG] Using ID=678436\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - only key\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the aeroworkflow API\n\t\t\t\t[DEBUG] Using KEY=VmFYK7WG3CkgVmTl:c*X\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - only id\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the aeroworkflow API\n\t\t\t\t[DEBUG] Using ID=678436\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the aeroworkflow API\n\t\t\t\t[DEBUG] Using KEY=VmFYK7WG3CkgVmTl:c*X\n\t\t\t\t[DEBUG] Using ID=cxu9w2g6\n\t\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/agora/agora.go",
    "content": "package agora\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\nconst agoraURL = \"https://api.agora.io\"\n\nvar (\n\t// Ensure the Scanner satisfies the interface at compile time.\n\t_ detectors.Detector = (*Scanner)(nil)\n\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"agora\", \"key\", \"token\"}) + `\\b([a-z0-9]{32})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"agora\", \"secret\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"agora\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Agora secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, secret := range secretMatches {\n\t\t\tresSecret := strings.TrimSpace(secret[1])\n\t\t\t/*\n\t\t\t\tas both agora key and secretMatch has same regex, the set of strings keyMatch for both probably me same.\n\t\t\t\twe need to avoid the scenario where key is same as secretMatch. This will reduce the number of matches we process.\n\t\t\t*/\n\t\t\tif resMatch == resSecret {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Agora,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resSecret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.getClient()\n\t\t\t\tisVerified, verificationErr := verifyAgora(ctx, client, resMatch, resSecret)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAgora(ctx context.Context, client *http.Client, resMatch, resSecret string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, agoraURL+\"/dev/v1/projects\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.SetBasicAuth(resSecret, resMatch)\n\tres, err := client.Do(req)\n\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\t// https://docs.agora.io/en/voice-calling/reference/agora-console-rest-api#get-all-projects\n\tswitch res.StatusCode {\n\tcase http.StatusOK, http.StatusCreated:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Agora\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/agora/agora_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage agora\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAgora_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tid := testSecrets.MustGetField(\"AGORA\")\n\tsecret := testSecrets.MustGetField(\"AGORA_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"AGORA_SECRET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a agora secret %s within agora id %s but verified\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Agora,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Agora,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a agora secret %s within agora id %s but verified\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Agora,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(context.DeadlineExceeded)\n\t\t\t\treturn []detectors.Result{r, r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"{}\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a agora secret %s within agora id %s but verified\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Agora,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"unexpected HTTP response status 500\"))\n\t\t\t\treturn []detectors.Result{r, r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Agora,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Agora,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Agora.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Agora.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/agora/agora_test.go",
    "content": "package agora\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAgora_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the agora API\n\t\t\t\t[DEBUG] Using Token=6p77f9gjhxx9mwdj86of7y7820bh49vw\n\t\t\t\t[DEBUG] Using Secret=qi6txx6vd0qzn6j01xj9rr6clyejvjw5\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"6p77f9gjhxx9mwdj86of7y7820bh49vwqi6txx6vd0qzn6j01xj9rr6clyejvjw5\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{agora 3devtbiys8b282kidr9u78kjq8xdtlo1}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA bc7c6tag5jfuhz4y7v6v05dx2wq2z1ua}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"3devtbiys8b282kidr9u78kjq8xdtlo1bc7c6tag5jfuhz4y7v6v05dx2wq2z1ua\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the agora API\n\t\t\t\t[DEBUG] Using 6p77f9gjhxx9mwdj86of7y7820bh49vw\n\t\t\t\t[DEBUG] Using qi6txx6vd0qzn6j01xj9rr6clyejvjw5\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - only key\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the agora API\n\t\t\t\t[DEBUG] Using Key=6p77f9gjhxx9mwdj86of7y7820bh49vw\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - only secret\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the agora API\n\t\t\t\t[DEBUG] Using Secret=qi6txx6vd0qzn6j01xj9rr6clyejvjw5\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the agora API\n\t\t\t\t[DEBUG] Using KEY=qi6txx6vd0qzn6j01xj9rr6clyejvjw\n\t\t\t\t[DEBUG] Using ID=qi6txx6vd0qzn6j01xj9rr6clyejvjw5yt\n\t\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/aha/aha.go",
    "content": "package aha\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\nvar (\n\t// Ensure the Scanner satisfies the interface at compile time.\n\t_ detectors.Detector = (*Scanner)(nil)\n\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"aha\"}) + `\\b([0-9a-f]{64})\\b`)\n\tURLPat = regexp.MustCompile(`\\b([A-Za-z0-9](?:[A-Za-z0-9\\-]{0,61}[A-Za-z0-9])\\.aha\\.io)`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"aha.io\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Aha\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Aha is a product management software suite. Aha API keys can be used to access and modify product data and workflows.\"\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Aha secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueFoundUrls = make(map[string]struct{})\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range URLPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueFoundUrls[match[1]] = struct{}{}\n\t}\n\n\t// if no url was found use the default\n\tif len(uniqueFoundUrls) == 0 {\n\t\tuniqueFoundUrls[\"aha.io\"] = struct{}{}\n\t}\n\n\tfor _, match := range matches {\n\t\tfor url := range uniqueFoundUrls {\n\t\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Aha,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + url),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.getClient()\n\t\t\t\tisVerified, verificationErr := verifyAha(ctx, client, resMatch, url)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAha(ctx context.Context, client *http.Client, resMatch, resURLMatch string) (bool, error) {\n\turl := fmt.Sprintf(\"https://%s/api/v1/me\", resURLMatch)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Accept\", \"application/vnd.aha+json; version=3\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\t// https://www.aha.io/api\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusNotFound, http.StatusForbidden:\n\t\t// 403 is a known case where an account is inactive bc of a trial ending or payment issue\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/aha/aha_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage aha\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAha_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tdomain := testSecrets.MustGetField(\"AHA_DOMAIN\")\n\tsecret := testSecrets.MustGetField(\"AHA_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"AHA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(200, \"{}\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a aha secret %s within %s but verified\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Aha,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a aha secret %s within %s but verified\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Aha,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(context.DeadlineExceeded)\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"{}\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a aha secret %s within %s but verified\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Aha,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"unexpected HTTP response status 500\"))\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Aha,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Aha.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Aha.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/aha/aha_test.go",
    "content": "package aha\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAha_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] sending request to the aha.io API\n\t\t\t\t[DEBUG] using key = 81a1411a7e276fd88819df3137eb406e0f281f8a8c417947ca4b025890c8541c\n\t\t\t\t[DEBUG] using host = example.aha.io\n\t\t\t\t[INFO] response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"81a1411a7e276fd88819df3137eb406e0f281f8a8c417947ca4b025890c8541cexample.aha.io\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{aha 3af0b286b668d9636fd68076d6c87a333fe285fd41593cfceab36b35606c915a}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA ACTp3nufSEO791nIReS5udnRVFcG9j6-CqBJogBxo1pbql.aha.io}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"3af0b286b668d9636fd68076d6c87a333fe285fd41593cfceab36b35606c915aACTp3nufSEO791nIReS5udnRVFcG9j6-CqBJogBxo1pbql.aha.io\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - key out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[INFO] sending request to the aha.io API\n\t\t\t\t[WARN] Do not commit the secrets\n\t\t\t\t[DEBUG] using key = 81a1411a7e276fd88819df3137eb406e0f281f8a8c417947ca4b025890c8541c\n\t\t\t\t[DEBUG] using host = example.aha.io\n\t\t\t\t[INFO] response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - only key\",\n\t\t\tinput: `\n\t\t\t\t[INFO] sending request to the aha.io API\n\t\t\t\t[DEBUG] using key = 81a1411a7e276fd88819df3137eb406e0f281f8a8c417947ca4b025890c8541c\n\t\t\t\t[INFO] response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"81a1411a7e276fd88819df3137eb406e0f281f8a8c417947ca4b025890c8541caha.io\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - only URL\",\n\t\t\tinput: `\n\t\t\t\t[INFO] sending request to the example.aha.io API\n\t\t\t\t[INFO] response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] sending request to the aha.io API\n\t\t\t\t[DEBUG] using key = 81a1411a7e276fd88819df3137eJ406e0f281f8a8c417947ca4b025890c8541c\n\t\t\t\t[DEBUG] using host = 1test.aha.io\n\t\t\t\t[INFO] response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/airbrakeprojectkey/airbrakeprojectkey.go",
    "content": "package airbrakeprojectkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"airbrake\"}) + `\\b([a-zA-Z-0-9]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"airbrake\"}) + `\\b([0-9]{6})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"airbrake\"}\n}\n\n// FromData will find and optionally verify AirbrakeProjectKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys, uniqueIds = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor _, matches := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueIds[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\tfor id := range uniqueIds {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AirbrakeProjectKey,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tRawV2:        []byte(key + id),\n\t\t\t}\n\t\t\ts1.ExtraData = map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/airbrake/\",\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, verificationErr := verifyAirbrakeProjectKey(ctx, client, key, id)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAirbrakeProjectKey(ctx context.Context, client *http.Client, key string, id string) (bool, error) {\n\turl := \"https://api.airbrake.io/api/v4/projects/\" + id + \"/deploys?key=\" + key\n\tpayload := strings.NewReader(`{\"environment\":\"production\",\"username\":\"john\",\"email\":\"john@smith.com\",\"repository\":\"https://github.com/airbrake/airbrake\",\"revision\":\"38748467ea579e7ae64f7815452307c9d05e05c5\",\"version\":\"v2.0\"}`)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, payload)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\t// handle according to detector API responses.\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AirbrakeProjectKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Airbrake is an error and performance monitoring service for web applications. Airbrake project keys can be used to report and track errors in applications.\"\n}\n"
  },
  {
    "path": "pkg/detectors/airbrakeprojectkey/airbrakeprojectkey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage airbrakeprojectkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAirbrakeProjectKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AIRBRAKEPROJECTKEY_TOKEN\")\n\tid := testSecrets.MustGetField(\"AIRBRAKEPROJECTKEY_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"AIRBRAKEPROJECTKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a airbrake secret %s within airbrake %s but verified\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AirbrakeProjectKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AirbrakeProjectKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AirbrakeProjectKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"AirbrakeProjectKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/airbrakeprojectkey/airbrakeprojectkey_test.go",
    "content": "package airbrakeprojectkey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAirBrakeProjectKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the airbrake API\n\t\t\t\t[DEBUG] Using airbrake Key=7B759RwRR5Txo9pDxXtPNcrTOj0zhvmR\n\t\t\t\t[DEBUG] Using airbrake ID=856019\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"7B759RwRR5Txo9pDxXtPNcrTOj0zhvmR856019\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{airbrake 691149}</id>\n  \t\t\t\t\t<secret>{airbrake AQAAABAAA hYNK8PlcGXZ6PXXDFJI89LCjpoM8koTx}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"hYNK8PlcGXZ6PXXDFJI89LCjpoM8koTx691149\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - key out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[INFO] airbrake API request handling\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using Key=7B759RwRR5Txo9pDxXtPNcrTOj0zhvmR\n\t\t\t\t[DEBUG] Using ID=856019\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - only key\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the airbrake API\n\t\t\t\t[DEBUG] Using airbrake Key=7B759RwRR5Txo9pDxXtPNcrTOj0zhvmR\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - only ID\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the airbrake API\n\t\t\t\t[DEBUG] Using airbrake ID=856019\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the airbrake API\n\t\t\t\t[DEBUG] Using airbrake Key=qwmnerBv56zx**cvkjqr78afvYU$90Op\n\t\t\t\t[DEBUG] Using airbrake ID=856019\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/airbrakeuserkey/airbrakeuserkey.go",
    "content": "package airbrakeuserkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"airbrake\"}) + `\\b([a-zA-Z-0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"airbrake\"}\n}\n\n// FromData will find and optionally verify AirbrakeUserKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_AirbrakeUserKey,\n\t\t\tRaw:          []byte(key),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/airbrake/\",\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyAirbrakeUserKey(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAirbrakeUserKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.airbrake.io/api/v4/projects?key=\"+key, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AirbrakeUserKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Airbrake is an error and performance monitoring service. Airbrake User Keys can be used to access and manage error reports and performance data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/airbrakeuserkey/airbrakeuserkey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage airbrakeuserkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAirbrakeUserKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AIRBRAKEUSERKEY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"AIRBRAKEUSERKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a airbrakeuserkey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AirbrakeUserKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AirbrakeUserKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AirbrakeUserKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"AirbrakeUserKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/airbrakeuserkey/airbrakeuserkey_test.go",
    "content": "package airbrakeuserkey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAirBrakeUserKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the airbrake API\n\t\t\t\t[DEBUG] Using Key=qsCGuilpkk2ngrsz75wtYqsCGuilpkk2ngrsz75w\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"qsCGuilpkk2ngrsz75wtYqsCGuilpkk2ngrsz75w\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{airbrake 691149}</id>\n  \t\t\t\t\t<secret>{airbrake AQAAABAAA UTDwMhGhuk0T04V0yqTqcKIwSSp7syUyQRG8JwoF}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"UTDwMhGhuk0T04V0yqTqcKIwSSp7syUyQRG8JwoF\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - key out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[DEBUG] airbrake api processing\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using Key=qsCGuilpkk2ngrsz75wtYqsCGuilpkk2ngrsz75w\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the airbrake API\n\t\t\t\t[DEBUG] Using airbrake Key=Qs%CGuil#pkk2ngrsz75wtYqsCGuilpkk2ngrsz75w\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/airship/airship.go",
    "content": "package airship\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"airship\"}) + `\\b([0-9a-zA-Z]{91})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"airship\"}\n}\n\n// FromData will find and optionally verify Airship secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Airship,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyAirshipKey(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAirshipKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://go.urbanairship.com/api/schedules\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Accept\", \"application/vnd.urbanairship+json; version=3\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Airship\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/airship/airship_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage airship\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAirship_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AIRSHIP\")\n\tinactiveSecret := testSecrets.MustGetField(\"AIRSHIP_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a airship secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Airship,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Airship,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Airship.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Airship.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/airship/airship_test.go",
    "content": "package airship\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAirship_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the airship API\n\t\t\t\t[DEBUG] Using Key=O3BV99CUDw3xYUAL0tHGYUe7mOj5PA5vTnLdJwULCTh9dxk9PmmTpL1kI846G3QGIsECVyVSsxZnIbfSwWc8xuX843W\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"O3BV99CUDw3xYUAL0tHGYUe7mOj5PA5vTnLdJwULCTh9dxk9PmmTpL1kI846G3QGIsECVyVSsxZnIbfSwWc8xuX843W\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{airship}</id>\n  \t\t\t\t\t<secret>{airship AQAAABAAA oVH3yIO1oAoXpK9Rc01EGNNTuw6d4Zyt07YNFmje644Ht00hvAaYwldNOV9vIPQw6dYHJLRgp2f75YdJ9OiICkYVhMI}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"oVH3yIO1oAoXpK9Rc01EGNNTuw6d4Zyt07YNFmje644Ht00hvAaYwldNOV9vIPQw6dYHJLRgp2f75YdJ9OiICkYVhMI\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - key out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[DEBUG] airship api processing\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using Key=O3BV99CUDw3xYUAL0tHGYUe7mOj5PA5vTnLdJwULCTh9dxk9PmmTpL1kI846G3QGIsECVyVSsxZnIbfSwWc8xuX843W\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the airship API\n\t\t\t\t[DEBUG] Using Key=O3BV99CUDw3xY#AL0tHGYUe7mOj5PA5vTnLdJwULCTh9dxk9PmmTpL1kI846G3QGIsECVyVSsxZnIbfSwWc8xuX843W\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/airtableoauth/airtableoauth.go",
    "content": "package airtableoauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// The detector will attempt to match access tokens generated through the Airtable OAuth flow\n\t// Airtable OAuth does not support generating access tokens using client ID and key\n\t// Reference: https://airtable.com/developers/web/api/oauth-reference\n\ttokenPat = regexp.MustCompile(`\\b([[:alnum:]]+\\.v1\\.[a-zA-Z0-9_-]+\\.[a-f0-9]+)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"airtable\"}\n}\n\n// FromData will find and optionally verify AirtableOAuth secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range tokenPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_AirtableOAuth,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\n\t\t\tif s1.Verified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\"token\": match}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\tendpoint := \"https://api.airtable.com/v0/meta/whoami\"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil, nil\n\tcase http.StatusUnauthorized:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AirtableOAuth\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/airtableoauth/airtableoauth_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage airtableoauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\n// TestAirtableoauth_FromChunk verifies the validity of an Airtable OAuth token\n// Note: The token validity test relies on an access token stored in the GCP secret manager.\n// Since Airtable OAuth tokens expire after 60 minutes, this test will eventually fail once the token becomes invalid.\n// The official guide linked below can be followed in order to generate a new valid access token:\n// https://airtable.com/developers/web/api/oauth-reference\n\nfunc TestAirtableoauth_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AIRTABLEOAUTH\")\n\tinactiveSecret := testSecrets.MustGetField(\"AIRTABLEOAUTH_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a airtableoauth secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AirtableOAuth,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AirtableOAuth,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a airtableoauth secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AirtableOAuth,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a airtableoauth secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AirtableOAuth,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Airtableoauth.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Airtableoauth.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/airtableoauth/airtableoauth_test.go",
    "content": "package airtableoauth\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAirtableoauth_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the airtable API\n\t\t\t\t[DEBUG] Using Key=oaajtCy2lVMUN1Cm5.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsImV4cGlyZXNBdCI6IjIwMjUtMDItMDNUMTk6NTY6MzcuMDAwWiIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwic2VjcmV0IjoiMzczNThlNzdlZjlhMjljY2Q5MWIwNmNlNTdkZDYxNDg0MWVmNmIyOWYwYjQ5ZWE0MTMxZGI4NzBkNTAzYTE1NyJ9.0d67c8b334048135a93615610445e4aa90c6af6222392b49eea9419e1d6717d0\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"oaajtCy2lVMUN1Cm5.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsImV4cGlyZXNBdCI6IjIwMjUtMDItMDNUMTk6NTY6MzcuMDAwWiIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwic2VjcmV0IjoiMzczNThlNzdlZjlhMjljY2Q5MWIwNmNlNTdkZDYxNDg0MWVmNmIyOWYwYjQ5ZWE0MTMxZGI4NzBkNTAzYTE1NyJ9.0d67c8b334048135a93615610445e4aa90c6af6222392b49eea9419e1d6717d0\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{airtable}</id>\n  \t\t\t\t\t<secret>{airtable AQAAABAAA iKMJv6D1mmUvunFTZLfm4RrYhdrt5JCBMv.v1.r8IBnGw7b_vW0fl0MDJqPRUEsDdHtNYW9ANwPFm40V_M4knoEaulKL-5lmtWoRq9fjG-GORe8efob5e658nTiOkdYC.8a8d3}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"iKMJv6D1mmUvunFTZLfm4RrYhdrt5JCBMv.v1.r8IBnGw7b_vW0fl0MDJqPRUEsDdHtNYW9ANwPFm40V_M4knoEaulKL-5lmtWoRq9fjG-GORe8efob5e658nTiOkdYC.8a8d3\"},\n\t\t},\n\t\t{\n\t\t\tname: \"finds all matches\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the airtable API\n\t\t\t\t[DEBUG] Using Key=oaajtCy2lVMUN1Cm5.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsImV4cGlyZXNBdCI6IjIwMjUtMDItMDNUMTk6NTY6MzcuMDAwWiIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwic2VjcmV0IjoiMzczNThlNzdlZjlhMjljY2Q5MWIwNmNlNTdkZDYxNDg0MWVmNmIyOWYwYjQ5ZWE0MTMxZGI4NzBkNTAzYTE1NyJ9.0d67c8b334048135a93615610445e4aa90c6af6222392b49eea9419e1d6717d0\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t\t[DEBUG] Using Key=oaaRYiYSlTFXZzxDM.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwiZXhwaXJlc0F0IjoiMjAyNS0wMS0yOVQwMDowMTo0NC4wMDBaIiwic2VjcmV0IjoiZjYyOWE1MWVkM2M0ZjU5ODlmOTcyMDU1ZjkwODk3NDA4NmU0NjQxY2JhODU5Y2FhZTJkZjliMWQwODg0ZjIzMiJ9.27a8998029ac9bdd599b435572821dcb63c60cbf62b9cb2ba2a73511e5553d66\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"oaajtCy2lVMUN1Cm5.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsImV4cGlyZXNBdCI6IjIwMjUtMDItMDNUMTk6NTY6MzcuMDAwWiIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwic2VjcmV0IjoiMzczNThlNzdlZjlhMjljY2Q5MWIwNmNlNTdkZDYxNDg0MWVmNmIyOWYwYjQ5ZWE0MTMxZGI4NzBkNTAzYTE1NyJ9.0d67c8b334048135a93615610445e4aa90c6af6222392b49eea9419e1d6717d0\",\n\t\t\t\t\"oaaRYiYSlTFXZzxDM.v1.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwiZXhwaXJlc0F0IjoiMjAyNS0wMS0yOVQwMDowMTo0NC4wMDBaIiwic2VjcmV0IjoiZjYyOWE1MWVkM2M0ZjU5ODlmOTcyMDU1ZjkwODk3NDA4NmU0NjQxY2JhODU5Y2FhZTJkZjliMWQwODg0ZjIzMiJ9.27a8998029ac9bdd599b435572821dcb63c60cbf62b9cb2ba2a73511e5553d66\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the airtable API\n\t\t\t\t[DEBUG] Using Key=oaaRYiYSlTFXZzxDM.v2.eyJ1c2VySWQiOiJ1c3JjQ09QVlJudGlrU1lzdyIsIm9hdXRoQXBwbGljYXRpb25JZCI6Im9hcG14aXcyUlRrVGlzcHJIIiwiZXhwaXJlc0F0IjoiMjAyNS0wMS0yOVQwMDowMTo0NC4wMDBaIiwic2VjcmV0IjoiZjYyOWE1MWVkM2M0ZjU5ODlmOTcyMDU1ZjkwODk3NDA4NmU0NjQxY2JhODU5Y2FhZTJkZjliMWQwODg0ZjIzMiJ9.27a8998029ac9bdd599b435572821dcb63c60cbf62b9cb2ba2a73511e5553d66\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/airtablepersonalaccesstoken/airtablepersonalaccesstoken.go",
    "content": "package airtablepersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\ttokenPat      = regexp.MustCompile(detectors.PrefixRegex([]string{\"airtable\"}) + `\\b(pat[[:alnum:]]{14}\\.[a-f0-9]{64})\\b`)\n)\n\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"airtable\"}\n}\n\n// FromData will find and optionally verify AirtableOAuth secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range tokenPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_AirtablePersonalAccessToken,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\n\t\t\tif s1.Verified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\"token\": match}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\tendpoint := \"https://api.airtable.com/v0/meta/whoami\"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil, nil\n\tcase http.StatusUnauthorized:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AirtablePersonalAccessToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/airtablepersonalaccesstoken/airtablepersonalaccesstoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage airtablepersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAirtablepersonalaccesstoken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AIRTABLEPERSONALACCESSTOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"AIRTABLEPERSONALACCESSTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find an airtable secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AirtablePersonalAccessToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AirtablePersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a airtablepersonalaccesstoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AirtablePersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a airtablepersonalaccesstoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AirtablePersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Airtablepersonalaccesstoken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Airtablepersonalaccesstoken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/airtablepersonalaccesstoken/airtablepersonalaccesstoken_test.go",
    "content": "package airtablepersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAirtablepersonalaccesstoken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the airtable API\n\t\t\t\t[DEBUG] Using Key=patfqpIZBPU6EAt5x.458546d9c77b21f8a98141f2a4039d5626010f19efc16c20d57c4f41d44c8c85\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"patfqpIZBPU6EAt5x.458546d9c77b21f8a98141f2a4039d5626010f19efc16c20d57c4f41d44c8c85\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{airtable}</id>\n  \t\t\t\t\t<secret>{airtable AQAAABAAA pat2kATFGrujqJTbT.e2082656c470902d83b47dc804e693df1deb30161affbda39d879a2cf44bef13}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"pat2kATFGrujqJTbT.e2082656c470902d83b47dc804e693df1deb30161affbda39d879a2cf44bef13\"},\n\t\t},\n\t\t{\n\t\t\tname: \"finds all matches\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using airtable Key=patfqpIZBPU6EAt5x.458546d9c77b21f8a98141f2a4039d5626010f19efc16c20d57c4f41d44c8c85\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t\t[DEBUG] Using airtable Key=pat0VXr5I2HcapZE8.da2606afb7d97e936719ec952a4a18b44045e385d4ddf4f38dcc246fb63f0165\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"patfqpIZBPU6EAt5x.458546d9c77b21f8a98141f2a4039d5626010f19efc16c20d57c4f41d44c8c85\",\n\t\t\t\t\"pat0VXr5I2HcapZE8.da2606afb7d97e936719ec952a4a18b44045e385d4ddf4f38dcc246fb63f0165\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the airtable API\n\t\t\t\t[DEBUG] Using Key=patfqpIZBPU6EAt5xe.458546d9c77b21f8a98141f2a403-d5626010f19efc16c20d57c4f41d44c8c85\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/airvisual/airvisual.go",
    "content": "package airvisual\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"airvisual\"}) + `\\b([a-z0-9-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"airvisual\"}\n}\n\n// FromData will find and optionally verify AirVisual secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_AirVisual,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyAirVisualKey(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAirVisualKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"https://api.airvisual.com/v2/countries?key=%s\", key), nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Accept\", \"application/vnd.airvisual+json; version=3\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AirVisual\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"AirVisual provides air quality information and monitoring. The API key allows access to various air quality data and services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/airvisual/airvisual_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage airvisual\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAirVisual_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AIRVISUAL\")\n\tinactiveSecret := testSecrets.MustGetField(\"AIRVISUAL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a airvisual secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AirVisual,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AirVisual,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AirVisual.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"AirVisual.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/airvisual/airvisual_test.go",
    "content": "package airvisual\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAirVisual_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the airvisual API\n\t\t\t\t[DEBUG] Using Key=qscgyygcsq-wdvvok7slklklaasnd8afafxd\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"qscgyygcsq-wdvvok7slklklaasnd8afafxd\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{airvisual}</id>\n  \t\t\t\t\t<secret>{airvisual AQAAABAAA rtcbsxiee3d5au8ik14g-8iqrsu8thl1pku8}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"rtcbsxiee3d5au8ik14g-8iqrsu8thl1pku8\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - key out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[DEBUG] airvisual api processing\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using Key=qscgyygcsq-wdvvok7slklklaasnd8afafxd\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the airvisual API\n\t\t\t\t[DEBUG] Using Key=wdvvok7slklklaasnd8afafxd\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/aiven/aiven.go",
    "content": "package aiven\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"aiven\"}) + `([a-zA-Z0-9/+=]{372})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"aiven\"}\n}\n\n// FromData will find and optionally verify Aiven secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Aiven,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyAivenKey(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAivenKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.aiven.io/v1/project\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"aivenv1 %s\", key))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Aiven\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/aiven/aiven_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage aiven\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAiven_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AIVEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"AIVEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a aiven secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Aiven,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Aiven,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Aiven.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Aiven.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/aiven/aiven_test.go",
    "content": "package aiven\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAiven_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the aiven API\n\t\t\t\t[DEBUG] Using Key = yb+Ygm82FfUworm2exB+Uk255p0uQKmmfx4ut1KfsZ3YI3Gp2xPYyxZgrwYabMxXXO4WPsK7xlLJRy0BWIpM2SKnzA2p69P8aOmYbl24ZiVGlLXyQxeVDDy7gru5Yzt=Y1UDLBpsW=hhGIKsrPgc/7hpxuEfEqbXJe5IBYO484F+ekaTmYN4nTF94O==3WuG+WuSW7zaYzXH1V==kZFj07zBtmShS0z/lW=N3HipH=oJjXI2pyFxU+A7vM9yHdUHoiZEOVoWsyp5zO1ajBOqFr=3jIIaXWmbH33dP2ZNQFJhqbeg6JlXA9GpfMFht5=ZCC1IirWCNp=UILbmZtvu9d2M8U0YNHwAGKtjrPS5lZvAU+W5s2Ti\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"yb+Ygm82FfUworm2exB+Uk255p0uQKmmfx4ut1KfsZ3YI3Gp2xPYyxZgrwYabMxXXO4WPsK7xlLJRy0BWIpM2SKnzA2p69P8aOmYbl24ZiVGlLXyQxeVDDy7gru5Yzt=Y1UDLBpsW=hhGIKsrPgc/7hpxuEfEqbXJe5IBYO484F+ekaTmYN4nTF94O==3WuG+WuSW7zaYzXH1V==kZFj07zBtmShS0z/lW=N3HipH=oJjXI2pyFxU+A7vM9yHdUHoiZEOVoWsyp5zO1ajBOqFr=3jIIaXWmbH33dP2ZNQFJhqbeg6JlXA9GpfMFht5=ZCC1IirWCNp=UILbmZtvu9d2M8U0YNHwAGKtjrPS5lZvAU+W5s2Ti\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{aiven}</id>\n  \t\t\t\t\t<secret>{aiven AQAAABAAA IGhXNR6g7rogABp/H2iDQu7TgkXpvn9KnwzJfeh+8p7M=JVsI2QoQ38mmQHt450bQC4wBOGFhV+9QT2KGWSMfTOxTUrUXygaLlwsXo/RBxKXyOdh=/L8EGGrqG6=qbd0UzDAfc0xeAfXd30RGj+Ypsrrvdda=ZPa32BBID5r2ClfJSbgpfWIpVC1b5vlqCdy5LIWABZJzjBC5VweqZ04XFaCh+15NuSQ4E0KdGwPdkrfxxjY20I1wDvlKxzxL7dfCly3KVlQv7KBEFSLaLRNRocPYToUXqU4yAXKvXf03K=k1mahpxFUp94c35k/n055LVs=xbyL6AKdW=sCCa1AFIYKBDMBprTsZ6Al7DHx=XA6qLNWYxS7}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"IGhXNR6g7rogABp/H2iDQu7TgkXpvn9KnwzJfeh+8p7M=JVsI2QoQ38mmQHt450bQC4wBOGFhV+9QT2KGWSMfTOxTUrUXygaLlwsXo/RBxKXyOdh=/L8EGGrqG6=qbd0UzDAfc0xeAfXd30RGj+Ypsrrvdda=ZPa32BBID5r2ClfJSbgpfWIpVC1b5vlqCdy5LIWABZJzjBC5VweqZ04XFaCh+15NuSQ4E0KdGwPdkrfxxjY20I1wDvlKxzxL7dfCly3KVlQv7KBEFSLaLRNRocPYToUXqU4yAXKvXf03K=k1mahpxFUp94c35k/n055LVs=xbyL6AKdW=sCCa1AFIYKBDMBprTsZ6Al7DHx=XA6qLNWYxS7\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - key out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[DEBUG] aiven api processing\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using Key=yb+Ygm82FfUworm2exB+Uk255p0uQKmmfx4ut1KfsZ3YI3Gp2xPYyxZgrwYabMxXXO4WPsK7xlLJRy0BWIpM2SKnzA2p69P8aOmYbl24ZiVGlLXyQxeVDDy7gru5Yzt=Y1UDLBpsW=hhGIKsrPgc/7hpxuEfEqbXJe5IBYO484F+ekaTmYN4nTF94O==3WuG+WuSW7zaYzXH1V==kZFj07zBtmShS0z/lW=N3HipH=oJjXI2pyFxU+A7vM9yHdUHoiZEOVoWsyp5zO1ajBOqFr=3jIIaXWmbH33dP2ZNQFJhqbeg6JlXA9GpfMFht5=ZCC1IirWCNp=UILbmZtvu9d2M8U0YNHwAGKtjrPS5lZvAU+W5s2Ti\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the aiven API\n\t\t\t\t[DEBUG] Using Key=SSs8PGhwqWzb4qfqiwLV/bNHfiQ2VSKyX88AAYm3+CGHbTe/FYXRNOYYHO=PXwuL/GftiES7j8ffzWW9p1dAyNc6hZZpoazmd+Vf1kbukZSL8QO/LdKFI/YFlupu0dELqQVHeZi/cJlnp6aQeY7zIJiHhJS51ZVdOamc=zOUMebry3BYOo2LhYIz+mLND7s5/cHZZpkEvTXrKnVf4vdYMl+fawv84AYCTo9pry8FQBsqRex2HL98kAiqhVYG+nLyRz/hZCo8owaRkzli1BUT4O63TSKJIgnECOBvyZz7o+yX92BhDe+B2Tllk3y2=qG5TiEl2sCJI8V5GJ1cz52RpXx2hVXMi=1Zl5CHpX8Adr9VMbj$Co\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/alchemy/alchemy.go",
    "content": "package alchemy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"alchemy\"}) + `\\b([0-9a-zA-Z_]{32}|alcht_[0-9a-zA-Z]{30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"alchemy\", \"alcht_\"}\n}\n\n// FromData will find and optionally verify Alchemy secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Alchemy,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://eth-mainnet.g.alchemy.com/v2/\"+token+\"/getNFTs/?owner=vitalik.eth\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\t// If the endpoint returns useful information, we can return it as a map.\n\t\treturn true, nil, nil\n\tcase http.StatusUnauthorized:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Alchemy\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/alchemy/alchemy_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage alchemy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAlchemy_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ALCHEMY\")\n\tinactiveSecret := testSecrets.MustGetField(\"ALCHEMY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a alchemy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Alchemy,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Alchemy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a alchemy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Alchemy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a alchemy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Alchemy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Alchemy.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Alchemy.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/alchemy/alchemy_test.go",
    "content": "package alchemy\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAlchemy_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the alchemy API\n\t\t\t\t[DEBUG] Using Key=alcht_2Cy8xCLyvrAf7lZKfhQhyCr4RAID9D\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"alcht_2Cy8xCLyvrAf7lZKfhQhyCr4RAID9D\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t\t\t<scope>GLOBAL</scope>\n\t\t\t\t\t<id>{alchemy}</id>\n\t\t\t\t\t<secret>{alchemy AQAAABAAA 5iqW7gKQVXvwnykF9xAVfenemmnUJznI}</secret>\n\t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n\t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"5iqW7gKQVXvwnykF9xAVfenemmnUJznI\"},\n\t\t},\n\t\t{\n\t\t\tname: \"finds all matches\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the alchemy API\n\t\t\t\t[DEBUG] Using Key=alcht_2Cy8xCLyvrAf7lZKfhQhyCr4RAID9D\n\t\t\t\t[ERROR] Response received 401 UnAuthorized\n\t\t\t\t[DEBUG] Using alchemy Key=xuQIeWFVEp8k8Uu9FwPx6X5C8IViOe1o\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"alcht_2Cy8xCLyvrAf7lZKfhQhyCr4RAID9D\", \"xuQIeWFVEp8k8Uu9FwPx6X5C8IViOe1o\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the alchemy API\n\t\t\t\t[DEBUG] Using Key=alcht_a2Cy8xCLyvrAf7lZKfhQhyCr4RAID9D\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/alconost/alconost.go",
    "content": "package alconost\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"alconost\"}) + `\\b([0-9Aa-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"alconost\"}\n}\n\n// FromData will find and optionally verify Alconost secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Alconost,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyAlconostKey(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAlconostKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\tdata := fmt.Sprintf(\"%s:\", key)\n\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://nitro.alconost.com/api/v1/account\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Alconost\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Alconost is a translation and localization service. Alconost API keys can be used to access and modify translation data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/alconost/alconost_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage alconost\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAlconost_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ALCONOST\")\n\tinactiveSecret := testSecrets.MustGetField(\"ALCONOST_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a alconost secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Alconost,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Alconost,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Alconost.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Alconost.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/alconost/alconost_test.go",
    "content": "package alconost\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAlconost_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the alconost API\n\t\t\t\t[DEBUG] Using Key=wdvnousa87acfxp9ioasrea4tbeasrfa\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"wdvnousa87acfxp9ioasrea4tbeasrfa\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{alconost}</id>\n  \t\t\t\t\t<secret>{alconost AQAAABAAA Awxzhkwff46dtkt5pnvdlss6t2kA44a7}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"Awxzhkwff46dtkt5pnvdlss6t2kA44a7\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - key out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[DEBUG] alconost api processing\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using Key=wdvnousa87acfxp9ioasrea4tbeasrfa\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the alconost API\n\t\t\t\t[DEBUG] Using Key=wdvnousa87acfxp9ioasra4tBeasrfa\n\t\t\t\t[INFO] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/alegra/alegra.go",
    "content": "package alegra\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"alegra\"}) + `\\b([a-z0-9-]{20})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"alegra\"}) + common.EmailPattern)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"alegra\"}\n}\n\n// FromData will find and optionally verify Alegra secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tuniqueTokens := make(map[string]struct{})\n\tuniqueIDs := make(map[string]struct{})\n\n\tfor _, match := range keyMatches {\n\t\tuniqueTokens[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range idMatches {\n\t\tid := match[0][strings.LastIndex(match[0], \" \")+1:]\n\t\tuniqueIDs[id] = struct{}{}\n\t}\n\n\tfor token := range uniqueTokens {\n\t\tfor id := range uniqueIDs {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Alegra,\n\t\t\t\tRaw:          []byte(token),\n\t\t\t\tRawV2:        []byte(token + \":\" + id),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, verificationErr := verifyCredentials(ctx, client, id, token)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, token)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyCredentials(ctx context.Context, client *http.Client, username, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.alegra.com/api/v1/users/self\", nil)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\treq.SetBasicAuth(username, token)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Alegra\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Alegra is a cloud-based accounting software. Alegra API keys can be used to access and modify accounting data and user information.\"\n}\n"
  },
  {
    "path": "pkg/detectors/alegra/alegra_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage alegra\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAlegra_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ALEGRA\")\n\tid := testSecrets.MustGetField(\"ACCOUNT_USER\")\n\tinactiveSecret := testSecrets.MustGetField(\"ALEGRA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a alegra secret %s within alegra %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Alegra,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Alegra,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Alegra.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Alegra.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/alegra/alegra_test.go",
    "content": "package alegra\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAlegra_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using alegra Key=wdvn-usa87a-fxp9ioas\n\t\t\t\t[DEBUG] Using alegra Email = testUser.1005@example.com\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"wdvn-usa87a-fxp9ioas:testUser.1005@example.com\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{alegra kk18@example.com}</id>\n  \t\t\t\t\t<secret>{alegra AQAAABAAA buihlmkfnh5m1lk5z6do}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"buihlmkfnh5m1lk5z6do:kk18@example.com\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - key out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[DEBUG] alegra api processing\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using Key=wdvn-usa87a-fxp9ioas\n\t\t\t\t[DEBUG] Using Email=testUser.1005@example.com\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using alegra Key=wdvn_usa87a-fxp9ioas\n\t\t\t\t[DEBUG] Using alegra Email=testUser.1005@example.com\n\t\t\t\t[INFO] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/aletheiaapi/aletheiaapi.go",
    "content": "package aletheiaapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"aletheiaapi\"}) + `\\b([A-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"aletheiaapi\"}\n}\n\n// FromData will find and optionally verify AletheiaApi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_AletheiaApi,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyAletheiaAPIKey(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAletheiaAPIKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\ttimeout := 10 * time.Second\n\tclient.Timeout = timeout\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.aletheiaapi.com/StockData?symbol=msft&summary=true\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Key\", key)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AletheiaApi\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"AletheiaApi is a service providing financial data. AletheiaApi keys can be used to access this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/aletheiaapi/aletheiaapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage aletheiaapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAletheiaApi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ALETHEIAAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"ALETHEIAAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a aletheiaapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AletheiaApi,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AletheiaApi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AletheiaApi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"AletheiaApi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/aletheiaapi/aletheiaapi_test.go",
    "content": "package aletheiaapi\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAleTheIaAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the aletheiaapi\n\t\t\t\t[DEBUG] Using Key=LY027C40U2KNNZLFO1WEU3XQZ13LW515\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"LY027C40U2KNNZLFO1WEU3XQZ13LW515\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{aletheiaapi}</id>\n  \t\t\t\t\t<secret>{aletheiaapi AQAAABAAA K7SOW2B8QH9QE435NLH07PH22XL4YOPG}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"K7SOW2B8QH9QE435NLH07PH22XL4YOPG\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - key out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[DEBUG] aletheiaapi api processing\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using Key=LY027C40U2KNNZLFO1WEU3XQZ13LW515\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the aletheiaapi\n\t\t\t\t[DEBUG] Using Key=LY027c40U2KNNZLFO1WEU3XQZ13LW515\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/algoliaadminkey/algoliaadminkey.go",
    "content": "package algoliaadminkey\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"slices\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"algolia\", \"docsearch\", \"appId\"}) + `\\b([A-Z0-9]{10})\\b`)\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"algolia\", \"docsearch\", \"apiKey\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n\n\tinvalidHosts = simple.NewCache[struct{}]()\n\n\terrNoHost = errors.New(\"no such host\")\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"algolia\", \"docsearch\"}\n}\n\n// FromData will find and optionally verify AlgoliaAdminKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tlogger := logContext.AddLogger(ctx).Logger().WithName(\"algoliaadminkey\")\n\tdataStr := string(data)\n\n\t// Deduplicate matches.\n\tidMatches := make(map[string]struct{})\n\tfor _, match := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tid := match[1]\n\t\tif detectors.StringShannonEntropy(id) > 2 {\n\t\t\tidMatches[id] = struct{}{}\n\t\t}\n\t}\n\tkeyMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tkey := match[1]\n\t\tif detectors.StringShannonEntropy(key) > 3 {\n\t\t\tkeyMatches[key] = struct{}{}\n\t\t}\n\t}\n\n\t// Test matches.\n\tfor key := range keyMatches {\n\t\tfor id := range idMatches {\n\t\t\tif invalidHosts.Exists(id) {\n\t\t\t\tlogger.V(3).Info(\"Skipping application id: no such host\", \"host\", id)\n\t\t\t\tdelete(idMatches, id)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AlgoliaAdminKey,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tRawV2:        []byte(id + \":\" + key),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\t// Verify if the key is a valid Algolia Admin Key.\n\t\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, id, key)\n\t\t\t\tr.Verified = isVerified\n\t\t\t\tr.ExtraData = extraData\n\t\t\t\tif verificationErr != nil {\n\t\t\t\t\tif errors.Is(verificationErr, errNoHost) {\n\t\t\t\t\t\tinvalidHosts.Set(id, struct{}{})\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tr.SetVerificationError(verificationErr, key)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, r)\n\t\t\tif r.Verified {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn results, nil\n}\n\n// https://www.algolia.com/doc/guides/security/api-keys/#access-control-list-acl\nvar nonSensitivePermissions = map[string]struct{}{\n\t\"listIndexes\": {},\n\t\"search\":      {},\n\t\"settings\":    {},\n}\n\nfunc verifyMatch(ctx context.Context, appId, apiKey string) (bool, map[string]string, error) {\n\t// https://www.algolia.com/doc/rest-api/search/#section/Base-URLs\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://\"+appId+\".algolia.net/1/keys/\"+apiKey, nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Set(\"X-Algolia-Application-Id\", appId)\n\treq.Header.Set(\"X-Algolia-API-Key\", apiKey)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\t// lookup xyz.algolia.net: no such host\n\t\tif strings.Contains(err.Error(), \"no such host\") {\n\t\t\treturn false, nil, errNoHost\n\t\t}\n\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tvar keyRes keyResponse\n\t\tif err := json.NewDecoder(res.Body).Decode(&keyRes); err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\n\t\t// Check if the key has sensitive permissions, even if it's not an Admin Key.\n\t\thasSensitivePerms := false\n\t\tfor _, acl := range keyRes.ACL {\n\t\t\tif _, ok := nonSensitivePermissions[acl]; !ok {\n\t\t\t\thasSensitivePerms = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !hasSensitivePerms {\n\t\t\treturn false, nil, nil\n\t\t}\n\n\t\tslices.Sort(keyRes.ACL)\n\t\textraData := map[string]string{\n\t\t\t\"acl\": strings.Join(keyRes.ACL, \",\"),\n\t\t}\n\t\tif keyRes.Description != \"\" && keyRes.Description != \"<redacted>\" {\n\t\t\textraData[\"description\"] = keyRes.Description\n\t\t}\n\t\treturn true, extraData, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil, nil\n\tcase http.StatusForbidden:\n\t\t// Invalidated key.\n\t\t// {\"message\":\"Invalid Application-ID or API key\",\"status\":403}\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\n// https://www.algolia.com/doc/rest-api/search/#tag/Api-Keys/operation/getApiKey\ntype keyResponse struct {\n\tACL         []string `json:\"acl\"`\n\tDescription string   `json:\"description\"`\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AlgoliaAdminKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Algolia is a search-as-a-service platform. Algolia Admin Keys can be used to manage indices and API keys, and perform administrative tasks.\"\n}\n"
  },
  {
    "path": "pkg/detectors/algoliaadminkey/algoliaadminkey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage algoliaadminkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAlgoliaAdminKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ALGOLIAADMINKEY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"ALGOLIAADMINKEY_INACTIVE\")\n\tid := testSecrets.MustGetField(\"ALGOLIAADMINKEY_APPID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a algolia secret %s within algolia %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AlgoliaAdminKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s%s\", secret, id)),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AlgoliaAdminKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s%s\", inactiveSecret, id)),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AlgoliaAdminKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"AlgoliaAdminKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/algoliaadminkey/algoliaadminkey_test.go",
    "content": "package algoliaadminkey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAlgoliaAdminKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using algolia Key=BsDaN7ZU7kFiUX5CpN8CUf3nkMaSeZYn\n\t\t\t\t[DEBUG] Using docsearch ID=844XQV5SUA\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"844XQV5SUA:BsDaN7ZU7kFiUX5CpN8CUf3nkMaSeZYn\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{appId 0VJ9I1WV78}</id>\n  \t\t\t\t\t<secret>{algolia AQAAABAAA 4AYm3wz7nfnX7Bqtw5e5Qo3Z5vfBe0eS}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"0VJ9I1WV78:4AYm3wz7nfnX7Bqtw5e5Qo3Z5vfBe0eS\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - key out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the algolia API\n\t\t\t\t[DEBUG] Using Key=BsDaN7ZU7kFiUX5CpN8CUf3nkMaSeZYn\n\t\t\t\t[DEBUG] Using ID=844XQV5SUA\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using algolia Key=BsD-N7ZU7kFiUX5CpN8CUf3nkMaSeZYn\n\t\t\t\t[DEBUG] Using docsearch ID=844XqV5SUA\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/alibaba/alibaba.go",
    "content": "package alibaba\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/rand\"\n\t\"crypto/sha1\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\ntype alibabaResp struct {\n\tRequestId string `json:\"RequestId\"`\n\tMessage   string `json:\"Message\"`\n\tRecommend string `json:\"Recommend\"`\n\tHostId    string `json:\"HostId\"`\n\tCode      string `json:\"Code\"`\n}\n\nconst alibabaURL = \"https://ecs.aliyuncs.com\"\n\nvar (\n\t// Ensure the Scanner satisfies the interface at compile time.\n\t_ detectors.Detector = (*Scanner)(nil)\n\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b([a-zA-Z0-9]{30})\\b`)\n\tidPat  = regexp.MustCompile(`\\b(LTAI[a-zA-Z0-9]{17,21})[\\\"';\\s]*`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"LTAI\"}\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc randString(n int) string {\n\tconst alphanum = \"0123456789abcdefghijklmnopqrstuvwxyz\"\n\tvar bytes = make([]byte, n)\n\t_, _ = rand.Read(bytes)\n\tfor i, b := range bytes {\n\t\tbytes[i] = alphanum[b%byte(len(alphanum))]\n\t}\n\treturn string(bytes)\n}\n\nfunc GetSignature(input, key string) string {\n\tkey_for_sign := []byte(key)\n\th := hmac.New(sha1.New, key_for_sign)\n\th.Write([]byte(input))\n\treturn base64.StdEncoding.EncodeToString(h.Sum(nil))\n}\n\nfunc buildStringToSign(method, input string) string {\n\tfilter := strings.Replace(input, \"+\", \"%20\", -1)\n\tfilter = strings.Replace(filter, \"%7E\", \"~\", -1)\n\tfilter = strings.Replace(filter, \"*\", \"%2A\", -1)\n\tfilter = method + \"&%2F&\" + url.QueryEscape(filter)\n\treturn filter\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Alibaba secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Alibaba,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resIdMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.getClient()\n\t\t\t\tisVerified, verificationErr := verifyAlibaba(ctx, client, resIdMatch, resMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAlibaba(ctx context.Context, client *http.Client, resIdMatch, resMatch string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, alibabaURL, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdateISO := time.Now().UTC().Format(\"2006-01-02T15:04:05Z07:00\")\n\tparams := req.URL.Query()\n\tparams.Add(\"AccessKeyId\", resIdMatch)\n\tparams.Add(\"Action\", \"DescribeRegions\")\n\tparams.Add(\"Format\", \"JSON\")\n\tparams.Add(\"SignatureMethod\", \"HMAC-SHA1\")\n\tparams.Add(\"SignatureNonce\", randString(16))\n\tparams.Add(\"SignatureVersion\", \"1.0\")\n\tparams.Add(\"Timestamp\", dateISO)\n\tparams.Add(\"Version\", \"2014-05-26\")\n\n\tstringToSign := buildStringToSign(req.Method, params.Encode())\n\tsignature := GetSignature(stringToSign, resMatch+\"&\") // Get Signature HMAC SHA1\n\tparams.Add(\"Signature\", signature)\n\treq.URL.RawQuery = params.Encode()\n\n\treq.Header.Add(\"Content-Type\", \"text/xml;charset=utf-8\")\n\treq.Header.Add(\"Content-Length\", strconv.Itoa(len(params.Encode())))\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\tvar alibabaResp alibabaResp\n\tif err = json.NewDecoder(res.Body).Decode(&alibabaResp); err != nil {\n\t\treturn false, err\n\t}\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusNotFound, http.StatusBadRequest:\n\t\t// 400 used for most of error cases\n\t\t// 404 used if the AccessKeyId is not valid\n\t\treturn false, nil\n\tdefault:\n\t\terr := fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\tif alibabaResp.Message != \"\" {\n\t\t\terr = fmt.Errorf(\"%s: %s, %s\", err, alibabaResp.Message, alibabaResp.Code)\n\t\t}\n\t\treturn false, err\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Alibaba\n}\n"
  },
  {
    "path": "pkg/detectors/alibaba/alibaba_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage alibaba\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAlibaba_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ALIBABA_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"ALIBABA_SECRET_INACTIVE\")\n\tid := testSecrets.MustGetField(\"ALIBABA_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a alibaba secret %s within alibaba %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Alibaba,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a alibaba secret %s within alibaba %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Alibaba,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(context.DeadlineExceeded)\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"{}\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a alibaba secret %s within alibaba %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Alibaba,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"unexpected HTTP response status 500\"))\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, real secrets, verification error due to broken json\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(418, \"{\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a alibaba secret %s within alibaba %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Alibaba,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"unexpected EOF\"))\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Alibaba,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Alibaba.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Alibaba.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/alibaba/alibaba_test.go",
    "content": "package alibaba\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAliBaba_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using Key=CwgR2UwgaWd7hgUdQkwFnK9vvEeO4R\n\t\t\t\t[DEBUG] Using ID=LTAIXgRPqwF1DhBf6Q1uZ5DrM\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"CwgR2UwgaWd7hgUdQkwFnK9vvEeO4RLTAIXgRPqwF1DhBf6Q1uZ5DrM\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{WX6OtM8pbcrXWMIGc5evYousFWBlBm}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA LTAImg3ZeAPatbAtEDS9HVZ}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"WX6OtM8pbcrXWMIGc5evYousFWBlBmLTAImg3ZeAPatbAtEDS9HVZ\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - ignore special characters at end\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using Key=CwgR2UwgaWd7hgUdQkwFnK9vvEeO4R\n\t\t\t\t[DEBUG] Using ID=LTAIXgRPqwF1DhBf6Q1uZ5DrM;\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"CwgR2UwgaWd7hgUdQkwFnK9vvEeO4RLTAIXgRPqwF1DhBf6Q1uZ5DrM\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using Key=CwgR2UwgaWd7hgUdQkwFnK9vvEeO4\n\t\t\t\t[DEBUG] Using ID=LTAIXgRPqwF1DhBf6Q1uZ5DrMYPW\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/alienvault/alienvault.go",
    "content": "package alienvault\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"alienvault\"}) + `\\b([a-z0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"alienvault\"}\n}\n\n// FromData will find and optionally verify AlienVault secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_AlienVault,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyAlienVaultKey(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AlienVault\n}\n\nfunc verifyAlienVaultKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://otx.alienvault.com/api/v1/users/me\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"X-OTX-API-KEY\", key)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/alienvault/alienvault_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage alienvault\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAlienVault_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ALIENVAULT\")\n\tinactiveSecret := testSecrets.MustGetField(\"ALIENVAULT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a alienvault secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AlienVault,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AlienVault,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AlienVault.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"AlienVault.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/alienvault/alienvault_test.go",
    "content": "package alienvault\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAlienVault_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the alienvault API\n\t\t\t\t[DEBUG] Using Key=3em7p52ec9ut4k9ccqha19rz3oyeqnij3mn3ivml577f8pb2179yz9totr648hmy\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"3em7p52ec9ut4k9ccqha19rz3oyeqnij3mn3ivml577f8pb2179yz9totr648hmy\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{alienvault}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA xyi7bj56t5b0hkinw4vz8qgffqhfb2ypemdnt407bke6s0ouuswvcdf5c1qpvse0}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"xyi7bj56t5b0hkinw4vz8qgffqhfb2ypemdnt407bke6s0ouuswvcdf5c1qpvse0\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - key out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Fetching data from alienvault\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using Key=3em7p52ec9ut4k9ccqha19rz3oyeqnij3mn3ivml577f8pb2179yz9totr648hmy\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the alienvault API\n\t\t\t\t[DEBUG] Using Key=3em7p52ec9ut4k9ccqha19rz3o_eqnij3mn3ivml577f8pb2179yz9totr648hmy\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/allsports/allsports.go",
    "content": "package allsports\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"allsports\"}) + `\\b([0-9a-z]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"allsports\"}\n}\n\n// FromData will find and optionally verify Allsports secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Allsports,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyAllSportsKey(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAllSportsKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://apiv2.allsportsapi.com/football/?met=Countries&APIkey=\"+key, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tbody := string(bodyBytes)\n\t\tif strings.Contains(body, \"Wrong login credentials\") {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Allsports\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Allsports API keys can be used to access and interact with the Allsports API, allowing retrieval of sports data and other related operations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/allsports/allsports_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage allsports\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAllsports_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ALLSPORTS\")\n\tinactiveSecret := testSecrets.MustGetField(\"ALLSPORTS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a allsports secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Allsports,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Allsports,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Allsports.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Allsports.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/allsports/allsports_test.go",
    "content": "package allsports\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAllSports_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the allsports API\n\t\t\t\t[DEBUG] Using Key=cq73u5azj3p3shfvzz3lw1typfqu6uduq7bophtq4veta7cnvd4s5htkb8lgk4vr\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"cq73u5azj3p3shfvzz3lw1typfqu6uduq7bophtq4veta7cnvd4s5htkb8lgk4vr\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{allsports}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA bj8yzu3awie5akwiwcb7esqygqx14gt65j9lrcpec0v28ckkswtyza1x9747gap5}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"bj8yzu3awie5akwiwcb7esqygqx14gt65j9lrcpec0v28ckkswtyza1x9747gap5\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - key out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[DEBUG] allsports api processing\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using Key=cq73u5azj3p3shfvzz3lw1typfqu6uduq7bophtq4veta7cnvd4s5htkb8lgk4vr\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the allsports API\n\t\t\t\t[DEBUG] Using Key=d1f2e3c4b5a6d7e8f9G0h1i2j3k4l5m6n7o8p9q0r1s2t3u4v5w6x7y8z9a0b1ce\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/amadeus/amadeus.go",
    "content": "package amadeus\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"amadeus\"}) + `\\b([0-9A-Za-z]{32})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"amadeus\"}) + `\\b([0-9A-Za-z]{16})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"amadeus\"}\n}\n\n// FromData will find and optionally verify Amadeus secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys, uniqueSecrets = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor _, matches := range secretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueSecrets[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\tfor secret := range uniqueSecrets {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Amadeus,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tRawV2:        []byte(key + secret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, verificationErr := verifyAdobeIOSecret(ctx, client, key, secret)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAdobeIOSecret(ctx context.Context, client *http.Client, key string, secret string) (bool, error) {\n\tpayload := strings.NewReader(\"grant_type=client_credentials&client_id=\" + key + \"&client_secret=\" + secret)\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://test.api.amadeus.com/v1/security/oauth2/token\", payload)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tbody := string(bodyBytes)\n\t\tif !strings.Contains(body, \"access_token\") {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Amadeus\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Amadeus provides travel technology solutions. Amadeus API keys can be used to access and modify travel-related data and services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/amadeus/amadeus_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage amadeus\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAmadeus_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tid := testSecrets.MustGetField(\"AMADEUS\")\n\tsecret := testSecrets.MustGetField(\"AMADEUS_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"AMADEUS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a amadeus secret %s within amadeus id %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Amadeus,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Amadeus,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Amadeus.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Amadeus.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/amadeus/amadeus_test.go",
    "content": "package amadeus\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAmadeus_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using amadeus Key=ttdveNai3Gj6Zrjvgz4fyBEWRLARCG6a\n\t\t\t\t[DEBUG] Using amadeus Secret=9wqrSr2qveaqgQns\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"ttdveNai3Gj6Zrjvgz4fyBEWRLARCG6a9wqrSr2qveaqgQns\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{amadeus ey6U46qCx26dqzMVWAGiibt6m65mM5w9}</id>\n  \t\t\t\t\t<secret>{amadeus AQAAABAAA Ew3TfmLHYaRjPnYO}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"ey6U46qCx26dqzMVWAGiibt6m65mM5w9Ew3TfmLHYaRjPnYO\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - key out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the amadeus API\n\t\t\t\t[DEBUG] Using Key=ttdveNai3Gj6Zrjvgz4fyBEWRLARCG6a\n\t\t\t\t[DEBUG] Using Secret=9wqrSr2qveaqgQns\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the amadeus API\n\t\t\t\t[DEBUG] Using amadeus Key=tthdveNai3Gj6Zrjvgz4fyBEWRLARCG6a\n\t\t\t\t[DEBUG] Using amadeus Secret=9wqrSr2qveacqgQns\n\t\t\t\t[INFO] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ambee/ambee.go",
    "content": "package ambee\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ambee\"}) + `\\b([0-9a-f]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ambee\"}\n}\n\n// FromData will find and optionally verify Ambee secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Ambee,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyAmbeeKey(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAmbeeKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.ambeedata.com/latest/by-lat-lng?lat=12&lng=77\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"x-api-key\", key)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Ambee\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ambee/ambee_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ambee\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAmbee_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AMBEE\")\n\tinactiveSecret := testSecrets.MustGetField(\"AMBEE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ambee secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ambee,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ambee,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Ambee.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Ambee.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ambee/ambee_test.go",
    "content": "package ambee\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAmbee_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the ambee API\n\t\t\t\t[DEBUG] Using Key=eccb41cc2d4dab96b748ed040e9b308161279820447ef4553ba6e6d20ecb9962\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"eccb41cc2d4dab96b748ed040e9b308161279820447ef4553ba6e6d20ecb9962\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{ambee}</id>\n  \t\t\t\t\t<secret>{ambee AQAAABAAA b91280c63e1571ad928d52947cc31a14ad1bf5a83088d0346b94f6683cf22138}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"b91280c63e1571ad928d52947cc31a14ad1bf5a83088d0346b94f6683cf22138\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - key out of prefix range\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Fetching data from ambee\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using Key=eccb41cc2d4dab96b748ed040e9b308161279820447ef4553ba6e6d20ecb9962\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the ambee API\n\t\t\t\t[DEBUG] Using Key=eccb41cc2d4dab96y748ed040e9b308161279820447ef4553ba6e6d20ecb9962\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/amplitudeapikey/amplitudeapikey.go",
    "content": "package amplitudeapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"amplitude\"}) + `\\b([0-9a-f]{32})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"amplitude\"}) + `\\b([0-9a-f]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"amplitude\"}\n}\n\n// FromData will find and optionally verify AmplitudeApiKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys, uniqueSecrets = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor _, matches := range secretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueSecrets[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\tfor secret := range uniqueSecrets {\n\t\t\t// regex for both key and secret are same so the set of strings could possibly be same as well\n\t\t\tif key == secret {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AmplitudeApiKey,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tRawV2:        []byte(key + secret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, verificationErr := verifyAdobeIOSecret(ctx, client, key, secret)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAdobeIOSecret(ctx context.Context, client *http.Client, key string, secret string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://amplitude.com/api/2/taxonomy/category\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.SetBasicAuth(key, secret)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AmplitudeApiKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/amplitudeapikey/amplitudeapikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage amplitudeapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAmplitudeApiKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tkey := testSecrets.MustGetField(\"AMPLITUDEAPI_KEY\")\n\tsecret := testSecrets.MustGetField(\"AMPLITUDEAPI_SECRET\")\n\tinactiveKey := testSecrets.MustGetField(\"AMPLITUDEAPI_KEY_INACTIVE\")\n\tinactiveSecret := testSecrets.MustGetField(\"AMPLITUDEAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find an amplitude key %s with amplitude secret %s within\", key, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AmplitudeApiKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AmplitudeApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AmplitudeApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AmplitudeApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AmplitudeApiKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no rawv2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"AmplitudeApiKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/amplitudeapikey/amplitudeapikey_test.go",
    "content": "package amplitudeapikey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAmplitudeAPIKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using amplitude Key=c2167730016af34b89e200ecf55710e8\n\t\t\t\t[DEBUG] Using amplitude Secret=5488620aa9073c09f1a16e2b1dc357b6\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"c2167730016af34b89e200ecf55710e85488620aa9073c09f1a16e2b1dc357b6\",\n\t\t\t\t\"5488620aa9073c09f1a16e2b1dc357b6c2167730016af34b89e200ecf55710e8\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{amplitude aac639f65d80ec2eec96e775f598ce13}</id>\n  \t\t\t\t\t<secret>{amplitude AQAAABAAA 8ac62041353622f9c5e4657807ff1eac}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"aac639f65d80ec2eec96e775f598ce138ac62041353622f9c5e4657807ff1eac\",\n\t\t\t\t\"8ac62041353622f9c5e4657807ff1eacaac639f65d80ec2eec96e775f598ce13\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using amplitude Key=c2167730016rf34b89e200ecf55710e8\n\t\t\t\t[DEBUG] Using amplitude Secret=5488620aa9073q09f1a16e2b1dc357b6\n\t\t\t\t[INFO] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/anthropic/anthropic.go",
    "content": "package anthropic\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(sk-ant-(?:admin01|api03)-[\\w\\-]{93}AA)\\b`)\n\n\t// verification endpoints\n\tapiKeyEndpoint   = \"https://api.anthropic.com/v1/models\"\n\tadminKeyEndpoint = \"https://api.anthropic.com/v1/organizations/api_keys\"\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sk-ant-api03\", \"sk-ant-admin01\"}\n}\n\n// FromData will find and optionally verify Anthropic secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeys := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, key := range keys {\n\t\tkeyMatch := strings.TrimSpace(key[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Anthropic,\n\t\t\tRaw:          []byte(keyMatch),\n\t\t\tExtraData:    make(map[string]string),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisAdminKey := isAdminKey(keyMatch)\n\t\t\tvar isVerified bool\n\t\t\tvar err error\n\n\t\t\tif isAdminKey {\n\t\t\t\tisVerified, err = verifyAnthropicKey(ctx, client, adminKeyEndpoint, keyMatch)\n\t\t\t\ts1.ExtraData[\"Type\"] = \"Admin Key\"\n\t\t\t} else if !isAdminKey {\n\t\t\t\tisVerified, err = verifyAnthropicKey(ctx, client, apiKeyEndpoint, keyMatch)\n\t\t\t\ts1.ExtraData[\"Type\"] = \"API Key\"\n\t\t\t} else {\n\t\t\t\treturn nil, errors.New(\"unknown key type detected for anthropic\")\n\t\t\t}\n\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(err, keyMatch)\n\n\t\t\tif s1.Verified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\"key\": keyMatch,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\n/*\nverifyAnthropicKey verify the anthropic key passed against the endpoint\n\nEndpoints:\n\n  - For api keys: https://docs.anthropic.com/en/api/models-list\n\n  - For admin keys:  https://docs.anthropic.com/en/api/admin-api/apikeys/list-api-keys\n*/\nfunc verifyAnthropicKey(ctx context.Context, client *http.Client, endpoint, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\n\treq.Header.Set(\"x-api-key\", key)\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"anthropic-version\", \"2023-06-01\")\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\n\tcase http.StatusNotFound, http.StatusUnauthorized:\n\t\t// 404 is returned if api key is disabled or not found\n\t\treturn false, nil\n\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Anthropic\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Anthropic is an AI research company. The API keys can be used to access their AI models and services.\"\n}\n\nfunc isAdminKey(key string) bool {\n\treturn strings.HasPrefix(key, \"sk-ant-admin01\")\n}\n"
  },
  {
    "path": "pkg/detectors/anthropic/anthropic_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage anthropic\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAnthropic_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tapiKey := testSecrets.MustGetField(\"ANTHROPIC\")\n\tinactiveSecret := testSecrets.MustGetField(\"ANTHROPIC_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a anthropic secret %s within\", apiKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Anthropic,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Anthropic,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a anthropic secret %s within\", apiKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Anthropic,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Anthropic.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"AnalysisInfo\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Anthropic.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/anthropic/anthropic_test.go",
    "content": "package anthropic\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAnthropic_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tSystem Log - Authentication Token Issued\n\t\t\t\tDate: 2025-02-04 14:32:10 UTC\n\t\t\t\tServer: api-secure-03.internal\n\t\t\t\tService: Anthropic API Gateway\n\t\t\t\tAPI Key: sk-ant-api03-abc123xyz-456def789ghij-klmnopqrstuvwx-3456yza789bcde-1234fghijklmnopby56aaaogaopaaaabc123xyzAA\n\t\t\t\tAdmin Key: sk-ant-admin01-abc12fake-456def789ghij-klmnopqrstuvwx-3456yza789bcde-12fakehijklmnopby56aaaogaopaaaabc123xyzAA\n\n\t\t\t\tLog Entry:\n\t\t\t\tA 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.\n\t\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"sk-ant-api03-abc123xyz-456def789ghij-klmnopqrstuvwx-3456yza789bcde-1234fghijklmnopby56aaaogaopaaaabc123xyzAA\",\n\t\t\t\t\"sk-ant-admin01-abc12fake-456def789ghij-klmnopqrstuvwx-3456yza789bcde-12fakehijklmnopby56aaaogaopaaaabc123xyzAA\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{anthropic}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA sk-ant-api03-Dtjm9IZ_rYhS_ihHLZmPXhjJ6PN8UPp7vNO7qO3735RRDpf8xbWGinsch0McONXznUm-4KWoA7WU2otvvwHBR5QRjiLakAA}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"sk-ant-api03-Dtjm9IZ_rYhS_ihHLZmPXhjJ6PN8UPp7vNO7qO3735RRDpf8xbWGinsch0McONXznUm-4KWoA7WU2otvvwHBR5QRjiLakAA\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tSystem Log - Authentication Token Issued\n\t\t\t\tDate: 2025-02-04 14:32:10 UTC\n\t\t\t\tServer: api-secure-03.internal\n\t\t\t\tService: Anthropic API Gateway\n\t\t\t\tAPI Key: sk-ant-api03-abc123xyz-456de-klMnopqrstuvwx-3456yza789bcde-1234fghijklmnopAA\n\n\t\t\t\tLog Entry:\n\t\t\t\tA 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.\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/anypoint/anypoint.go",
    "content": "package anypoint\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\\b`)\n\torgPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"anypoint\"}\n}\n\n// FromData will find and optionally verify Anypoint secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys, uniquePats = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor _, matches := range orgPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniquePats[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\tfor org := range uniquePats {\n\t\t\t// regex for both key and org are same, so to avoid same string processing\n\t\t\tif key == org {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Anypoint,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tRawV2:        []byte(key + org),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, verificationErr := verifyAnypointSecret(ctx, client, key, org)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAnypointSecret(ctx context.Context, client *http.Client, key string, org string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"https://anypoint.mulesoft.com/apiplatform/repository/v2/organizations/%s/apis/by-name?apiName=%s\", org, \"\"), nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Anypoint\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/anypoint/anypoint_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage anypoint\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAnypoint_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ANYPOINT_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"ANYPOINT_INACTIVE\")\n\torganizationId := testSecrets.MustGetField(\"ANYPOINT_ORGANIZATIONID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find an anypoint secret %s within organization %s\", secret, organizationId)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Anypoint,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Anypoint,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Anypoint.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Anypoint.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/anypoint/anypoint_test.go",
    "content": "package anypoint\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAnypoint_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t# Anypoint Secret Configuration File\n\t\t\t\t# Organization details\n\t\t\t\tORG_NAME=my_organization\n\t\t\t\tORG_ID=abcd1234-ef56-gh78-ij90-klmn1234opqr\n\n\t\t\t\t# OAuth tokens\n\t\t\t\tACCESS_TOKEN=abcxyz123\n\t\t\t\tREFRESH_TOKEN=zyxwvutsrqponmlkji9876543210abcd\n\n\t\t\t\t# API keys\n\t\t\t\tSECRET_KEY=1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6\n\n\t\t\t\t# Endpoints\n\t\t\t\tSERVICE_URL=https://api.example.com/v1/resource\n\t\t\t\t`,\n\t\t\twant: []string{\"1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6abcd1234-ef56-gh78-ij90-klmn1234opqr\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{anypoint org rdogw4dd-6x3l-2nm3-jvl5-qi8dyheccgj7}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA 7jhlugw8-3tfb-7ju2-0i0y-7un6qxvknbvz}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"7jhlugw8-3tfb-7ju2-0i0y-7un6qxvknbvzrdogw4dd-6x3l-2nm3-jvl5-qi8dyheccgj7\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t# Anypoint Secret Configuration File\n\t\t\t\t# Organization details\n\t\t\t\tORG_NAME=my_organization\n\t\t\t\tORG_ID=abcd1234-ef56-gh78-ij90-klmn1234opqr\n\n\t\t\t\t# OAuth tokens\n\t\t\t\tACCESS_TOKEN=abcxyz123\n\t\t\t\tREFRESH_TOKEN=zyxwvutsrqponmlkji9876543210abcd\n\n\t\t\t\t# API keys\n\t\t\t\tSECRET_KEY=1a2b3C4d-5E6f-7g8H-9i0J-k1l2M3n4o5p6\n\n\t\t\t\t# Endpoints\n\t\t\t\tSERVICE_URL=https://api.example.com/v1/resource\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/anypointoauth2/anypointoauth2.go",
    "content": "package anypointoauth2\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tidPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"anypoint\", \"id\"}) + `\\b([0-9a-f]{32})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"anypoint\", \"secret\"}) + `\\b([0-9a-fA-F]{32})\\b`)\n\n\tverificationUrl = \"https://anypoint.mulesoft.com/accounts/oauth2/token\"\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"anypoint\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Anypoint secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueIDs, uniqueSecrets = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, matches := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueIDs[matches[1]] = struct{}{}\n\t}\n\n\tfor _, matches := range secretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueSecrets[matches[1]] = struct{}{}\n\t}\n\n\tfor id := range uniqueIDs {\n\t\tfor secret := range uniqueSecrets {\n\t\t\tif id == secret {\n\t\t\t\t// Avoid processing the same string for both id and secret.\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AnypointOAuth2,\n\t\t\t\tRaw:          []byte(secret),\n\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s\", id, secret)),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.getClient()\n\t\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, id, secret)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t\tif isVerified {\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\"id\":     id,\n\t\t\t\t\t\t\"secret\": secret,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\n\t\t\tif s1.Verified {\n\t\t\t\t// Anypoint client IDs and secrets are mapped one-to-one, so if a pair\n\t\t\t\t// is verified, we can remove that secret from the uniqueSecrets map.\n\t\t\t\tdelete(uniqueSecrets, secret)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, id, secret string) (bool, error) {\n\tpayload := strings.NewReader(`{\"grant_type\":\"client_credentials\",\"client_id\":\"` + id + `\",\"client_secret\":\"` + secret + `\"}`)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, verificationUrl, payload)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\t// The endpoint responds with status 200 for valid Organization credentials and 422 for Client credentials.\n\tcase http.StatusOK, http.StatusUnprocessableEntity:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AnypointOAuth2\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/anypointoauth2/anypointoauth2_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage anypointoauth2\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAnypointOAuth2_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tclientID := testSecrets.MustGetField(\"ANYPOINT_CLIENT_ID\")\n\tclientSecret := testSecrets.MustGetField(\"ANYPOINT_CLIENT_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"ANYPOINT_INACTIVE_SECRET\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find an anypoint secret %s within anypoint organization id %s\", clientSecret, clientID)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AnypointOAuth2,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AnypointOAuth2,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Anypoint.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"AnypointOAuth2.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/anypointoauth2/anypointoauth2_test.go",
    "content": "package anypointoauth2\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAnypoint_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t# Anypoint Secret Configuration File\n\t\t\t\t# Organization details\n\t\t\t\tORG_NAME=my_organization\n\t\t\t\tORG_ID=abcd1234-ef56-gh78-ij90-klmn1234opqr\n\n\t\t\t\t# OAuth tokens\n\t\t\t\tCLIENT_ID=e3cd10a87f53b2dfa4b5fd606e7d9eca\n\t\t\t\tCLIENT_SECRET=ACE9d7E606Df5B4AFD2B35f78A01DC3E\n\n\t\t\t\t# API keys\n\t\t\t\tSECRET_KEY=1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6\n\n\t\t\t\t# Endpoints\n\t\t\t\tSERVICE_URL=https://api.example.com/v1/resource\n\t\t\t\t`,\n\t\t\twant: []string{\"e3cd10a87f53b2dfa4b5fd606e7d9eca:ACE9d7E606Df5B4AFD2B35f78A01DC3E\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{anypoint id 17c55fba5c93de5646b10507c36fbc23}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA 8E6Ef8F8d5De05d8BF1491e1ecC37b31}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"17c55fba5c93de5646b10507c36fbc23:8E6Ef8F8d5De05d8BF1491e1ecC37b31\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t# Anypoint Secret Configuration File\n\t\t\t\t# Organization details\n\t\t\t\tORG_NAME=my_organization\n\t\t\t\tORG_ID=abcd1234-ef56-gh78-ij90-klmn1234opqr\n\n\t\t\t\t# OAuth tokens\n\t\t\t\tCLIENT_ID=k4lzc5ty98tnfu3a11y8gnv5vb1281as\n\t\t\t\tCLIENT_SECRET=ACE9d7E606Df5B4AFD2B35f78A01DC3E\n\n\t\t\t\t# API keys\n\t\t\t\tSECRET_KEY=1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6\n\n\t\t\t\t# Endpoints\n\t\t\t\tSERVICE_URL=https://api.example.com/v1/resource\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apacta/apacta.go",
    "content": "package apacta\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"apacta\"}) + `\\b([a-z0-9-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"apacta\"}\n}\n\n// FromData will find and optionally verify Apacta secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Apacta,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyApactaKey(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyApactaKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"https://app.apacta.com/api/v1/time_entries?api_key=%s\", key), nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Apacta\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/apacta/apacta_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage apacta\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestApacta_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"APACTA\")\n\tinactiveSecret := testSecrets.MustGetField(\"APACTA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a apacta secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Apacta,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Apacta,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Apacta.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Apacta.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apacta/apacta_test.go",
    "content": "package apacta\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestApacta_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"POST\", \"https://api.example.com/v1/resource\", bytes.NewBuffer([]byte(\"{}\")))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tapactaSecret := \"Bearer abcd1234-ef56-gh78-ij90-klmn1234opqr\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", apactaSecret)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"abcd1234-ef56-gh78-ij90-klmn1234opqr\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{apacta}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA w8-p59rc70q0unyupknadu5sr8bf5us04mpt}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"w8-p59rc70q0unyupknadu5sr8bf5us04mpt\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"POST\", \"https://api.example.com/v1/resource\", bytes.NewBuffer([]byte(\"{}\")))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tapactaSecret := \"Bearer abcD$1234-ef56-gH78-ij90-klmn1234opqr\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", apactaSecret)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/api2cart/api2cart.go",
    "content": "package api2cart\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"api2cart\"}) + `\\b([0-9a-f]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"api2cart\"}\n}\n\n// FromData will find and optionally verify Api2Cart secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Api2Cart,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyApi2CartKey(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyApi2CartKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"https://api.api2cart.com/v1.1/account.cart.list.json?api_key=%s\", key), nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Accept\", \"application/json\")\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tbody, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tvar result Response\n\t\tif err := json.Unmarshal(body, &result); err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tif result.ReturnCode == 0 {\n\t\t\treturn true, nil\n\t\t}\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n\treturn false, nil\n}\n\ntype Response struct {\n\tReturnCode int `json:\"return_code\"`\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Api2Cart\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/api2cart/api2cart_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage api2cart\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestApi2Cart_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"API2CART\")\n\tinactiveSecret := testSecrets.MustGetField(\"API2CART_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a api2cart secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Api2Cart,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Api2Cart,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Api2Cart.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Api2Cart.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/api2cart/api2cart_test.go",
    "content": "package api2cart\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestApi2Cart_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tTo integrate with API2Cart, ensure you have the following credentials in your configuration file.\n\t\t\t\tYour API2CART key is 2afddb813193eb9d3b5bd99bf5d834cd, which you will need to access the API securely. \n\n\t\t\t\tThe following endpoints are available for your use:\n\t\t\t\t- Get Products: https://api.api2cart.com/v1.0/products/get\n\t\t\t\t- Add Product: https://api.api2cart.com/v1.0/products/add\n\t\t\t\t`,\n\t\t\twant: []string{\"2afddb813193eb9d3b5bd99bf5d834cd\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{api2cart}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA b36c17e9dc0dba67480e864cf69879c3}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"b36c17e9dc0dba67480e864cf69879c3\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tTo integrate with API2Cart, ensure you have the following credentials in your configuration file.\n\t\t\t\tYour API2CART key is 68d746609J4240840734c22836725d76, which you will need to access the API securely. \n\n\t\t\t\tThe following endpoints are available for your use:\n\t\t\t\t- Get Products: https://api.api2cart.com/v1.0/products/get\n\t\t\t\t- Add Product: https://api.api2cart.com/v1.0/products/add\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apideck/apideck.go",
    "content": "package apideck\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(sk_live_[a-z0-9A-Z-]{93})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"apideck\"}) + `\\b([a-z0-9A-Z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"apideck\"}\n}\n\n// FromData will find and optionally verify ApiDeck secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys, uniqueIds = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor _, matches := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueIds[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\tfor id := range uniqueIds {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_ApiDeck,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tRawV2:        []byte(key + id),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, verificationErr := verifyAdobeIOSecret(ctx, client, key, id)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAdobeIOSecret(ctx context.Context, client *http.Client, key string, id string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://unify.apideck.com/vault/consumers\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"x-apideck-app-id\", id)\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ApiDeck\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ApiDeck is a platform that provides a unified API to connect multiple services. ApiDeck keys can be used to access and manage these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/apideck/apideck_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage apideck\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestApiDeck_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"APIDECK_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"APIDECK_INACTIVE\")\n\tid := testSecrets.MustGetField(\"APIDECK_APPID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a apideck secret %s within apideckid %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ApiDeck,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ApiDeck,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ApiDeck.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no rawv2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ApiDeck.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apideck/apideck_test.go",
    "content": "package apideck\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestApiDeck_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the apideck API\n\t\t\t\t[DEBUG] Using Key=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\n\t\t\t\t[DEBUG] Using apideck ID=VfKE9Zh2ZatnqmrloqDu3PCnkNBR6Io4TlSbsG1P\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"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\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{apideck id J6rYP2lzThxp9JeGg74TDgAXvfQsvzonsHpYHDsG}</id>\n  \t\t\t\t\t<secret>{apideck AQAAABAAA sk_live_R5S2B88smT6QfTsUc3o3DedI2hbbcnZwvQKjyudQ41V0T38L8qUDPUTlBDcVE2NwRp1PowPYqnmAHlZ-W1Yr7AWGvpCvT}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"sk_live_R5S2B88smT6QfTsUc3o3DedI2hbbcnZwvQKjyudQ41V0T38L8qUDPUTlBDcVE2NwRp1PowPYqnmAHlZ-W1Yr7AWGvpCvTJ6rYP2lzThxp9JeGg74TDgAXvfQsvzonsHpYHDsG\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the apideck API\n\t\t\t\t[DEBUG] Using Key=sk_live_GKE08ADdkDV1DQ4vDfaW4ejDHy-TkotfxDmHvQMLX0HRvhtfPwku6olGvsG2vXBg869A0hsOPHHOw48SAF2GO7jBMs6Rt\n\t\t\t\t[DEBUG] Using apideck ID=VfKE9Zh2ZatnqmrloqDu3PC_kNBR6Io4TlSbsG1P\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apiflash/apiflash.go",
    "content": "package apiflash\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"apiflash\"}) + `\\b([a-z0-9]{32})\\b`)\n\n\turlToCapture = \"http://google.com\" // a fix constant url to capture to verify the access key\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"apiflash\"}\n}\n\n// FromData will find and optionally verify Apiflash secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueAPIKeys := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueAPIKeys[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor key := range uniqueAPIKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Apiflash,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyAPIFlash(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, key)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Apiflash\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Apiflash is a screenshot API service. Apiflash keys can be used to access and utilize the screenshot API service.\"\n}\n\nfunc verifyAPIFlash(ctx context.Context, client *http.Client, accessKey string) (bool, error) {\n\treq, 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)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apiflash/apiflash_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage apiflash\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestApiflash_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"APIFLASH\")\n\turl := testSecrets.MustGetField(\"API_URL\")\n\tinactiveSecret := testSecrets.MustGetField(\"APIFLASH_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a apiflash secret %s within apiflash %s\", secret, url)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Apiflash,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Apiflash,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Apiflash.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Apiflash.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apiflash/apiflash_test.go",
    "content": "package apiflash\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestApiFlash_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the apiflash API\n\t\t\t\t[DEBUG] Using Key=grevetn5owrs1ybhxtcen0ibvg2mi85x\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"grevetn5owrs1ybhxtcen0ibvg2mi85x\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{apiflash}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA axlzvcf9m7jyyts833f9gmtcpqe5b26o}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"axlzvcf9m7jyyts833f9gmtcpqe5b26o\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the apiflash API\n\t\t\t\t[DEBUG] Using Key=grevetn5owRs1ybhxtcen0ibvg2mi85x\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apifonica/apifonica.go",
    "content": "package apifonica\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"apifonica\"}\n}\n\n// FromData will find and optionally verify Apifonica secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys, uniqueTokens = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokens[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\tfor token := range uniqueTokens {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_ApiFonica,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, verificationErr := verifyApifonicaSecret(ctx, client, key, token)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyApifonicaSecret(ctx context.Context, client *http.Client, key string, token string) (bool, error) {\n\tdata := fmt.Sprintf(\"%s:%s\", key, token)\n\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.apifonica.com/v2/accounts\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ApiFonica\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/apifonica/apifonica_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage apifonica\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestApifonica_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"APIFONICA_SECRET\")\n\ttoken := testSecrets.MustGetField(\"APIFONICA_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"APIFONICA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a apifonica secret %s within apifonica token %s\", secret, token)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ApiFonica,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ApiFonica,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Apifonica.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Apifonica.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apifonica/apifonica_test.go",
    "content": "package apifonica\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestApiFonica_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the apifonica API\n\t\t\t\t[DEBUG] Using Key=4rv0hdx5188-3q48-2luk-e8v5-dyuuf8l44ib7\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"4rv0hdx5188-3q48-2luk-e8v5-dyuuf8l44ib7\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{apifonica}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA fvzlzj17xzz-lwon-842u-46bs-5spcl2g7u9eb}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"fvzlzj17xzz-lwon-842u-46bs-5spcl2g7u9eb\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the apifonica API\n\t\t\t\t[DEBUG] Using Key=4rv0hdx51889-3q48-2luk-e8wv5-dyuuf8l44ib7\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apify/apify.go",
    "content": "package apify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(apify\\_api\\_[a-zA-Z-0-9]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"apify\"}\n}\n\n// FromData will find and optionally verify Apify secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Apify,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyApifyKey(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyApifyKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.apify.com/v2/acts?token=\"+key+\"&my=true&offset=10&limit=99&desc=true\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Apify\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Apify is a platform for web scraping and automation. Apify API keys can be used to access and control Apify actors and tasks.\"\n}\n"
  },
  {
    "path": "pkg/detectors/apify/apify_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage apify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestApify_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"APIFY\")\n\tinactiveSecret := testSecrets.MustGetField(\"APIFY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a apify secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Apify,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Apify,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Apify.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Apify.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apify/apify_test.go",
    "content": "package apify\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestApiFy_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using Key=apify_api_dXB1vLsglgTexUYm3JTAx2BHTjVuDBbvPl8R\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"apify_api_dXB1vLsglgTexUYm3JTAx2BHTjVuDBbvPl8R\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{user-id}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA apify_api_RpTLEX9U18xfGl90wDaT2V9R-YX0TlMpxIzi}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"apify_api_RpTLEX9U18xfGl90wDaT2V9R-YX0TlMpxIzi\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using Key=apify_api_dXB1vLPglgTex_UYm3JTAx2BHTjVuDBbvPl8R\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apilayer/apilayer.go",
    "content": "package apilayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"apilayer\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"apilayer\"}\n}\n\n// FromData will find and optionally verify Apilayer secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Apilayer,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyAPILayerKey(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAPILayerKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.apilayer.com/number_verification/countries\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"apikey\", key)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Apilayer\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/apilayer/apilayer_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage apilayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestApilayer_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"APILAYER\")\n\tinactiveSecret := testSecrets.MustGetField(\"APILAYER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a apilayer secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Apilayer,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Apilayer,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Apilayer.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Apilayer.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apilayer/apilayer_test.go",
    "content": "package apilayer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestApiLayer_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the apilayer API\n\t\t\t\t[DEBUG] Using Key=qnHT110fihCn49wOm5b2h3ACTRmksbg0\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"qnHT110fihCn49wOm5b2h3ACTRmksbg0\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{apilayer}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA HHTi3DYZIqt57j5WVHvXHHboXpCnm6CW}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"HHTi3DYZIqt57j5WVHvXHHboXpCnm6CW\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the apilayer API\n\t\t\t\t[DEBUG] Using Key=qnHT110fiha-Cn49wOm5b2h3ACTRmksbg0\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apimatic/apimatic.go",
    "content": "package apimatic\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tapiKeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"apimatic\", \"apikey\"}) + `\\b([a-zA-Z0-9_-]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"apimatic\"}\n}\n\n// FromData will find and optionally verify APIMatic secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueApiKeys := make(map[string]struct{})\n\tfor _, matches := range apiKeyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueApiKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor apiKey := range uniqueApiKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_APIMatic,\n\t\t\tRaw:          []byte(apiKey),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyAPImaticKey(ctx, client, apiKey)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\n\t}\n\treturn results, nil\n}\n\nfunc verifyAPImaticKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\ttimeout := 10 * time.Second\n\tclient.Timeout = timeout\n\n\t// api docs: https://docs.apimatic.io/platform-api/#/http/api-endpoints/code-generation-external-apis/list-all-code-generations\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.apimatic.io/code-generations\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// authentication documentation: https://docs.apimatic.io/platform-api/#/http/guides/authentication\n\treq.Header.Set(\"Authorization\", \"X-Auth-Key \"+key)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_APIMatic\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"APIMatic provides tools for generating SDKs, API documentation, and code snippets. APIMatic credentials can be used to access and manage these tools and services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/apimatic/apimatic_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage apimatic\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAPIMatic_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"APIMATIC\")\n\tpass := testSecrets.MustGetField(\"APIMATIC_PASS\")\n\tinactiveSecret := testSecrets.MustGetField(\"APIMATIC_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a apimatic secret %s within apimatic pass %s\", secret, pass)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_APIMatic,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_APIMatic,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"APIMatic.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"APIMatic.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apimatic/apimatic_test.go",
    "content": "package apimatic\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestApiMatic_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc validateApiMatic() bool {\n\t\t\t\t\tapiMaticKey := \"rc6iLoUEFGGAWNLsuBJnmsh4tZB-oCxcDUmc45HIPcuiQvfUEuqo8wb9YrUd2LyB\"\n\n\t\t\t\t\t// isActive check if the key is active or not\n\t\t\t\t\treturn isActive(apiMaticKey)\n\t\t\t\t}`,\n\t\t\twant: []string{\"rc6iLoUEFGGAWNLsuBJnmsh4tZB-oCxcDUmc45HIPcuiQvfUEuqo8wb9YrUd2LyB\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{apimatic}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA 2eqQBh9HkE-5Mq5Ma_vOEvvyt-x9shcZ-T5B7hSY1C5xvTl7qLMwGL6QAoNYmMcF}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"2eqQBh9HkE-5Mq5Ma_vOEvvyt-x9shcZ-T5B7hSY1C5xvTl7qLMwGL6QAoNYmMcF\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc validateApiMatic() bool {\n\t\t\t\t\tapiMaticKey := \"rc6iLoUEFGGAWNLsuBJnmsh4tZB@oCxcDUmc45HIPcuiQvfUEuqo8wb9YrUd2LyB\"\n\n\t\t\t\t\t// isActive check if the key is active or not\n\t\t\t\t\treturn isActive(apiMaticKey)\n\t\t\t\t}`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apimetrics/apimetrics.go",
    "content": "package apimetrics\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"apimetrics\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"apimetrics\"}\n}\n\n// FromData will find and optionally verify ApiMetrics secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ApiMetrics,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyAPIMetricsKey(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAPIMetricsKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://client.apimetrics.io/api/2/calls/\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ApiMetrics\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ApiMetrics is a tool for monitoring the performance of APIs. ApiMetrics keys can be used to access and manage API monitors.\"\n}\n"
  },
  {
    "path": "pkg/detectors/apimetrics/apimetrics_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage apimetrics\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestApiMetrics_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"APIMETRICS\")\n\tinactiveSecret := testSecrets.MustGetField(\"APIMETRICS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a apimetrics secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ApiMetrics,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ApiMetrics,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ApiMetrics.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ApiMetrics.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apimetrics/apimetrics_test.go",
    "content": "package apimetrics\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestApiMetrics_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc validateApiMetrics() bool {\n\t\t\t\t\tapiMetrics := \"5po8TFGawiYNCc1ct4ofWkBqzIfA6IeO\"\n\n\t\t\t\t\t// isActive check if the key is active or not\n\t\t\t\t\treturn isActive(apiMetrics)\n\t\t\t\t}`,\n\t\t\twant: []string{\"5po8TFGawiYNCc1ct4ofWkBqzIfA6IeO\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{apimetrics}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA XpLTBFZccOgbbtVht4OaZzsrgKdh42RX}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"XpLTBFZccOgbbtVht4OaZzsrgKdh42RX\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc validateApiMetrics() bool {\n\t\t\t\t\tapiMetrics := \"5po8TFGawiYNCc1c4ofWkBqzIfA6IeO\"\n\n\t\t\t\t\t// isActive check if the key is active or not\n\t\t\t\t\treturn isActive(apiMetrics)\n\t\t\t\t}`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apitemplate/apitemplate.go",
    "content": "package apitemplate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"apitemplate\"}) + `\\b([0-9a-zA-Z]{39})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"apitemplate\"}\n}\n\n// FromData will find and optionally verify APITemplate secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_APITemplate,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyAPITemplateKey(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAPITemplateKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.apitemplate.io/v1/list-templates\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"X-API-KEY\", key)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_APITemplate\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/apitemplate/apitemplate_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage apitemplate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAPITemplate_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"APITEMPLATE\")\n\tinactiveSecret := testSecrets.MustGetField(\"APITEMPLATE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a apitemplate secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_APITemplate,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_APITemplate,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"APITemplate.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"APITemplate.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apitemplate/apitemplate_test.go",
    "content": "package apitemplate\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestApiTemplate_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc validateKey() bool {\n\t\t\t\t\tapiTemplate := \"EeOPHL7PyBlUk0qkJX72sDtdNL3WLdpxg1czllR\"\n\n\t\t\t\t\t// isActive check if the key is active or not\n\t\t\t\t\treturn isActive(apiTemplate)\n\t\t\t\t}`,\n\t\t\twant: []string{\"EeOPHL7PyBlUk0qkJX72sDtdNL3WLdpxg1czllR\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{apitemplate}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA oVqX8yfzlUtzudNnvlWKNI4pNKTKTwlaKmxlcX5}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"oVqX8yfzlUtzudNnvlWKNI4pNKTKTwlaKmxlcX5\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc validateKey() bool {\n\t\t\t\t\tapiTemplate := \"EeOPHL7PyBlUk0qkJAX72sDtdNL3WLdpxg1czllR\"\n\n\t\t\t\t\t// isActive check if the key is active or not\n\t\t\t\t\treturn isActive(apiTemplate)\n\t\t\t\t}`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apollo/apollo.go",
    "content": "package apollo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"apollo\"}) + `\\b([a-zA-Z0-9]{22})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"apollo\"}\n}\n\n// FromData will find and optionally verify Apollo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Apollo,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyApolloKey(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyApolloKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.apollo.io/api/v1/mixed_people/search\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"x-api-key\", key)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Apollo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Apollo is a sales intelligence platform. Apollo API keys can be used to access and modify data within the Apollo platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/apollo/apollo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage apollo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestApollo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"APOLLO\")\n\tinactiveSecret := testSecrets.MustGetField(\"APOLLO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a apollo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Apollo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Apollo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Apollo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Apollo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apollo/apollo_test.go",
    "content": "package apollo\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestApollo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc validateApolloKey() bool {\n\t\t\t\t\tapiKey := \"897TJ1HevanW9Ye6nv6Ojj\"\n\t\t\t\t\tlog.Println(\"Checking API key status...\")\n\n\t\t\t\t\tif !isActive(apiKey) {\n\t\t\t\t\t\tlog.Println(\"API key is inactive or invalid.\")\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\n\t\t\t\t\tlog.Println(\"API key is valid and active.\")\n\t\t\t\t\treturn true\n\t\t\t\t}`,\n\t\t\twant: []string{\"897TJ1HevanW9Ye6nv6Ojj\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{apollo}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA S2wg2NMlgalg9AUsrXPd1O}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"S2wg2NMlgalg9AUsrXPd1O\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc validateApolloKey() bool {\n\t\t\t\t\tapiKey := \"897TJ1HevanW9Ye-nv6Ojj\"\n\t\t\t\t\tlog.Println(\"Checking API key status...\")\n\n\t\t\t\t\tif !isActive(apiKey) {\n\t\t\t\t\t\tlog.Println(\"API key is inactive or invalid.\")\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\n\t\t\t\t\tlog.Println(\"API key is valid and active.\")\n\t\t\t\t\treturn true\n\t\t\t\t}`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/appcues/appcues.go",
    "content": "package appcues\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"appcues\"}) + `\\b([a-z0-9-]{36})\\b`)\n\tuserPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"appcues\"}) + `\\b([a-z0-9-]{39})\\b`)\n\tidPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"appcues\"}) + `\\b([0-9]{5})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"appcues\"}\n}\n\n// FromData will find and optionally verify Appcues secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tuserMatches := userPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, userMatch := range userMatches {\n\n\t\t\tresUserMatch := strings.TrimSpace(userMatch[1])\n\n\t\t\tfor _, idMatch := range idMatches {\n\n\t\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Appcues,\n\t\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\t\tRawV2:        []byte(resMatch + resUserMatch),\n\t\t\t\t}\n\t\t\t\tif verify {\n\t\t\t\t\tisVerified, err := verifyMatch(ctx, client, resUserMatch, resMatch, resIdMatch)\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\ts1.SetVerificationError(err, resUserMatch, resMatch, resIdMatch)\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, resUserMatch, resMatch, resIdMatch string) (bool, error) {\n\t// Reference: https://api.appcues.com/v2/docs?_gl=1#responses\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"https://api.appcues.com/v2/accounts/%s/flows\", resIdMatch), http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.SetBasicAuth(resUserMatch, resMatch)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden, http.StatusBadRequest:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Appcues\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/appcues/appcues_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage appcues\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAppcues_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"APPCUES\")\n\tuser := testSecrets.MustGetField(\"APPCUES_USER\")\n\tid := testSecrets.MustGetField(\"APPCUES_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"APPCUES_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a appcues secret %s within appcues user %s and appcues id %s\", secret, user, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Appcues,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Appcues,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Appcues,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Appcues,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Appcues.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no rawv2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Appcues.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/appcues/appcues_test.go",
    "content": "package appcues\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAppCues_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the appcues API\n\t\t\t\t[DEBUG] Using appcues Key=5g5n4yazu-dpqp3g6qt3gn59wrxhqf2mqipm\n\t\t\t\t[DEBUG] Using appcues User=truffle-security-lrv10a8l4u23xp5gkvg819\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t\t[INFO] APPCUES_ID=57843\n\t\t\t`,\n\t\t\twant: []string{\"5g5n4yazu-dpqp3g6qt3gn59wrxhqf2mqipmtruffle-security-lrv10a8l4u23xp5gkvg819\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{appcues 91712}</id>\n\t\t\t\t\t<username>{appcues ubdcpht45hlfdywxv89ympnvtcnydl3uv-0umfu}</username>\n  \t\t\t\t\t<secret>{appcues AQAAABAAA w9hyyfghqirj8uwcmtv05-n4fppzl-in223u}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"w9hyyfghqirj8uwcmtv05-n4fppzl-in223uubdcpht45hlfdywxv89ympnvtcnydl3uv-0umfu\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the appcues API\n\t\t\t\t[DEBUG] Using appcues Key=5g5n4yazu-dpqp3g6qt3gn59wrxhqf2mqipm\n\t\t\t\t[DEBUG] Using appcues User=truffle_security-lrv10a8l4u23xp5gkvg819\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t\t[INFO] ID=57843\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/appfollow/appfollow.go",
    "content": "package appfollow\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"appfollow\"}) + `\\b(eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9\\.[0-9A-Za-z]{74}\\.[0-9A-Z-a-z\\-_]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"appfollow\"}\n}\n\n// FromData will find and optionally verify Appfollow secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Appfollow,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, err := verifyMatch(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\t// Reference: https://docs.api.appfollow.io/reference/users_list_api_v2_account_users_get-1\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.appfollow.io/api/v2/account/users\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"X-AppFollow-API-Token\", token)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\tswitch res.StatusCode {\n\tcase http.StatusOK, http.StatusPaymentRequired, http.StatusUnprocessableEntity:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Appfollow\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Appfollow is a service used for app monitoring and analytics. Appfollow API tokens can be used to access and manage app data and analytics.\"\n}\n"
  },
  {
    "path": "pkg/detectors/appfollow/appfollow_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage appfollow\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAppfollow_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"APPFOLLOW\")\n\tinactiveSecret := testSecrets.MustGetField(\"APPFOLLOW_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a appfollow secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Appfollow,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Appfollow,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Appfollow.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Appfollow.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/appfollow/appfollow_test.go",
    "content": "package appfollow\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAppFollow_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc validateAppFollowKey() bool {\n\t\t\t\t\tkey := \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.hdMLjiIayyb5cgbcVtjKywQwqeNKnsxZEhnJnX6wzhnblpmpjF4c2mbdmVVylTayE6M8ZE3h4V.fmnUM4cjvbe1JMFDuBSwWNEYQFHrD5AEm6p2Ir9w7K6\"\n\n\t\t\t\t\t// isActive check if the key is active or not\n\t\t\t\t\treturn isActive(key)\n\t\t\t\t}`,\n\t\t\twant: []string{\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.hdMLjiIayyb5cgbcVtjKywQwqeNKnsxZEhnJnX6wzhnblpmpjF4c2mbdmVVylTayE6M8ZE3h4V.fmnUM4cjvbe1JMFDuBSwWNEYQFHrD5AEm6p2Ir9w7K6\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{appfollow}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.YwK6gJ8sMVylaDNuXRiGFLRR1kgZaLF45EbJ0qHSRaW4CRtWaqWciTZZXxkk4a4wLh8f7cTTlb.wvTVCRC1RLCpd98q4WK3ef6M3TBrb08AkS9-jNOdA_r}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.YwK6gJ8sMVylaDNuXRiGFLRR1kgZaLF45EbJ0qHSRaW4CRtWaqWciTZZXxkk4a4wLh8f7cTTlb.wvTVCRC1RLCpd98q4WK3ef6M3TBrb08AkS9-jNOdA_r\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc validateAppFollowKey() bool {\n\t\t\t\t\tapiKey := \"eyJ0eXAiOiJKV1QiLCJhbGCiOiJIUzI1NiJ9.hdMLjiIayyb5cgbcVtjKywQwqeNKnsxZEhnJnX6wzhnblpmpjF4c2mbdVylTayE6M8ZE3h4V.fmnUM4cjvbe1JMFDuBSwWNEYQFHrDEm6p2Ir9w7K6\"\n\t\t\t\t\tlog.Println(\"Checking API key status...\")\n\n\t\t\t\t\tif !isActive(apiKey) {\n\t\t\t\t\t\tlog.Println(\"API key is inactive or invalid.\")\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\n\t\t\t\t\tlog.Println(\"API key is valid and active.\")\n\t\t\t\t\treturn true\n\t\t\t\t}`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/appointedd/appointedd.go",
    "content": "package appointedd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"appointedd\"}) + `\\b([a-zA-Z0-9=+]{88})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"appointedd\"}\n}\n\n// FromData will find and optionally verify appointedd secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Appointedd,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\t\tif verify {\n\t\t\tisVerified, err := verifyMatch(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, secret string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.appointedd.com/v1/availability/slots\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"X-API-KEY\", secret)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\treturn strings.Contains(string(bodyBytes), \"total\"), nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Appointedd\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Appointedd provides online booking and scheduling services. The API key can be used to access and manage booking data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/appointedd/appointedd_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage appointedd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAppointedd_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"APPOINTEDD\")\n\tinactiveSecret := testSecrets.MustGetField(\"APPOINTEDD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a appointedd secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Appointedd,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Appointedd,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Appointedd.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Appointedd.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/appointedd/appointedd_test.go",
    "content": "package appointedd\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAppFollow_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc validateAppointeddKey() bool {\n\t\t\t\t\tappointeddKey := \"Ci0a2bSpRyFcZyEXBEr9RHzf3xXllqO=XVoh+t0L0s8T2s3MFntfWhBlovqLaqEadtuJ9=Jy6yCOXmhbpEZPfY7Y\"\n\t\t\t\t\tlog.Println(\"Checking API key status...\")\n\n\t\t\t\t\tif !isActive(appointeddKey) {\n\t\t\t\t\t\tlog.Println(\"API key is inactive or invalid.\")\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\n\t\t\t\t\tlog.Println(\"API key is valid and active.\")\n\t\t\t\t\treturn true\n\t\t\t\t}`,\n\t\t\twant: []string{\"Ci0a2bSpRyFcZyEXBEr9RHzf3xXllqO=XVoh+t0L0s8T2s3MFntfWhBlovqLaqEadtuJ9=Jy6yCOXmhbpEZPfY7Y\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{appointedd}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA 2pRMKW=JrG9+xYmqlJMa4Omf9goqsSqsM3mIaqG8tG4lwnVrKIslbn=IpLIz7GTDEJUcQ0wlr6B+UjfvSY9XKXwu}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"2pRMKW=JrG9+xYmqlJMa4Omf9goqsSqsM3mIaqG8tG4lwnVrKIslbn=IpLIz7GTDEJUcQ0wlr6B+UjfvSY9XKXwu\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc validateAppointeddKey() bool {\n\t\t\t\t\tappointeddKey := \"Ci0a2bSpRyFcZyEXBEr9RHzf3xXllqO-XVoh+t0L0s8T2s3MFntfWhBlovqLaqEadtuJ9-Jy6yCOXmhbpEZPfY7Y\"\n\t\t\t\t\tlog.Println(\"Checking API key status...\")\n\n\t\t\t\t\tif !isActive(appointeddKey) {\n\t\t\t\t\t\tlog.Println(\"API key is inactive or invalid.\")\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\n\t\t\t\t\tlog.Println(\"API key is valid and active.\")\n\t\t\t\t\treturn true\n\t\t\t\t}`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/appoptics/appoptics.go",
    "content": "package appoptics\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"appoptics\"}) + `\\b([0-9a-zA-Z_-]{71})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"appoptics\"}\n}\n\n// FromData will find and optionally verify Appoptics secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_AppOptics,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.appoptics.com/v1/metrics\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdata := fmt.Sprintf(\"%s:\", resMatch)\n\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AppOptics\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"AppOptics is a cloud-based infrastructure monitoring service. AppOptics API keys can be used to access and manage monitoring data and configurations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/appoptics/appoptics_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage appoptics\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAppoptics_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"APPOPTICS\")\n\tinactiveSecret := testSecrets.MustGetField(\"APPOPTICS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a appoptics secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AppOptics,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AppOptics,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Appoptics.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Appoptics.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/appoptics/appoptics_test.go",
    "content": "package appoptics\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAppOptics_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc validateAppOpticsKey() bool {\n\t\t\t\t\tappopticsKey := \"Xwl4ViaAFDLrAmFX9g1blkUVC5dJj2he3a1tzkpJ4-PznQukQruRjqMFbEG73L92LJyBGMZ\"\n\t\t\t\t\tlog.Println(\"Checking API key status...\")\n\n\t\t\t\t\tif !isActive(appopticsKey) {\n\t\t\t\t\t\tlog.Println(\"API key is inactive or invalid.\")\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\n\t\t\t\t\tlog.Println(\"API key is valid and active.\")\n\t\t\t\t\treturn true\n\t\t\t\t}`,\n\t\t\twant: []string{\"Xwl4ViaAFDLrAmFX9g1blkUVC5dJj2he3a1tzkpJ4-PznQukQruRjqMFbEG73L92LJyBGMZ\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{appoptics}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA zxsb8yzT0RbIJ1TAalB87LOVUcT1b4uEgvT4tXCcSqv_gcmlrx5aQRleHPDFKePjpHFof5J}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"zxsb8yzT0RbIJ1TAalB87LOVUcT1b4uEgvT4tXCcSqv_gcmlrx5aQRleHPDFKePjpHFof5J\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc validateAppOpticsKey() bool {\n\t\t\t\t\tappopticsKey := \"Xwl4ViaAFDLrAmFX9g1blkUVC5dJj2h:3a1tzkpJ43PznQukQruRjqMFbEG73L92LJyBGMZ\"\n\t\t\t\t\tlog.Println(\"Checking API key status...\")\n\n\t\t\t\t\tif !isActive(appopticsKey) {\n\t\t\t\t\t\tlog.Println(\"API key is inactive or invalid.\")\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\n\t\t\t\t\tlog.Println(\"API key is valid and active.\")\n\t\t\t\t\treturn true\n\t\t\t\t}`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/appsynergy/appsynergy.go",
    "content": "package appsynergy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"appsynergy\"}) + `\\b([a-z0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"appsynergy\"}\n}\n\n// FromData will find and optionally verify AppSynergy secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_AppSynergy,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, err := verifyMatch(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, secret string) (bool, error) {\n\tpayload := strings.NewReader(`{\"html\":\"<html><body><h1>Hello World</h1></body></html>\",\"filename\":\"HelloWorld.pdf\"}`)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://www.appsynergy.com/api?action=HTML2PDF&apiKey=\"+secret, payload)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tcase http.StatusBadRequest:\n\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tbody := string(bodyBytes)\n\t\tif strings.Contains(body, \"Invalid API Key\") {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, fmt.Errorf(\"status bad request invalid api key message not found: %d\", res.StatusCode)\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AppSynergy\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"AppSynergy is a platform for building cloud applications. AppSynergy API keys can be used to access and manage applications and data within the platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/appsynergy/appsynergy_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage appsynergy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAppSynergy_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"APPSYNERGY\")\n\tinactiveSecret := testSecrets.MustGetField(\"APPSYNERGY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a appsynergy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AppSynergy,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AppSynergy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AppSynergy.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"AppSynergy.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/appsynergy/appsynergy_test.go",
    "content": "package appsynergy\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAppSynergy_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc validateAppSynergyKey() bool {\n\t\t\t\t\tappSyneregyKey := \"mg1pgwlndtq7rbk8i3kum344aso8ggp02ximdhsp8nsqasd3btxf84lz9ekfdpwo\"\n\t\t\t\t\tlog.Println(\"Checking API key status...\")\n\n\t\t\t\t\tif !isActive(appSyneregyKey) {\n\t\t\t\t\t\tlog.Println(\"API key is inactive or invalid.\")\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\n\t\t\t\t\tlog.Println(\"API key is valid and active.\")\n\t\t\t\t\treturn true\n\t\t\t\t}`,\n\t\t\twant: []string{\"mg1pgwlndtq7rbk8i3kum344aso8ggp02ximdhsp8nsqasd3btxf84lz9ekfdpwo\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{appsynergy}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA ri1vn9m2otlg3yi8wwjegltc1t3bi4ljogg6c80onnrox2t9fuim6tce430fhklz}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"ri1vn9m2otlg3yi8wwjegltc1t3bi4ljogg6c80onnrox2t9fuim6tce430fhklz\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc validateAppSynergyKey() bool {\n\t\t\t\t\tappSyneregyKey := \"mg1pgwlndtq7rbk8i3kum_44aso8ggp02ximdhsp8nsqasd3btxf84lz9ekfdpwo\"\n\t\t\t\t\tlog.Println(\"Checking API key status...\")\n\n\t\t\t\t\tif !isActive(appSyneregyKey) {\n\t\t\t\t\t\tlog.Println(\"API key is inactive or invalid.\")\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\n\t\t\t\t\tlog.Println(\"API key is valid and active.\")\n\t\t\t\t\treturn true\n\t\t\t\t}`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apptivo/apptivo.go",
    "content": "package apptivo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"apptivo\"}) + `\\b([a-z0-9-]{36})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"apptivo\"}) + `\\b([a-zA-Z0-9-]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"apptivo\"}\n}\n\n// FromData will find and optionally verify Apptivo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, idMatch := range idMatches {\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Apptivo,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resIdMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, err := verifyMatch(ctx, client, resMatch, resIdMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(err, resMatch, resIdMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, apiKey, accessKey string) (bool, error) {\n\treq, 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)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\treturn strings.Contains(string(bodyBytes), `displayName`), nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Apptivo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/apptivo/apptivo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage apptivo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestApptivo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"APPTIVO\")\n\tid := testSecrets.MustGetField(\"APPTIVO_KEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"APPTIVO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a apptivo secret %s within apptivo %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Apptivo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Apptivo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Apptivo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Apptivo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/apptivo/apptivo_test.go",
    "content": "package apptivo\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestApptivo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the apptivo API\n\t\t\t\t[DEBUG] Using apptivo Key=fox94at7-8dj92ns-cdxhag4470yqp0o2c8y\n\t\t\t\t[DEBUG] Using apptivo ID=C27YfQFKcUue8OxfEiAcqzrPVII-pb3V\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"fox94at7-8dj92ns-cdxhag4470yqp0o2c8yC27YfQFKcUue8OxfEiAcqzrPVII-pb3V\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{apptivo o9qB77Q9cCXfuV-TWyCWUumiAbZc2Z7i}</id>\n  \t\t\t\t\t<secret>{apptivo AQAAABAAA juqc5-sw846p0cj43wy8eex6rr4v8-9oa3dh}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"juqc5-sw846p0cj43wy8eex6rr4v8-9oa3dho9qB77Q9cCXfuV-TWyCWUumiAbZc2Z7i\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the apptivo API\n\t\t\t\t[DEBUG] Using apptivo Key=fOx94aT7-8dj92ns-cdxhag4470yqp0o2c8y\n\t\t\t\t[DEBUG] Using apptivo ID=C27YfQF-cUue8OxfEiAcqzrPVII-pb3V\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/artifactory/artifactory.go",
    "content": "package artifactory\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n\tdetectors.EndpointSetter\n}\n\nvar (\n\t// Ensure the Scanner satisfies the interface at compile time.\n\t_ detectors.Detector           = (*Scanner)(nil)\n\t_ detectors.EndpointCustomizer = (*Scanner)(nil)\n\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(AKCp[a-zA-Z0-9]{69})\\b`)\n\tURLPat = regexp.MustCompile(`\\b([A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9]\\.jfrog\\.io)`)\n\n\tinvalidHosts = simple.NewCache[struct{}]()\n\n\terrNoHost = errors.New(\"no such host\")\n)\n\nfunc (Scanner) CloudEndpoint() string { return \"\" }\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"artifactory\", \"jfrog.io\", \"AKCp\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Artifactory secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueTokens, uniqueUrls = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokens[match[1]] = struct{}{}\n\t}\n\n\tvar foundUrls = make([]string, 0)\n\n\tfor _, match := range URLPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tfoundUrls = append(foundUrls, match[1])\n\t}\n\n\t// add found + configured endpoints to the list\n\tfor _, endpoint := range s.Endpoints(foundUrls...) {\n\t\t// if any configured endpoint has `https://` remove it because we append that during verification\n\t\tendpoint = strings.TrimPrefix(endpoint, \"https://\")\n\t\tuniqueUrls[endpoint] = struct{}{}\n\t}\n\n\tfor token := range uniqueTokens {\n\t\tfor url := range uniqueUrls {\n\t\t\tif invalidHosts.Exists(url) {\n\t\t\t\tdelete(uniqueUrls, url)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_ArtifactoryAccessToken,\n\t\t\t\tRaw:          []byte(token),\n\t\t\t\tRawV2:        []byte(token + url),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, verificationErr := verifyArtifactory(ctx, s.getClient(), url, token)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\tif verificationErr != nil {\n\t\t\t\t\tif errors.Is(verificationErr, errNoHost) {\n\t\t\t\t\t\tinvalidHosts.Set(url, struct{}{})\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\ts1.SetVerificationError(verificationErr, token)\n\n\t\t\t\t\tif isVerified {\n\t\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\t\"domain\": url,\n\t\t\t\t\t\t\t\"token\":  token,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyArtifactory(ctx context.Context, client *http.Client, resURLMatch, resMatch string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://\"+resURLMatch+\"/artifactory/api/system/ping\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"X-JFrog-Art-Api\", resMatch)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\t// lookup foo.jfrog.io: no such host\n\t\tif strings.Contains(err.Error(), \"no such host\") {\n\t\t\treturn false, errNoHost\n\t\t}\n\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tif strings.Contains(string(body), \"OK\") {\n\t\t\treturn true, nil\n\t\t}\n\n\t\treturn false, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden, http.StatusFound: // 302 can occur if the url is incorrect\n\t\t// https://jfrog.com/help/r/jfrog-rest-apis/error-responses\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", resp.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ArtifactoryAccessToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Artifactory is a repository manager that supports all major package formats. Artifactory access tokens can be used to authenticate and perform operations on repositories.\"\n}\n"
  },
  {
    "path": "pkg/detectors/artifactory/artifactory_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage artifactory\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestArtifactory_FromChunk(t *testing.T) {\n\t// NOTE: Using mock secrets because JFrog deprecated AKCp API keys (disabled creation end of Q3 2024).\n\t// Real AKCp keys can no longer be generated, so we cannot test actual verification scenarios.\n\t// These mock keys follow the correct format: AKCp + 69 alphanumeric characters = 73 total\n\t// Reference: https://jfrog.com/help/r/jfrog-release-information/artifactory-7.47.10-cloud-self-hosted\n\tmockSecret := \"AKCp5bueTFpfypEqQbGJPp7eHFi28fBivfWczrjbPb9erDff9LbXZbj6UsRExVXA8asWGc9fM\"\n\tappURL := \"trufflehog.jfrog.io\"\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, unverified - mock key (cannot verify deprecated AKCp format)\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a artifactory secret %s and domain %s\", mockSecret, appURL)),\n\t\t\t\tverify: false, // Cannot verify - AKCp API keys are deprecated and no valid keys available\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ArtifactoryAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.s.UseFoundEndpoints(true)\n\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Artifactory.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Artifactory.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/artifactory/artifactory_test.go",
    "content": "package artifactory\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestArtifactory_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname             string\n\t\tinput            string\n\t\tcloudEndpoint    string\n\t\tuseCloudEndpoint bool\n\t\tuseFoundEndpoint bool\n\t\twant             []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the artifactory API\n\t\t\t\t[DEBUG] Using Key=AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE\n\t\t\t\t[INFO] rwxtOp.jfrog.io\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\tuseCloudEndpoint: false,\n\t\t\tuseFoundEndpoint: true,\n\t\t\twant: []string{\n\t\t\t\t\"AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE\" +\n\t\t\t\t\t\"rwxtOp.jfrog.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{artifactory}</id>\n  \t\t\t\t\t<secret>AKCp8budTFpbypBqQbGJPp7eHFi28fBivfWczrjbPb9erDff9LbXZbj6UsRExVXA8asWGc9fM</secret>\n\t\t\t\t\t<domain>{HTTPnGQZ79vjWXze.jfrog.io}</domain>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\tuseCloudEndpoint: false,\n\t\t\tuseFoundEndpoint: true,\n\t\t\twant: []string{\n\t\t\t\t\"AKCp8budTFpbypBqQbGJPp7eHFi28fBivfWczrjbPb9erDff9LbXZbj6UsRExVXA8asWGc9fM\" +\n\t\t\t\t\t\"HTTPnGQZ79vjWXze.jfrog.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - with cloud endpoints\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the artifactory API\n\t\t\t\t[DEBUG] Using Key=AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\tcloudEndpoint:    \"cloudendpoint.jfrog.io\",\n\t\t\tuseCloudEndpoint: true,\n\t\t\tuseFoundEndpoint: false,\n\t\t\twant: []string{\n\t\t\t\t\"AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE\" +\n\t\t\t\t\t\"cloudendpoint.jfrog.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - with cloud and found endpoints\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the artifactory API\n\t\t\t\t[DEBUG] Using Key=AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE\n\t\t\t\t[INFO] rwxtOp.jfrog.io\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\tcloudEndpoint:    \"cloudendpoint.jfrog.io\",\n\t\t\tuseCloudEndpoint: true,\n\t\t\tuseFoundEndpoint: true,\n\t\t\twant: []string{\n\t\t\t\t\"AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE\" +\n\t\t\t\t\t\"cloudendpoint.jfrog.io\",\n\t\t\t\t\"AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE\" +\n\t\t\t\t\t\"rwxtOp.jfrog.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - with disabled found endpoints\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the artifactory API\n\t\t\t\t[DEBUG] Using Key=AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE\n\t\t\t\t[INFO] rwxtOp.jfrog.io\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\tcloudEndpoint:    \"cloudendpoint.jfrog.io\",\n\t\t\tuseCloudEndpoint: true,\n\t\t\tuseFoundEndpoint: false,\n\t\t\twant: []string{\n\t\t\t\t\"AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE\" +\n\t\t\t\t\t\"cloudendpoint.jfrog.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - with https in configured endpoint\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the artifactory API\n\t\t\t\t[DEBUG] Using Key=AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\tcloudEndpoint:    \"https://cloudendpoint.jfrog.io\",\n\t\t\tuseCloudEndpoint: true,\n\t\t\tuseFoundEndpoint: false,\n\t\t\twant: []string{\n\t\t\t\t\"AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE\" +\n\t\t\t\t\t\"cloudendpoint.jfrog.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern - wrong prefix\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the artifactory API\n\t\t\t\t[DEBUG] Using Key=XYZp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmdsY8ghxFGgehZcK3UGNgy5TxHWdE\n\t\t\t\t[INFO] rwxtOp.jfrog.io\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\tuseFoundEndpoint: true,\n\t\t\twant:             nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern - too short\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the artifactory API\n\t\t\t\t[DEBUG] Using Key=AKCp5e2gMx8TtJNDtrsuPq7Jz24Rqjkjf1d1iiy1GuEjmd\n\t\t\t\t[INFO] rwxtOp.jfrog.io\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\tuseFoundEndpoint: true,\n\t\t\twant:             nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t// this detector uses endpoint customizer interface so we need to enable them based on test case\n\t\t\td.UseFoundEndpoints(test.useFoundEndpoint)\n\t\t\td.UseCloudEndpoint(test.useCloudEndpoint)\n\t\t\t// if the test case provides cloud endpoint, then use it\n\t\t\tif test.useCloudEndpoint && test.cloudEndpoint != \"\" {\n\t\t\t\td.SetCloudEndpoint(test.cloudEndpoint)\n\t\t\t}\n\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/artifactoryreferencetoken/artifactoryreferencetoken.go",
    "content": "package artifactoryreferencetoken\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n\tdetectors.EndpointSetter\n}\n\nvar (\n\t// Ensure the Scanner satisfies the interface at compile time.\n\t_ detectors.Detector           = (*Scanner)(nil)\n\t_ detectors.EndpointCustomizer = (*Scanner)(nil)\n\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Reference tokens are base64-encoded strings starting with \"reftkn:01|<version>:<expiry>:<random>\"\n\t// The base64 encoding of \"reftkn\" is \"cmVmdGtu\", total length is always 64 characters\n\ttokenPat = regexp.MustCompile(`\\b(cmVmdGtu[A-Za-z0-9]{56})\\b`)\n\turlPat   = regexp.MustCompile(`\\b([A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9]\\.jfrog\\.io)`)\n\n\tinvalidHosts = simple.NewCache[struct{}]()\n\terrNoHost    = errors.New(\"no such host\")\n)\n\nfunc (Scanner) CloudEndpoint() string { return \"\" }\n\n// Keywords are used for efficiently pre-filtering chunks.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"cmVmdGtu\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Artifactory Reference tokens in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueTokens, uniqueUrls = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, match := range tokenPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokens[match[1]] = struct{}{}\n\t}\n\n\tfoundUrls := make([]string, 0)\n\tfor _, match := range urlPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tfoundUrls = append(foundUrls, match[1])\n\t}\n\n\t// Add found + configured endpoints to the list\n\tfor _, endpoint := range s.Endpoints(foundUrls...) {\n\t\t// If any configured endpoint has `https://` remove it because we append that during verification\n\t\tendpoint = strings.TrimPrefix(endpoint, \"https://\")\n\t\tuniqueUrls[endpoint] = struct{}{}\n\t}\n\n\tfor token := range uniqueTokens {\n\t\tfor url := range uniqueUrls {\n\t\t\tif invalidHosts.Exists(url) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_ArtifactoryReferenceToken,\n\t\t\t\tRaw:          []byte(token),\n\t\t\t\tRawV2:        []byte(token + url),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, verificationErr := verifyToken(ctx, s.getClient(), url, token)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\tif verificationErr != nil {\n\t\t\t\t\tif errors.Is(verificationErr, errNoHost) {\n\t\t\t\t\t\tinvalidHosts.Set(url, struct{}{})\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\ts1.SetVerificationError(verificationErr, token)\n\t\t\t\t}\n\n\t\t\t\tif isVerified {\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\"domain\": url,\n\t\t\t\t\t\t\"token\":  token,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyToken(ctx context.Context, client *http.Client, host, token string) (bool, error) {\n\t// https://jfrog.com/help/r/jfrog-rest-apis/get-token-by-id\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet,\n\t\t\"https://\"+host+\"/access/api/v1/tokens/me\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Set(\"Authorization\", \"Bearer \"+token)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"no such host\") {\n\t\t\treturn false, errNoHost\n\t\t}\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\t// JFrog returns 200 with HTML for invalid subdomains, so we need to check Content-Type\n\t\tcontentType := resp.Header.Get(\"Content-Type\")\n\t\tif strings.Contains(contentType, \"application/json\") {\n\t\t\treturn true, nil\n\t\t}\n\t\t// HTML response indicates invalid subdomain/redirect - treat as invalid host\n\t\treturn false, errNoHost\n\tcase http.StatusForbidden:\n\t\t// 403 - the authenticated principal has no permissions to get the token\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\t// 401 - invalid/expired token\n\t\treturn false, nil\n\tdefault:\n\t\t// 404 - endpoint not found (possibly wrong URL or old Artifactory version)\n\t\t// 302 and 500+\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", resp.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ArtifactoryReferenceToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"JFrog Artifactory is a binary repository manager. Reference tokens are 64-character access tokens that can be used to authenticate API requests, providing access to repositories, builds, and artifacts.\"\n}\n"
  },
  {
    "path": "pkg/detectors/artifactoryreferencetoken/artifactoryreferencetoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage artifactoryreferencetoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestArtifactoryreferencetoken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tinstanceURL := testSecrets.MustGetField(\"ARTIFACTORY_URL\")\n\tsecret := testSecrets.MustGetField(\"ARTIFACTORYREFERENCETOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"ARTIFACTORYREFERENCETOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a artifactoryreferencetoken secret %s and domain %s within\", secret, instanceURL)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ArtifactoryReferenceToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a artifactoryreferencetoken secret %s and domain %s within but not valid\", inactiveSecret, instanceURL)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ArtifactoryReferenceToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a artifactoryreferencetoken secret %s and domain %s within\", secret, instanceURL)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ArtifactoryReferenceToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(302, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a artifactoryreferencetoken secret %s and domain %s within\", secret, instanceURL)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ArtifactoryReferenceToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.s.UseFoundEndpoints(true)\n\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Artifactoryreferencetoken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"primarySecret\", \"AnalysisInfo\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Artifactoryreferencetoken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/artifactoryreferencetoken/artifactoryreferencetoken_test.go",
    "content": "package artifactoryreferencetoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestArtifactoryReferenceToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname             string\n\t\tinput            string\n\t\tcloudEndpoint    string\n\t\tuseCloudEndpoint bool\n\t\tuseFoundEndpoint bool\n\t\twant             []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern - environment variable\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Connecting to Artifactory\n\t\t\t\t[DEBUG] Using reference token: cmVmdGtuOjAxOjAwMDAwMDAwMDA6awJQVlZkdEVyWXJ2cVNSemAABVQ1bwaBSWtE\n\t\t\t\t[INFO] Connected to trufflehog.jfrog.io\n\t\t\t`,\n\t\t\tuseCloudEndpoint: false,\n\t\t\tuseFoundEndpoint: true,\n\t\t\twant: []string{\n\t\t\t\t\"cmVmdGtuOjAxOjAwMDAwMDAwMDA6awJQVlZkdEVyWXJ2cVNSemAABVQ1bwaBSWtEtrufflehog.jfrog.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - config file\",\n\t\t\tinput: `\n\t\t\t\tartifactory:\n\t\t\t\t  url: https://trufflehog.jfrog.io\n\t\t\t\t  reference_token: cmVmdGtuOjAxOjE3NjkxNjY0NjE6RE2ZeXpdsU1sOENUUG1RqXqDawNeMrJaTapu\n\t\t\t`,\n\t\t\tuseCloudEndpoint: false,\n\t\t\tuseFoundEndpoint: true,\n\t\t\twant: []string{\n\t\t\t\t\"cmVmdGtuOjAxOjE3NjkxNjY0NjE6RE2ZeXpdsU1sOENUUG1RqXqDawNeMrJaTaputrufflehog.jfrog.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - curl command\",\n\t\t\tinput: `\n\t\t\t\tcurl -H \"Authorization: Bearer cmVmdGtuOjAxOjE3NzE0OTkzNzY6RG9OS0QxOHVLduRyyUtNrneMwqt6a33TNUZV\" \\\n\t\t\t\t  https://trufflehog.jfrog.io/artifactory/api/system/ping\n\t\t\t`,\n\t\t\tuseCloudEndpoint: false,\n\t\t\tuseFoundEndpoint: true,\n\t\t\twant: []string{\n\t\t\t\t\"cmVmdGtuOjAxOjE3NzE0OTkzNzY6RG9OS0QxOHVLduRyyUtNrneMwqt6a33TNUZVtrufflehog.jfrog.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - with cloud endpoint\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Connecting to Artifactory\n\t\t\t\t[DEBUG] Using reference token: cmVmdGtuOjAxOjAwMDAwMDAwMDA6awJQVlZkdEVyWXJ2cVNSemAABVQ1bwaBSWtE\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\tcloudEndpoint:    \"cloudendpoint.jfrog.io\",\n\t\t\tuseCloudEndpoint: true,\n\t\t\tuseFoundEndpoint: false,\n\t\t\twant: []string{\n\t\t\t\t\"cmVmdGtuOjAxOjAwMDAwMDAwMDA6awJQVlZkdEVyWXJ2cVNSemAABVQ1bwaBSWtEcloudendpoint.jfrog.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - with cloud and found endpoints\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Connecting to Artifactory\n\t\t\t\t[DEBUG] Using reference token: cmVmdGtuOjAxOjAwMDAwMDAwMDA6awJQVlZkdEVyWXJ2cVNSemAABVQ1bwaBSWtE\n\t\t\t\t[INFO] trufflehog.jfrog.io\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\tcloudEndpoint:    \"cloudendpoint.jfrog.io\",\n\t\t\tuseCloudEndpoint: true,\n\t\t\tuseFoundEndpoint: true,\n\t\t\twant: []string{\n\t\t\t\t\"cmVmdGtuOjAxOjAwMDAwMDAwMDA6awJQVlZkdEVyWXJ2cVNSemAABVQ1bwaBSWtEcloudendpoint.jfrog.io\",\n\t\t\t\t\"cmVmdGtuOjAxOjAwMDAwMDAwMDA6awJQVlZkdEVyWXJ2cVNSemAABVQ1bwaBSWtEtrufflehog.jfrog.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - with disabled found endpoints\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Connecting to Artifactory\n\t\t\t\t[DEBUG] Using reference token: cmVmdGtuOjAxOjAwMDAwMDAwMDA6awJQVlZkdEVyWXJ2cVNSemAABVQ1bwaBSWtE\n\t\t\t\t[INFO] trufflehog.jfrog.io\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\tcloudEndpoint:    \"cloudendpoint.jfrog.io\",\n\t\t\tuseCloudEndpoint: true,\n\t\t\tuseFoundEndpoint: false,\n\t\t\twant: []string{\n\t\t\t\t\"cmVmdGtuOjAxOjAwMDAwMDAwMDA6awJQVlZkdEVyWXJ2cVNSemAABVQ1bwaBSWtEcloudendpoint.jfrog.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - with https in configured endpoint\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Connecting to Artifactory\n\t\t\t\t[DEBUG] Using reference token: cmVmdGtuOjAxOjAwMDAwMDAwMDA6awJQVlZkdEVyWXJ2cVNSemAABVQ1bwaBSWtE\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\tcloudEndpoint:    \"https://cloudendpoint.jfrog.io\",\n\t\t\tuseCloudEndpoint: true,\n\t\t\tuseFoundEndpoint: false,\n\t\t\twant: []string{\n\t\t\t\t\"cmVmdGtuOjAxOjAwMDAwMDAwMDA6awJQVlZkdEVyWXJ2cVNSemAABVQ1bwaBSWtEcloudendpoint.jfrog.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"finds multiple tokens\",\n\t\t\tinput: `\n\t\t\t\t# Primary token\n\t\t\t\texport ARTIFACTORY_TOKEN=cmVmdGtuOjAxOjAwMDAwMDAwMDA6awJQVlZkdEVyWXJ2cVNSemAABVQ1bwaBSWtE\n\t\t\t\t# Backup token\n\t\t\t\texport ARTIFACTORY_TOKEN_BACKUP=cmVmdGtuOjAxOjE3NjkxNjY0NjE6RE2ZeXpdsU1sOENUUG1RqXqDawNeMrJaTapu\n\t\t\t\texport ARTIFACTORY_URL=https://trufflehog.jfrog.io\n\t\t\t`,\n\t\t\tuseCloudEndpoint: false,\n\t\t\tuseFoundEndpoint: true,\n\t\t\twant: []string{\n\t\t\t\t\"cmVmdGtuOjAxOjAwMDAwMDAwMDA6awJQVlZkdEVyWXJ2cVNSemAABVQ1bwaBSWtEtrufflehog.jfrog.io\",\n\t\t\t\t\"cmVmdGtuOjAxOjE3NjkxNjY0NjE6RE2ZeXpdsU1sOENUUG1RqXqDawNeMrJaTaputrufflehog.jfrog.io\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern - too short\",\n\t\t\tinput: `\n\t\t\t\t[DEBUG] Using token: cmVmdGtuOjAxOjAwMDAwMDAwMDA6SHORT\n\t\t\t\t[INFO] URL: trufflehog.jfrog.io\n\t\t\t`,\n\t\t\tuseCloudEndpoint: false,\n\t\t\tuseFoundEndpoint: true,\n\t\t\twant:             nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern - wrong prefix\",\n\t\t\tinput: `\n\t\t\t\t[DEBUG] Using token: aBcDeFgHOjAxOjAwMDAwMDAwMDA6awJQVlZkdEVyWXJ2cVNSemAABVQ1bwaBSWtE\n\t\t\t\t[INFO] URL: trufflehog.jfrog.io\n\t\t\t`,\n\t\t\tuseCloudEndpoint: false,\n\t\t\tuseFoundEndpoint: true,\n\t\t\twant:             nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t// Configure endpoint customizer based on test case\n\t\t\td.UseFoundEndpoints(test.useFoundEndpoint)\n\t\t\td.UseCloudEndpoint(test.useCloudEndpoint)\n\t\t\tif test.useCloudEndpoint && test.cloudEndpoint != \"\" {\n\t\t\t\td.SetCloudEndpoint(test.cloudEndpoint)\n\t\t\t}\n\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 && len(test.want) > 0 {\n\t\t\t\tt.Errorf(\"keywords were not matched: %v\", d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"expected %d results, got %d\", len(test.want), len(results))\n\t\t\t\tfor _, r := range results {\n\t\t\t\t\tt.Logf(\"got: %s\", string(r.RawV2))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/artsy/artsy.go",
    "content": "package artsy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"artsy\"}) + `\\b([0-9a-zA-Z]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"artsy\"}) + `\\b([0-9a-zA-Z]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"artsy\"}\n}\n\n// FromData will find and optionally verify Artsy secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Artsy,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resIdMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, err := verifyMatch(ctx, client, resIdMatch, resMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, id, secret string) (bool, error) {\n\t// Reference: https://developers.artsy.net/v2/docs/authentication\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://api.artsy.net/api/tokens/xapp_token?client_id=\"+id+\"&client_secret=\"+secret, http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\tswitch res.StatusCode {\n\tcase http.StatusCreated:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Artsy\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Artsy is an online platform for discovering, buying, and selling art. Artsy API keys can be used to access Artsy's services and data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/artsy/artsy_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage artsy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestArtsy_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ARTSY\")\n\tinactiveSecret := testSecrets.MustGetField(\"ARTSY_INACTIVE\")\n\tid := testSecrets.MustGetField(\"ARTSY_CLIENTID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a artsy secret %s within artsyid %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Artsy,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Artsy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Artsy.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no rawv2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Artsy.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/artsy/artsy_test.go",
    "content": "package artsy\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestArtsy_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the artsy API\n\t\t\t\t[DEBUG] Using Key=rU0K6hwGw9AeANtXrZ8FQJT9jn4sRdlj\n\t\t\t\t[DEBUG] Using artsy ID=hvQ2fMvUPNczDCdmzi0i\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"rU0K6hwGw9AeANtXrZ8FQJT9jn4sRdljhvQ2fMvUPNczDCdmzi0i\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{artsy Mbw4Tihfv1ttrspD1yXk}</id>\n  \t\t\t\t\t<secret>{artsy AQAAABAAA 3V4gtw8ZmDShAfzq2KKb3w0gZODnzxp7}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"3V4gtw8ZmDShAfzq2KKb3w0gZODnzxp7Mbw4Tihfv1ttrspD1yXk\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the artsy API\n\t\t\t\t[DEBUG] Using Key=rU0K6hwGw9AeANtX-Z8FQJT9jn4sRdlj\n\t\t\t\t[DEBUG] Using artsy ID=hvQ2fMvUPN_zDCdmzi0i\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/asanaoauth/asanaoauth.go",
    "content": "package asanaoauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"asana\"}) + `\\b([a-z\\/:0-9]{51})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"asana\"}\n}\n\n// FromData will find and optionally verify AsanaOauth secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_AsanaOauth,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, err := verifyMatch(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\ts1.AnalysisInfo = map[string]string{\"key\": resMatch}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://app.asana.com/api/1.0/users/me\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AsanaOauth\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/asanaoauth/asanaoauth_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage asanaoauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAsanaOauth_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ASANAOAUTH_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"ASANAOAUTH_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a asana secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AsanaOauth,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AsanaOauth,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AsanaOauth.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"AsanaOauth.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/asanaoauth/asanaoauth_test.go",
    "content": "package asanaoauth\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAsanaOauth_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the asana API\n\t\t\t\t[DEBUG] Using Key=q5przi0tmp6xpo7rpsd0q:kl0qg:2gdj3jyumq04q9kcqk/qxdo\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"q5przi0tmp6xpo7rpsd0q:kl0qg:2gdj3jyumq04q9kcqk/qxdo\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{asana}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA omzmg54nn5wa21sh6qwg:dos10bfl1f6vnqcs9lcdwkbqb68gti}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"omzmg54nn5wa21sh6qwg:dos10bfl1f6vnqcs9lcdwkbqb68gti\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the asana API\n\t\t\t\t[DEBUG] Using Key=q5przi0tmP6xpo7rpsd0q;kl0qg:2gdj3jyumq04q9kcqk/qxdo\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken.go",
    "content": "package asanapersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Updated pattern to handle both old and new token formats\n\t// Old format: [digits]/[16+ digits]:[32+ chars]\n\t// New format: [digits]/[16+ digits]/[16+ digits]:[32+ chars]\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"asana\"}) + `\\b([0-9]{1,}\\/[0-9]{16,}(?:\\/[0-9]{16,})?:[A-Za-z0-9]{32,})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"asana\"}\n}\n\n// FromData will find and optionally verify AsanaPersonalAccessToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_AsanaPersonalAccessToken,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, err := verifyMatch(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://app.asana.com/api/1.0/users/me\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AsanaPersonalAccessToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage asanapersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAsanaPersonalAccessToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\ttestNewSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\toldFormatSecret := testSecrets.MustGetField(\"ASANA_PAT\")\n\tnewFormatSecret := testNewSecrets.MustGetField(\"ASANA_PAT_NEW\")\n\tinactiveOldFormatSecret := testSecrets.MustGetField(\"ASANA_PAT_INACTIVE\")\n\tinactiveNewFormatSecret := testNewSecrets.MustGetField(\"ASANA_PAT_NEW_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a asana secret %s within\", oldFormatSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AsanaPersonalAccessToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a asana secret %s within but unverified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AsanaPersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified - new format\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a asana secret %s within\", newFormatSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AsanaPersonalAccessToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified - new format\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a asana secret %s but unverified\", inactiveNewFormatSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AsanaPersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\tfor _, tt := range tests {\n\t\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t\ts := Scanner{}\n\t\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\t\tt.Errorf(\"AsanaPersonalAccessToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tfor i := range got {\n\t\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t\t}\n\t\t\t\t\tgot[i].Raw = nil\n\t\t\t\t}\n\t\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"AsanaPersonalAccessToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/asanapersonalaccesstoken/asanapersonalaccesstoken_test.go",
    "content": "package asanapersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAsanaPersonalAccessToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern - old format\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the asana API\n\t\t\t\t[DEBUG] Using Old Format asana Key=5947/1724908107002616220416212965:Yv3DoiSFhtsgUwN3AcnXWjK8zabQHKSHBRHpuNKVjz3oCcpyDIdXRm3GL4SUDkTMFoTb\n\t\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t\t[DEBUG] Using new format asana Key=7/9823746598123746/8923746598123456:7f1a3c9be84d2a6c4e7d9c32bf1e7f88\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"5947/1724908107002616220416212965:Yv3DoiSFhtsgUwN3AcnXWjK8zabQHKSHBRHpuNKVjz3oCcpyDIdXRm3GL4SUDkTMFoTb\",\n\t\t\t\t\"7/9823746598123746/8923746598123456:7f1a3c9be84d2a6c4e7d9c32bf1e7f88\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{asana}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA 891435852083139681602524390768273271357927849104481/366163755073364840345913922341185329292536814045275090976491644844014597476863956806652784056747/17480879147700616278211801017829125:Hb7meGPLBz7jH7e1fiHetN355omiO9Zt8fewjSOX4qfUoWDzvvlNA6lBx9rNuR8EAEElmtmmL9J4ilO8m2D56n}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"891435852083139681602524390768273271357927849104481/366163755073364840345913922341185329292536814045275090976491644844014597476863956806652784056747/17480879147700616278211801017829125:Hb7meGPLBz7jH7e1fiHetN355omiO9Zt8fewjSOX4qfUoWDzvvlNA6lBx9rNuR8EAEElmtmmL9J4ilO8m2D56n\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the asana API\n\t\t\t\t[DEBUG] Using Old Format asana Key=5947766540345/172490810700261:Yv3DoiSFhjK8zabQHKSHBRHpuNKVjz3oCcpyDIdXRm3GL4SUDkTMFoTbRDCHe8tTBHxdtoXItn\n\t\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t\t[DEBUG] Using new format asana Key=7/98237465/8923746598156:7f1a3c9be84d2a6c4e7d9c32bf1e7f88\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/assemblyai/assemblyai.go",
    "content": "package assemblyai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"assemblyai\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"assemblyai\"}\n}\n\n// FromData will find and optionally verify Assemblyai secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_AssemblyAI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, err := verifyMatch(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.assemblyai.com/v2/transcript\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Authorization\", token)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AssemblyAI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/assemblyai/assemblyai_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage assemblyai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAssemblyai_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ASSEMBLYAI\")\n\tinactiveSecret := testSecrets.MustGetField(\"ASSEMBLYAI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a assemblyai secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AssemblyAI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AssemblyAI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Assemblyai.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Assemblyai.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/assemblyai/assemblyai_test.go",
    "content": "package assemblyai\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAssemblyAI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using assemblyai Key=mlhekyjhs96mx0r2cxbzky4jzr83fw1q\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"mlhekyjhs96mx0r2cxbzky4jzr83fw1q\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{assemblyai}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA s0c8a99g0w6qbwybdxn4uowzemk1xlca}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"s0c8a99g0w6qbwybdxn4uowzemk1xlca\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using assemblyai Key=Mlhekyjzr83fw1qr2cxbzky4jzr83f1q\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/atera/atera.go",
    "content": "package atera\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"atera\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"atera\"}\n}\n\n// FromData will find and optionally verify Atera secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Atera,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, err := verifyMatch(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://app.atera.com/api/v3/alerts\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Accept\", \"application/json\")\n\treq.Header.Add(\"X-API-KEY\", token)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Atera\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/atera/atera_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage atera\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAtera_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ATERA\")\n\tinactiveSecret := testSecrets.MustGetField(\"ATERA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find an atera secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Atera,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Atera,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Atera.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Atera.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/atera/atera_test.go",
    "content": "package atera\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAtera_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the atera API\n\t\t\t\t[DEBUG] Using Key=yoo3d5pu3t4zxd6x1vhk7ykmjqarbsv1\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"yoo3d5pu3t4zxd6x1vhk7ykmjqarbsv1\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{atera}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA uvyn0qy0ec96pgxfr2s3i4bqv1znl7yg}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"uvyn0qy0ec96pgxfr2s3i4bqv1znl7yg\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the atera API\n\t\t\t\t[DEBUG] Using Key=yOO3d5pu3t4zxd6x1vhk7ykmjqarbs_1\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/atlassian/v1/atlassian.go",
    "content": "package atlassian\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nfunc (s Scanner) Version() int { return 1 }\n\ntype OrgRes struct {\n\tData []struct {\n\t\tAttributes struct {\n\t\t\tName string `json:\"name\"`\n\t\t} `json:\"attributes\"`\n\t} `json:\"data\"`\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"atlassian\"}) + `\\b([a-zA-Z-0-9]{24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"atlassian\"}\n}\n\n// Description returns a description for the result being detected\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// FromData will find and optionally verify Atlassian secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Atlassian,\n\t\t\tRaw:          []byte(match),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/atlassian/\",\n\t\t\t\t\"version\":        fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, orgResponse, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\tif orgResponse != nil {\n\t\t\t\ts1.ExtraData[\"Organization\"] = orgResponse.Data[0].Attributes.Name\n\t\t\t}\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\n\t\t\tif isVerified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\"key\": match,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, *OrgRes, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.atlassian.com/admin/v1/orgs\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\treq.Header.Add(\"Accept\", \"application/json\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\t// If the endpoint returns useful information, we can return it as a map.\n\t\tvar orgResponse OrgRes\n\t\tif err = json.NewDecoder(res.Body).Decode(&orgResponse); err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\t\treturn true, &orgResponse, nil\n\tcase http.StatusUnauthorized:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Atlassian\n}\n"
  },
  {
    "path": "pkg/detectors/atlassian/v1/atlassian_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage atlassian\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAtlassian_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ATLASSIAN\")\n\tinactiveSecret := testSecrets.MustGetField(\"ATLASSIAN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a atlassian secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Atlassian,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Atlassian,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a atlassian secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Atlassian,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a atlassian secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Atlassian,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Atlassian.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"ExtraData\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Atlassian.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/atlassian/v1/atlassian_test.go",
    "content": "package atlassian\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAtlassian_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the atlassian API\n\t\t\t\t[DEBUG] Using Key=aB1cD2eF3gH4iJ5kL6mN7oP8\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"aB1cD2eF3gH4iJ5kL6mN7oP8\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{atlassian}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA r6RkiQao3PgqY9MOKtonpJdU}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"r6RkiQao3PgqY9MOKtonpJdU\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/atlassian/v2/atlassian.go",
    "content": "package atlassian\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nfunc (s Scanner) Version() int { return 2 }\n\ntype OrgRes struct {\n\tData []struct {\n\t\tAttributes struct {\n\t\t\tName string `json:\"name\"`\n\t\t} `json:\"attributes\"`\n\t} `json:\"data\"`\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\n\t// Example: ATCTT3xFfGN0GsZNgOGrQSHSnxiJVi00oHlRicyM0yMNuKCBfw6qOHVcCy4Hm89GnclGb_W-1qAkxqCn5XbuyoX54bNhpK5yFKGFR7ocV6FByvL_P9Sb3tFnbUg3T3I3S_RGCBLMSN7Nsa4GJv8JEJ6bzvDmX-oJ8AnrazMU-zZ5hb-u3t2ERew=366BFE3A\n\tkeyPat = regexp.MustCompile(`\\b(ATCTT3xFfG[A-Za-z0-9+/=_-]+=[A-Za-z0-9]{8})\\b`)\n\t// Example: 123e4567-e89b-12d3-a456-426614174000\n\torganizationIdPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ATCTT3xFfG\"}\n}\n\n// Description returns a description for the result being detected\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// FromData will find and optionally verify Atlassian secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tuniqueOrgIdMatches := make(map[string]struct{})\n\tfor _, match := range organizationIdPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueOrgIdMatches[match[1]] = struct{}{}\n\t}\n\tif len(uniqueOrgIdMatches) == 0 {\n\t\t// we only need an org ID to pass into AnalysisInfo\n\t\t// if we don't find one, we can still verify the key\n\t\t// we can add a dummy entry here just to make sure a result is returned\n\t\tuniqueOrgIdMatches[\"\"] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\tfor orgId := range uniqueOrgIdMatches {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Atlassian,\n\t\t\t\tRaw:          []byte(match),\n\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/atlassian/\",\n\t\t\t\t\t\"version\":        fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, orgResponse, verificationErr := verifyMatch(ctx, client, match)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\tif orgResponse != nil && len(orgResponse.Data) > 0 {\n\t\t\t\t\ts1.ExtraData[\"Organization\"] = orgResponse.Data[0].Attributes.Name\n\t\t\t\t}\n\t\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t\t\tif s1.Verified {\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\"key\": match,\n\t\t\t\t\t}\n\t\t\t\t\tif orgId != \"\" {\n\t\t\t\t\t\ts1.AnalysisInfo[\"organization_id\"] = orgId\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, *OrgRes, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.atlassian.com/admin/v1/orgs\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\treq.Header.Add(\"Accept\", \"application/json\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\t// If the endpoint returns useful information, we can return it as a map.\n\t\tvar orgResponse OrgRes\n\t\tif err = json.NewDecoder(res.Body).Decode(&orgResponse); err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\t\treturn true, &orgResponse, nil\n\tcase http.StatusUnauthorized:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Atlassian\n}\n"
  },
  {
    "path": "pkg/detectors/atlassian/v2/atlassian_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage atlassian\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAtlassian_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ATLASSIAN\")\n\tinactiveSecret := testSecrets.MustGetField(\"ATLASSIAN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a atlassian secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Atlassian,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Atlassian,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a atlassian secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Atlassian,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a atlassian secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Atlassian,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Atlassian.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"ExtraData\", \"primarySecret\", \"AnalysisInfo\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Atlassian.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/atlassian/v2/atlassian_test.go",
    "content": "package atlassian\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"gopkg.in/h2non/gock.v1\"\n)\n\nfunc TestAtlassian_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the atlassian API\n\t\t\t\t[DEBUG] Using Key=ATCTT3xFfGN0GsZNgOGrQSHSnxiJVi00oHlRicyM0yMNuKCBfw6qOHVcCy4Hm89GnclGb_W-1qAkxqCn5XbuyoX54bNhpK5yFKGFR7ocV6FByvL_P9Sb3tFnbUg3T3I3S_RGCBLMSN7Nsa4GJv8JEJ6bzvDmX-oJ8AnrazMU-zZ5hb-u3t2ERew=366BFE3A\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t\t`,\n\t\t\twant: []string{\"ATCTT3xFfGN0GsZNgOGrQSHSnxiJVi00oHlRicyM0yMNuKCBfw6qOHVcCy4Hm89GnclGb_W-1qAkxqCn5XbuyoX54bNhpK5yFKGFR7ocV6FByvL_P9Sb3tFnbUg3T3I3S_RGCBLMSN7Nsa4GJv8JEJ6bzvDmX-oJ8AnrazMU-zZ5hb-u3t2ERew=366BFE3A\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{98651}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA ATCTT3xFfGXc59Vkq40qLX=iEOIrJRZ}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"ATCTT3xFfGXc59Vkq40qLX=iEOIrJRZ\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestAtlassian_AnalysisInfo_KeyAndOrgId tests if both the key and organization id are populated into AnalysisInfo\n// given that they are present in the input data chunk\nfunc TestAtlassian_AnalysisInfo_KeyAndOrgId(t *testing.T) {\n\tclient := common.SaneHttpClient()\n\td := Scanner{client: client}\n\n\tkey := \"ATCTT3xFfGN0GsZNgOGrQSHSnxiJVi00oHlRicyM0yMNuKCBfw6qOHVcCy4Hm89GnclGb_W-1qAkxqCn5XbuyoX54bNhpK5yFKGFR7ocV6FByvL_P9Sb3tFnbUg3T3I3S_RGCBLMSN7Nsa4GJv8JEJ6bzvDmX-oJ8AnrazMU-zZ5hb-u3t2ERew=366BFE3A\"\n\torgId := \"123j4567-e89b-12d3-a456-426614174000\"\n\n\tdefer gock.Off()\n\tdefer gock.RestoreClient(client)\n\tgock.InterceptClient(client)\n\tgock.New(\"https://api.atlassian.com\").\n\t\tGet(\"/admin/v1/orgs\").\n\t\tMatchHeader(\"Accept\", \"application/json\").\n\t\tMatchHeader(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key)).\n\t\tReply(http.StatusOK).\n\t\tJSON(map[string]any{\n\t\t\t\"Data\": []map[string]any{},\n\t\t})\n\n\tt.Run(\"key and organization id both present\", func(t *testing.T) {\n\t\tinput := fmt.Sprintf(`\n\t\t[INFO] Sending request to the atlassian API\n\t\t[DEBUG] Using Key=%s\n\t\t[DEBUG] Using Organization ID=%s\n\t\t[INFO] Response received: 200 OK\n\t\t`, key, orgId)\n\n\t\tresults, err := d.FromData(context.Background(), true, []byte(input))\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, results, 1, \"mismatch in result count: expected %d, got %d\", 1, len(results))\n\t\tresult := results[0]\n\t\trequire.NotNil(t, result.AnalysisInfo, \"AnalysisInfo is nil\")\n\n\t\tassert.Equal(t, key, result.AnalysisInfo[\"key\"], \"mismatch in key\")\n\t\tassert.Equal(t, orgId, result.AnalysisInfo[\"organization_id\"], \"mismatch in organization_id\")\n\t})\n}\n\n// TestAtlassian_AnalysisInfo_KeyOnly tests if only key is populated into AnalysisInfo\n// given that only the key and no organization_id is present in the input data chunk\nfunc TestAtlassian_AnalysisInfo_KeyOnly(t *testing.T) {\n\tclient := common.SaneHttpClient()\n\td := Scanner{client: client}\n\n\tkey := \"ATCTT3xFfGN0GsZNgOGrQSHSnxiJVi00oHlRicyM0yMNuKCBfw6qOHVcCy4Hm89GnclGb_W-1qAkxqCn5XbuyoX54bNhpK5yFKGFR7ocV6FByvL_P9Sb3tFnbUg3T3I3S_RGCBLMSN7Nsa4GJv8JEJ6bzvDmX-oJ8AnrazMU-zZ5hb-u3t2ERew=366BFE3A\"\n\n\tdefer gock.Off()\n\tdefer gock.RestoreClient(client)\n\tgock.InterceptClient(client)\n\tgock.New(\"https://api.atlassian.com\").\n\t\tGet(\"/admin/v1/orgs\").\n\t\tMatchHeader(\"Accept\", \"application/json\").\n\t\tMatchHeader(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key)).\n\t\tReply(http.StatusOK).\n\t\tJSON(map[string]any{\n\t\t\t\"Data\": []map[string]any{},\n\t\t})\n\tt.Run(\"only key present\", func(t *testing.T) {\n\n\t\tinput := fmt.Sprintf(`\n\t\t[INFO] Sending request to the atlassian API\n\t\t[DEBUG] Using Key=%s\n\t\t[INFO] Response received: 200 OK\n\t\t`, key)\n\n\t\tresults, err := d.FromData(context.Background(), true, []byte(input))\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, results, 1, \"mismatch in result count: expected %d, got %d\", 1, len(results))\n\t\tresult := results[0]\n\t\trequire.NotNil(t, result.AnalysisInfo, \"AnalysisInfo is nil\")\n\n\t\tassert.Equal(t, key, result.AnalysisInfo[\"key\"], \"mismatch in key\")\n\t})\n}\n"
  },
  {
    "path": "pkg/detectors/audd/audd.go",
    "content": "package audd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"audd\"}) + `\\b([a-z0-9-]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"audd\"}\n}\n\n// FromData will find and optionally verify Audd secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Audd,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.audd.io/setCallbackUrl/?api_token=%s&url=https://yourwebsite.com/callbacks_handler/\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `\"status\":\"success\"`)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Audd\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Audd is a music recognition service. Audd API tokens can be used to access the Audd API services for recognizing music and obtaining metadata.\"\n}\n"
  },
  {
    "path": "pkg/detectors/audd/audd_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage audd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAudd_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AUDD\")\n\tinactiveSecret := testSecrets.MustGetField(\"AUDD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a audd secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Audd,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Audd,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Audd.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Audd.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/audd/audd_test.go",
    "content": "package audd\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAudd_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the audd API\n\t\t\t\t[DEBUG] Using Key=60fzzcspq2balbxn7f3hi2nvg3h07h4z\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"60fzzcspq2balbxn7f3hi2nvg3h07h4z\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{audd}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA uv2kv0x8htfhgnugnsbys7a8oyky5ryb}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"uv2kv0x8htfhgnugnsbys7a8oyky5ryb\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the audd API\n\t\t\t\t[DEBUG] Using Key=60fzzcspq2balbxn7f3hi2nvg3h07h4zY\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/auth0managementapitoken/auth0managementapitoken.go",
    "content": "package auth0managementapitoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.MaxSecretSizeProvider = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithLocalAddresses\n\n\t// long jwt token but note this is default 8640000 seconds = 24 hours but could be set to maximum 2592000 seconds = 720 hours = 30 days\n\t// at https://manage.auth0.com/dashboard/us/dev-63memjo3/apis/management/explorer\n\tmanagementAPITokenPat = regexp.MustCompile(`\\b(ey[a-zA-Z0-9._-]+)\\b`)\n\tdomainPat             = regexp.MustCompile(`([a-zA-Z0-9\\-]{2,16}\\.[a-zA-Z0-9_-]{2,3}\\.auth0\\.com)`) // could be part of url\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string { return []string{\"auth0\"} }\n\nconst maxSecretSize = 5000\n\nfunc (Scanner) MaxSecretSize() int64 { return maxSecretSize }\n\n// FromData will find and optionally verify Auth0ManagementApiToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmanagementAPITokenMatches := managementAPITokenPat.FindAllStringSubmatch(dataStr, -1)\n\tdomainMatches := domainPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, managementApiTokenMatch := range managementAPITokenMatches {\n\t\tmanagementAPITokenRes := strings.TrimSpace(managementApiTokenMatch[1])\n\t\tif len(managementAPITokenRes) < 2000 || len(managementAPITokenRes) > 5000 {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, domainMatch := range domainMatches {\n\t\t\tdomainRes := strings.TrimSpace(domainMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Auth0ManagementApiToken,\n\t\t\t\tRedacted:     domainRes,\n\t\t\t\tRaw:          []byte(managementAPITokenRes),\n\t\t\t\tRawV2:        []byte(managementAPITokenRes + domainRes),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, err := verifyMatch(ctx, client, managementAPITokenRes, domainRes)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(err, managementAPITokenRes)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token, domain string) (bool, error) {\n\t/*\n\t\tcurl -H \"Authorization: Bearer $token\" https://domain/api/v2/users\n\t\tReference: https://auth0.com/docs/api/management/v2/users/get-users\n\t*/\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://\"+domain+\"/api/v2/users\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\tswitch res.StatusCode {\n\tcase http.StatusOK, http.StatusForbidden:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Auth0ManagementApiToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/auth0managementapitoken/auth0managementapitoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage auth0managementapitoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAuth0ManagementApiToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\t// use 2592000 for 30 days, this is the maximum allowed\n\tmanagementApiToken := testSecrets.MustGetField(\"AUTH0_MANAGEMENT_APITOKEN\")\n\tinactiveManagementApiToken := testSecrets.MustGetField(\"AUTH0_MANAGEMENT_APITOKEN_INACTIVE\")\n\tdomain := testSecrets.MustGetField(\"AUTH0_MANAGEMENT_DOMAIN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a auth0 secret %s domain %s\", managementApiToken, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Auth0ManagementApiToken,\n\t\t\t\t\tRawV2:        []byte(managementApiToken + domain),\n\t\t\t\t\tRedacted:     domain,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Auth0ManagementApiToken,\n\t\t\t\t\tRawV2:        []byte(inactiveManagementApiToken + domain),\n\t\t\t\t\tRedacted:     domain,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Auth0ManagementApiToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Auth0ManagementApiToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/auth0managementapitoken/auth0managementapitoken_test.go",
    "content": "package auth0managementapitoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\t// TODO: Refactor the fake token generation if possible\n\tvalidPattern = generateRandomString() // this has the exact token string only which can be used in want too\n)\n\nfunc TestAuth0ManagementApitToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: makeFakeTokenString(validPattern, \"Truffle-security.org.auth0.com\"),\n\t\t\twant:  []string{validPattern + \"Truffle-security.org.auth0.com\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tauth0_credentials:\n\t\t\t\t\tapiToken: eywT2nGMZwOcbsUVBwfiRPEl8P_wnmo6XfdUoGVwxDfOSjNyqhYqFdi.KojZZOM8Ox\n\t\t\t\t\tdomain: Truffle-security.org.auth0.com\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// makeFakeTokenString take a string token as parameter and make a string that looks like a token for testing\nfunc makeFakeTokenString(token, domain string) string {\n\treturn fmt.Sprintf(\"auth0:\\n apiToken: %s \\n domain: %s\", token, domain)\n}\n\n// generateRandomString generates exactly 2001 char string for a fake token to pass the check in detector for testing\nfunc generateRandomString() string {\n\tconst length = 2001\n\tconst charset = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_\"\n\tconst charsetWithBoundaryChars = charset + \".-\"\n\n\trandom := rand.New(rand.NewSource(time.Now().UnixNano()))\n\n\tvar builder strings.Builder\n\tbuilder.Grow(length)\n\n\tfor i := 0; i < length-1; i++ {\n\t\trandomChar := charsetWithBoundaryChars[random.Intn(len(charset))]\n\t\tbuilder.WriteByte(randomChar)\n\t}\n\n\t// ensure last character is not boundary character\n\tlastChar := charset[random.Intn(len(charset))]\n\tbuilder.WriteByte(lastChar)\n\n\t// append ey in start as the token must start with 'ey'\n\treturn fmt.Sprintf(\"ey%s\", builder.String())\n}\n"
  },
  {
    "path": "pkg/detectors/auth0oauth/auth0oauth.go",
    "content": "package auth0oauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithLocalAddresses\n\n\tclientIdPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"auth0\"}) + `\\b([a-zA-Z0-9_-]{32,60})\\b`)\n\tclientSecretPat = regexp.MustCompile(`\\b([a-zA-Z0-9_-]{64,})\\b`)\n\tdomainPat       = regexp.MustCompile(`\\b([a-zA-Z0-9][a-zA-Z0-9._-]*auth0\\.com)\\b`) // could be part of url\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"auth0\"}\n}\n\n// FromData will find and optionally verify Auth0oauth secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tuniqueDomainMatches := make(map[string]struct{})\n\tuniqueClientIDs := make(map[string]struct{})\n\tuniqueSecrets := make(map[string]struct{})\n\tfor _, m := range domainPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueDomainMatches[strings.TrimSpace(m[1])] = struct{}{}\n\t}\n\tfor _, m := range clientIdPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueClientIDs[strings.TrimSpace(m[1])] = struct{}{}\n\t}\n\tfor _, m := range clientSecretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueSecrets[strings.TrimSpace(m[1])] = struct{}{}\n\t}\n\n\tfor clientIdRes := range uniqueClientIDs {\n\t\tfor clientSecretRes := range uniqueSecrets {\n\t\t\tfor domainRes := range uniqueDomainMatches {\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Auth0oauth,\n\t\t\t\t\tRedacted:     clientIdRes,\n\t\t\t\t\tRaw:          []byte(clientSecretRes),\n\t\t\t\t\tRawV2:        []byte(clientIdRes + clientSecretRes),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\n\t\t\t\t\tclient := s.client\n\t\t\t\t\tif client == nil {\n\t\t\t\t\t\tclient = defaultClient\n\t\t\t\t\t}\n\n\t\t\t\t\tisVerified, err := verifyTuple(ctx, client, domainRes, clientIdRes, clientSecretRes)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\ts1.SetVerificationError(err, clientIdRes)\n\t\t\t\t\t}\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyTuple(ctx context.Context, client *http.Client, domainRes, clientId, clientSecret string) (bool, error) {\n\t/*\n\t   curl --request POST \\\n\t     --url 'https://YOUR_DOMAIN/oauth/token' \\\n\t     --header 'content-type: application/x-www-form-urlencoded' \\\n\t     --data 'grant_type=authorization_code&client_id=W44JmL3qD6LxHeEJyKe9lMuhcwvPOaOq&client_secret=YOUR_CLIENT_SECRET&code=AUTHORIZATION_CODE&redirect_uri=undefined'\n\t*/\n\n\tdata := url.Values{}\n\tdata.Set(\"grant_type\", \"authorization_code\")\n\tdata.Set(\"client_id\", clientId)\n\tdata.Set(\"client_secret\", clientSecret)\n\tdata.Set(\"code\", \"AUTHORIZATION_CODE\")\n\tdata.Set(\"redirect_uri\", \"undefined\")\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://\"+domainRes+\"/oauth/token\", strings.NewReader(data.Encode())) // URL-encoded payload\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\t// This condition will never meet due to invalid request body\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tcase http.StatusForbidden:\n\t\t// cross check about 'invalid_grant' or 'unauthorized_client' in response body\n\t\tbodyBytes, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tbodyStr := string(bodyBytes)\n\t\tif strings.Contains(bodyStr, \"invalid_grant\") || strings.Contains(bodyStr, \"unauthorized_client\") {\n\t\t\treturn true, nil\n\t\t}\n\t\treturn false, nil\n\tcase http.StatusNotFound:\n\t\t// domain does not exists - 404 not found\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", resp.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Auth0oauth\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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\"\n}\n"
  },
  {
    "path": "pkg/detectors/auth0oauth/auth0oauth_integeration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage auth0oauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAuth0oauth_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tdomain := testSecrets.MustGetField(\"AUTH0_DOMAIN\")\n\tclientId := testSecrets.MustGetField(\"AUTH0_CLIENT_ID\")\n\tclientSecret := testSecrets.MustGetField(\"AUTH0_CLIENT_SECRET\")\n\n\tdomainUnauthorized := testSecrets.MustGetField(\"AUTH0_DOMAIN_UNAUTHORIZED\")\n\tclientIdUnauthorized := testSecrets.MustGetField(\"AUTH0_CLIENT_ID_UNAUTHORIZED\")\n\tclientSecretUnauthorized := testSecrets.MustGetField(\"AUTH0_CLIENT_SECRET_UNAUTHORIZED\")\n\n\tnotFoundDomain := testSecrets.MustGetField(\"AUTH0_DOMAIN_NOT_FOUND\")\n\tinactiveClientSecret := testSecrets.MustGetField(\"AUTH0_CLIENT_SECRET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a auth0 client id %s client secret %s domain %s\", clientId, clientSecret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Auth0oauth,\n\t\t\t\t\tRedacted:     clientId,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unauthorized\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a auth0 client id %s client secret %s domain %s\", clientIdUnauthorized, clientSecretUnauthorized, domainUnauthorized)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Auth0oauth,\n\t\t\t\t\tRedacted:     clientIdUnauthorized,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Auth0oauth,\n\t\t\t\t\tRedacted:     clientId,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"domain does not exists\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a auth0 client id %s client secret %s domain %s\", clientId, clientSecret, notFoundDomain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Auth0oauth,\n\t\t\t\t\tRedacted:     clientId,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Auth0oauth.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw v2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Auth0oauth.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/auth0oauth/auth0oauth_test.go",
    "content": "package auth0oauth\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAuth0oAuth_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t# do not share these credentials\n\t\t\t\tauth0_credentials file: \n\t\t\t\t\tauth0_clientID: kYWr_tL4eYBtqIIvKfSf2-e4T9Cw1CtwE8ufoESVBB7Hi1U\n\t\t\t\t\tsecret: rXwGtKCleBsaUfpchggQEAy_yhzWnqv4_GzJivBif85bqiJi3ZA63DAauoJ2PF27fvS-MBqIYgxH0vZaL1s5314lgPDLqHXjZsY59PSew63A_L6rySqcy5J3rFcGcpdeSQ_tTx1kCXOZY_JUy \n\t\t\t\t\tdomain: 9-KhTIdSopSaMQ2v1YxdFEJN-HNgt7Mn7E8xkfQNqd51AzSGQu2yRaFauth0.com\n\t\t\t\t`,\n\t\t\twant: []string{\"kYWr_tL4eYBtqIIvKfSf2-e4T9Cw1CtwE8ufoESVBB7Hi1UrXwGtKCleBsaUfpchggQEAy_yhzWnqv4_GzJivBif85bqiJi3ZA63DAauoJ2PF27fvS-MBqIYgxH0vZaL1s5314lgPDLqHXjZsY59PSew63A_L6rySqcy5J3rFcGcpdeSQ_tTx1kCXOZY_JUy\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{auth0 rP_yIAV6HD3Oe4zr6KawRXGbq6UCWbeC1kbjQkVhqG4vcLCc2}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA 1PMNVllg_WHl2OGdPLSs73Z1NHjQ85nafV2qqKbQivoqEz4RSo6MFBoNxF-XqFKjEyt6WJfZvAslDPrwY-B-MLsN13rgxRrAiFw9d8Rl1e0uC0FCNDC5EALR9kq7cs4Atz_Dv4r5YT8drkV1_T5HMjH8SJb2B-jD}</secret>\n  \t\t\t\t\t<domain>{kXFuauth0.com}</domain>\n\t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"rP_yIAV6HD3Oe4zr6KawRXGbq6UCWbeC1kbjQkVhqG4vcLCc21PMNVllg_WHl2OGdPLSs73Z1NHjQ85nafV2qqKbQivoqEz4RSo6MFBoNxF-XqFKjEyt6WJfZvAslDPrwY-B-MLsN13rgxRrAiFw9d8Rl1e0uC0FCNDC5EALR9kq7cs4Atz_Dv4r5YT8drkV1_T5HMjH8SJb2B-jD\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t# do not share these credentials\n\t\t\t\tauth0_credentials file: \n\t\t\t\t\tauth0_clientID: e4T9Cw1CtwE8ufoESVBB7Hi1U-e4T9Cw1CtwE8ufoESVBB7Hi1U\n\t\t\t\t\tsecret: MBqIYgxH0vZaL1s5314lgPDLqHX^ZsY59PSew63A_L6rySqcy5J3rFcGcpdeSQ_+tTx1kCXOZY_JUy-rXwGtKCleBsaUfpchggQEAy_yhzWnqv4_GzJivBif85bqiJi3ZA63DAauoJ2PF27fvS \n\t\t\t\t\tdomain: 9-KhTIdSopSaMQ2v1YxdFEJN#qd51AzSGQu2yRaFauth1.com\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/autodesk/autodesk.go",
    "content": "package autodesk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"autodesk\"}) + `\\b([0-9A-Za-z]{32})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"autodesk\"}) + `\\b([0-9A-Za-z]{16})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"autodesk\"}\n}\n\n// FromData will find and optionally verify Autodesk secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, secretMatch := range secretMatches {\n\t\t\tresSecret := strings.TrimSpace(secretMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Autodesk,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resSecret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tpayload := strings.NewReader(fmt.Sprintf(`grant_type=client_credentials&client_id=%s&client_secret=%s`, resMatch, resSecret))\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://developer.api.autodesk.com/authentication/v1/authenticate\", payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Autodesk\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Autodesk provides software services for design and engineering. Autodesk API keys can be used to access and modify data within Autodesk services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/autodesk/autodesk_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage autodesk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAutodesk_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tid := testSecrets.MustGetField(\"AUTODESK_ID\")\n\tsecret := testSecrets.MustGetField(\"AUTODESK_SECRET\")\n\tinactiveID := testSecrets.MustGetField(\"AUTODESK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a autodesk secret %s within autodesk id %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Autodesk,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Autodesk,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Autodesk.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Autodesk.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/autodesk/autodesk_test.go",
    "content": "package autodesk\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAutoDesk_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using autodesk Key=2j8Rl67MjoMruYfyIBgGzy2pxcxIQfet\n\t\t\t\t[DEBUG] Using autodesk Secret=rHfzZhsSRruLM3Fn\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"2j8Rl67MjoMruYfyIBgGzy2pxcxIQfetrHfzZhsSRruLM3Fn\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{autodesk 0xjHuuRZc8n0YS6MGd8e3OakAySlK27q}</id>\n  \t\t\t\t\t<secret>{autodesk AQAAABAAA 0TvJm15Ew8KADWTN}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"0xjHuuRZc8n0YS6MGd8e3OakAySlK27q0TvJm15Ew8KADWTN\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the API\n\t\t\t\t[DEBUG] Using autodesk Key=2mm8Rl67MjoMruYfyIBg5#zy2pxcxIQfet\n\t\t\t\t[DEBUG] Using autodesk Secret=RHGklpa\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/autoklose/autoklose.go",
    "content": "package autoklose\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"autoklose\"}) + `\\b([a-zA-Z0-9-]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"autoklose\"}\n}\n\n// FromData will find and optionally verify Autoklose secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Autoklose,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, extraData, err := verifyMatch(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\t// API Documentation: https://api.aklab.xyz/#auth-info-fd71acd1-2e41-4991-8789-3edfd258479a\n\turl := fmt.Sprintf(\"https://api.autoklose.com/api/me/?api_token=%s\", token)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\treq.Header.Add(\"Accept\", \"application/json\")\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\n\t\tvar responseBody map[string]interface{}\n\t\tif err := json.Unmarshal(bodyBytes, &responseBody); err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\n\t\tif email, ok := responseBody[\"email\"].(string); ok {\n\t\t\treturn true, map[string]string{\"email\": email}, nil\n\t\t}\n\t\treturn true, nil, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Autoklose\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/autoklose/autoklose_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage autoklose\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAutoklose_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AUTOKLOSE\")\n\tinactiveSecret := testSecrets.MustGetField(\"AUTOKLOSE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a autoklose secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Autoklose,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"email\": \"mladen.stevanovic@vanillasoft.com\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Autoklose,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Autoklose.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Autoklose.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/autoklose/autoklose_test.go",
    "content": "package autoklose\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAutoKlose_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the autoklose API\n\t\t\t\t[DEBUG] Using Key=KRXaU9GK3f9yHG1FS-mbwhsIXdW22epH\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"KRXaU9GK3f9yHG1FS-mbwhsIXdW22epH\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{autoklose}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA Z6Q4KENlmgGJT-M-BLoup9Dmyj2YVC-I}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"Z6Q4KENlmgGJT-M-BLoup9Dmyj2YVC-I\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the autoklose API\n\t\t\t\t[DEBUG] Using Key=KRXaU9GK3f[yHG1FS$]bwhsIXdW22epH\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/autopilot/autopilot.go",
    "content": "package autopilot\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"autopilot\"}) + `\\b([0-9a-f]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"autopilot\"}\n}\n\n// FromData will find and optionally verify AutoPilot secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_AutoPilot,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api2.autopilothq.com/v1/account\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"autopilotapikey\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AutoPilot\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"AutoPilot is a marketing automation platform. AutoPilot API keys can be used to access and manage marketing data and campaigns.\"\n}\n"
  },
  {
    "path": "pkg/detectors/autopilot/autopilot_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage autopilot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAutoPilot_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AUTOPILOT\")\n\tinactiveSecret := testSecrets.MustGetField(\"AUTOPILOT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a autopilot secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AutoPilot,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AutoPilot,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AutoPilot.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"AutoPilot.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/autopilot/autopilot_test.go",
    "content": "package autopilot\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAutoPilot_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the autopilot API\n\t\t\t\t[DEBUG] Using Key=0fd87cfb1ca6c38c5f1ae5be7b0e395e\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"0fd87cfb1ca6c38c5f1ae5be7b0e395e\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{autopilot}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA 60aa8204a2b1dec8af7de45737fed7be}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"60aa8204a2b1dec8af7de45737fed7be\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the autopilot API\n\t\t\t\t[DEBUG] Using Key=KRXaU9GK3f[yHG1FS$]bwhsIXdW22epH\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/avazapersonalaccesstoken/avazapersonalaccesstoken.go",
    "content": "package avazapersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\t// The number prefix increments for every Personal Access Token created.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"avaza\"}) + `\\b([0-9]+-[0-9a-f]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"avaza\"}\n}\n\n// FromData will find and optionally verify AvazaPersonalAccessToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_AvazaPersonalAccessToken,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, err := verifyMatch(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\t// API Documentation: https://api.avaza.com/swagger/ui/index#!/Account/Account_Get\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.avaza.com/api/Account\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AvazaPersonalAccessToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/avazapersonalaccesstoken/avazapersonalaccesstoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage avazapersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAvazaPersonalAccessToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AVAZAPERSONALACCESSTOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"AVAZAPERSONALACCESSTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a avaza secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AvazaPersonalAccessToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AvazaPersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AvazaPersonalAccessToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"AvazaPersonalAccessToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/avazapersonalaccesstoken/avazapersonalaccesstoken_test.go",
    "content": "package avazapersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAvazaPersonalAccessToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the avaza API\n\t\t\t\t[DEBUG] Using Key=01818612883613176996369293-f113ceb9cf4fa63dc367ab4815b0e1edf890745f\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"01818612883613176996369293-f113ceb9cf4fa63dc367ab4815b0e1edf890745f\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{avaza}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA 6605785514902-06e236581be50b798459a53fcb7609032bf813f7}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"6605785514902-06e236581be50b798459a53fcb7609032bf813f7\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the avaza API\n\t\t\t\t[DEBUG] Using Key=01818612883613176996369293-fzz3ceb0mf4fp63dh367xb4815b0e1edf890745f\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/aviationstack/aviationstack.go",
    "content": "package aviationstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"aviationstack\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"aviationstack\"}\n}\n\n// FromData will find and optionally verify AviationStack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_AviationStack,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, err := verifyMatch(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\tclient.Timeout = 10 * time.Second\n\turl := fmt.Sprintf(\"https://api.aviationstack.com/v1/flights?access_key=%s\", token)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AviationStack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"AviationStack is a service providing real-time flight status and aviation data. The API key can be used to access this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/aviationstack/aviationstack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage aviationstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAviationStack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AVIATIONSTACK\")\n\tinactiveSecret := testSecrets.MustGetField(\"AVIATIONSTACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a aviationstack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AviationStack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AviationStack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AviationStack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"AviationStack.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/aviationstack/aviationstack_test.go",
    "content": "package aviationstack\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAviationStack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the aviationstack API\n\t\t\t\t[DEBUG] Using Key=osh0kjinsc2atoaqntoy1hdjppg54449\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"osh0kjinsc2atoaqntoy1hdjppg54449\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{aviationstack}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA 464r3ib5xzipgd36zdzpvm09p00juu0b}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"464r3ib5xzipgd36zdzpvm09p00juu0b\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the aviationstack API\n\t\t\t\t[DEBUG] Using Key=OSh0lMjinsc2atoaqnto[]1hdjppg5449\n\t\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/aws/access_keys/accesskey.go",
    "content": "package access_keys\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tawshttp \"github.com/aws/aws-sdk-go-v2/aws/transport/http\"\n\t\"github.com/aws/aws-sdk-go-v2/config\"\n\t\"github.com/aws/aws-sdk-go-v2/credentials\"\n\t\"github.com/aws/aws-sdk-go-v2/service/sts\"\n\t\"github.com/aws/smithy-go/middleware\"\n\tsmithyhttp \"github.com/aws/smithy-go/transport/http\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aws\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype scanner struct {\n\tverificationClient config.HTTPClient\n\tskipIDs            map[string]struct{}\n\tdetectors.AccountFilter\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\nfunc New(opts ...func(*scanner)) *scanner {\n\tscanner := &scanner{\n\t\tskipIDs: map[string]struct{}{},\n\t}\n\tfor _, opt := range opts {\n\t\topt(scanner)\n\t}\n\n\treturn scanner\n}\n\nfunc WithSkipIDs(skipIDs []string) func(*scanner) {\n\treturn func(s *scanner) {\n\t\tids := map[string]struct{}{}\n\t\tfor _, id := range skipIDs {\n\t\t\tids[id] = struct{}{}\n\t\t}\n\n\t\ts.skipIDs = ids\n\t}\n}\n\nfunc WithAllowedAccounts(accounts []string) func(*scanner) {\n\treturn func(s *scanner) {\n\t\ts.SetAllowedAccounts(accounts)\n\t}\n}\n\nfunc WithDeniedAccounts(accounts []string) func(*scanner) {\n\treturn func(s *scanner) {\n\t\ts.SetDeniedAccounts(accounts)\n\t}\n}\n\n// Ensure the scanner satisfies the interface at compile time.\nvar _ interface {\n\tdetectors.Detector\n\tdetectors.CustomResultsCleaner\n\tdetectors.MultiPartCredentialProvider\n} = (*scanner)(nil)\n\nvar (\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\t// Key types are from this list https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids\n\tidPat = regexp.MustCompile(`\\b((?:AKIA|ABIA|ACCA)[A-Z0-9]{16})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s scanner) Keywords() []string {\n\treturn []string{\n\t\t\"AKIA\",\n\t\t\"ABIA\",\n\t\t\"ACCA\",\n\t}\n}\n\n// The recommended way by AWS is to use the SDK's http client.\n// https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/configure-http.html\n// Note: Using default http.Client causes SignatureInvalid error in response. therefore, based on http default client implementation, we are using the same configuration.\nfunc getDefaultBuildableClient() *awshttp.BuildableClient {\n\treturn awshttp.NewBuildableClient().\n\t\tWithTimeout(common.DefaultResponseTimeout).\n\t\tWithDialerOptions(func(dialer *net.Dialer) {\n\t\t\tdialer.Timeout = 2 * time.Second\n\t\t\tdialer.KeepAlive = 5 * time.Second\n\t\t}).\n\t\tWithTransportOptions(func(tr *http.Transport) {\n\t\t\ttr.Proxy = http.ProxyFromEnvironment\n\t\t\ttr.MaxIdleConns = 5\n\t\t\ttr.IdleConnTimeout = 5 * time.Second\n\t\t\ttr.TLSHandshakeTimeout = 3 * time.Second\n\t\t\ttr.ExpectContinueTimeout = 1 * time.Second\n\t\t})\n}\n\nfunc (s scanner) getAWSBuilableClient() config.HTTPClient {\n\tif s.verificationClient == nil {\n\t\ts.verificationClient = getDefaultBuildableClient()\n\t}\n\treturn s.verificationClient\n}\n\n// FromData will find and optionally verify AWS secrets in a given set of bytes.\nfunc (s scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tlogger := logContext.AddLogger(ctx).Logger().WithName(\"aws\")\n\tdataStr := string(data)\n\tdataStr = aws.UrlEncodedReplacer.Replace(dataStr)\n\n\t// Filter & deduplicate matches.\n\tidMatches := make(map[string]struct{})\n\tfor _, matches := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tidMatches[matches[1]] = struct{}{}\n\t}\n\tsecretMatches := make(map[string]struct{})\n\tfor _, matches := range aws.SecretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tsecretMatches[matches[1]] = struct{}{}\n\t}\n\n\t// Process matches.\n\tfor idMatch := range idMatches {\n\t\tif detectors.StringShannonEntropy(idMatch) < aws.RequiredIdEntropy {\n\t\t\tcontinue\n\t\t}\n\t\tif s.skipIDs != nil {\n\t\t\tif _, ok := s.skipIDs[idMatch]; ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tfor secretMatch := range secretMatches {\n\t\t\tif detectors.StringShannonEntropy(secretMatch) < aws.RequiredSecretEntropy {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AWS,\n\t\t\t\tRaw:          []byte(idMatch),\n\t\t\t\tRedacted:     idMatch,\n\t\t\t\tRawV2:        []byte(idMatch + \":\" + secretMatch),\n\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\"resource_type\": aws.ResourceTypes[idMatch[:4]],\n\t\t\t\t},\n\t\t\t\tAnalysisInfo: map[string]string{\n\t\t\t\t\t\"access_key_id\":     idMatch,\n\t\t\t\t\t\"secret_access_key\": secretMatch,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\t// Decode the AWS Account ID.\n\t\t\taccountID, err := aws.GetAccountNumFromID(idMatch)\n\t\t\tisCanary := false\n\t\t\tif err != nil {\n\t\t\t\tlogger.V(3).Info(\"Failed to decode AWS Account ID\", \"err\", err)\n\t\t\t} else {\n\t\t\t\ts1.ExtraData[\"account\"] = accountID\n\n\t\t\t\t// Check if this is a canary token\n\t\t\t\tif _, ok := thinkstCanaryList[accountID]; ok {\n\t\t\t\t\tisCanary = true\n\t\t\t\t\ts1.ExtraData[\"message\"] = thinkstMessage\n\t\t\t\t}\n\t\t\t\tif _, ok := thinkstKnockoffsCanaryList[accountID]; ok {\n\t\t\t\t\tisCanary = true\n\t\t\t\t\ts1.ExtraData[\"message\"] = thinkstKnockoffsMessage\n\t\t\t\t}\n\n\t\t\t\tif isCanary {\n\t\t\t\t\ts1.ExtraData[\"is_canary\"] = \"true\"\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\t// Check account filtering before verification for ALL secrets (including canaries)\n\t\t\t\tif accountID != \"\" {\n\t\t\t\t\tif s.ShouldSkipAccount(accountID) {\n\t\t\t\t\t\tvar skipReason string\n\t\t\t\t\t\tif s.IsInDenyList(accountID) {\n\t\t\t\t\t\t\tskipReason = aws.VerificationErrAccountIDInDenyList\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tskipReason = aws.VerificationErrAccountIDNotInAllowList\n\t\t\t\t\t\t}\n\t\t\t\t\t\ts1.SetVerificationError(fmt.Errorf(\"%s\", skipReason), secretMatch)\n\t\t\t\t\t\tresults = append(results, s1)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Perform verification based on token type\n\t\t\t\tif isCanary {\n\t\t\t\t\t// Canary verification logic\n\t\t\t\t\tverified, arn, err := s.verifyCanary(ctx, idMatch, secretMatch)\n\t\t\t\t\ts1.Verified = verified\n\t\t\t\t\tif arn != \"\" {\n\t\t\t\t\t\ts1.ExtraData[\"arn\"] = arn\n\t\t\t\t\t}\n\t\t\t\t\ts1.SetVerificationError(err, secretMatch)\n\t\t\t\t} else {\n\t\t\t\t\t// Normal verification logic\n\t\t\t\t\tisVerified, extraData, verificationErr := s.verifyMatch(ctx, idMatch, secretMatch, len(secretMatches) > 1)\n\t\t\t\t\ts1.Verified = isVerified\n\n\t\t\t\t\t// Log if the calculated ID does not match the ID value from verification.\n\t\t\t\t\t// Should only be edge cases at most.\n\t\t\t\t\tif accountID != \"\" && extraData[\"account\"] != \"\" && extraData[\"account\"] != s1.ExtraData[\"account\"] {\n\t\t\t\t\t\tlogger.V(2).Info(\"Calculated account ID does not match actual account ID\", \"calculated\", accountID, \"actual\", extraData[\"account\"])\n\t\t\t\t\t}\n\n\t\t\t\t\t// Append the extraData to the existing ExtraData map.\n\t\t\t\t\tfor k, v := range extraData {\n\t\t\t\t\t\ts1.ExtraData[k] = v\n\t\t\t\t\t}\n\t\t\t\t\ts1.SetVerificationError(verificationErr, secretMatch)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !s1.Verified && aws.FalsePositiveSecretPat.MatchString(secretMatch) {\n\t\t\t\t// Unverified results that look like hashes are probably not secrets\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t\t// 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.\n\t\t\tif s1.Verified {\n\t\t\t\tdelete(secretMatches, secretMatch)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s scanner) ShouldCleanResultsIrrespectiveOfConfiguration() bool {\n\treturn true\n}\n\nconst (\n\tmethod   = \"GET\"\n\tservice  = \"sts\"\n\thost     = \"sts.amazonaws.com\"\n\tregion   = \"us-east-1\"\n\tendpoint = \"https://sts.amazonaws.com\"\n)\n\nfunc (s scanner) verifyMatch(ctx context.Context, resIDMatch, resSecretMatch string, retryOn403 bool) (bool, map[string]string, error) {\n\t// Prep AWS Creds for STS\n\tcfg, err := config.LoadDefaultConfig(ctx,\n\t\tconfig.WithRegion(region),\n\t\tconfig.WithHTTPClient(s.getAWSBuilableClient()),\n\t\tconfig.WithCredentialsProvider(\n\t\t\tcredentials.NewStaticCredentialsProvider(resIDMatch, resSecretMatch, \"\"),\n\t\t),\n\t)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\t// Create STS client\n\tstsClient := sts.NewFromConfig(cfg, func(o *sts.Options) {\n\t\to.APIOptions = append(o.APIOptions, replaceUserAgentMiddleware)\n\t})\n\n\t// Make the GetCallerIdentity API call\n\tresp, err := stsClient.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{})\n\tif err != nil {\n\t\t// Experimentation has indicated that if you make multiple GetCallerIdentity requests within five seconds that\n\t\t// share a key ID but are signed with different secrets the second one will be rejected with a 403 that\n\t\t// carries a SignatureDoesNotMatch code in its body. This happens even if the second ID-secret pair is\n\t\t// valid. Since this is exactly our access pattern, we need to work around it.\n\t\t//\n\t\t// Fortunately, experimentation has also revealed a workaround: simply resubmit the second request. The\n\t\t// response to the resubmission will be as expected.\n\t\t//\n\t\t// We are clearly deep in the guts of AWS implementation details here, so this all might change with no\n\t\t// notice. If you're here because something in this detector broke, you have my condolences.\n\t\tif strings.Contains(err.Error(), \"StatusCode: 403\") {\n\t\t\tif retryOn403 {\n\t\t\t\treturn s.verifyMatch(ctx, resIDMatch, resSecretMatch, false)\n\t\t\t}\n\t\t\treturn false, nil, nil\n\t\t} else if strings.Contains(err.Error(), \"InvalidClientTokenId\") {\n\t\t\treturn false, nil, nil\n\t\t}\n\t\treturn false, nil, fmt.Errorf(\"request returned unexpected error: %w\", err)\n\t}\n\n\textraData := map[string]string{\n\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/aws/\",\n\t\t\"account\":        *resp.Account,\n\t\t\"user_id\":        *resp.UserId,\n\t\t\"arn\":            *resp.Arn,\n\t}\n\treturn true, extraData, nil\n}\n\nfunc (s scanner) CleanResults(results []detectors.Result) []detectors.Result {\n\treturn aws.CleanResults(results)\n}\n\nfunc (s scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AWS\n}\n\nfunc (s scanner) Description() string {\n\treturn \"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.\"\n}\n\n// Adds a custom Build middleware to the stack to replace the User-Agent header of the final request\n// https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/middleware.html\nfunc replaceUserAgentMiddleware(stack *middleware.Stack) error {\n\treturn stack.Build.Add(\n\t\tmiddleware.BuildMiddlewareFunc(\n\t\t\t\"ReplaceUserAgent\",\n\t\t\tfunc(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) (\n\t\t\t\tout middleware.BuildOutput, metadata middleware.Metadata, err error,\n\t\t\t) {\n\t\t\t\treq, ok := in.Request.(*smithyhttp.Request)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn next.HandleBuild(ctx, in)\n\t\t\t\t}\n\t\t\t\treq.Header.Set(\"User-Agent\", common.UserAgent())\n\t\t\t\treturn next.HandleBuild(ctx, in)\n\t\t\t},\n\t\t),\n\t\tmiddleware.After,\n\t)\n}\n"
  },
  {
    "path": "pkg/detectors/aws/access_keys/accesskey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage access_keys\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/brianvoe/gofakeit/v7\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nconst canaryAccessKeyID = \"AKIASP2TPHJSQH3FJRUX\"\n\nvar unverifiedSecretClient = common.ConstantResponseHttpClient(403, `{\"Error\": {\"Code\": \"InvalidClientTokenId\"} }`)\n\n// Our AWS detector interacts with AWS in an (expectedly) uncommon way that triggers some odd AWS behavior. (This odd\n// behavior doesn't affect \"normal\" AWS use, so it's not really \"broken\" - it's just something that we have to work\n// around.) The AWS detector code has a long comment explaining this in more detail, but the basic issue is that AWS STS\n// is stateful, so the behavior of these tests can vary depending on which of them you run, and in which order. This\n// particular test (TestAWS_FromChunk_InvalidValidReuseIDSequence) duplicates some logic in the \"big\" test table in the\n// other test in this file, but extracting it in this way as well makes it fail more consistently when it's supposed to\n// fail, which is why it's extracted.\nfunc TestAWS_FromChunk_InvalidValidReuseIDSequence(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AWS\")\n\tid := testSecrets.MustGetField(\"AWS_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"AWS_INACTIVE\")\n\n\td := scanner{}\n\n\tignoreOpts := []cmp.Option{cmpopts.IgnoreFields(detectors.Result{}, \"RawV2\", \"Raw\", \"verificationError\")}\n\n\tgot, err := d.FromData(ctx, true, []byte(fmt.Sprintf(\"aws %s %s\", id, inactiveSecret)))\n\tif assert.NoError(t, err) {\n\t\twant := []detectors.Result{\n\t\t\t{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AWS,\n\t\t\t\tVerified:     false,\n\t\t\t\tRedacted:     \"AKIAZAVB57H55F3T4BKH\",\n\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\"resource_type\": \"Access key\",\n\t\t\t\t\t\"account\":       \"619888638459\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tif diff := cmp.Diff(got, want, ignoreOpts...); diff != \"\" {\n\t\t\tt.Errorf(\"AWS.FromData() (valid ID, invalid secret) diff: (-got +want)\\n%s\", diff)\n\t\t}\n\t}\n\n\tgot, err = d.FromData(ctx, true, []byte(fmt.Sprintf(\"aws %s %s\", id, secret)))\n\tif assert.NoError(t, err) {\n\t\twant := []detectors.Result{\n\t\t\t{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AWS,\n\t\t\t\tVerified:     true,\n\t\t\t\tRedacted:     \"AKIAZAVB57H55F3T4BKH\",\n\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\"resource_type\":  \"Access key\",\n\t\t\t\t\t\"account\":        \"619888638459\",\n\t\t\t\t\t\"arn\":            \"arn:aws:iam::619888638459:user/trufflehog-aws-detector-tester\",\n\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/aws/\",\n\t\t\t\t\t\"user_id\":        \"AIDAZAVB57H5V3Q4ACRGM\",\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tif diff := cmp.Diff(got, want, ignoreOpts...); diff != \"\" {\n\t\t\tt.Errorf(\"AWS.FromData() (valid secret after invalid secret using same ID) diff: (-got +want)\\n%s\", diff)\n\t\t}\n\t}\n}\n\nfunc TestAWS_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AWS\")\n\tid := testSecrets.MustGetField(\"AWS_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"AWS_INACTIVE\")\n\tinactiveID := id[:len(id)-3] + \"XYZ\"\n\n\thash := gofakeit.Password(true, true, true, false, false, 10)\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                  string\n\t\ts                     scanner\n\t\targs                  args\n\t\twant                  []detectors.Result\n\t\twantErr               bool\n\t\twantVerificationError bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a aws secret %s within aws %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AWS,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     \"AKIAZAVB57H55F3T4BKH\",\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"resource_type\":  \"Access key\",\n\t\t\t\t\t\t\"account\":        \"619888638459\",\n\t\t\t\t\t\t\"arn\":            \"arn:aws:iam::619888638459:user/trufflehog-aws-detector-tester\",\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/aws/\",\n\t\t\t\t\t\t\"user_id\":        \"AIDAZAVB57H5V3Q4ACRGM\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    scanner{verificationClient: unverifiedSecretClient},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AWS,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"AKIAZAVB57H55F3T4BKH\",\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"resource_type\": \"Access key\",\n\t\t\t\t\t\t\"account\":       \"619888638459\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found two, one included for every ID found\",\n\t\t\ts:    scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AWS,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"AKIAZAVB57H55F3T4XYZ\",\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"resource_type\": \"Access key\",\n\t\t\t\t\t\t\"account\":       \"619888638459\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AWS,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     \"AKIAZAVB57H55F3T4BKH\",\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"resource_type\":  \"Access key\",\n\t\t\t\t\t\t\"account\":        \"619888638459\",\n\t\t\t\t\t\t\"arn\":            \"arn:aws:iam::619888638459:user/trufflehog-aws-detector-tester\",\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/aws/\",\n\t\t\t\t\t\t\"user_id\":        \"AIDAZAVB57H5V3Q4ACRGM\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found, because unverified secret was a hash\",\n\t\t\ts:    scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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.\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found two, returned both because the active secret for one paired with the inactive ID, despite the hash\",\n\t\t\ts:    scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AWS,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     \"AKIAZAVB57H55F3T4BKH\",\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"resource_type\":  \"Access key\",\n\t\t\t\t\t\t\"account\":        \"619888638459\",\n\t\t\t\t\t\t\"arn\":            \"arn:aws:iam::619888638459:user/trufflehog-aws-detector-tester\",\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/aws/\",\n\t\t\t\t\t\t\"user_id\":        \"AIDAZAVB57H5V3Q4ACRGM\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified, with leading +\",\n\t\t\ts: scanner{\n\t\t\t\tverificationClient: unverifiedSecretClient,\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AWS,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"AKIAZAVB57H55F3T4BKH\",\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"resource_type\": \"Access key\",\n\t\t\t\t\t\t\"account\":       \"619888638459\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"skipped\",\n\t\t\ts: scanner{\n\t\t\t\tskipIDs: map[string]struct{}{\n\t\t\t\t\t\"AKIAZAVB57H55F3T4BKH\": {},\n\t\t\t\t},\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for http timeout\",\n\t\t\ts: scanner{\n\t\t\t\tverificationClient: common.SaneHttpClientTimeOut(1 * time.Microsecond),\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a aws secret %s within aws %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AWS,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"AKIAZAVB57H55F3T4BKH\",\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"resource_type\": \"Access key\",\n\t\t\t\t\t\t\"account\":       \"619888638459\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:               false,\n\t\t\twantVerificationError: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified due to unexpected http response status\",\n\t\t\ts: scanner{\n\t\t\t\tverificationClient: common.ConstantResponseHttpClient(500, \"internal server error\"),\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a aws secret %s within aws %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AWS,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"AKIAZAVB57H55F3T4BKH\",\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"resource_type\": \"Access key\",\n\t\t\t\t\t\t\"account\":       \"619888638459\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:               false,\n\t\t\twantVerificationError: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified due to invalid aws_secret with valid canary access_key_id\",\n\t\t\ts:    scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a aws secret %s within aws %s\", inactiveSecret, canaryAccessKeyID)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AWS,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     canaryAccessKeyID,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"resource_type\": \"Access key\",\n\t\t\t\t\t\t\"account\":       \"171436882533\",\n\t\t\t\t\t\t\"is_canary\":     \"true\",\n\t\t\t\t\t\t\"message\":       \"This is an AWS canary token generated at canarytokens.org, and was not set off; learn more here: https://trufflesecurity.com/canaries\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:               false,\n\t\t\twantVerificationError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, valid canary token with no verification\",\n\t\t\ts:    scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a aws secret %s within aws %s\", secret, canaryAccessKeyID)),\n\t\t\t\tverify: false,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AWS,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     canaryAccessKeyID,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"resource_type\": \"Access key\",\n\t\t\t\t\t\t\"account\":       \"171436882533\",\n\t\t\t\t\t\t\"is_canary\":     \"true\",\n\t\t\t\t\t\t\"message\":       \"This is an AWS canary token generated at canarytokens.org, and was not set off; learn more here: https://trufflesecurity.com/canaries\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:               false,\n\t\t\twantVerificationError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"verified secret checked directly after unverified secret with same key id\",\n\t\t\ts:    scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"%s\\n%s\\n%s\", inactiveSecret, id, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AWS,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"AKIAZAVB57H55F3T4BKH\",\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"resource_type\": \"Access key\",\n\t\t\t\t\t\t\"account\":       \"619888638459\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AWS,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     \"AKIAZAVB57H55F3T4BKH\",\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"resource_type\":  \"Access key\",\n\t\t\t\t\t\t\"account\":        \"619888638459\",\n\t\t\t\t\t\t\"arn\":            \"arn:aws:iam::619888638459:user/trufflehog-aws-detector-tester\",\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/aws/\",\n\t\t\t\t\t\t\"user_id\":        \"AIDAZAVB57H5V3Q4ACRGM\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := tt.s\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AWS.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationError {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError %v, verification error = %v\", tt.wantVerificationError, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := []cmp.Option{\n\t\t\t\tcmpopts.IgnoreFields(detectors.Result{}, \"RawV2\", \"Raw\", \"verificationError\"),\n\t\t\t\tcmpopts.SortSlices(func(x, y detectors.Result) bool {\n\t\t\t\t\treturn x.Redacted < y.Redacted\n\t\t\t\t}),\n\t\t\t}\n\n\t\t\tsortResults(tt.want)\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts...); diff != \"\" {\n\t\t\t\tt.Errorf(\"AWS.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Helper function to sort results due to the order of the redacted\nfunc sortResults(results []detectors.Result) {\n\tsort.SliceStable(results, func(i, j int) bool {\n\t\treturn results[i].Redacted < results[j].Redacted\n\t})\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/aws/access_keys/accesskey_test.go",
    "content": "package access_keys\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAWS_Pattern(t *testing.T) {\n\td := scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\taws credentials{\n\t\t\t\t\tid: ABIAS9L8MS5IPHTZPPUQ\n\t\t\t\t\tsecret: .v2QPKHl7LcdVYsjaR4LgQiZ1zw3MAnMyiondXC63;\n\t\t\t\t}\n\t\t\t`,\n\t\t\twant: []string{\"ABIAS9L8MS5IPHTZPPUQ:v2QPKHl7LcdVYsjaR4LgQiZ1zw3MAnMyiondXC63\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{AKIAWGXZ9OPDOWUJMZGI}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA .v2QPKHl7LcdVYsjaR4LgQiZ1zw3MAnMyiondXC63;}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"AKIAWGXZ9OPDOWUJMZGI:v2QPKHl7LcdVYsjaR4LgQiZ1zw3MAnMyiondXC63\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\taws credentials{\n\t\t\t\t\tid: AKIAs9L8MS5iPHTZPPUQ\n\t\t\t\t\tsecret: $YenOG.PKHl7LcdVYsjaR4LgQiZ1zw3MAnMyiondXC63;\n\t\t\t\t}\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAWS_WithAllowedAccounts(t *testing.T) {\n\taccounts := []string{\"123456789012\", \"999888777666\"}\n\ts := New(WithAllowedAccounts(accounts))\n\n\t// Test that allowed accounts are properly configured\n\tshouldSkip := s.ShouldSkipAccount(\"123456789012\")\n\trequire.False(t, shouldSkip)\n\trequire.True(t, s.IsInAllowList(\"123456789012\"))\n\n\t// Test that non-allowed accounts are skipped\n\tshouldSkip = s.ShouldSkipAccount(\"111222333444\")\n\trequire.True(t, shouldSkip)\n\trequire.False(t, s.IsInAllowList(\"111222333444\"))\n}\n\nfunc TestAWS_WithDeniedAccounts(t *testing.T) {\n\taccounts := []string{\"123456789012\", \"999888777666\"}\n\ts := New(WithDeniedAccounts(accounts))\n\n\t// Test that denied accounts are properly skipped\n\tshouldSkip := s.ShouldSkipAccount(\"123456789012\")\n\trequire.True(t, shouldSkip)\n\trequire.True(t, s.IsInDenyList(\"123456789012\"))\n\n\t// Test that non-denied accounts are not skipped\n\tshouldSkip = s.ShouldSkipAccount(\"111222333444\")\n\trequire.False(t, shouldSkip)\n\trequire.False(t, s.IsInDenyList(\"111222333444\"))\n}\n\nfunc TestAWS_CanaryTokenFiltering(t *testing.T) {\n\t// Using known canary token from integration tests\n\tcanaryAccessKeyID := \"AKIASP2TPHJSQH3FJRUX\" // Account ID: 171436882533\n\tcanarySecret := \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\"\n\ttestData := []byte(fmt.Sprintf(\"%s:%s\", canaryAccessKeyID, canarySecret))\n\n\tt.Run(\"debug canary detection\", func(t *testing.T) {\n\t\t// First, let's test basic canary detection without verification\n\t\ts := New()\n\n\t\tresults, err := s.FromData(context.Background(), false, testData) // verify = false\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, results, 1)\n\n\t\tresult := results[0]\n\t\tt.Logf(\"Result without verification - Verified: %v, Account: %s, IsCanary: %s, Message: %s\",\n\t\t\tresult.Verified, result.ExtraData[\"account\"], result.ExtraData[\"is_canary\"], result.ExtraData[\"message\"])\n\n\t\t// Should detect as canary but not verify (since verify=false)\n\t\trequire.False(t, result.Verified)\n\t\trequire.Equal(t, \"171436882533\", result.ExtraData[\"account\"])\n\t\trequire.Equal(t, \"true\", result.ExtraData[\"is_canary\"])\n\t\trequire.Contains(t, result.ExtraData[\"message\"], \"canarytokens.org\")\n\t})\n\n\tt.Run(\"canary token with allow list - account not allowed\", func(t *testing.T) {\n\t\t// Configure scanner with allow list that excludes the canary account\n\t\ts := New(WithAllowedAccounts([]string{\"123456789012\", \"999888777666\"}))\n\n\t\tresults, err := s.FromData(context.Background(), true, testData)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, results, 1)\n\n\t\tresult := results[0]\n\t\t// Should detect the canary token but not verify it due to filtering\n\t\trequire.False(t, result.Verified)\n\t\trequire.NotNil(t, result.VerificationError())\n\t\trequire.Contains(t, result.VerificationError().Error(), \"not in the allow list\")\n\t\trequire.Equal(t, \"171436882533\", result.ExtraData[\"account\"])\n\t\trequire.Equal(t, \"true\", result.ExtraData[\"is_canary\"])\n\t})\n\n\tt.Run(\"canary token with deny list - account denied\", func(t *testing.T) {\n\t\t// Configure scanner with deny list that includes the canary account\n\t\ts := New(WithDeniedAccounts([]string{\"171436882533\", \"123456789012\"}))\n\n\t\tresults, err := s.FromData(context.Background(), true, testData)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, results, 1)\n\n\t\tresult := results[0]\n\t\t// Should detect the canary token but not verify it due to filtering\n\t\trequire.False(t, result.Verified)\n\t\trequire.NotNil(t, result.VerificationError())\n\t\trequire.Contains(t, result.VerificationError().Error(), \"in the deny list\")\n\t\trequire.Equal(t, \"171436882533\", result.ExtraData[\"account\"])\n\t\trequire.Equal(t, \"true\", result.ExtraData[\"is_canary\"])\n\t})\n\n\tt.Run(\"precedence test - deny list takes precedence over allow list\", func(t *testing.T) {\n\t\t// Configure scanner where canary account is in both allow and deny lists\n\t\ts := New(\n\t\t\tWithAllowedAccounts([]string{\"171436882533\", \"123456789012\"}),\n\t\t\tWithDeniedAccounts([]string{\"171436882533\"}),\n\t\t)\n\n\t\tresults, err := s.FromData(context.Background(), true, testData)\n\t\trequire.NoError(t, err)\n\t\trequire.Len(t, results, 1)\n\n\t\tresult := results[0]\n\t\t// Should detect the canary token but not verify it since deny takes precedence\n\t\trequire.False(t, result.Verified)\n\t\trequire.NotNil(t, result.VerificationError())\n\t\trequire.Contains(t, result.VerificationError().Error(), \"in the deny list\")\n\t\trequire.Equal(t, \"171436882533\", result.ExtraData[\"account\"])\n\t\trequire.Equal(t, \"true\", result.ExtraData[\"is_canary\"])\n\t})\n}\n"
  },
  {
    "path": "pkg/detectors/aws/access_keys/canary.go",
    "content": "package access_keys\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\t\"github.com/aws/aws-sdk-go-v2/config\"\n\t\"github.com/aws/aws-sdk-go-v2/credentials\"\n\t\"github.com/aws/aws-sdk-go-v2/service/sns\"\n)\n\nconst thinkstMessage = \"This is an AWS canary token generated at canarytokens.org.\"\nconst thinkstKnockoffsMessage = \"This is an off brand AWS Canary inspired by canarytokens.org.\"\n\nvar (\n\tthinkstCanaryList = map[string]struct{}{\n\t\t\"052310077262\": {},\n\t\t\"171436882533\": {},\n\t\t\"534261010715\": {},\n\t\t\"595918472158\": {},\n\t\t\"717712589309\": {},\n\t\t\"819147034852\": {},\n\t\t\"992382622183\": {},\n\t\t\"730335385048\": {},\n\t\t\"266735846894\": {},\n\t\t\"893192397702\": {},\n\t}\n\tthinkstKnockoffsCanaryList = map[string]struct{}{\n\t\t\"044858866125\": {},\n\t\t\"251535659677\": {},\n\t\t\"344043088457\": {},\n\t\t\"351906852752\": {},\n\t\t\"390477818340\": {},\n\t\t\"426127672474\": {},\n\t\t\"427150556519\": {},\n\t\t\"439872796651\": {},\n\t\t\"445142720921\": {},\n\t\t\"465867158099\": {},\n\t\t\"637958123769\": {},\n\t\t\"693412236332\": {},\n\t\t\"732624840810\": {},\n\t\t\"735421457923\": {},\n\t\t\"959235150393\": {},\n\t\t\"982842642351\": {},\n\t}\n)\n\nfunc (s scanner) verifyCanary(ctx context.Context, resIDMatch, resSecretMatch string) (bool, string, error) {\n\t// Prep AWS Creds for SNS\n\tcfg, err := config.LoadDefaultConfig(ctx,\n\t\tconfig.WithRegion(region),\n\t\tconfig.WithHTTPClient(s.getAWSBuilableClient()),\n\t\tconfig.WithCredentialsProvider(\n\t\t\tcredentials.NewStaticCredentialsProvider(resIDMatch, resSecretMatch, \"\"),\n\t\t),\n\t)\n\tif err != nil {\n\t\treturn false, \"\", err\n\t}\n\tsvc := sns.NewFromConfig(cfg, func(o *sns.Options) {\n\t\to.APIOptions = append(o.APIOptions, replaceUserAgentMiddleware)\n\t})\n\n\t// Prep vars and Publish to SNS\n\t_, err = svc.Publish(ctx, &sns.PublishInput{\n\t\tMessage:     aws.String(\"foo\"),\n\t\tPhoneNumber: aws.String(\"1\"),\n\t})\n\n\tif strings.Contains(err.Error(), \"not authorized to perform\") {\n\t\tarn := strings.Split(err.Error(), \"User: \")[1]\n\t\tarn = strings.Split(arn, \" is not authorized to perform: \")[0]\n\t\treturn true, arn, nil\n\t} else if strings.Contains(err.Error(), \"does not match the signature you provided\") {\n\t\treturn false, \"\", nil\n\t} else if strings.Contains(err.Error(), \"status code: 403\") || strings.Contains(err.Error(), \"InvalidClientTokenId\") {\n\t\treturn false, \"\", nil\n\t} else {\n\t\treturn false, \"\", err\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/aws/common.go",
    "content": "package aws\n\nimport regexp \"github.com/wasilibs/go-re2\"\n\nconst (\n\tRequiredIdEntropy     = 3.0\n\tRequiredSecretEntropy = 4.25\n)\n\n// Verification error messages\nconst (\n\tVerificationErrAccountIDInDenyList     = \"Account ID is in the deny list for verification\"\n\tVerificationErrAccountIDNotInAllowList = \"Account ID is not in the allow list for verification\"\n)\n\nvar SecretPat = regexp.MustCompile(`(?:[^A-Za-z0-9+/]|\\A)([A-Za-z0-9+/]{40})(?:[^A-Za-z0-9+/]|\\z)`)\n\ntype IdentityResponse struct {\n\tGetCallerIdentityResponse struct {\n\t\tGetCallerIdentityResult struct {\n\t\t\tAccount string `json:\"Account\"`\n\t\t\tArn     string `json:\"Arn\"`\n\t\t\tUserID  string `json:\"UserId\"`\n\t\t} `json:\"GetCallerIdentityResult\"`\n\t\tResponseMetadata struct {\n\t\t\tRequestID string `json:\"RequestId\"`\n\t\t} `json:\"ResponseMetadata\"`\n\t} `json:\"GetCallerIdentityResponse\"`\n}\n\ntype Error struct {\n\tCode    string `json:\"Code\"`\n\tMessage string `json:\"Message\"`\n}\n\ntype ErrorResponseBody struct {\n\tError Error `json:\"Error\"`\n}\n"
  },
  {
    "path": "pkg/detectors/aws/session_keys/sessionkey.go",
    "content": "package session_keys\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aws\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype scanner struct {\n\t*detectors.CustomMultiPartCredentialProvider\n\tverificationClient *http.Client\n\tskipIDs            map[string]struct{}\n\tdetectors.AccountFilter\n}\n\nfunc New(opts ...func(*scanner)) *scanner {\n\tscanner := &scanner{\n\t\tskipIDs: map[string]struct{}{},\n\t}\n\tfor _, opt := range opts {\n\t\topt(scanner)\n\t}\n\n\tscanner.CustomMultiPartCredentialProvider = detectors.NewCustomMultiPartCredentialProvider(2048)\n\treturn scanner\n}\n\nfunc WithSkipIDs(skipIDs []string) func(*scanner) {\n\treturn func(s *scanner) {\n\t\tids := map[string]struct{}{}\n\t\tfor _, id := range skipIDs {\n\t\t\tids[id] = struct{}{}\n\t\t}\n\n\t\ts.skipIDs = ids\n\t}\n}\n\nfunc WithAllowedAccounts(accounts []string) func(*scanner) {\n\treturn func(s *scanner) {\n\t\ts.SetAllowedAccounts(accounts)\n\t}\n}\n\nfunc WithDeniedAccounts(accounts []string) func(*scanner) {\n\treturn func(s *scanner) {\n\t\ts.SetDeniedAccounts(accounts)\n\t}\n}\n\n// Ensure the scanner satisfies the interface at compile time.\nvar _ interface {\n\tdetectors.Detector\n\tdetectors.CustomResultsCleaner\n} = (*scanner)(nil)\n\nvar (\n\tdefaultVerificationClient = common.SaneHttpClient()\n\n\t// Key types are from this list https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids\n\tidPat      = regexp.MustCompile(`\\b((?:ASIA)[A-Z0-9]{16})\\b`)\n\tsessionPat = regexp.MustCompile(`(?:[^A-Za-z0-9+/]|\\A)([a-zA-Z0-9+/]{100,}={0,3})(?:[^A-Za-z0-9+/=]|\\z)`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s scanner) Keywords() []string {\n\treturn []string{\"ASIA\"}\n}\n\n// FromData will find and optionally verify AWS secrets in a given set of bytes.\nfunc (s scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tlogger := logContext.AddLogger(ctx).Logger().WithName(\"awssessionkey\")\n\tdataStr := string(data)\n\tdataStr = aws.UrlEncodedReplacer.Replace(dataStr)\n\n\t// Filter & deduplicate matches.\n\tidMatches := make(map[string]struct{})\n\tfor _, matches := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tidMatches[matches[1]] = struct{}{}\n\t}\n\tsecretMatches := make(map[string]struct{})\n\tfor _, matches := range aws.SecretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tsecretMatches[matches[1]] = struct{}{}\n\t}\n\tsessionMatches := make(map[string]struct{})\n\tfor _, matches := range sessionPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tsessionMatches[matches[1]] = struct{}{}\n\t}\n\n\t// Process matches.\n\tfor idMatch := range idMatches {\n\t\tif detectors.StringShannonEntropy(idMatch) < aws.RequiredIdEntropy {\n\t\t\tcontinue\n\t\t}\n\t\tif s.skipIDs != nil {\n\t\t\tif _, ok := s.skipIDs[idMatch]; ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tfor secretMatch := range secretMatches {\n\t\t\tif detectors.StringShannonEntropy(secretMatch) < aws.RequiredSecretEntropy {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfor sessionMatch := range sessionMatches {\n\t\t\t\tif detectors.StringShannonEntropy(sessionMatch) < 4.5 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif !checkSessionToken(sessionMatch, secretMatch) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AWSSessionKey,\n\t\t\t\t\tRaw:          []byte(idMatch),\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s:%s\", idMatch, secretMatch, sessionMatch)),\n\t\t\t\t\tRedacted:     idMatch,\n\t\t\t\t\tExtraData:    make(map[string]string),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\t// If we haven't already found an AWS Account ID for this ID (via API), calculate one for filtering.\n\t\t\t\t\tvar accountIDForFiltering string\n\t\t\t\t\tif accountID, err := aws.GetAccountNumFromID(idMatch); err == nil {\n\t\t\t\t\t\taccountIDForFiltering = accountID\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check account filtering before verification\n\t\t\t\t\tif accountIDForFiltering != \"\" {\n\t\t\t\t\t\tif s.ShouldSkipAccount(accountIDForFiltering) {\n\t\t\t\t\t\t\tvar skipReason string\n\t\t\t\t\t\t\tif s.IsInDenyList(accountIDForFiltering) {\n\t\t\t\t\t\t\t\tskipReason = aws.VerificationErrAccountIDInDenyList\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tskipReason = aws.VerificationErrAccountIDNotInAllowList\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ts1.SetVerificationError(fmt.Errorf(\"%s\", skipReason), secretMatch)\n\t\t\t\t\t\t\t// If we haven't already found an AWS Account ID for this ID (via API), calculate one.\n\t\t\t\t\t\t\tif _, ok := s1.ExtraData[\"account\"]; !ok {\n\t\t\t\t\t\t\t\tif accountID, err := aws.GetAccountNumFromID(idMatch); err != nil {\n\t\t\t\t\t\t\t\t\tlogger.V(3).Info(\"Failed to decode AWS Account ID\", \"err\", err)\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\ts1.ExtraData[\"account\"] = accountID\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tresults = append(results, s1)\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tisVerified, extraData, verificationErr := s.verifyMatch(ctx, idMatch, secretMatch, sessionMatch, true)\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\tif extraData != nil {\n\t\t\t\t\t\ts1.ExtraData = extraData\n\t\t\t\t\t}\n\t\t\t\t\ts1.SetVerificationError(verificationErr, secretMatch)\n\t\t\t\t}\n\n\t\t\t\tif !s1.Verified && aws.FalsePositiveSecretPat.MatchString(secretMatch) {\n\t\t\t\t\t// Unverified results that look like hashes are probably not secrets\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// If we haven't already found an AWS Account ID for this ID (via API), calculate one.\n\t\t\t\tif _, ok := s1.ExtraData[\"account\"]; !ok {\n\t\t\t\t\tif accountID, err := aws.GetAccountNumFromID(idMatch); err != nil {\n\t\t\t\t\t\tlogger.V(3).Info(\"Failed to decode AWS Account ID\", \"err\", err)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts1.ExtraData[\"account\"] = accountID\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t\t// 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.\n\t\t\t\tif s1.Verified {\n\t\t\t\t\tdelete(sessionMatches, secretMatch)\n\t\t\t\t\tdelete(sessionMatches, sessionMatch)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s scanner) ShouldCleanResultsIrrespectiveOfConfiguration() bool {\n\treturn true\n}\n\nconst (\n\tmethod   = \"GET\"\n\tservice  = \"sts\"\n\thost     = \"sts.amazonaws.com\"\n\tregion   = \"us-east-1\"\n\tendpoint = \"https://sts.amazonaws.com\"\n)\n\nfunc (s scanner) verifyMatch(ctx context.Context, resIDMatch, resSecretMatch string, resSessionMatch string, retryOn403 bool) (bool, map[string]string, error) {\n\t// REQUEST VALUES.\n\tnow := time.Now().UTC()\n\tdatestamp := now.Format(\"20060102\")\n\tamzDate := now.Format(\"20060102T150405Z\")\n\n\treq, err := http.NewRequestWithContext(ctx, method, endpoint, nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\t// TASK 1: CREATE A CANONICAL REQUEST.\n\t// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html\n\tcanonicalURI := \"/\"\n\tcanonicalHeaders := \"host:\" + host + \"\\n\" + \"x-amz-date:\" + amzDate + \"\\n\" + \"x-amz-security-token:\" + resSessionMatch + \"\\n\"\n\tsignedHeaders := \"host;x-amz-date;x-amz-security-token\"\n\talgorithm := \"AWS4-HMAC-SHA256\"\n\tcredentialScope := fmt.Sprintf(\"%s/%s/%s/aws4_request\", datestamp, region, service)\n\n\tparams := req.URL.Query()\n\tparams.Add(\"Action\", \"GetCallerIdentity\")\n\tparams.Add(\"Version\", \"2011-06-15\")\n\tcanonicalQuerystring := params.Encode()\n\tpayloadHash := aws.GetHash(\"\") // empty payload\n\tcanonicalRequest := method + \"\\n\" + canonicalURI + \"\\n\" + canonicalQuerystring + \"\\n\" + canonicalHeaders + \"\\n\" + signedHeaders + \"\\n\" + payloadHash\n\n\t// TASK 2: CREATE THE STRING TO SIGN.\n\tstringToSign := algorithm + \"\\n\" + amzDate + \"\\n\" + credentialScope + \"\\n\" + aws.GetHash(canonicalRequest)\n\n\t// TASK 3: CALCULATE THE SIGNATURE.\n\t// https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html\n\thash := aws.GetHMAC([]byte(fmt.Sprintf(\"AWS4%s\", resSecretMatch)), []byte(datestamp))\n\thash = aws.GetHMAC(hash, []byte(region))\n\thash = aws.GetHMAC(hash, []byte(service))\n\thash = aws.GetHMAC(hash, []byte(\"aws4_request\"))\n\n\tsignature2 := aws.GetHMAC(hash, []byte(stringToSign)) // Get Signature HMAC SHA256\n\tsignature := hex.EncodeToString(signature2)\n\n\t// TASK 4: ADD SIGNING INFORMATION TO THE REQUEST.\n\tauthorizationHeader := fmt.Sprintf(\"%s Credential=%s/%s, SignedHeaders=%s, Signature=%s\",\n\t\talgorithm, resIDMatch, credentialScope, signedHeaders, signature)\n\n\treq.Header.Add(\"Authorization\", authorizationHeader)\n\treq.Header.Add(\"x-amz-date\", amzDate)\n\treq.Header.Add(\"x-amz-security-token\", resSessionMatch)\n\n\treq.URL.RawQuery = params.Encode()\n\n\tclient := s.verificationClient\n\tif client == nil {\n\t\tclient = defaultVerificationClient\n\t}\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\tidentityInfo := aws.IdentityResponse{}\n\t\tif err := json.NewDecoder(res.Body).Decode(&identityInfo); err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\n\t\textraData := map[string]string{\n\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/aws/\",\n\t\t\t\"account\":        identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.Account,\n\t\t\t\"user_id\":        identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.UserID,\n\t\t\t\"arn\":            identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.Arn,\n\t\t}\n\t\treturn true, extraData, nil\n\t} else if res.StatusCode == 403 {\n\t\t// Experimentation has indicated that if you make two GetCallerIdentity requests within five seconds that\n\t\t// share a key ID but are signed with different secrets the second one will be rejected with a 403 that\n\t\t// carries a SignatureDoesNotMatch code in its body. This happens even if the second ID-secret pair is\n\t\t// valid. Since this is exactly our access pattern, we need to work around it.\n\t\t//\n\t\t// Fortunately, experimentation has also revealed a workaround: simply resubmit the second request. The\n\t\t// response to the resubmission will be as expected. But there's a caveat: You can't have closed the body of\n\t\t// the response to the original second request, or read to its end, or the resubmission will also yield a\n\t\t// SignatureDoesNotMatch. For this reason, we have to re-request all 403s. We can't re-request only\n\t\t// SignatureDoesNotMatch responses, because we can only tell whether a given 403 is a SignatureDoesNotMatch\n\t\t// after decoding its response body, which requires reading the entire response body, which disables the\n\t\t// workaround.\n\t\t//\n\t\t// We are clearly deep in the guts of AWS implementation details here, so this all might change with no\n\t\t// notice. If you're here because something in this detector broke, you have my condolences.\n\t\tif retryOn403 {\n\t\t\treturn s.verifyMatch(ctx, resIDMatch, resSecretMatch, resSessionMatch, false)\n\t\t}\n\n\t\tvar body aws.ErrorResponseBody\n\t\tif err = json.NewDecoder(res.Body).Decode(&body); err != nil {\n\t\t\treturn false, nil, fmt.Errorf(\"couldn't parse the sts response body (%v)\", err)\n\t\t}\n\t\t// All instances of the code I've seen in the wild are PascalCased but this check is\n\t\t// case-insensitive out of an abundance of caution\n\t\tif strings.EqualFold(body.Error.Code, \"InvalidClientTokenId\") {\n\t\t\treturn false, nil, nil\n\t\t} else if strings.EqualFold(body.Error.Code, \"ExpiredToken\") {\n\t\t\t// ExpiredToken: The security token included in the request is expired\n\t\t\treturn false, nil, nil\n\t\t}\n\t\treturn 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)\n\t} else {\n\t\treturn false, nil, fmt.Errorf(\"request to %v returned unexpected status %d\", res.Request.URL, res.StatusCode)\n\t}\n}\n\nfunc (s scanner) CleanResults(results []detectors.Result) []detectors.Result {\n\treturn aws.CleanResults(results)\n}\n\n// Reference: https://nitter.poast.org/TalBeerySec/status/1816449053841838223#m\nfunc checkSessionToken(sessionToken string, secret string) bool {\n\tif !(strings.Contains(sessionToken, \"YXdz\") || strings.Contains(sessionToken, \"Jb3JpZ2luX2Vj\")) ||\n\t\tstrings.Contains(sessionToken, secret) {\n\t\t// Handle error if the sessionToken is not a valid base64 string\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (s scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AWSSessionKey\n}\n\nfunc (s scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/aws/session_keys/sessionkeys_test.go",
    "content": "package session_keys\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAWSSessionKey_Pattern(t *testing.T) {\n\td := New()\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\taws credentials{\n\t\t\t\t\tid: ASIABBKK02W42Q3IPSPG\n\t\t\t\t\tsecret: fkhIiUwQY32Zu9e4a86g9r3WpTzfE1aXljVcgn8O\n\t\t\t\t\tsession: aSqfp/GTZbJP+tXPNCZ9GoveoM0vgxtlYXdzPQ2uYNMPPgUkt0VT7SoTLasAo7iVqWWREOUC6DEenlcgDEKyzIEgQW5Ju/b9K/Z176uD2HJYCfq/lyowHtt5PvJi7LRuf/urSorGbTcqNUvPi42YP1Ps/4F6He9hQA1io3EAGBC3ICGHXWf2IlvFoTNUyPTqhjnPEKMWZ42jblqNAdD7hLpzNXmmGhdLCjy99XK8+gjHdZHkOeD/FIjRPRZ7Jl0tdwdqFEwzRVCzL2uelMVMd3UaZ+d4I4Kf+J464piO//jxx48Fs/mG3zr5ba9m2S+6gvUZJq4j+0uJ+jf6cG/x2G9XSybqYQRwvxfNquKB4TcKiGVH5+ZbJT4ASkARadwoSPMGfvMPje+X2zAziSzXfsxYfIQKf6iJ9p7VavlDGi+Acr4kwFXW5IfQs4uGk6AVQFsoZK3o1hhLOkuOwWQEWhDQGNLXwJbFqXfELOnUQvM0Z5NUm46bjAAi4g+X9gLPNR/KjzXuuTTaWYrQEjXLb7PxS0sIttAb1w+sTXXtc1kDIsABC6KcsyGlEwji5sLkbkUa=\n\t\t\t\t}\n\t\t\t`,\n\t\t\twant: []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=\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{ASIABBKK02W42Q3IPSPG}</id>\n\t\t\t\t\t<secret>{AQAAABAAA fkhIiUwQY32Zu9e4a86g9r3WpTzfE1aXljVcgn8O}</secret>\n  \t\t\t\t\t<session>{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=}</session>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []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=\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\taws credentials{\n\t\t\t\t\tid: ASIABBKK02W42Q3IPSPG\n\t\t\t\t\tsecret: $YenOG.PKHl7LcdVYsjaR4LgQiZ1zw3MAnMyiondXC63;\n\t\t\t\t}\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestAWSSessionKey_WithAllowedAccounts(t *testing.T) {\n\taccounts := []string{\"123456789012\", \"999888777666\"}\n\ts := New(WithAllowedAccounts(accounts))\n\n\t// Test that allowed accounts are properly configured\n\tshouldSkip := s.ShouldSkipAccount(\"123456789012\")\n\trequire.False(t, shouldSkip)\n\trequire.True(t, s.IsInAllowList(\"123456789012\"))\n\n\t// Test that non-allowed accounts are skipped\n\tshouldSkip = s.ShouldSkipAccount(\"111222333444\")\n\trequire.True(t, shouldSkip)\n\trequire.False(t, s.IsInAllowList(\"111222333444\"))\n}\n\nfunc TestAWSSessionKey_WithDeniedAccounts(t *testing.T) {\n\taccounts := []string{\"123456789012\", \"999888777666\"}\n\ts := New(WithDeniedAccounts(accounts))\n\n\t// Test that denied accounts are properly skipped\n\tshouldSkip := s.ShouldSkipAccount(\"123456789012\")\n\trequire.True(t, shouldSkip)\n\trequire.True(t, s.IsInDenyList(\"123456789012\"))\n\n\t// Test that non-denied accounts are not skipped\n\tshouldSkip = s.ShouldSkipAccount(\"111222333444\")\n\trequire.False(t, shouldSkip)\n\trequire.False(t, s.IsInDenyList(\"111222333444\"))\n}\n"
  },
  {
    "path": "pkg/detectors/aws/utils.go",
    "content": "package aws\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/base32\"\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n)\n\n// ResourceTypes derived from: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids\nvar ResourceTypes = map[string]string{\n\t\"ABIA\": \"AWS STS service bearer token\",\n\t\"ACCA\": \"Context-specific credential\",\n\t\"AGPA\": \"User group\",\n\t\"AIDA\": \"IAM user\",\n\t\"AIPA\": \"Amazon EC2 instance profile\",\n\t\"AKIA\": \"Access key\",\n\t\"ANPA\": \"Managed policy\",\n\t\"ANVA\": \"Version in a managed policy\",\n\t\"APKA\": \"Public key\",\n\t\"AROA\": \"Role\",\n\t\"ASCA\": \"Certificate\",\n\t\"ASIA\": \"Temporary (AWS STS) access key IDs\",\n}\n\n// UrlEncodedReplacer helps capture base64-encoded results that may be url-encoded.\n// TODO: Add this as a decoder, or make it a more generic.\nvar UrlEncodedReplacer = strings.NewReplacer(\n\t\"%2B\", \"+\",\n\t\"%2b\", \"+\",\n\t\"%2F\", \"/\",\n\t\"%2f\", \"/\",\n\t\"%3d\", \"=\",\n\t\"%3D\", \"=\",\n)\n\n// Hashes, like those for git, do technically match the secret pattern.\n// But they are extremely unlikely to be generated as an actual AWS secret.\n// So when we find them, if they're not verified, we should ignore the result.\nvar FalsePositiveSecretPat = regexp.MustCompile(`[a-f0-9]{40}`)\n\nfunc GetAccountNumFromID(id string) (string, error) {\n\t// Function to get the account number from an AWS ID (no verification required)\n\t// Source: https://medium.com/@TalBeerySec/a-short-note-on-aws-key-id-f88cc4317489\n\tif len(id) < 4 {\n\t\treturn \"\", fmt.Errorf(\"AWSID is too short\")\n\t}\n\tif id[4] == 'I' || id[4] == 'J' {\n\t\treturn \"\", fmt.Errorf(\"can't get account number from AKIAJ/ASIAJ or AKIAI/ASIAI keys\")\n\t}\n\ttrimmedAWSID := id[4:]\n\tdecodedBytes, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(strings.ToUpper(trimmedAWSID))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif len(decodedBytes) < 6 {\n\t\treturn \"\", fmt.Errorf(\"decoded AWSID is too short\")\n\t}\n\n\tdata := make([]byte, 8)\n\tcopy(data[2:], decodedBytes[0:6])\n\tz := binary.BigEndian.Uint64(data)\n\tconst mask uint64 = 0x7fffffffff80\n\taccountNum := (z & mask) >> 7\n\treturn fmt.Sprintf(\"%012d\", accountNum), nil\n}\n\nfunc GetHash(input string) string {\n\tdata := []byte(input)\n\thasher := sha256.New()\n\thasher.Write(data)\n\treturn hex.EncodeToString(hasher.Sum(nil))\n}\n\nfunc GetHMAC(key []byte, data []byte) []byte {\n\thasher := hmac.New(sha256.New, key)\n\thasher.Write(data)\n\treturn hasher.Sum(nil)\n}\n\nfunc CleanResults(results []detectors.Result) []detectors.Result {\n\tif len(results) == 0 {\n\t\treturn results\n\t}\n\n\t// For every ID, we want at most one result, preferably verified.\n\tidResults := map[string]detectors.Result{}\n\tfor _, result := range results {\n\t\t// Always accept the verified result as the result for the given ID.\n\t\tif result.Verified {\n\t\t\tidResults[result.Redacted] = result\n\t\t\tcontinue\n\t\t}\n\n\t\t// Only include an unverified result if we don't already have a result for a given ID.\n\t\tif _, exist := idResults[result.Redacted]; !exist {\n\t\t\tidResults[result.Redacted] = result\n\t\t}\n\t}\n\n\tvar out []detectors.Result\n\tfor _, r := range idResults {\n\t\tout = append(out, r)\n\t}\n\treturn out\n}\n"
  },
  {
    "path": "pkg/detectors/axonaut/axonaut.go",
    "content": "package axonaut\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"axonaut\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"axonaut\"}\n}\n\n// FromData will find and optionally verify Axonaut secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Axonaut,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, err := verifyMatch(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://axonaut.com/api/v2/companies?type=all&sort=id\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"userApiKey\", key)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Axonaut\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/axonaut/axonaut_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage axonaut\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAxonaut_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AXONAUT\")\n\tinactiveSecret := testSecrets.MustGetField(\"AXONAUT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a axonaut secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Axonaut,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Axonaut,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Axonaut.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Axonaut.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/axonaut/axonaut_test.go",
    "content": "package axonaut\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAxonaut_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the axonaut API\n\t\t\t\t[DEBUG] Using Key=4ve4aj6v38uiadaq9hcgpupp2b3lh2k8\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"4ve4aj6v38uiadaq9hcgpupp2b3lh2k8\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{axonaut}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA m7mnuk7p3buc87b2ok29e7ykp2xqkkx0}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"m7mnuk7p3buc87b2ok29e7ykp2xqkkx0\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the axonaut API\n\t\t\t\t[DEBUG] Using Key=ASIABBKK02W42Q3IPSPG\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/aylien/aylien.go",
    "content": "package aylien\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"aylien\"}) + `\\b([a-z0-9]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"aylien\"}) + `\\b([a-z0-9]{8})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"aylien\"}\n}\n\n// FromData will find and optionally verify Aylien secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Aylien,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resIdMatch),\n\t\t\t}\n\t\t\tif verify {\n\t\t\t\tisVerified, err := verifyMatch(ctx, client, resIdMatch, resMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, id, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.aylien.com/news/stories\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"X-AYLIEN-NewsAPI-Application-ID\", id)\n\treq.Header.Add(\"X-AYLIEN-NewsAPI-Application-Key\", key)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Aylien\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/aylien/aylien_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage aylien\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAylien_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AYLIEN\")\n\tid := testSecrets.MustGetField(\"AYLIEN_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"AYLIEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a aylien secret %s within aylien %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Aylien,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Aylien,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Aylien.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Aylien.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/aylien/aylien_test.go",
    "content": "package aylien\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAylien_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t# do not share these credentials\n\t\t\t\taylien credentials:\n\t\t\t\t\taylien key: cr479du2l9pkmhar8gw5hufofvwp86q9\n\t\t\t\t\taylien id: y3ejw028\n\t\t\t\t# valid till Dec 2025\n\t\t\t\t`,\n\t\t\twant: []string{\"cr479du2l9pkmhar8gw5hufofvwp86q9y3ejw028\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{aylien wmxv7ckn}</id>\n  \t\t\t\t\t<secret>{aylien AQAAABAAA i09t8rb5r7otvq8sdrfjunakcso157mh}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"i09t8rb5r7otvq8sdrfjunakcso157mhwmxv7ckn\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t# do not share these credentials\n\t\t\t\taylien credentials:\n\t\t\t\t\taylien key: cr4U9du2l9pkmhar8gw5hufofvWp86q9\n\t\t\t\t\taylien id: y3ejwA8\n\t\t\t\t# valid till Dec 2025\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ayrshare/ayrshare.go",
    "content": "package ayrshare\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ayrshare\"}) + `\\b([A-Z0-9]{8}-[A-Z0-9]{8}-[A-Z0-9]{8}-[A-Z0-9]{8})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ayrshare\"}\n}\n\n// FromData will find and optionally verify Ayrshare secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Ayrshare,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, extraData, err := verifyMatch(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, key string) (bool, map[string]string, error) {\n\t// Reference: https://www.ayrshare.com/docs/apis/user/profile-details\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://app.ayrshare.com/api/user\", http.NoBody)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\t\tvar responseBody map[string]any\n\t\tif err := json.Unmarshal(bodyBytes, &responseBody); err == nil {\n\t\t\tif email, ok := responseBody[\"email\"].(string); ok {\n\t\t\t\treturn true, map[string]string{\"email\": email}, nil\n\t\t\t}\n\t\t}\n\t\treturn true, nil, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil, nil\n\tcase http.StatusForbidden:\n\t\t// Invalid Bearer tokens get a 403 Forbidden response despite what is stated in the docs.\n\t\t// Documentation: https://www.ayrshare.com/docs/errors/errors-http\n\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\t\tif strings.Contains(string(bodyBytes), \"API Key not valid\") {\n\t\t\treturn false, nil, nil\n\t\t}\n\t}\n\treturn false, nil, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Ayrshare\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Ayrshare provides social media management services. Ayrshare API keys can be used to manage social media accounts and posts.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ayrshare/ayrshare_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ayrshare\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAyrshare_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AYRSHARE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"AYRSHARE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ayrshare secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ayrshare,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ayrshare,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Ayrshare.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Ayrshare.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ayrshare/ayrshare_test.go",
    "content": "package ayrshare\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAyrShare_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the ayrshare API\n\t\t\t\t[DEBUG] Using Key=2FTJTA1C-BXO0DV4J-HGTP9E62-QHQSILY1\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"2FTJTA1C-BXO0DV4J-HGTP9E62-QHQSILY1\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{ayrshare}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA I1WPQLUQ-NCNHEI13-1MF4HJZQ-EEDDVZYO}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"I1WPQLUQ-NCNHEI13-1MF4HJZQ-EEDDVZYO\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the ayrshare API\n\t\t\t\t[DEBUG] Using Key=KRXaU9GK3f[yHG1FS$]bwhsIXdW22epH\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azure_batch/azurebatch.go",
    "content": "package azure_batch\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\turlPat    = regexp.MustCompile(`https://(.{1,50})\\.(.{1,50})\\.batch\\.azure\\.com`)\n\tsecretPat = regexp.MustCompile(`[A-Za-z0-9+/=]{88}`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\".batch.azure.com\"}\n}\n\n// FromData will find and optionally verify Azurebatch secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\turlMatches := urlPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, urlMatch := range urlMatches {\n\n\t\tfor _, secretMatch := range secretMatches {\n\n\t\t\tendpoint := urlMatch[0]\n\t\t\taccountName := urlMatch[1]\n\t\t\taccountKey := secretMatch[0]\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AzureBatch,\n\t\t\t\tRaw:          []byte(endpoint),\n\t\t\t\tRawV2:        []byte(endpoint + accountKey),\n\t\t\t\tRedacted:     endpoint,\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\t\t\t\tisVerified, err := verifyMatch(ctx, client, endpoint, accountName, accountKey)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(err)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t\tif s1.Verified {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, endpoint, accountName, accountKey string) (bool, error) {\n\t// Reference: https://learn.microsoft.com/en-us/rest/api/batchservice/application/list\n\turl := fmt.Sprintf(\"%s/applications?api-version=2020-09-01.12.0\", endpoint)\n\n\tdate := time.Now().UTC().Format(http.TimeFormat)\n\tstringToSign := fmt.Sprintf(\n\t\t\"GET\\n\\n\\n\\n\\napplication/json\\n%s\\n\\n\\n\\n\\n\\n%s\\napi-version:%s\",\n\t\tdate,\n\t\tstrings.ToLower(fmt.Sprintf(\"/%s/applications\", accountName)),\n\t\t\"2020-09-01.12.0\",\n\t)\n\tkey, _ := base64.StdEncoding.DecodeString(accountKey)\n\th := hmac.New(sha256.New, key)\n\th.Write([]byte(stringToSign))\n\tsignature := base64.StdEncoding.EncodeToString(h.Sum(nil))\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"SharedKey %s:%s\", accountName, signature))\n\treq.Header.Set(\"Date\", date)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\t// If the host is not found, we can assume that the endpoint is invalid\n\t\tif strings.Contains(err.Error(), \"no such host\") {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, err\n\t}\n\tdefer resp.Body.Close()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusForbidden:\n\t\t// Key is either invalid or the account is disabled.\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code %d for %s\", resp.StatusCode, url)\n\t}\n}\n\nfunc (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {\n\treturn false, \"\"\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AzureBatch\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/azure_batch/azurebatch_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage azure_batch\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAzurebatch_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\turl := testSecrets.MustGetField(\"AZUREBATCH_URL\")\n\tsecret := testSecrets.MustGetField(\"AZUREBATCH_KEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"AZUREBATCH_KEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azurebatch secret %s and %s within\", url, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureBatch,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureBatch,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AzureBatch.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"RawV2\", \"Raw\", \"Redacted\", \"verificationError\")\n\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"AzureBatch.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azure_batch/azurebatch_test.go",
    "content": "package azure_batch\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAzureBatch_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the ayrshare API\n\t\t\t\t[DEBUG] Using Secret = BXIMbhBlC3=5hIbqCEKvq7opaV2ZfO0XWbcnasZmPm/AJfQqdcnt/AVmKkJ8Qw80Zc1rQDaw+2Ytxc1hDq1m/LB0\n\t\t\t\t[INFO] https://JrxlYxT+0hW.YSA.batch.azure.com\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"https://JrxlYxT+0hW.YSA.batch.azure.comBXIMbhBlC3=5hIbqCEKvq7opaV2ZfO0XWbcnasZmPm/AJfQqdcnt/AVmKkJ8Qw80Zc1rQDaw+2Ytxc1hDq1m/LB0\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{https://pb0bik2a59qznkh87pdd6twjlgzpmxz.pfv9bpr2hujs.batch.azure.com}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA XJc2nGZvqPAXYfHxsiwUDBA4ynHzGc9nQl1Ih16lk19=2+qqeJUDp5eBxWVrE0LQYlnbeu/orbEtblFL218S4Wko}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"https://pb0bik2a59qznkh87pdd6twjlgzpmxz.pfv9bpr2hujs.batch.azure.comXJc2nGZvqPAXYfHxsiwUDBA4ynHzGc9nQl1Ih16lk19=2+qqeJUDp5eBxWVrE0LQYlnbeu/orbEtblFL218S4Wko\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the ayrshare API\n\t\t\t\t[DEBUG] Using Secret=BXIMbhBlC3=5hIbqCEKvq7op!V2ZfO0XWbcnasZmPm/AJfQqdcnt/AVmKkJ8Qw80Zc1rQDaw+2Ytxc1hDq1m/\n\t\t\t\t[INFO] http://invalid.this.batch.azure.com\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azure_cosmosdb/azure_cosmosdb.go",
    "content": "package azure_cosmosdb\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\tdbKeyPattern = regexp.MustCompile(`([A-Za-z0-9]{86}==)`)\n\t// account name can contain only lowercase letters, numbers and the `-` character, must be between 3 and 44 characters long.\n\taccountUrlPattern = regexp.MustCompile(`([a-z0-9-]{3,44}\\.(?:documents|table\\.cosmos)\\.azure\\.com)`)\n\n\tinvalidHosts = simple.NewCache[struct{}]()\n\n\terrNoHost = errors.New(\"no such host\")\n)\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AzureCosmosDBKeyIdentifiable\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\".documents.azure.com\", \".table.cosmos.azure.com\"}\n}\n\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeyMatches, uniqueAccountMatches = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, match := range dbKeyPattern.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeyMatches[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range accountUrlPattern.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueAccountMatches[match[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeyMatches {\n\t\tfor accountUrl := range uniqueAccountMatches {\n\t\t\tif invalidHosts.Exists(accountUrl) {\n\t\t\t\tdelete(uniqueAccountMatches, accountUrl)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AzureCosmosDBKeyIdentifiable,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tRawV2:        []byte(\"key: \" + key + \" account_url: \" + accountUrl), // key: <key> account_url: <account_url>\n\t\t\t\tExtraData:    map[string]string{},\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tvar verified bool\n\t\t\t\tvar verificationErr error\n\n\t\t\t\tclient := s.getClient()\n\n\t\t\t\t// perform verification based on db type\n\t\t\t\tif strings.Contains(accountUrl, \".documents.azure.com\") {\n\t\t\t\t\tverified, verificationErr = verifyCosmosDocumentDB(client, accountUrl, key)\n\t\t\t\t\ts1.ExtraData[\"DB Type\"] = \"Document\"\n\n\t\t\t\t} else if strings.Contains(accountUrl, \".table.cosmos.azure.com\") {\n\t\t\t\t\tverified, verificationErr = verifyCosmosTableDB(client, accountUrl, key)\n\t\t\t\t\ts1.ExtraData[\"DB Type\"] = \"Table\"\n\t\t\t\t}\n\n\t\t\t\ts1.Verified = verified\n\t\t\t\tif verificationErr != nil {\n\t\t\t\t\tif errors.Is(verificationErr, errNoHost) {\n\t\t\t\t\t\tinvalidHosts.Set(accountUrl, struct{}{})\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\n// documentation: https://learn.microsoft.com/en-us/rest/api/cosmos-db/list-databases\nfunc verifyCosmosDocumentDB(client *http.Client, accountUrl, key string) (bool, error) {\n\t// decode the base64 encoded key\n\tdecodedKey, err := base64.StdEncoding.DecodeString(key)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to decode key: %v\", err)\n\t}\n\n\treq, err := http.NewRequest(http.MethodGet, fmt.Sprintf(\"https://%s:443/dbs\", accountUrl), nil)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to create request: %v\", err)\n\t}\n\n\tdateRFC1123 := time.Now().UTC().Format(\"Mon, 02 Jan 2006 15:04:05 GMT\")\n\tauthHeader := fmt.Sprintf(\"type=master&ver=1.0&sig=%s\", url.QueryEscape(createDocumentsSignature(decodedKey, dateRFC1123)))\n\n\t// required headers\n\t// docs: https://learn.microsoft.com/en-us/rest/api/cosmos-db/common-cosmosdb-rest-request-headers\n\treq.Header.Set(\"Authorization\", authHeader)\n\treq.Header.Set(\"x-ms-date\", dateRFC1123)\n\treq.Header.Set(\"x-ms-version\", \"2018-12-31\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\t// lookup foo.documents.azure.com: no such host\n\t\tif strings.Contains(err.Error(), \"no such host\") {\n\t\t\treturn false, errNoHost\n\t\t}\n\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\t// Check response status code\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\nfunc createDocumentsSignature(decodedKey []byte, dateRFC1123 string) string {\n\tstringToSign := fmt.Sprintf(\n\t\t\"%s\\n%s\\n%s\\n%s\\n\\n\",\n\t\tstrings.ToLower(http.MethodGet),\n\t\tstrings.ToLower(\"dbs\"),\n\t\t\"\",\n\t\tstrings.ToLower(dateRFC1123),\n\t)\n\n\t// compute HMAC-SHA256 signature\n\tmac := hmac.New(sha256.New, decodedKey)\n\tmac.Write([]byte(stringToSign))\n\n\treturn base64.StdEncoding.EncodeToString(mac.Sum(nil))\n}\n"
  },
  {
    "path": "pkg/detectors/azure_cosmosdb/azure_cosmosdb_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage azure_cosmosdb\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCosmosDB_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tkey := testSecrets.MustGetField(\"COSMOSDB_KEY\")\n\taccountUrl := testSecrets.MustGetField(\"COSMOSDB_ACCOUNT\")\n\tinactiveKey := testSecrets.MustGetField(\"COSMOSDB_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cosmosdb key: %s and account url: %s within\", key, accountUrl)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureCosmosDBKeyIdentifiable,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureCosmosDBKeyIdentifiable,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CosmosDB.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"CosmosDB.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azure_cosmosdb/azure_cosmosdb_test.go",
    "content": "package azure_cosmosdb\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestCosmosDB_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid document db pattern\",\n\t\t\tinput: `\n\t\t\t\tCluster name: Cluster name must be at least 3 characters and at most 40 characters.\n\t\t\t\tCluster name must only contain lowercase letters, numbers, and hyphens.\n\t\t\t\tThe cluster name must not start or end in a hyphen.\n\t\t\t\t// config\n\t\t\t\tcosmosKey: FakeeP35zYGPXaEUfakeU7S8kcOY7NI7id8ddbHfakeAifake8Bbql1mXhMF2t0wQ0FAKEPQrwZZACDb3msoAg==\n\t\t\t\thttps://trufflesecurity-fake.documents.azure.com:443`,\n\t\t\twant: []string{\"key: FakeeP35zYGPXaEUfakeU7S8kcOY7NI7id8ddbHfakeAifake8Bbql1mXhMF2t0wQ0FAKEPQrwZZACDb3msoAg== account_url: trufflesecurity-fake.documents.azure.com\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{jc0338vpo7bd3rn99vu2trdbo.table.cosmos.azure.com}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA tiHd2l1I3MptBj4s1zomhyIAuCJmR1bzxvGluBVW2k0JJ7Z6vmybKYiM7OY5HtDkvLVxyDD2ACW0GW2fug0cET==}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"key: tiHd2l1I3MptBj4s1zomhyIAuCJmR1bzxvGluBVW2k0JJ7Z6vmybKYiM7OY5HtDkvLVxyDD2ACW0GW2fug0cET== account_url: jc0338vpo7bd3rn99vu2trdbo.table.cosmos.azure.com\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid table db pattern\",\n\t\t\tinput: `\n\t\t\t\tCluster name: Cluster name must be at least 3 characters and at most 40 characters.\n\t\t\t\tCluster name must only contain lowercase letters, numbers, and hyphens.\n\t\t\t\tThe cluster name must not start or end in a hyphen.\n\t\t\t\t// config\n\t\t\t\tcosmosKey: FakeeP35zYGPXaEUfakeU7S8kcOY7NI7id8ddbHfakeAifake8Bbql1mXhMF2t0wQ0FAKEPQrwZZACDb3msoAg==\n\t\t\t\thttps://trufflesecurity-fake.table.cosmos.azure.com:443`,\n\t\t\twant: []string{\"key: FakeeP35zYGPXaEUfakeU7S8kcOY7NI7id8ddbHfakeAifake8Bbql1mXhMF2t0wQ0FAKEPQrwZZACDb3msoAg== account_url: trufflesecurity-fake.table.cosmos.azure.com\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tFakeeP35zYGPXaEUfakeU7S8kcOY7I7id8ddbHfakeAifake8Bbql1mXhMF2t0wQ0FAKEPQrwZZACDb3msoAg==\n\t\t\t\thttps://not-a-host.documents.azure.com:443`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azure_cosmosdb/table.go",
    "content": "package azure_cosmosdb\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\nfunc verifyCosmosTableDB(client *http.Client, accountUrl, key string) (bool, error) {\n\t// decode the base64 encoded key\n\tdecodedKey, err := base64.StdEncoding.DecodeString(key)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to decode key: %v\", err)\n\t}\n\n\treq, err := http.NewRequest(http.MethodGet, fmt.Sprintf(\"https://%s:443/Tables\", accountUrl), nil)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to create request: %v\", err)\n\t}\n\n\t// extract abc123 from abc123.table.cosmos.azure.com\n\taccountName := strings.TrimPrefix(accountUrl, \".table.cosmos.azure.com\")\n\n\tdateRFC1123 := time.Now().UTC().Format(\"Mon, 02 Jan 2006 15:04:05 GMT\")\n\tauthHeader := fmt.Sprintf(\"SharedKeyLite %s:%s\", accountName, createTablesSignature(decodedKey, accountName, dateRFC1123))\n\n\t// required headers\n\t// docs: https://learn.microsoft.com/en-us/rest/api/cosmos-db/common-cosmosdb-rest-request-headers\n\treq.Header.Set(\"Authorization\", authHeader)\n\treq.Header.Set(\"x-ms-date\", dateRFC1123)\n\treq.Header.Set(\"x-ms-version\", \"2019-02-02\")\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\t// lookup foo.table.cosmos.azure.com: no such host\n\t\tif strings.Contains(err.Error(), \"no such host\") {\n\t\t\treturn false, errNoHost\n\t\t}\n\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\t// Check response status code\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\nfunc createTablesSignature(decodedKey []byte, accountName, dateRFC1123 string) string {\n\t// create string to sign (method + date)\n\tstringToSign := fmt.Sprintf(\"%s\\n%s\", dateRFC1123, fmt.Sprintf(\"/%s/Tables\", accountName))\n\n\t// Compute HMAC-SHA256 signature\n\th := hmac.New(sha256.New, decodedKey)\n\th.Write([]byte(stringToSign))\n\tsignature := base64.StdEncoding.EncodeToString(h.Sum(nil))\n\n\treturn signature\n}\n"
  },
  {
    "path": "pkg/detectors/azure_entra/common.go",
    "content": "package azure_entra\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"golang.org/x/sync/singleflight\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n)\n\nconst uuidStr = `[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}`\n\nvar (\n\t// Tenants can be identified with a UUID or an `*.onmicrosoft.com` domain.\n\t//\n\t// See:\n\t// 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\n\t// https://learn.microsoft.com/en-us/microsoft-365/admin/setup/domains-faq?view=o365-worldwide#why-do-i-have-an--onmicrosoft-com--domain\n\ttenantIdPat = regexp.MustCompile(fmt.Sprintf(\n\t\t//language=regexp\n\t\t`(?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))`,\n\t\tuuidStr,\n\t\tuuidStr,\n\t\tuuidStr,\n\t\tuuidStr,\n\t))\n\ttenantOnMicrosoftPat = regexp.MustCompile(`([\\w-]+\\.onmicrosoft\\.com)`)\n\n\tclientIdPat = regexp.MustCompile(fmt.Sprintf(\n\t\t`(?i)(?:(?:app(?:lication)?|client)(?:[ ._-]?id)?|username| -u)(?:.|\\s){0,45}?(%s)`, uuidStr))\n)\n\n// FindTenantIdMatches returns a list of potential tenant IDs in the provided |data|.\nfunc FindTenantIdMatches(data string) map[string]struct{} {\n\tuniqueMatches := make(map[string]struct{})\n\n\tfor _, match := range tenantIdPat.FindAllStringSubmatch(data, -1) {\n\t\tvar m string\n\t\tif match[1] != \"\" {\n\t\t\tm = strings.ToLower(match[1])\n\t\t} else if match[2] != \"\" {\n\t\t\tm = strings.ToLower(match[2])\n\t\t} else if match[3] != \"\" {\n\t\t\tm = strings.ToLower(match[3])\n\t\t} else if match[4] != \"\" {\n\t\t\tm = strings.ToLower(match[4])\n\t\t}\n\t\tif _, ok := detectors.UuidFalsePositives[detectors.FalsePositive(m)]; ok {\n\t\t\tcontinue\n\t\t} else if detectors.StringShannonEntropy(m) < 3 {\n\t\t\tcontinue\n\t\t}\n\t\tuniqueMatches[m] = struct{}{}\n\t}\n\tfor _, match := range tenantOnMicrosoftPat.FindAllStringSubmatch(data, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\treturn uniqueMatches\n}\n\n// FindClientIdMatches returns a list of potential client UUIDs in the provided |data|.\nfunc FindClientIdMatches(data string) map[string]struct{} {\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range clientIdPat.FindAllStringSubmatch(data, -1) {\n\t\tm := strings.ToLower(match[1])\n\t\tif _, ok := detectors.UuidFalsePositives[detectors.FalsePositive(m)]; ok {\n\t\t\tcontinue\n\t\t} else if detectors.StringShannonEntropy(m) < 3 {\n\t\t\tcontinue\n\t\t}\n\t\tuniqueMatches[m] = struct{}{}\n\t}\n\treturn uniqueMatches\n}\n\nvar (\n\ttenantCache = simple.NewCache[bool]()\n\ttenantGroup singleflight.Group\n)\n\n// TenantExists returns whether the tenant exists according to Microsoft's well-known OpenID endpoint.\nfunc TenantExists(ctx context.Context, client *http.Client, tenant string) bool {\n\t// Use cached value where possible.\n\tif tenantExists, isCached := tenantCache.Get(tenant); isCached {\n\t\treturn tenantExists\n\t}\n\n\t// https://www.codingexplorations.com/blog/understanding-singleflight-in-golang-a-solution-for-eliminating-redundant-work\n\ttenantExists, _, _ := tenantGroup.Do(tenant, func() (interface{}, error) {\n\t\tresult := queryTenant(ctx, client, tenant)\n\t\ttenantCache.Set(tenant, result)\n\t\treturn result, nil\n\t})\n\n\treturn tenantExists.(bool)\n}\n\nfunc queryTenant(ctx context.Context, client *http.Client, tenant string) bool {\n\tlogger := ctx.Logger().WithName(\"azure\").WithValues(\"tenant\", tenant)\n\n\ttenantUrl := fmt.Sprintf(\"https://login.microsoftonline.com/%s/.well-known/openid-configuration\", tenant)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, tenantUrl, nil)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\tlogger.Error(err, \"Failed to check if tenant exists\")\n\t\treturn false\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true\n\tcase http.StatusBadRequest:\n\t\tlogger.V(4).Info(\"Tenant does not exist.\")\n\t\treturn false\n\tdefault:\n\t\tbodyBytes, _ := io.ReadAll(res.Body)\n\t\tlogger.Error(nil, \"WARNING: Unexpected response when checking if tenant exists\", \"status_code\", res.StatusCode, \"body\", string(bodyBytes))\n\t\treturn false\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azure_entra/common_test.go",
    "content": "package azure_entra\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n)\n\ntype testCase struct {\n\tInput    string\n\tExpected map[string]struct{}\n}\n\nfunc runPatTest(t *testing.T, tests map[string]testCase, matchFunc func(data string) map[string]struct{}) {\n\tt.Helper()\n\tfor name, test := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tmatches := matchFunc(test.Input)\n\t\t\tif len(matches) == 0 {\n\t\t\t\tif len(test.Expected) != 0 {\n\t\t\t\t\tt.Fatalf(\"no matches found, expected: %v\", test.Expected)\n\t\t\t\t\treturn\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(test.Expected, matches); diff != \"\" {\n\t\t\t\tt.Errorf(\"expected: %s, actual: %s\", test.Expected, matches)\n\t\t\t\treturn\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_FindTenantIdMatches(t *testing.T) {\n\tcases := map[string]testCase{\n\t\t// Tenant ID\n\t\t\"audience\": {\n\t\t\tInput: `az offazure hyperv site create --location \"eastus\" --service-principal-identity-details \\\n\tapplication-id=\"cbcfc473-97da-45dd-8a00-3612d1ddf35a\" \\\n\taudience=\"https://bced5192-08c4-4470-9a94-666fea59efb07/aadapp\" `,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"bced5192-08c4-4470-9a94-666fea59efb0\": {},\n\t\t\t},\n\t\t},\n\t\t\"tenant\": {\n\t\t\tInput: `        \"cas.authn.azure-active-directory.login-url=https://login.microsoftonline.com/common/\",\n        \"cas.authn.azure-active-directory.tenant=8e439f30-da7a-482c-bd23-e45d0a732000\"`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"8e439f30-da7a-482c-bd23-e45d0a732000\": {},\n\t\t\t},\n\t\t},\n\t\t\"tanentId\": {\n\t\t\tInput: `azure.grantType=client_credentials\nazure.tanentId=029e3b51-60dd-47aa-81ad-3c15b389db86`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"029e3b51-60dd-47aa-81ad-3c15b389db86\": {},\n\t\t\t},\n\t\t},\n\t\t\"tenantid\": {\n\t\t\tInput: ` file:\n    folder-location: test\n    tenantid: ${vcap.services.user-authentication-service.credentials.tenantid:317fb200-a693-4062-a4fb-9d131fcd2d3c}`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"317fb200-a693-4062-a4fb-9d131fcd2d3c\": {},\n\t\t\t},\n\t\t},\n\t\t\"tenant id\": {\n\t\t\tInput: `1. Enter the tenant id \"2ce99e96-b41b-47a0-b37c-16a22bceb8c0\"`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"2ce99e96-b41b-47a0-b37c-16a22bceb8c0\": {},\n\t\t\t},\n\t\t},\n\t\t\"tenant_id\": {\n\t\t\tInput: `location = \"eastus\"\nsubscription_id = \"47ab1364-000d-4a53-838d-1537b1e3b49f\"\ntenant_id = \"57aabdfc-6ce0-4828-94a2-9abe277892ec\"`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"57aabdfc-6ce0-4828-94a2-9abe277892ec\": {},\n\t\t\t},\n\t\t},\n\t\t\"tenant-id\": {\n\t\t\tInput: `      active-directory:\n        enabled: true\n        profile:\n          tenant-id: c32654ed-6931-4bae-bb23-a8b9e420e0f4\n        credential:`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"c32654ed-6931-4bae-bb23-a8b9e420e0f4\": {},\n\t\t\t},\n\t\t},\n\t\t\"tid\": {\n\t\t\tInput: ` \"sub\": \"jIzit1WEdXqAH9KZXz-e-UcqsVa1pyPoh-2hw3xjEO4\",\n  \"tenant_region_scope\": \"AS\",\n  \"tid\": \"974fde14-c3a4-481b-9b03-cfce18213a07\",\n  \"uti\": \"2Y26RWHsWEiqhD2vi_PFAg\",`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"974fde14-c3a4-481b-9b03-cfce18213a07\": {},\n\t\t\t},\n\t\t},\n\t\t\"login.microsoftonline.com\": {\n\t\t\tInput: `  auth: {\n    authority: 'https://login.microsoftonline.com/7bb339cb-e94c-4a85-884c-48ebd9bb28c3',\n    redirectUri: 'http://localhost:8080/landing'\n`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"7bb339cb-e94c-4a85-884c-48ebd9bb28c3\": {},\n\t\t\t},\n\t\t},\n\t\t\"login.windows.net\": {\n\t\t\tInput: `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\" \\'`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"7bb339cb-e94c-4a85-884c-48ebd9bb28c3\": {},\n\t\t\t},\n\t\t},\n\t\t\"sts.windows.net\": {\n\t\t\tInput: `{\n  \"aud\": \"00000003-0000-0000-c000-000000000000\",\n  \"iss\": \"https://sts.windows.net/974fde14-c3a4-481b-9b03-cfce182c3a07/\",\n  \"iat\": 1641799220,`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"974fde14-c3a4-481b-9b03-cfce182c3a07\": {},\n\t\t\t},\n\t\t},\n\t\t\"oauth paths\": {\n\t\t\tInput: `        \"authPath\": \"/9b4bfaea-dd1c-4add-b1de-e10f51c65fd3/oauth2/v2.0/authorize\",\n\t\t/32896ed7-d559-401b-85cf-167143d61be0/B2C_1A_Tapio_Signin/v2.0\n\t\t/461858f4-9c0d-46e0-a9e6-aefc4889aad6/B2C_1_sign_up_or_sign_in/SelfAsserted?tx=S\n\t\t-ArgumentList \"/3f548be2-31e9-4681-839e-bc80d461f367/common/oauth2/authorize\"\n        \"jwks_uri\": \"/6babcaad-604b-40ac-a9d7-9fd97c0b779f/discovery/keys\",\n\t\tMetadataLocation = \"/b55f0c51-61a7-45c3-84df-33569b247796/federationmetadata/2007-06/federationmetadata.xml?appid=3245199b-1a5d-42df-93ce-e64ac7f5b938\n        \"kerberos_endpoint\": \"/a4067d12-2fc0-4367-a213-9e4031cbc173/kerberos\",\n\t\t/b2326b8a-059d-48ca-96ac-8d8d5d841860/login\n\t\t\"userinfo_endpoint\": \"/6ba4caad-604b-40ac-a9d7-9fd97c0b779f/openid/userinfo\"\n\t\t…en-US\",\"urlLogin\":\"/9673e9a8-aa57-4461-9336-5fd3f0034e18/reprocess?ctx=rQIIAZ2QvWvbQA…\n\t\t/6c912b97-d9f0-4472-a96a-d82de2f1d438/resume?ctx=rQIIAZVTP\n\t\t// /aa8306d8-5417-43cc-b8e8-7e77b918682c/v2.0/.well-known/openid-configuration\n\t\t// /051aeb51-408b-403b-b95c-4ff3b303a08a/token\n\t\t\"/4a5378f9-29f4-4d3e-be89-669d03ada9d8/uxlogout\"\n\t\t/dc38a67a-f981-4e24-ba16-4443ada44484/wsfed\n`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"051aeb51-408b-403b-b95c-4ff3b303a08a\": {},\n\t\t\t\t\"32896ed7-d559-401b-85cf-167143d61be0\": {},\n\t\t\t\t\"3f548be2-31e9-4681-839e-bc80d461f367\": {},\n\t\t\t\t\"461858f4-9c0d-46e0-a9e6-aefc4889aad6\": {},\n\t\t\t\t\"4a5378f9-29f4-4d3e-be89-669d03ada9d8\": {},\n\t\t\t\t\"6ba4caad-604b-40ac-a9d7-9fd97c0b779f\": {},\n\t\t\t\t\"6babcaad-604b-40ac-a9d7-9fd97c0b779f\": {},\n\t\t\t\t\"6c912b97-d9f0-4472-a96a-d82de2f1d438\": {},\n\t\t\t\t\"9673e9a8-aa57-4461-9336-5fd3f0034e18\": {},\n\t\t\t\t\"9b4bfaea-dd1c-4add-b1de-e10f51c65fd3\": {},\n\t\t\t\t\"a4067d12-2fc0-4367-a213-9e4031cbc173\": {},\n\t\t\t\t\"aa8306d8-5417-43cc-b8e8-7e77b918682c\": {},\n\t\t\t\t\"b2326b8a-059d-48ca-96ac-8d8d5d841860\": {},\n\t\t\t\t\"b55f0c51-61a7-45c3-84df-33569b247796\": {},\n\t\t\t\t\"dc38a67a-f981-4e24-ba16-4443ada44484\": {},\n\t\t\t},\n\t\t},\n\t\t\"x-anchor-mailbox\": {\n\t\t\t// The tenantID can be encoded in this parameter.\n\t\t\t// https://github.com/AzureAD/microsoft-authentication-library-for-python/blob/95a63a7fe97d91b99979e5bf78e03f6acf40a286/msal/application.py#L185-L186\n\t\t\t// https://github.com/silverhack/monkey365/blob/b3f43c4a2d014fcc3aae0a4103c8f2610fbb4980/core/utils/Get-MonkeySecCompBackendUri.ps1#L70\n\t\t\tInput: `      User-Agent:\n      - python-requests/2.31.0\n      X-AnchorMailbox:\n        - Oid:2b9b0cb5-d707-42e3-9504-d9b76ac7bec5@86843c34-863b-44d3-bb14-4f14e7c0564d\n      x-client-current-telemetry:\n        - 4|84,3|`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"86843c34-863b-44d3-bb14-4f14e7c0564d\": {},\n\t\t\t},\n\t\t},\n\n\t\t// Tenant onmicrosoft.com\n\t\t\"onmicrosoft tenant\": {\n\t\t\tInput: `  \"oid\": \"7be15f3a-d9b5-4080-ba37-95aa2e3d244e\",\n  \"platf\": \"3\",\n  \"puid\": \"10032001170600C8\",\n  \"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\",\n  \"signin_state\": [\n    \"kmsi\"\n  ],\n  \"sub\": \"jIzit1WEdXqAH9KZXz-e-UcqsVa1pyPoh-2hw3xjEO4\",\n  \"tenant_region_scope\": \"AS\",\n  \"unique_name\": \"ben@xhoaxiuqng.onmicrosoft.com\",\n  \"uti\": \"2Y26RWHsWEiqhD2vi_PFAg\",\n  \"ver\": \"1.0\",\n  \"wids\": [\n    \"62e90394-69f5-4237-9190-012177145e10\",\n    \"b79fbf4d-3ef9-4689-8143-76b194e85509\"\n  ],`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"xhoaxiuqng.onmicrosoft.com\": {},\n\t\t\t},\n\t\t},\n\n\t\t// Arbitrary test cases\n\t\t\"spacing\": {\n\t\t\tInput: `| Variable name     | Description                                                   | Example value                         |\n| ----------------- | ------------------------------------------------------------- | ------------------------------------- |\n| AFASBaseUri       | Base URI of the AFAS REST API endpoint for this environment   | https://12345.rest.afas.online/ProfitRestServices |\n| AFASToke          | App token in XML format for this environment                  | \\<token>\\<version>1\\</version>\\<data>D5R324DD5F4TRD945E530ED3CDD70D94BBDEC4C732B43F285ECB12345678\\</data>\\</token>    |\n| AADtenantID       | Id of the Azure tenant                                        | 12fc345b-0c67-4cde-8902-dabf2cad34b5  |\n| AADAppId          | Id of the Azure app                                           | f12345c6-7890-1f23-b456-789eb0bb1c23  |\n| AADAppSecret      | Secret of the Azure app                                       | G1X2HsBw-co3dTIB45RE6vY.mSU~6u.7.8    |`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"12fc345b-0c67-4cde-8902-dabf2cad34b5\": {},\n\t\t\t},\n\t\t},\n\t\t\"newline\": {\n\t\t\tInput: `        {\\n   \\\"mode\\\": \\\"Manual\\\"\\n  },\\n  \\\"bootstrapProfile\\\": {\\n   \\\"artifactSource\\\":\n        \\\"Direct\\\"\\n  }\\n },\\n \\\"identity\\\": {\\n  \\\"type\\\": \\\"SystemAssigned\\\",\\n\n        \\ \\\"principalId\\\":\\\"00000000-0000-0000-0000-000000000001\\\",\\n  \\\"tenantId\\\":\n        \\\"d0a69dfd-9b9e-4833-9c33-c7903dd2e012\\\"\\n },\\n \\\"sku\\\": {\\n  \\\"name\\\": \\\"Base\\\",\\n\n        \\ \\\"tier\\\": \\\"Free\\\"\\n }\\n}\"\n    headers:`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"d0a69dfd-9b9e-4833-9c33-c7903dd2e012\": {},\n\t\t\t},\n\t\t},\n\n\t\t// False positives\n\t\t\"tid shouldn't match clientId\": {\n\t\t\tInput:    `\"userId\": \"jdoe@businesscorp.ca\", \"isUserIdDisplayable\": true, \"isMRRT\": true, \"_clientId\": \"d3590ed6-52b3-4102-aeff-aad2292ab01c\", }`,\n\t\t\tExpected: nil,\n\t\t},\n\t\t\"tid shouldn't match subscription_id\": {\n\t\t\tInput: `location = \"eastus\"\nsubscription_id = \"47ab1364-000d-4a53-838d-1537b1e3b49f\"`,\n\t\t\tExpected: nil,\n\t\t},\n\t}\n\n\trunPatTest(t, cases, FindTenantIdMatches)\n}\n\nfunc Test_FindClientIdMatches(t *testing.T) {\n\tcases := map[string]testCase{\n\t\t\"app\": {\n\t\t\tInput: `var app = \"4ba50db1-3f3f-4521-8a9a-1be0864d922a\"`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"4ba50db1-3f3f-4521-8a9a-1be0864d922a\": {},\n\t\t\t},\n\t\t},\n\t\t\"appid\": {\n\t\t\tInput: `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\n{\n  \"appId\": \"4ba50db1-3f3f-4521-8a9a-1be0864d922a\",\n  \"displayName\": \"azure-cli-2022-12-02-15-40-24\",`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"4ba50db1-3f3f-4521-8a9a-1be0864d922a\": {},\n\t\t\t},\n\t\t},\n\t\t\"app_id\": {\n\t\t\tInput: `msal:\n\tapp_id: 'b9cbc91c-c890-4824-a487-91611bb0615a'`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"b9cbc91c-c890-4824-a487-91611bb0615a\": {},\n\t\t\t},\n\t\t},\n\t\t\"application\": {\n\t\t\tInput: `const application = \\x60902aeb6d-29c7-4f6e-849d-4b933117e320\\x60`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"902aeb6d-29c7-4f6e-849d-4b933117e320\": {},\n\t\t\t},\n\t\t},\n\t\t\"applicationid\": {\n\t\t\tInput: `# Login using Service Principal\n$ApplicationId = \"1e002bca-c6e2-446e-a29e-a221909fe8aa\"`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"1e002bca-c6e2-446e-a29e-a221909fe8aa\": {},\n\t\t\t},\n\t\t},\n\t\t\"application id\": {\n\t\t\tInput: `The application id is \"029e3b51-60dd-47aa-81ad-3c15b389db86\", you need to`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"029e3b51-60dd-47aa-81ad-3c15b389db86\": {},\n\t\t\t},\n\t\t},\n\t\t\"application_id\": {\n\t\t\tInput: `        credential:\n          application_id: |\n\t\t\tbafe0126-03eb-4917-b3ff-4601c4e8f12f`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"bafe0126-03eb-4917-b3ff-4601c4e8f12f\": {},\n\t\t\t},\n\t\t},\n\t\t\"application-id\": {\n\t\t\tInput: `vcap.services.msal.application-id: 0704100e-7e76-4e62-bfb6-70bfd33906e2`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"0704100e-7e76-4e62-bfb6-70bfd33906e2\": {},\n\t\t\t},\n\t\t},\n\t\t\"client\": {\n\t\t\tInput: `String client = \"902aeb6d-29c7-4f6e-849d-4b933117e320\";`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"902aeb6d-29c7-4f6e-849d-4b933117e320\": {},\n\t\t\t},\n\t\t},\n\t\t\"clientid\": {\n\t\t\tInput: `export const msalConfig = {\n  auth: {\n    clientId: '82c54108-535c-40b2-87dc-2db599df3810',`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"82c54108-535c-40b2-87dc-2db599df3810\": {},\n\t\t\t},\n\t\t},\n\t\t\"client id\": {\n\t\t\tInput: `The client ID is: a54e584d-6fc4-464c-8479-dc67b5d87ab9`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"a54e584d-6fc4-464c-8479-dc67b5d87ab9\": {},\n\t\t\t},\n\t\t},\n\t\t\"client_id\": {\n\t\t\tInput: `location = \"eastus\"\nclient_id = \"89d5bd08-0d51-42cd-8eab-382c3ce11199\"\nsubscription_id = \"47ab1364-000d-4a53-838d-1537b1e3b49f\"\n`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"89d5bd08-0d51-42cd-8eab-382c3ce11199\": {},\n\t\t\t},\n\t\t},\n\t\t\"client-id\": {\n\t\t\tInput: `@TestPropertySource(properties = {\n        \"cas.authn.azure-active-directory.client-id=532c556b-1260-483f-9695-68d087fcd965\",\n        \"cas.authn.azure-active-directory.client-secret`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"532c556b-1260-483f-9695-68d087fcd965\": {},\n\t\t\t},\n\t\t},\n\t\t\"username\": {\n\t\t\tInput: `az login --service-principal --username \"21e144ac-532d-49ad-ba15-1c40694ce8b1\" --password`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"21e144ac-532d-49ad-ba15-1c40694ce8b1\": {},\n\t\t\t},\n\t\t},\n\t\t\"-u\": {\n\t\t\tInput: `az login --service-principal -u \"21e144ac-532d-49ad-ba15-1c40694ce8b1\" -p`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"21e144ac-532d-49ad-ba15-1c40694ce8b1\": {},\n\t\t\t},\n\t\t},\n\n\t\t// Arbitrary test cases\n\t\t\"spacing\": {\n\t\t\tInput: `| Variable name     | Description                                                   | Example value                         |\n| ----------------- | ------------------------------------------------------------- | ------------------------------------- |\n| AFASBaseUri       | Base URI of the AFAS REST API endpoint for this environment   | https://12345.rest.afas.online/ProfitRestServices |\n| AFASToke          | App token in XML format for this environment                  | \\<token>\\<version>1\\</version>\\<data>D5R324DD5F4TRD945E530ED3CDD70D94BBDEC4C732B43F285ECB12345678\\</data>\\</token>    |\n| AADtenantID       | Id of the Azure tenant                                        | 12fc345b-0c67-4cde-8902-dabf2cad34b5  |\n| AADAppId          | Id of the Azure app                                           | f12345c6-7890-1f23-b456-789eb0bb1c23  |\n| AADAppSecret      | Secret of the Azure app                                       | G1X2HsBw-co3dTIB45RE6vY.mSU~6u.7.8    |`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"f12345c6-7890-1f23-b456-789eb0bb1c23\": {},\n\t\t\t},\n\t\t},\n\t}\n\n\trunPatTest(t, cases, FindClientIdMatches)\n}\n"
  },
  {
    "path": "pkg/detectors/azure_entra/refreshtoken/refreshtoken.go",
    "content": "package refreshtoken\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/golang-jwt/jwt/v5\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ interface {\n\tdetectors.Detector\n\tdetectors.MaxSecretSizeProvider\n\tdetectors.StartOffsetProvider\n} = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\trefreshTokenPat = regexp.MustCompile(`\\b[01]\\.A[\\w-]{50,}(?:\\.\\d)?\\.Ag[\\w-]{250,}(?:\\.A[\\w-]{200,})?`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"0.A\", \"1.A\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AzureRefreshToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Azure Entra ID refresh tokens provide long-lasting access to an account.\"\n}\n\nfunc (Scanner) MaxSecretSize() int64 { return 2048 }\n\nfunc (Scanner) StartOffset() int64 { return 4096 }\n\n// FromData will find and optionally verify Azure RefreshToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\ttokenMatches := findTokenMatches(dataStr)\n\tif len(tokenMatches) == 0 {\n\t\treturn\n\t}\n\tclientMatches := azure_entra.FindClientIdMatches(dataStr)\n\tif len(clientMatches) == 0 {\n\t\tclientMatches[defaultClientId] = struct{}{}\n\t}\n\ttenantMatches := azure_entra.FindTenantIdMatches(dataStr)\n\tif len(tenantMatches) == 0 {\n\t\ttenantMatches[defaultTenantId] = struct{}{}\n\t}\n\n\treturn s.processMatches(ctx, tokenMatches, clientMatches, tenantMatches, verify), err\n}\n\nfunc (s Scanner) processMatches(ctx context.Context, refreshTokens, clientIds, tenantIds map[string]struct{}, verify bool) (results []detectors.Result) {\n\tlogCtx := logContext.AddLogger(ctx)\n\tinvalidClientsForTenant := make(map[string]map[string]struct{})\n\tvalidTenants := make(map[string]struct{})\n\nTokenLoop:\n\tfor token := range refreshTokens {\n\t\tvar (\n\t\t\tr        *detectors.Result\n\t\t\tclientId string\n\t\t\ttenantId string\n\t\t)\n\n\tClientLoop:\n\t\tfor cId := range clientIds {\n\t\t\tclientId = cId\n\t\t\tfor tId := range tenantIds {\n\t\t\t\ttenantId = tId\n\n\t\t\t\t// Skip known invalid tenants.\n\t\t\t\tinvalidClients := invalidClientsForTenant[tenantId]\n\t\t\t\tif invalidClients == nil {\n\t\t\t\t\tinvalidClients = map[string]struct{}{}\n\t\t\t\t\tinvalidClientsForTenant[tenantId] = invalidClients\n\t\t\t\t}\n\t\t\t\tif _, ok := invalidClients[clientId]; ok {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tclient := s.client\n\t\t\t\t\tif client == nil {\n\t\t\t\t\t\tclient = defaultClient\n\t\t\t\t\t}\n\n\t\t\t\t\tif _, ok := validTenants[tenantId]; !ok {\n\t\t\t\t\t\tif azure_entra.TenantExists(logCtx, client, tenantId) {\n\t\t\t\t\t\t\tvalidTenants[tenantId] = struct{}{}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdelete(tenantIds, tenantId)\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, token, clientId, tenantId)\n\t\t\t\t\t// Handle errors.\n\t\t\t\t\tif verificationErr != nil {\n\t\t\t\t\t\tif errors.Is(verificationErr, ErrTenantNotFound) {\n\t\t\t\t\t\t\t// Tenant doesn't exist. This shouldn't happen with the check above.\n\t\t\t\t\t\t\tdelete(tenantIds, tenantId)\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t} else if errors.Is(verificationErr, ErrClientNotFoundInTenant) {\n\t\t\t\t\t\t\t// Tenant is valid but the ClientID doesn't exist.\n\t\t\t\t\t\t\tinvalidClients[clientId] = struct{}{}\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t} else if errors.Is(verificationErr, ErrTokenExpired) {\n\t\t\t\t\t\t\tcontinue TokenLoop\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Received an unexpected/unhandled error type.\n\t\t\t\t\t\t\tr = createResult(token, clientId, tenantId, isVerified, extraData, verificationErr)\n\t\t\t\t\t\t\tbreak ClientLoop\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// The result is verified or there's only one associated client and tenant.\n\t\t\t\t\tif isVerified {\n\t\t\t\t\t\tr = createResult(token, clientId, tenantId, isVerified, extraData, verificationErr)\n\t\t\t\t\t\tbreak ClientLoop\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif r == nil {\n\t\t\t// Only include the clientId and tenantId if we're confident which one it is.\n\t\t\tif len(clientIds) != 1 || clientId == defaultClientId {\n\t\t\t\tclientId = \"\"\n\t\t\t}\n\t\t\tif len(tenantIds) != 1 || tenantId == defaultTenantId {\n\t\t\t\ttenantId = \"\"\n\t\t\t}\n\t\t\tr = createResult(token, clientId, tenantId, false, nil, nil)\n\t\t}\n\n\t\tresults = append(results, *r)\n\t}\n\treturn results\n}\n\nconst defaultTenantId = \"common\"\nconst defaultClientId = \"d3590ed6-52b3-4102-aeff-aad2292ab01c\" // Microsoft Office\n\nvar (\n\tErrTokenExpired           = errors.New(\"token expired\")\n\tErrTenantNotFound         = errors.New(\"tenant not found\")\n\tErrClientNotFoundInTenant = errors.New(\"application was not found in tenant\")\n)\n\n// https://learn.microsoft.com/en-us/advertising/guides/authentication-oauth-get-tokens?view=bingads-13#refresh-accesstoken\nfunc verifyMatch(ctx context.Context, client *http.Client, refreshToken string, clientId string, tenantId string) (bool, map[string]string, error) {\n\tdata := url.Values{}\n\tdata.Set(\"client_id\", clientId)\n\tdata.Set(\"scope\", \"https://graph.microsoft.com/.default\")\n\tdata.Set(\"refresh_token\", refreshToken)\n\tdata.Set(\"grant_type\", \"refresh_token\")\n\n\ttokenUrl := fmt.Sprintf(\"https://login.microsoftonline.com/%s/oauth2/v2.0/token\", tenantId)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, tokenUrl, bytes.NewBufferString(data.Encode()))\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\t// Refresh token is valid.\n\tif res.StatusCode == http.StatusOK {\n\t\tvar okResp successResponse\n\t\tif err := json.NewDecoder(res.Body).Decode(&okResp); err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\n\t\textraData := map[string]string{\n\t\t\t\"Tenant\": tenantId,\n\t\t\t\"Client\": clientId,\n\t\t\t\"Scope\":  okResp.Scope,\n\t\t}\n\n\t\t// Add claims from the access token.\n\t\ttoken, _ := jwt.Parse(okResp.AccessToken, nil)\n\t\tif token != nil {\n\t\t\tclaims := token.Claims.(jwt.MapClaims)\n\n\t\t\tif app := fmt.Sprint(claims[\"app_displayname\"]); app != \"\" {\n\t\t\t\textraData[\"Application\"] = app\n\t\t\t}\n\n\t\t\t// The user information can be in a few claims.\n\t\t\tswitch {\n\t\t\tcase claims[\"email\"] != nil:\n\t\t\t\textraData[\"User\"] = fmt.Sprint(claims[\"email\"])\n\t\t\tcase claims[\"upn\"] != nil:\n\t\t\t\textraData[\"User\"] = fmt.Sprint(claims[\"upn\"])\n\t\t\tcase claims[\"unique_name\"]:\n\t\t\t\textraData[\"User\"] = fmt.Sprint(claims[\"unique_name\"])\n\t\t\t}\n\t\t}\n\t\treturn true, extraData, nil\n\t}\n\n\t// Credentials *probably* aren't valid.\n\tvar errResp errorResponse\n\tif err := json.NewDecoder(res.Body).Decode(&errResp); err != nil {\n\t\treturn false, nil, err\n\t}\n\n\tswitch res.StatusCode {\n\tcase http.StatusBadRequest:\n\t\t// Error codes can be looked up by removing the `AADSTS` prefix.\n\t\t// https://login.microsoftonline.com/error?code=9002313\n\t\td := errResp.Description\n\t\tswitch {\n\t\tcase strings.HasPrefix(d, \"AADSTS70008:\"),\n\t\t\tstrings.HasPrefix(d, \"AADSTS700082:\"),\n\t\t\tstrings.HasPrefix(d, \"AADSTS70043:\"):\n\t\t\t// https://login.microsoftonline.com/error?code=70008\n\t\t\t// https://login.microsoftonline.com/error?code=700082\n\t\t\t// https://login.microsoftonline.com/error?code=70043\n\t\t\treturn false, nil, ErrTokenExpired\n\t\tcase strings.HasPrefix(d, \"AADSTS700016:\"):\n\t\t\t// https://login.microsoftonline.com/error?code=700016\n\t\t\treturn false, nil, ErrClientNotFoundInTenant\n\t\tcase strings.HasPrefix(d, \"AADSTS90002:\"):\n\t\t\t// https://login.microsoftonline.com/error?code=90002\n\t\t\treturn false, nil, ErrTenantNotFound\n\t\tcase strings.HasPrefix(d, \"AADSTS9002313:\"):\n\t\t\t// This seems to be a generic \"invalid token\" error code.\n\t\t\t// 'invalid_grant': AADSTS9002313: Invalid request. Request is malformed or invalid.\n\t\t\treturn false, nil, nil\n\t\tdefault:\n\t\t\treturn false, nil, fmt.Errorf(\"unexpected error '%s': %s\", errResp.Error, errResp.Description)\n\t\t}\n\tcase http.StatusUnauthorized:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\ntype successResponse struct {\n\tScope       string `json:\"scope\"`\n\tAccessToken string `json:\"access_token\"`\n}\n\ntype errorResponse struct {\n\tError       string `json:\"error\"`\n\tDescription string `json:\"error_description\"`\n}\n\n// region Helper methods.\nfunc findTokenMatches(data string) map[string]struct{} {\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range refreshTokenPat.FindAllStringSubmatch(data, -1) {\n\t\tm := match[0]\n\t\tif detectors.StringShannonEntropy(m) < 4 {\n\t\t\tcontinue\n\t\t}\n\t\tuniqueMatches[m] = struct{}{}\n\t}\n\treturn uniqueMatches\n}\n\nfunc createResult(refreshToken, clientId, tenantId string, verified bool, extraData map[string]string, err error) *detectors.Result {\n\tr := &detectors.Result{\n\t\tDetectorType: detectorspb.DetectorType_AzureRefreshToken,\n\t\tRaw:          []byte(refreshToken),\n\t\tExtraData:    extraData,\n\t\tVerified:     verified,\n\t}\n\tr.SetVerificationError(err, refreshToken)\n\n\tif clientId != \"\" && tenantId != \"\" {\n\t\tvar sb strings.Builder\n\t\tsb.WriteString(`{`)\n\t\tsb.WriteString(`\"refreshToken\":\"` + refreshToken + `\"`)\n\t\tsb.WriteString(`,\"clientId\":\"` + clientId + `\"`)\n\t\tsb.WriteString(`,\"tenantId\":\"` + tenantId + `\"`)\n\t\tsb.WriteString(`}`)\n\t\tr.RawV2 = []byte(sb.String())\n\t}\n\n\treturn r\n}\n\n// endregion\n"
  },
  {
    "path": "pkg/detectors/azure_entra/refreshtoken/refreshtoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage refreshtoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRefreshToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AZUREREFRESHTOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"AZUREREFRESHTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azurerefreshtoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureRefreshToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureRefreshToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azurerefreshtoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureRefreshToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azurerefreshtoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureRefreshToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Azurerefreshtoken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Azurerefreshtoken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azure_entra/refreshtoken/refreshtoken_test.go",
    "content": "package refreshtoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestRefreshToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t// Valid - 0.\n\t\t{\n\t\t\tname:  \"valid - token only\",\n\t\t\tinput: `\"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\"`,\n\t\t\twant:  []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\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid - token+client+tenant\",\n\t\t\tinput: `\n\t\t\t\t\t{\n\t\t\t\t\t\t\"tokenType\": \"Bearer\",\n\t\t\t\t\t\t\"expiresIn\": 4742,\n\t\t\t\t\t\t\"expiresOn\": \"2024-06-07 09:09:22.294640\",\n\t\t\t\t\t\t\"resource\": \"https://graph.windows.net\",\n\t\t\t\t\t\t\"accessToken\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\",\n\t\t\t\t\t\t\"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\",\n\t\t\t\t\t\t\"familyName\": \"Doe\",\n\t\t\t\t\t\t\"givenName\": \"John\",\n\t\t\t\t\t\t\"identityProvider\": \"live.com\",\n\t\t\t\t\t\t\"tenantId\": \"16515984-9303-47f6-a59f-917611c8cb2b\",\n\t\t\t\t\t\t\"userId\": \"john.doe@outlook.com\",\n\t\t\t\t\t\t\"isUserIdDisplayable\": true,\n\t\t\t\t\t\t\"isMRRT\": true,\n\t\t\t\t\t\t\"_clientId\": \"1b730954-1685-4b74-9bfd-dac224a7b894\",\n\t\t\t\t\t\t\"_authority\": \"https://login.microsoftonline.com/16515984-9303-47f6-a59f-917611c8cb2b\"\n\t\t\t\t\t}`,\n\t\t\twant: []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\"}`},\n\t\t},\n\t\t{\n\t\t\tname: \"valid - 0. in README\",\n\t\t\tinput: `\n\t\t\t\t### Connection settings\n\n\t\t\t\tThe connection settings are defined in the automation variables.\n\t\t\t\t1. Create the following [user defined variables](https://docs.helloid.com/hc/en-us/articles/360014169933-How-to-Create-and-Manage-User-Defined-Variables)\n\n\t\t\t\t| Variable name     | Description                                                   | Example value                         |\n\t\t\t\t| ----------------- | ------------------------------------------------------------- | ------------------------------------- |\n\t\t\t\t| AFASBaseUri       | Base URI of the AFAS REST API endpoint for this environment   | https://12345.rest.afas.online/ProfitRestServices |\n\t\t\t\t| AFASToke          | App token in XML format for this environment                  | \\<token>\\<version>1\\</version>\\<data>D5R324DD5F4TRD945E530ED3CDD70D94BBDEC4C732B43F285ECB12345678\\</data>\\</token>    |\n\t\t\t\t| AADtenantID       | Id of the Azure tenant                                        | 12fc345b-0c67-4cde-8902-dabf2cad34b5  |\n\t\t\t\t| AADAppId          | Id of the Azure app                                           | f12345c6-7890-1f23-b456-789eb0bb1c23  |\n\t\t\t\t| 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 |\n\n\t\t\t\t## contents`,\n\t\t\twant: []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\"}`},\n\t\t},\n\n\t\t// Valid 1.\n\t\t{\n\t\t\tname: \"valid - 1. token only\",\n\t\t\tinput: ` \"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\",\n\t\t\t\t\t\"expires_at\": 1733138350.558087\n\t\t\t\t\t}`,\n\t\t\twant: []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\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid - 1. tenant+client+token\",\n\t\t\tinput: `\n\t\t\t\tasync function getAccessToken() {\n\t\t\t\tconst tenantId = \"31d1b7f4-4c4c-44cf-8d4e-b63e8512543e\";\n\t\t\t\tconst clientId = \"16ed71fb-067e-47d9-b4bc-7656b14f1c5e\";\n\t\t\t\tconst clientSecret = \"\"; //para que funcione en sus ambientes tienen que poner el secreto, \n\t\t\t\t\t\t\t\t\t\t//si no lo tienen me lo piden y se los comparto por whatsapp, \n\t\t\t\t\t\t\t\t\t\t//lo tuvé que quitar porque no me dejaba hacer commit de los cambios en el repositorio\n\t\t\t\tconst scope = \"https://analysis.windows.net/powerbi/api/.default\";\n\t\t\t\tlet 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\";\n\t\t\t\t`,\n\t\t\twant: []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\"}`},\n\t\t},\n\t\t{\n\t\t\tname: \"valid - 1. with more than 3 segments\",\n\t\t\tinput: `- request:\n\t\t\t\t\tbody: 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\n\t\t\t\t\theaders:`,\n\t\t\twant: []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`},\n\t\t},\n\n\t\t// Invalid\n\t\t{\n\t\t\tname:  \"invalid - too short\",\n\t\t\tinput: `\"refresh_token\": \"0.AXEAFN5Pl6TDG0ibA8_OGCw6B-kFbFJoXnhBqmJD9wukrpZxAMc.AgABAAAAAAD--DLA3VO7QrddgJg7WevrAgDs_wQA9P9g0VCdz8sm...\"`,\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid - low entropy\",\n\t\t\tinput: `\"refresh_token\": \"0.Axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.Agxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"`,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azure_entra/serviceprincipal/sp.go",
    "content": "package serviceprincipal\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/golang-jwt/jwt/v5\"\n)\n\nvar (\n\tDescription                = \"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.\"\n\tErrConditionalAccessPolicy = errors.New(\"access blocked by Conditional Access policies (AADSTS53003)\")\n\tErrSecretInvalid           = errors.New(\"invalid client secret provided\")\n\tErrSecretExpired           = errors.New(\"the provided secret is expired\")\n\tErrTenantNotFound          = errors.New(\"tenant not found\")\n\tErrClientNotFoundInTenant  = errors.New(\"application was not found in tenant\")\n)\n\ntype TokenOkResponse struct {\n\tAccessToken string `json:\"access_token\"`\n}\n\ntype TokenErrResponse struct {\n\tError       string `json:\"error\"`\n\tDescription string `json:\"error_description\"`\n}\n\n// VerifyCredentials attempts to get a token using the provided client credentials.\n// See: https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#get-a-token\nfunc VerifyCredentials(ctx context.Context, client *http.Client, tenantId string, clientId string, clientSecret string) (bool, map[string]string, error) {\n\tdata := url.Values{}\n\tdata.Set(\"client_id\", clientId)\n\tdata.Set(\"scope\", \"https://graph.microsoft.com/.default\")\n\tdata.Set(\"client_secret\", clientSecret)\n\tdata.Set(\"grant_type\", \"client_credentials\")\n\n\ttokenUrl := fmt.Sprintf(\"https://login.microsoftonline.com/%s/oauth2/v2.0/token\", tenantId)\n\tencodedData := data.Encode()\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, tokenUrl, strings.NewReader(encodedData))\n\tif err != nil {\n\t\treturn false, nil, nil\n\t}\n\treq.Header.Set(\"Accept\", \"application/json\")\n\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\treq.Header.Set(\"Content-Length\", strconv.Itoa(len(encodedData)))\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\t// Credentials are valid.\n\tif res.StatusCode == http.StatusOK {\n\t\tvar okResp TokenOkResponse\n\t\tif err := json.NewDecoder(res.Body).Decode(&okResp); err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\n\t\textraData := map[string]string{\n\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/azure/\",\n\t\t\t\"tenant\":         tenantId,\n\t\t\t\"client\":         clientId,\n\t\t}\n\n\t\t// Add claims from the access token.\n\t\tif token, _ := jwt.Parse(okResp.AccessToken, nil); token != nil {\n\t\t\tclaims := token.Claims.(jwt.MapClaims)\n\n\t\t\tif app := claims[\"app_displayname\"]; app != nil {\n\t\t\t\textraData[\"application\"] = fmt.Sprint(app)\n\t\t\t}\n\t\t}\n\t\treturn true, extraData, nil\n\t}\n\n\t// Credentials *probably* aren't valid.\n\tvar errResp TokenErrResponse\n\tif err := json.NewDecoder(res.Body).Decode(&errResp); err != nil {\n\t\treturn false, nil, err\n\t}\n\n\tswitch res.StatusCode {\n\tcase http.StatusBadRequest, http.StatusUnauthorized:\n\t\t// Error codes can be looked up by removing the `AADSTS` prefix.\n\t\t// https://login.microsoftonline.com/error?code=9002313\n\t\t// TODO: Handle AADSTS900382 (https://github.com/Azure/azure-sdk-for-js/issues/30557)\n\t\td := errResp.Description\n\t\tswitch {\n\t\tcase strings.HasPrefix(d, \"AADSTS53003:\"):\n\t\t\treturn false, nil, ErrConditionalAccessPolicy\n\t\tcase strings.HasPrefix(d, \"AADSTS700016:\"):\n\t\t\t// https://login.microsoftonline.com/error?code=700016\n\t\t\treturn false, nil, ErrClientNotFoundInTenant\n\t\tcase strings.HasPrefix(d, \"AADSTS7000215:\"):\n\t\t\t// https://login.microsoftonline.com/error?code=7000215\n\t\t\treturn false, nil, ErrSecretInvalid\n\t\tcase strings.HasPrefix(d, \"AADSTS7000222:\"):\n\t\t\t// The secret has expired.\n\t\t\t// https://login.microsoftonline.com/error?code=7000222\n\t\t\treturn false, nil, ErrSecretExpired\n\t\tcase strings.HasPrefix(d, \"AADSTS90002:\"):\n\t\t\t// https://login.microsoftonline.com/error?code=90002\n\t\t\treturn false, nil, ErrTenantNotFound\n\t\tdefault:\n\t\t\treturn false, nil, fmt.Errorf(\"unexpected error '%s': %s\", errResp.Error, errResp.Description)\n\t\t}\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azure_entra/serviceprincipal/v1/spv1.go",
    "content": "package v1\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra/serviceprincipal\"\n\tv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra/serviceprincipal/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ interface {\n\tdetectors.Detector\n\tdetectors.Versioner\n} = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// TODO: Azure storage access keys and investigate other types of creds.\n\t// https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#second-case-access-token-request-with-a-certificate\n\t// https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#third-case-access-token-request-with-a-federated-credential\n\t//clientSecretPat = regexp.MustCompile(`(?i)(?:secret|password| -p[ =]).{0,80}?([\\w~@[\\]:.?*/+=-]{31,34}`)\n\t// TODO: Tighten this regex and replace it with above.\n\tsecretPat = regexp.MustCompile(`(?i)(?:secret|password| -p[ =]).{0,80}[^A-Za-z0-9!#$%&()*+,\\-./:;<=>?@[\\\\\\]^_{|}~]([A-Za-z0-9!#$%&()*+,\\-./:;<=>?@[\\\\\\]^_{|}~]{31,34})[^A-Za-z0-9!#$%&()*+,\\-./:;<=>?@[\\\\\\]^_{|}~]`)\n)\n\nfunc (s Scanner) Version() int {\n\treturn 1\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"azure\", \"az\", \"entra\", \"msal\", \"login.microsoftonline.com\", \".onmicrosoft.com\"}\n}\n\n// FromData will find and optionally verify Azure secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tclientSecrets := findSecretMatches(dataStr)\n\tif len(clientSecrets) == 0 {\n\t\treturn\n\t}\n\tclientIds := azure_entra.FindClientIdMatches(dataStr)\n\tif len(clientIds) == 0 {\n\t\treturn\n\t}\n\ttenantIds := azure_entra.FindTenantIdMatches(dataStr)\n\n\tclient := s.client\n\tif client == nil {\n\t\tclient = defaultClient\n\t}\n\t// The handling logic is identical for both versions.\n\tresults = append(results, v2.ProcessData(ctx, clientSecrets, clientIds, tenantIds, verify, client)...)\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Azure\n}\n\nfunc (s Scanner) Description() string {\n\treturn serviceprincipal.Description\n}\n\nfunc findSecretMatches(data string) map[string]struct{} {\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range secretPat.FindAllStringSubmatch(data, -1) {\n\t\tm := match[1]\n\t\t// Ignore secrets that are handled by the V2 detector.\n\t\tif v2.SecretPat.MatchString(m) {\n\t\t\tcontinue\n\t\t}\n\t\tuniqueMatches[m] = struct{}{}\n\t}\n\treturn uniqueMatches\n}\n"
  },
  {
    "path": "pkg/detectors/azure_entra/serviceprincipal/v1/spv1_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage v1\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAzure_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AZURE_SECRET\")\n\tsecretInactive := testSecrets.MustGetField(\"AZURE_INACTIVE\")\n\tid := testSecrets.MustGetField(\"AZURE_ID\")\n\ttenantId := testSecrets.MustGetField(\"AZURE_TENANT_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(`\n\t\t\t\ttenant_id=%s\n\t\t\t\tclient_id=%s\n\t\t\t\tclient_secret=%s\n\t\t\t\tclient_secret=%s\n\t\t\t\t`, tenantId, id, secretInactive, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Azure,\n\t\t\t\t\tRedacted:     id,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(`\n\t\t\t\ttenant_id=%s\n\t\t\t\tclient_id=%s\n\t\t\t\tclient_secret=%s\n\t\t\t\t`, tenantId, id, secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Azure,\n\t\t\t\t\tRedacted:     id,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Azure.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Azure.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azure_entra/serviceprincipal/v1/spv1_test.go",
    "content": "package v1\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n)\n\ntype testCase struct {\n\tInput    string\n\tExpected map[string]struct{}\n}\n\nfunc Test_FindClientSecretMatches(t *testing.T) {\n\tcases := map[string]testCase{\n\t\t\"client_secret\": {\n\t\t\tInput: `    \"TenantId\": \"3d7e0652-b03d-4ed2-bf86-f1299cecde17\",\n    \"ClientSecret\": \"gHduiL_j6t4b6DG?Qr-G6M@IOS?mX3B9\",`,\n\t\t\tExpected: map[string]struct{}{\"gHduiL_j6t4b6DG?Qr-G6M@IOS?mX3B9\": {}},\n\t\t},\n\t\t\"client_secret1\": {\n\t\t\tInput: `   public static string clientId = \"413ff05b-6d54-41a7-9271-9f964bc10624\";\n        public static string clientSecret = \"k72~odcN_6TbVh5D~19_1Qkj~87trteArL\";\n\n        private const string `,\n\t\t\tExpected: map[string]struct{}{\"k72~odcN_6TbVh5D~19_1Qkj~87trteArL\": {}},\n\t\t},\n\t\t\"client_secret2\": {\n\t\t\tInput: `    \"azClientSecret\": \"2bWD_tu3~9B0_.R0W3BFJN-Hu_xjfR8EL5\",\n    \"kvVaultUri\": \"https://corp.vault.azure.net/\",`,\n\t\t\tExpected: map[string]struct{}{\"2bWD_tu3~9B0_.R0W3BFJN-Hu_xjfR8EL5\": {}},\n\t\t},\n\t\t\"client_secret3\": {\n\t\t\tInput: `# COMMAND ----------\n\nclientID = \"193e3d24-8d04-404c-95a9-074efaa83147\"\ntenantID = \"28241a04-7ac0-44f1-a996-84dc181f9861\"\nsecret = \"a2djRWTXDS1iMbThoK.C7e:yVsUdL3[:\"`,\n\t\t\tExpected: map[string]struct{}{\"a2djRWTXDS1iMbThoK.C7e:yVsUdL3[:\": {}},\n\t\t},\n\t\t\"client_secret4\": {\n\t\t\tInput: `tenantID = \"9f37a392-g0ae-1280-9796-f1864210effc\"\nsecret = \"s.1_56k~5jmRDm23y.dTg5_XjTAcRjCbH.\"\n\n# COMMAND ----------\n\nconfigs = {\"fs.azure.account.auth.type\": \"OAuth\"`,\n\t\t\tExpected: map[string]struct{}{\"s.1_56k~5jmRDm23y.dTg5_XjTAcRjCbH.\": {}},\n\t\t},\n\t\t\"client_secret5\": {\n\t\t\tInput: `public class HardcodedAzureCredentials {\n\tprivate final String clientId = \"81734019-15a3-50t8-3253-5abe78abc3a1\";\n\tprivate final String username = \"username@example.onmicrosoft.com\";\n\tprivate final String clientSecret = \"1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_\";`,\n\t\t\tExpected: map[string]struct{}{\"1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_\": {}},\n\t\t},\n\t\t// https://github.com/kedacore/keda/blob/main/pkg/scalers/azure_log_analytics_scaler_test.go\n\t\t\"client_secret6\": {\n\t\t\tInput: `const (\n\ttenantID                    = \"d248da64-0e1e-4f79-b8c6-72ab7aa055eb\"\n\tclientID                    = \"41826dd4-9e0a-4357-a5bd-a88ad771ea7d\"\n\tclientSecret                = \"U6DtAX5r6RPZxd~l12Ri3X8J9urt5Q-xs\"\n\tworkspaceID                 = \"074dd9f8-c368-4220-9400-acb6e80fc325\"`,\n\t\t\tExpected: map[string]struct{}{\"U6DtAX5r6RPZxd~l12Ri3X8J9urt5Q-xs\": {}},\n\t\t},\n\t\t\"client_secret7\": {\n\t\t\tInput: `  \"AZUREAD-AKS-APPID-SECRET\": \"xW25Gt-Mf0.ue3jFqE68jtFqtt-4L_8R51\",\n  \"AZUREAD-AKS-TENANTID\": \"d3a761f8-e7ea-473a-b907-1e7b3ef92aa9\",`,\n\t\t\tExpected: map[string]struct{}{\"xW25Gt-Mf0.ue3jFqE68jtFqtt-4L_8R51\": {}},\n\t\t},\n\t\t\"client_secret8\": {\n\t\t\tInput:    ` \"AZUREAD-AKS-APPID-SECRET\": \"8w__IGsaY.6g6jUxb1.pPGK262._pgX.q-\",`,\n\t\t\tExpected: map[string]struct{}{\"8w__IGsaY.6g6jUxb1.pPGK262._pgX.q-\": {}},\n\t\t},\n\t\t// \"client_secret6\": {\n\t\t//\tInput:    ``,\n\t\t//\tExpected: map[string]struct{}{\"\": {}},\n\t\t// },\n\n\t\t\"password\": {\n\t\t\tInput: `# Login using Service Principal\n$ApplicationId = \"5cec5dfb-0ac4-4938-b477-3f9638881b93\"\n$SecuredPassword = ConvertTo-SecureString -String \"gHduiL_j6t4b6DG?Qr-G6M@IOS?mX3B9\" -AsPlainText -Force\n$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $ApplicationId, $SecuredPassword`,\n\t\t\tExpected: map[string]struct{}{\"gHduiL_j6t4b6DG?Qr-G6M@IOS?mX3B9\": {}},\n\t\t},\n\n\t\t// False positives\n\t\t\"placeholder_secret\": {\n\t\t\tInput: `- Log in with a service principal using a client secret:\n\naz login --service-principal --username {{http://azure-cli-service-principal}} --password {{secret}} --tenant {{someone.onmicrosoft.com}}`,\n\t\t\tExpected: nil,\n\t\t},\n\t\t// \"client_secret3\": {\n\t\t//\tInput: ``,\n\t\t//\tExpected: map[string]struct{}{\n\t\t//\t\t\"\": {},\n\t\t//\t},\n\t\t// },\n\t}\n\n\tfor name, test := range cases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tmatches := findSecretMatches(test.Input)\n\t\t\tif len(matches) == 0 {\n\t\t\t\tif len(test.Expected) != 0 {\n\t\t\t\t\tt.Fatalf(\"no matches found, expected: %v\", test.Expected)\n\t\t\t\t\treturn\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(test.Expected, matches); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azure_entra/serviceprincipal/v2/spv2.go",
    "content": "package v2\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra/serviceprincipal\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ interface {\n\tdetectors.Detector\n\tdetectors.Versioner\n} = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\tSecretPat = regexp.MustCompile(`(?:[^a-zA-Z0-9_~.-]|\\A)([a-zA-Z0-9_~.-]{3}\\dQ~[a-zA-Z0-9_~.-]{31,34})(?:[^a-zA-Z0-9_~.-]|\\z)`)\n)\n\nfunc (s Scanner) Version() int {\n\treturn 2\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"q~\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Azure\n}\n\nfunc (s Scanner) Description() string {\n\treturn serviceprincipal.Description\n}\n\n// FromData will find and optionally verify Azure secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tclientSecrets := findSecretMatches(dataStr)\n\tif len(clientSecrets) == 0 {\n\t\treturn results, nil\n\t}\n\tclientIds := azure_entra.FindClientIdMatches(dataStr)\n\ttenantIds := azure_entra.FindTenantIdMatches(dataStr)\n\n\tclient := s.client\n\tif client == nil {\n\t\tclient = defaultClient\n\t}\n\n\tresults = append(results, ProcessData(ctx, clientSecrets, clientIds, tenantIds, verify, client)...)\n\treturn results, nil\n}\n\nfunc ProcessData(ctx context.Context, clientSecrets, clientIds, tenantIds map[string]struct{}, verify bool, client *http.Client) (results []detectors.Result) {\n\tlogCtx := logContext.AddLogger(ctx)\n\tinvalidClientsForTenant := make(map[string]map[string]struct{})\n\nSecretLoop:\n\tfor clientSecret := range clientSecrets {\n\t\tvar (\n\t\t\tr        *detectors.Result\n\t\t\tclientId string\n\t\t\ttenantId string\n\t\t)\n\n\tClientLoop:\n\t\tfor cId := range clientIds {\n\t\t\tclientId = cId\n\t\t\tfor tId := range tenantIds {\n\t\t\t\ttenantId = tId\n\n\t\t\t\t// Skip known invalid tenants.\n\t\t\t\tinvalidClients := invalidClientsForTenant[tenantId]\n\t\t\t\tif invalidClients == nil {\n\t\t\t\t\tinvalidClients = map[string]struct{}{}\n\t\t\t\t\tinvalidClientsForTenant[tenantId] = invalidClients\n\t\t\t\t}\n\t\t\t\tif _, ok := invalidClients[clientId]; ok {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tif !azure_entra.TenantExists(logCtx, client, tenantId) {\n\t\t\t\t\t\t// Tenant doesn't exist\n\t\t\t\t\t\tdelete(tenantIds, tenantId)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\t// Tenant exists, ensure this isn't attempted as a clientId.\n\t\t\t\t\tdelete(clientIds, tenantId)\n\n\t\t\t\t\tisVerified, extraData, verificationErr := serviceprincipal.VerifyCredentials(ctx, client, tenantId, clientId, clientSecret)\n\t\t\t\t\t// Handle errors.\n\t\t\t\t\tif verificationErr != nil {\n\t\t\t\t\t\tswitch {\n\t\t\t\t\t\tcase errors.Is(verificationErr, serviceprincipal.ErrConditionalAccessPolicy):\n\t\t\t\t\t\t\t// Do nothing.\n\t\t\t\t\t\tcase errors.Is(verificationErr, serviceprincipal.ErrSecretInvalid):\n\t\t\t\t\t\t\tcontinue ClientLoop\n\t\t\t\t\t\tcase errors.Is(verificationErr, serviceprincipal.ErrSecretExpired):\n\t\t\t\t\t\t\tcontinue SecretLoop\n\t\t\t\t\t\tcase errors.Is(verificationErr, serviceprincipal.ErrTenantNotFound):\n\t\t\t\t\t\t\t// Tenant doesn't exist. This shouldn't happen with the check above.\n\t\t\t\t\t\t\tdelete(tenantIds, tenantId)\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\tcase errors.Is(verificationErr, serviceprincipal.ErrClientNotFoundInTenant):\n\t\t\t\t\t\t\t// Tenant is valid but the ClientID doesn't exist.\n\t\t\t\t\t\t\tinvalidClients[clientId] = struct{}{}\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// The result is verified or there's only one associated client and tenant.\n\t\t\t\t\tif isVerified || (len(clientIds) == 1 && len(tenantIds) == 1) {\n\t\t\t\t\t\tr = createResult(tenantId, clientId, clientSecret, isVerified, extraData, verificationErr)\n\t\t\t\t\t\tbreak ClientLoop\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif r == nil {\n\t\t\t// Only include the clientId and tenantId if we're confident which one it is.\n\t\t\tif len(clientIds) != 1 {\n\t\t\t\tclientId = \"\"\n\t\t\t}\n\t\t\tif len(tenantIds) != 1 {\n\t\t\t\ttenantId = \"\"\n\t\t\t}\n\t\t\tr = createResult(tenantId, clientId, clientSecret, false, nil, nil)\n\t\t}\n\n\t\tresults = append(results, *r)\n\t}\n\treturn results\n}\n\nfunc createResult(tenantId string, clientId string, clientSecret string, verified bool, extraData map[string]string, err error) *detectors.Result {\n\tr := &detectors.Result{\n\t\tDetectorType: detectorspb.DetectorType_Azure,\n\t\tRaw:          []byte(clientSecret),\n\t\tExtraData:    extraData,\n\t\tVerified:     verified,\n\t\tRedacted:     clientSecret[:5] + \"...\",\n\t}\n\tr.SetVerificationError(err, clientSecret)\n\n\t// Tenant ID is required for verification, but it may not always be present.\n\t// e.g., ACR or Azure SQL use client id+secret without tenant.\n\tif clientId != \"\" && tenantId != \"\" {\n\t\tvar sb strings.Builder\n\t\tsb.WriteString(`{`)\n\t\tsb.WriteString(`\"clientSecret\":\"` + clientSecret + `\"`)\n\t\tsb.WriteString(`,\"clientId\":\"` + clientId + `\"`)\n\t\tsb.WriteString(`,\"tenantId\":\"` + tenantId + `\"`)\n\t\tsb.WriteString(`}`)\n\t\tr.RawV2 = []byte(sb.String())\n\t}\n\n\treturn r\n}\n\nfunc findSecretMatches(data string) map[string]struct{} {\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range SecretPat.FindAllStringSubmatch(data, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\treturn uniqueMatches\n}\n"
  },
  {
    "path": "pkg/detectors/azure_entra/serviceprincipal/v2/spv2_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage v2\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAzure_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AZURE_SECRET\")\n\tsecretInactive := testSecrets.MustGetField(\"AZURE_INACTIVE\")\n\tid := testSecrets.MustGetField(\"AZURE_ID\")\n\ttenantId := testSecrets.MustGetField(\"AZURE_TENANT_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(`\n\t\t\t\ttenant_id=%s\n\t\t\t\tclient_id=%s\n\t\t\t\tclient_secret=%s\n\t\t\t\tclient_secret=%s\n\t\t\t\t`, tenantId, id, secretInactive, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Azure,\n\t\t\t\t\tRedacted:     id,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(`\n\t\t\t\ttenant_id=%s\n\t\t\t\tclient_id=%s\n\t\t\t\tclient_secret=%s\n\t\t\t\t`, tenantId, id, secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Azure,\n\t\t\t\t\tRedacted:     id,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Azure.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Azure.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azure_entra/serviceprincipal/v2/spv2_test.go",
    "content": "package v2\n\nimport (\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n)\n\ntype testCase struct {\n\tInput    string\n\tExpected map[string]struct{}\n}\n\nfunc Test_FindClientSecretMatches(t *testing.T) {\n\tcases := map[string]testCase{\n\t\t\"secret\": {\n\t\t\tInput: `servicePrincipal:\n  tenantId: \"608e4ac4-2ca8-40dd-a046-4064540a1cde\"\n  clientId: \"1474bfe8-663c-486e-9daf-f1f580302218\"\n  clientSecret: \"R028Q~ZOKzgCYyhr1ZJNNKhP8gUcD3Dpy2jMqaXf\"\nagentImage: \"karbar.azurecr.io/kar-agent\"`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"R028Q~ZOKzgCYyhr1ZJNNKhP8gUcD3Dpy2jMqaXf\": {},\n\t\t\t},\n\t\t},\n\t\t\"secret_start_with_dash\": {\n\t\t\tInput: `azure:\n      active-directory:\n        enabled: true\n        profile:\n          tenant-id: 11111111-1111-1111-1111-111111111111\n        credential:\n          client-id: 00000000-0000-0000-0000-000000000000\n          client-secret: -bs8Q~F9mPSWiDihY0NIpcQjAWoUoQ.c-seM-c0_`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"-bs8Q~F9mPSWiDihY0NIpcQjAWoUoQ.c-seM-c0_\": {},\n\t\t\t},\n\t\t},\n\t\t\"secret_end_with_dash\": {\n\t\t\tInput: `OPENID_CLIENT_ID=8595f61a-109a-497d-8c8f-566b733e95fe\nOPENID_CLIENT_SECRET=aZ78Q~C~--E4dgsHZklBWtAw0mdajUHAaXXG5cq-\nOPENID_GRANT_TYPE=client_credentials`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"aZ78Q~C~--E4dgsHZklBWtAw0mdajUHAaXXG5cq-\": {},\n\t\t\t},\n\t\t},\n\t\t\"client_secret\": {\n\t\t\tInput: `      \"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\",`,\n\t\t\tExpected: map[string]struct{}{\n\t\t\t\t\"-6s8Q~.Q9CKMOXHGs_BA3ig2wUzyDRyulhWEOc3u\": {},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, test := range cases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tmatches := findSecretMatches(test.Input)\n\t\t\tif len(matches) == 0 {\n\t\t\t\tif len(test.Expected) != 0 {\n\t\t\t\t\tt.Fatalf(\"no matches found, expected: %v\", test.Expected)\n\t\t\t\t\treturn\n\t\t\t\t} else {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(test.Expected, matches); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azure_openai/azure_openai.go",
    "content": "package azure_openai\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\n// Scanner detects API keys for Azure's OpenAI service.\n// https://learn.microsoft.com/en-us/azure/ai-services/openai/reference\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\t// TODO: Investigate custom `azure-api.net` endpoints.\n\t// https://github.com/openai/openai-python#microsoft-azure-openai\n\tazureUrlPat = regexp.MustCompile(`(?i)([a-z0-9-]+\\.openai\\.azure\\.com)`)\n\tazureKeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"api[_.-]?key\", \"openai[_.-]?key\"}) + `\\b(?-i:([a-f0-9]{32}))\\b`)\n\n\tinvalidServices = simple.NewCache[struct{}]()\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\".openai.azure.com\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AzureOpenAI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Azure OpenAI provides various AI models and services. The API keys can be used to access and interact with these models and services.\"\n}\n\n// FromData will find and optionally verify OpenAI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// De-duplicate results.\n\ttokens := make(map[string]struct{})\n\tfor _, match := range azureKeyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\ttokens[match[1]] = struct{}{}\n\t}\n\tif len(tokens) == 0 {\n\t\treturn\n\t}\n\turls := make(map[string]struct{})\n\tfor _, match := range azureUrlPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tu := match[1]\n\t\tif invalidServices.Exists(u) {\n\t\t\tcontinue\n\t\t}\n\t\turls[u] = struct{}{}\n\t}\n\n\t// Process results.\n\tlogCtx := logContext.AddLogger(ctx)\n\tfor token := range tokens {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: s.Type(),\n\t\t\tRedacted:     token[:3] + \"...\" + token[25:],\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tfor url := range urls {\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = common.SaneHttpClient()\n\t\t\t\t}\n\n\t\t\t\tisVerified, extraData, verificationErr := verifyAzureToken(logCtx, client, url, token)\n\t\t\t\tif isVerified || len(urls) == 1 {\n\t\t\t\t\ts1.RawV2 = []byte(token + \":\" + url)\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\ts1.ExtraData = extraData\n\t\t\t\t\ts1.SetVerificationError(verificationErr, token)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\t// Instance doesn't exist.\n\t\t\t\t// Verification issue: lookup azsdk-east-us.openai.azure.com: no such host\n\t\t\t\tif verificationErr != nil && strings.Contains(verificationErr.Error(), \"no such host\") {\n\t\t\t\t\tdelete(urls, url)\n\t\t\t\t\tinvalidServices.Set(url, struct{}{})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\treturn\n}\n\nfunc verifyAzureToken(ctx logContext.Context, client *http.Client, baseUrl, token string) (bool, map[string]string, error) {\n\t// TODO: Replace this with a more suitable long-term endpoint.\n\t// Most endpoints require additional info, e.g., deployment name, which complicates verification.\n\t// This may be retired in the future, so we should look for another candidate.\n\t// https://learn.microsoft.com/en-us/answers/questions/1371786/get-azure-openai-deployments-in-api\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"https://%s/openai/deployments?api-version=2023-03-15-preview\", baseUrl), nil)\n\tif err != nil {\n\t\treturn false, nil, nil\n\t}\n\n\treq.Header.Set(\"Api-Key\", token)\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tbody, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\n\t\tvar deployments deploymentsResponse\n\t\tif err := json.Unmarshal(body, &deployments); err != nil {\n\t\t\tif json.Valid(body) {\n\t\t\t\treturn false, nil, fmt.Errorf(\"failed to decode response %s: %w\", req.URL, err)\n\t\t\t} else {\n\t\t\t\t// If the response isn't JSON it's highly unlikely to be valid.\n\t\t\t\treturn false, nil, nil\n\t\t\t}\n\t\t}\n\n\t\t// JSON unmarshal doesn't check whether the structure actually matches.\n\t\tif deployments.Object == \"\" {\n\t\t\treturn false, nil, nil\n\t\t}\n\n\t\t// No extra data available at the moment.\n\t\treturn true, nil, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected response status %d for %s\", res.StatusCode, req.URL)\n\t}\n}\n\ntype deploymentsResponse struct {\n\tData   []deployment `json:\"data\"`\n\tObject string       `json:\"object\"`\n}\n\ntype deployment struct {\n\tID string `json:\"id\"`\n}\n"
  },
  {
    "path": "pkg/detectors/azure_openai/azure_openai_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage azure_openai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestAzureOpenAI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AZUREOPENAI\")\n\tinactiveSecret := testSecrets.MustGetField(\"AZUREOPENAI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azureopenai secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureOpenAI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureOpenAI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azureopenai secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureOpenAI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azureopenai secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureOpenAI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Azureopenai.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Azureopenai.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azure_openai/azure_openai_test.go",
    "content": "package azure_openai\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAzureOpenAI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"Generic environment variables\",\n\t\t\tinput: `export OPENAI_API_VERSION=2023-07-15-preview\n\t\t\t\t\texport OPENAI_API_TYPE=AZURE\n\t\t\t\t\texport OPENAI_API_BASE=https://james-test-gpt4.openai.azure.com/\n\t\t\t\t\texport OPENAI_API_KEY=3397348fcdcb4a5fbeb6cceb5a6a284f`,\n\t\t\twant: []string{\"3397348fcdcb4a5fbeb6cceb5a6a284f\"},\n\t\t},\n\t\t{\n\t\t\tname: \"Generic non-structured\",\n\t\t\tinput: `# {'input': ['This is a test query.'], 'engine': 'text-embedding-ada-002'}\n\t\t\t\t\t# url /openai/deployments/text-embedding-ada-002/embeddings?api-version=2022-12-01\n\t\t\t\t\t# params {'input': ['This is a test query.'], 'encoding_format': 'base64'}\n\t\t\t\t\t# headers None\n\t\t\t\t\t# 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\n\t\t\t\t\t# api_version=2022-12-01 data='{\"input\": [\"This is a test query.\"], \"encoding_format\": \"base64\"}' message='Post details'\n\t\t\t\t\t# https://notebook-openai01.openai.azure.com/openai/deployments/text-embedding-ada-002/embeddings?api-version=2022-12-01\n\t\t\t\t\t# {'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\",\n\t\t\t\t\t\"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'}`,\n\t\t\twant: []string{\"49eb7c2d3acd41f4ac31fef59ceacbba\"},\n\t\t},\n\t\t{\n\t\t\tname: \"Python\",\n\t\t\tinput: `import openai\n\n\t\t\topenai.api_key = '1bb7dff73fe449de829363ea03bab134'\n\t\t\topenai.api_base = \"https://hrcop-openai.openai.azure.com/\"\n\t\t\t`,\n\t\t\twant: []string{\"1bb7dff73fe449de829363ea03bab134\"},\n\t\t},\n\t\t{\n\t\t\tname: \"Python environment variables\",\n\t\t\tinput: `os.environ[\"OPENAI_API_TYPE\"] = \"azure\"\n\t\t\t\t\tos.environ[\"OPENAI_API_VERSION\"] = \"2023-03-15-preview\"\n\t\t\t\t\tos.environ[\"OPENAI_API_BASE\"] = \"https://superhackathonai101-openai.openai.azure.com/\"\n\t\t\t\t\tos.environ[\"OPENAI_API_KEY\"] = '1bb7dde73fe449de229361ea03bab234'`,\n\t\t\twant: []string{\"1bb7dde73fe449de229361ea03bab234\"},\n\t\t},\n\t\t{\n\t\t\tname: \"TypeScript\",\n\t\t\tinput: `import OpenAI from \"openai\";\n\t\t\t\t\texport const openai = new OpenAI({\n\t\t\t\t\tapiKey: \"3375e3ad9a874cd6bd954b6f163be84f\",\n\t\t\t\t\tbaseURL:\n\t\t\t\t\t\t\"https://kumar-azure.openai.azure.com/openai/deployments/ChatAutoUpdate\",\n\t\t\t\t\tdefaultQuery: { \"api-version\": \"2023-06-01-preview\" },\n\t\t\t\t\t});`,\n\t\t\twant: []string{\"3375e3ad9a874cd6bd954b6f163be84f\"},\n\t\t},\n\t\t{\n\t\t\tname: \"OpenAi key name\",\n\t\t\tinput: `{\n\t\t\t\t\t\"IsEncrypted\": false,\n\t\t\t\t\t\"Values\": {\n\t\t\t\t\t\t\"AZURE_OPENAI_ENDPOINT\": \"https://bcdemo-openai.openai.azure.com/\",\n\t\t\t\t\t\t\"AZURE_OPENAI_KEY\": \"57d2de35873840b5ad59d742e90e974e\"\n\t\t\t\t\t}\n\t\t\t\t\t}`,\n\t\t\twant: []string{\"57d2de35873840b5ad59d742e90e974e\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azure_storage/storage.go",
    "content": "package azure_storage\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\tnamePat = 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.\n\tkeyPat  = regexp.MustCompile(`(?i:(?:Access|Account|Storage)[_.-]?Key)(?:.|\\s){0,25}?([a-zA-Z0-9+\\/-]{86,88}={0,2})`)\n\n\t// https://learn.microsoft.com/en-us/azure/storage/common/storage-use-emulator\n\ttestNames = map[string]struct{}{\n\t\t\"devstoreaccount1\": {},\n\t\t\"storagesample\":    {},\n\t}\n\ttestKeys = map[string]struct{}{\n\t\t\"Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==\": {},\n\t}\n)\n\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\n\t\t\"DefaultEndpointsProtocol=http\", \"EndpointSuffix\", \"core.windows.net\",\n\t\t\"AccountName\", \"Account_Name\", \"Account.Name\", \"Account-Name\",\n\t\t\"StorageAccount\", \"Storage_Account\", \"Storage.Account\", \"Storage-Account\",\n\t\t\"AccountKey\", \"Account_Key\", \"Account.Key\", \"Account-Key\",\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AzureStorage\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// Deduplicate results.\n\tnames := make(map[string]struct{})\n\tfor _, matches := range namePat.FindAllStringSubmatch(dataStr, -1) {\n\t\tvar name string\n\t\tif matches[1] != \"\" {\n\t\t\tname = matches[1]\n\t\t} else {\n\t\t\tname = matches[2]\n\t\t}\n\t\tif _, ok := testNames[name]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tnames[name] = struct{}{}\n\t}\n\tif len(names) == 0 {\n\t\treturn results, nil\n\t}\n\n\tkeys := make(map[string]struct{})\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tkey := matches[1]\n\t\tif _, ok := testKeys[key]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tkeys[key] = struct{}{}\n\t}\n\tif len(keys) == 0 {\n\t\treturn results, nil\n\t}\n\n\t// Check results.\n\tfor name := range names {\n\t\tvar s1 detectors.Result\n\t\tfor key := range keys {\n\t\t\ts1 = detectors.Result{\n\t\t\t\tDetectorType: s.Type(),\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tRawV2:        []byte(fmt.Sprintf(`{\"accountName\":\"%s\",\"accountKey\":\"%s\"}`, name, key)),\n\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\"Account_name\": name,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, verificationErr := s.verifyMatch(ctx, client, name, key, s1.ExtraData)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, key)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t\tif s1.Verified {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\ntype storageResponse struct {\n\tContainers struct {\n\t\tContainer []container `xml:\"Container\"`\n\t} `xml:\"Containers\"`\n}\n\ntype container struct {\n\tName string `xml:\"Name\"`\n}\n\nfunc (s Scanner) verifyMatch(ctx context.Context, client *http.Client, name string, key string, extraData map[string]string) (bool, error) {\n\t// https://learn.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key\n\tnow := time.Now().UTC().Format(http.TimeFormat)\n\tstringToSign := \"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\"\n\taccountKeyBytes, _ := base64.StdEncoding.DecodeString(key)\n\th := hmac.New(sha256.New, accountKeyBytes)\n\th.Write([]byte(stringToSign))\n\tsignature := base64.StdEncoding.EncodeToString(h.Sum(nil))\n\n\turl := \"https://\" + name + \".blob.core.windows.net/?comp=list\"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Set(\"x-ms-date\", now)\n\treq.Header.Set(\"x-ms-version\", \"2019-12-12\")\n\treq.Header.Set(\"Authorization\", \"SharedKey \"+name+\":\"+signature)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\t// If the host is not found, we can assume that the accountName is not valid\n\t\tif strings.Contains(err.Error(), \"no such host\") {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\t// parse response\n\t\tresponse := storageResponse{}\n\t\tif err := xml.NewDecoder(res.Body).Decode(&response); err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\t// update the extra data with container names only\n\t\tif len(response.Containers.Container) > 0 {\n\t\t\tvar b strings.Builder\n\t\t\tfor i, c := range response.Containers.Container {\n\t\t\t\tif i > 0 {\n\t\t\t\t\tb.WriteString(\", \")\n\t\t\t\t}\n\t\t\t\tb.WriteString(c.Name)\n\t\t\t}\n\t\t\textraData[\"container_names\"] = b.String()\n\t\t}\n\n\t\treturn true, nil\n\tcase http.StatusForbidden:\n\t\t// 403 if account id or key is invalid, or if the account is disabled\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azure_storage/storage_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage azure_storage\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAzurestorage_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AZURE_STORAGE\")\n\tinactiveSecret := testSecrets.MustGetField(\"AZURE_STORAGE_INACTIVE\")\n\n\taccountNamePat := regexp.MustCompile(`AccountName=(?P<account_name>[^;]+);AccountKey`)\n\taccountName := accountNamePat.FindStringSubmatch(secret)[1]\n\tvalidKeyInvalidAccountName := strings.Replace(secret, accountName, \"invalid\", 1)\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azurestorage secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureStorage,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"account_name\": \"teststoragebytruffle\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureStorage,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"account_name\": \"teststoragebytruffle\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azurestorage secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureStorage,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"account_name\": \"teststoragebytruffle\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"context deadline exceeded\"), secret)\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azurestorage secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureStorage,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"account_name\": \"teststoragebytruffle\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"unexpected HTTP response status 404\"), secret)\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found secret with invalid account name\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azurestorage secret %s within\", validKeyInvalidAccountName)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureStorage,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"account_name\": \"invalid\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Azuretorage.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Azurestorage.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azure_storage/storage_test.go",
    "content": "package azure_storage\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAzureStorage_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t// True Positive\n\t\t// CONNECTION STRINGS\n\t\t{\n\t\t\tname:  `connection_string_1`,\n\t\t\tinput: `DefaultEndpointsProtocol=https;AccountName=storagetest123;AccountKey=YutGV0Vlauqsobd5tPWz2AKwHhBXMEWsAH+rSbz0UZUfaMVj1CFrcNQK47ygmrC4vHmc7eOp1LdM+AStk5mMyA==;EndpointSuffix=core.windows.net`,\n\t\t\twant:  []string{`{\"accountName\":\"storagetest123\",\"accountKey\":\"YutGV0Vlauqsobd5tPWz2AKwHhBXMEWsAH+rSbz0UZUfaMVj1CFrcNQK47ygmrC4vHmc7eOp1LdM+AStk5mMyA==\"}`},\n\t\t},\n\t\t{\n\t\t\tname:  `connection_string_2`,\n\t\t\tinput: `EndpointSuffix=core.windows.net;AccountKey=ldlKgoKPJhRjPJTkaC5c/QNqtu4sVQRc/teGJ0MZHbDYEHdvBV5z8JEfJK+evE87D7U8TzMZ0G2C+ASt2B4ifg==;AccountName=storagetest123;DefaultEndpointsProtocol=http`,\n\t\t\twant:  []string{`{\"accountName\":\"storagetest123\",\"accountKey\":\"ldlKgoKPJhRjPJTkaC5c/QNqtu4sVQRc/teGJ0MZHbDYEHdvBV5z8JEfJK+evE87D7U8TzMZ0G2C+ASt2B4ifg==\"}`},\n\t\t},\n\t\t{\n\t\t\tname:  `connection_string_3`,\n\t\t\tinput: `\t\t\tpublic const string SharedStorageKey = \"DefaultEndpointsProtocol=https;AccountName=huntappstorage;AccountKey=rrttFty/b52ET/e8VqpMSN+ZqAUP7hcXVkdekrPX58gsMZyOCrE+igN07t3lyi7tAV0+OrJFBaDtMe06YJ2tFw==;EndpointSuffix=core.windows.net\";`,\n\t\t\twant:  []string{`{\"accountName\":\"huntappstorage\",\"accountKey\":\"rrttFty/b52ET/e8VqpMSN+ZqAUP7hcXVkdekrPX58gsMZyOCrE+igN07t3lyi7tAV0+OrJFBaDtMe06YJ2tFw==\"}`},\n\t\t},\n\t\t{\n\t\t\tname: `connection_string_multiline`,\n\t\t\tinput: `\nexport const DevelopmentConnectionString = 'DefaultEndpointsProtocol=http;AccountName=macdemostorage;\n\t\t\tAccountKey=Jby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;\n\t\t\tQueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;';`,\n\t\t\twant: []string{`{\"accountName\":\"macdemostorage\",\"accountKey\":\"Jby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==\"}`},\n\t\t},\n\n\t\t// LANGUAGES\n\t\t// TODO:\n\t\t// - https://github.com/Satyamk21/az204/blob/75f340c5bbfb34c1477a6885e216d5ae0972a380/Lab%203.txt#L22\n\n\t\t// https://github.com/facebookincubator/velox/blob/98e958c0df498efd7cf44a2078cc71caeb7aed23/velox/connectors/hive/storage_adapters/abfs/tests/AzuriteServer.h#L32-L36\n\t\t{\n\t\t\tname: `cpp`,\n\t\t\tinput: `static const std::string AzuriteAccountName{\"storagetest123\"};\nstatic const std::string AzuriteContainerName{\"test\"};\n// the default key of Azurite Server used for connection\nstatic const std::string AzuriteAccountKey{\n    \"qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA==\"};`,\n\t\t\twant: []string{`{\"accountName\":\"storagetest123\",\"accountKey\":\"qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA==\"}`},\n\t\t},\n\t\t// https://github.com/MicrosoftDX/Dash/blob/03c4bb55f9e84fd03ee943559c128c4d5c2a31c2/DashServer.Tests/RequestAuthTests.cs#L29\n\t\t{\n\t\t\tname: `dotnet1`,\n\t\t\tinput: `           _ctx = InitializeConfigAndCreateTestBlobs(ctx, \"datax1\", new Dictionary<string, string>\n                {\n                    { \"AccountName\", \"dashstorage1\" },\n                    { \"AccountKey\", \"8jqRVtXUWiEthgIhR+dFwrB8gh3lFuquvJQ1v4eabObIj7okI1cZIuzY8zZHmEdpcC0f+XlUkbFwAhjTfyrLIg==\" },\n                    { \"SecondaryAccountKey\", \"Klari9ZbVdFQ35aULCfqqehCsd136amhusMHWynTpz2Pg+GyQMJw3GH177hvEQbaZ2oeRYk3jw0mIaV3ehNIRg==\" },\n                },`,\n\t\t\twant: []string{\n\t\t\t\t`{\"accountName\":\"dashstorage1\",\"accountKey\":\"8jqRVtXUWiEthgIhR+dFwrB8gh3lFuquvJQ1v4eabObIj7okI1cZIuzY8zZHmEdpcC0f+XlUkbFwAhjTfyrLIg==\"}`,\n\t\t\t\t`{\"accountName\":\"dashstorage1\",\"accountKey\":\"Klari9ZbVdFQ35aULCfqqehCsd136amhusMHWynTpz2Pg+GyQMJw3GH177hvEQbaZ2oeRYk3jw0mIaV3ehNIRg==\"}`,\n\t\t\t},\n\t\t},\n\t\t// https://github.com/Satyamk21/az204/blob/75f340c5bbfb34c1477a6885e216d5ae0972a380/Lab%203.txt#L11\n\t\t{\n\t\t\tname: `dotnet2`,\n\t\t\tinput: `public class Program\n{\n    private const string blobServiceEndpoint = \"https://k21storagemedia.blob.core.windows.net/\";\n\n    private const string storageAccountName = \"k21storagemedia\";\n\n    private const string storageAccountKey = \"DFdukxfl0SwO4NB91bi/FTPh9BMEKr6Z5wWf+tGDfXMakXvGVp/NDzAUjWc/9171OqoDvXSj1o8N+AStUk1GXg==\";    \n\n\n    //The following code to create a new asynchronous Main method\n    public static async Task Main(string[] args)`,\n\t\t\twant: []string{`{\"accountName\":\"k21storagemedia\",\"accountKey\":\"DFdukxfl0SwO4NB91bi/FTPh9BMEKr6Z5wWf+tGDfXMakXvGVp/NDzAUjWc/9171OqoDvXSj1o8N+AStUk1GXg==\"}`},\n\t\t},\n\t\t// 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\n\t\t{\n\t\t\tname: `java`,\n\t\t\tinput: `public class AzuriteContainer extends GenericContainer<AzuriteContainer> {\n    public static final String DEFAULT_ACCOUNT_NAME = \"storagetest123\";\n    public static final String DEFAULT_ACCOUNT_KEY\n            = \"qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA==\";\n\n    public static final String IMAGE_NAME = \"mcr.microsoft.com/azure-storage/azurite:3.27.0\";`,\n\t\t\twant: []string{`{\"accountName\":\"storagetest123\",\"accountKey\":\"qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA==\"}`},\n\t\t},\n\t\t// https://github.com/Azure/azure-storage-node/blob/6873387fc65bad6d577babe278be2ee2e6071493/test/common/connectionstringparsertests.js\n\t\t{\n\t\t\tname: `javascript`,\n\t\t\tinput: `   var parsedConnectionString = ServiceSettings.parseAndValidateKeys(defaultConnectionString + endpointsConnectionString, validKeys);\n    assert.equal(parsedConnectionString['DefaultEndpointsProtocol'], 'https');\n    assert.equal(parsedConnectionString['AccountName'], 'storagetest123');\n    assert.equal(parsedConnectionString['AccountKey'], 'KWPLd0rpW2T0U7K2pVpF8rYr1BgYtR7wYQk33AYiXeUoquiaY6o0TWqduxmPHlqeCNZ3LU0DHptbeIHy5l/Yhg==');\n    assert.equal(parsedConnectionString['BlobEndpoint'], 'myBlobEndpoint');\n    assert.equal(parsedConnectionString['QueueEndpoint'], 'myQueueEndpoint');\n    assert.equal(parsedConnectionString['TableEndpoint'], 'myTableEndpoint');`,\n\t\t\twant: []string{`{\"accountName\":\"storagetest123\",\"accountKey\":\"KWPLd0rpW2T0U7K2pVpF8rYr1BgYtR7wYQk33AYiXeUoquiaY6o0TWqduxmPHlqeCNZ3LU0DHptbeIHy5l/Yhg==\"}`},\n\t\t},\n\t\t// https://github.com/nextcloud/server/blob/81a9e19ace190ea0a64d52d95d341e25c7ad618b/tests/preseed-config.php#L89\n\t\t{\n\t\t\tname: `php`,\n\t\t\tinput: `\t'arguments' => [\n\t\t\t'container' => 'test',\n\t\t\t'account_name' => 'storagetest123',\n\t\t\t'account_key' => 'qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA==',\n\t\t\t'endpoint' => 'http://' . (getenv('DRONE') === 'true' ? 'azurite' : 'localhost') . ':10000/devstoreaccount1',\n\t\t\t'autocreate' => true\n\t\t]`,\n\t\t\twant: []string{`{\"accountName\":\"storagetest123\",\"accountKey\":\"qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA==\"}`},\n\t\t},\n\t\t// https://github.com/Azure/azure-sdk-for-js/blob/2719dcfbe835a2da3003876dcb5d77efba95f912/sdk/cosmosdb/cosmos/test/public/common/_fakeTestSecrets.ts\n\t\t{\n\t\t\tname: `typescript`,\n\t\t\tinput: `export const name =\n  process.env.ACCOUNT_NAME || \"storagename123\";\nexport const key =\n  process.env.ACCOUNT_KEY ||\n  \"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==\";`,\n\t\t\twant: []string{`{\"accountName\":\"storagename123\",\"accountKey\":\"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==\"}`},\n\t\t},\n\n\t\t// FORMATS\n\t\t// TODO: Doesn't work.\n\t\t// https://github.com/Azure/azure-quickstart-templates/blob/03e792429fbc65c9353335611933746364590b22/quickstarts/microsoft.datafactory/data-factory-hive-transformation/azuredeploy.parameters.json#L9C38-L9C38\n\t\t//\t{\n\t\t//\t\tname: `json`,\n\t\t//\t\tinput: `    \"storageAccountName\": {\n\t\t//  \"value\": \"changemeazurestorage\"\n\t\t//},\n\t\t//\"storageAccountKey\": {\n\t\t//  \"value\": \"YA1gKAMY34PeVgEWPF8FdbQO+U0nFkd3SaFE4d32K16AYL/DowrTYun8anOdAiCnMkCiRYm+PxUh5mw7a7lVcA==\"\n\t\t//},`,\n\t\t//\t\twant: []string{`{\"accountName\":\"storagetest123\",\"accountKey\":\"qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA==\"}`},\n\t\t//\t},\n\t\t// https://github.com/ClickHouse/ClickHouse/blob/eba52b318d67d85330c9c1781499b7ff27fb7c0e/tests/integration/test_storage_azure_blob_storage/configs/named_collections.xml\n\t\t{\n\t\t\tname: `xml`,\n\t\t\tinput: `        <azure_conf2>\n            <account_name>storagetest123</account_name>\n            <account_key>qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA==</account_key>\n        </azure_conf2>`,\n\t\t\twant: []string{`{\"accountName\":\"storagetest123\",\"accountKey\":\"qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA==\"}`},\n\t\t},\n\t\t// https://github.com/hubblestack/hubble/blob/f9b7bf38752bd16b27d050a3b8787652a1c6319b/hubblestack/fileserver/azurefs.py\n\t\t{\n\t\t\tname: `yaml1`,\n\t\t\tinput: `    azurefs:\n      - account_name: mystorage\n        account_key: 'fNH9cRp0+qVIVYZ+5rnZAhHc9ycOUcJnHtzpfOr0W0sxrtL2KVLuMe1xDfLwmfed+JJInZaEdWVCPHD4d/oqeA=='\n        container_name: my_container\n        proxy: 10.10.10.10:8080`,\n\t\t\twant: []string{`{\"accountName\":\"mystorage\",\"accountKey\":\"fNH9cRp0+qVIVYZ+5rnZAhHc9ycOUcJnHtzpfOr0W0sxrtL2KVLuMe1xDfLwmfed+JJInZaEdWVCPHD4d/oqeA==\"}`},\n\t\t},\n\t\t{\n\t\t\tname: `yaml2`,\n\t\t\tinput: `  - name: filesharevolume\n    azureFile:\n      sharename: containershare\n      storageAccountName: newstore100033323\n      storageAccountKey: Ar4/2iY8L0rEMeQaijINnfaMJr7vqjfbPgmJayw6Pu5l9ZI+GrFDm1uIWOqXk5RQLrTiXfBwWY6hAbPEIQqy1g==`,\n\t\t\twant: []string{`{\"accountName\":\"newstore100033323\",\"accountKey\":\"Ar4/2iY8L0rEMeQaijINnfaMJr7vqjfbPgmJayw6Pu5l9ZI+GrFDm1uIWOqXk5RQLrTiXfBwWY6hAbPEIQqy1g==\"}`},\n\t\t},\n\t\t// This was manually base64-decoded since that doesn't work in unit tests.\n\t\t// https://github.com/fabric8io/configmapcontroller/blob/master/vendor/k8s.io/kubernetes/examples/azure_file/secret/azure-secret.yaml\n\t\t{\n\t\t\tname: `yaml_3`,\n\t\t\tinput: `apiVersion: v1\n\t\tkind: Secret\n\t\tmetadata:\n\t\t name: azure-secret\n\t\ttype: Opaque\n\t\tdata:\n\t\t azurestorageaccountname: k8stest\n\t\t azurestorageaccountkey: xIF1zJbnnojFLMSkBp50mx02rHsMK2sjU7mFt4L13hoB7drAaJ8jD6+A443jJogV7y2FUOhQCWPmM6YaNHy7qg==\n`,\n\t\t\twant: []string{`{\"accountName\":\"k8stest\",\"accountKey\":\"xIF1zJbnnojFLMSkBp50mx02rHsMK2sjU7mFt4L13hoB7drAaJ8jD6+A443jJogV7y2FUOhQCWPmM6YaNHy7qg==\"}`},\n\t\t},\n\n\t\t// MISC\n\t\t// https://github.com/Azure-Samples/nested-virtualization-image-builder/blob/cf0373a421343b00ce3d261be99ddced80deb55b/README.md?plain=1#L54\n\t\t{\n\t\t\tname:  `blob_url`,\n\t\t\tinput: `\"name\": \"storagetest123.blob.core.windows.net\", \"accountKey\":\"hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w==\"`,\n\t\t\twant:  []string{`{\"accountName\":\"storagetest123\",\"accountKey\":\"hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w==\"}`},\n\t\t},\n\t\t{\n\t\t\tname:  `random_cli_1`,\n\t\t\tinput: `go run .\\main.go -debug -dest=\"https://kenfau.blob.core.windows.net/ss3/\" -AzureDefaultAccountName=\"kenfoo\" -AzureDefaultAccountKey=\"hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w==\"`,\n\t\t\twant: []string{\n\t\t\t\t`{\"accountName\":\"kenfau\",\"accountKey\":\"hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w==\"}`,\n\t\t\t\t`{\"accountName\":\"kenfoo\",\"accountKey\":\"hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w==\"}`,\n\t\t\t},\n\t\t},\n\t\t// - https://github.com/nwoolls/AzureStorageCleanup/blob/980e5cb163c78e9446e70d2513ba5a7ed9051a7a/README.md?plain=1#L24\n\t\t{\n\t\t\tname: `random_cli_2`,\n\t\t\tinput: `AzureStorageCleanup.exe \n    -storagename storageaccount\n    -storagekey hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w==\n    -container sqlbackup\n    -mindaysold 60\n    -searchpattern .*\n    -recursive\n    -whatif`,\n\t\t\twant: []string{`{\"accountName\":\"storageaccount\",\"accountKey\":\"hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w==\"}`},\n\t\t},\n\t\t// https://github.com/dahlej/rpi-spark-titantic/blob/d00b8f5b4696aeb2113e9452c24bb31b7f9a0242/tmp.txt#L9\n\t\t{\n\t\t\tname: `random_cli_3`,\n\t\t\tinput: `$ bin/spark-submit --master \\\n    k8s://test-cluster.eastus2.cloudapp.azure.com:443 \\\n    --deploy-mode cluster \\\n    --name copyLocations \\\n    --class io.timpark.CopyData \\\n    --conf spark.copydata.containerpath=wasb://containers@storagetest123.blob.core.windows.net \\\n    --conf spark.copydata.storageaccount=storagetest123 \\\n    --conf spark.copydata.storageaccountkey=hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w== \\`,\n\t\t\twant: []string{`{\"accountName\":\"storagetest123\",\"accountKey\":\"hGeB3WqDyx0mGsQMsQDl+gmnXa51ZODiBtcXJpMoRhPjkDm79f9ErNfaYizXm7nkElix8n2uBwNk6KY8Rc866w==\"}`},\n\t\t},\n\t\t{\n\t\t\tname: `custom_config_1`,\n\t\t\tinput: `driver := ArtifactDriver{\n\t\tAccountName: \"storagetest123\",\n\t\tAccountKey: \"qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA==\",\n\t\tContainer:  \"test\",\n\t}`,\n\t\t\twant: []string{`{\"accountName\":\"storagetest123\",\"accountKey\":\"qYaZm3m8+Z2aYAiSDzzvStkTUgZXl29U76lDJ0qiob7bbV4g7kjtwO+FI2QoptGdZgEdtsAYzG8T0hl5TeftWA==\"}`},\n\t\t},\n\t\t// https://github.com/MicrosoftDX/Dash/blob/master/LoadTestDotNet/GetBlobCoded.cs\n\t\t{\n\t\t\tname: `storage_account_1`,\n\t\t\tinput: `                this.Context.Add(\"StorageEndPoint\", \"http://dashstorage3.blob.core.windows.net\");\n                this.Context.Add(\"StorageAccount\", \"dashstorage3\");\n                this.Context.Add(\"AccountKey\", \"TP+G/9FTZRP1he1EpKilMercxSbMyqtaI9xTbc/3HqT2/FkxyIk1wVlBdemDFuYKStmlkFqHc7049l8McTd8NQ==\");\n                this.Context.Add(\"SendChunked\", false);`,\n\t\t\twant: []string{`{\"accountName\":\"dashstorage3\",\"accountKey\":\"TP+G/9FTZRP1he1EpKilMercxSbMyqtaI9xTbc/3HqT2/FkxyIk1wVlBdemDFuYKStmlkFqHc7049l8McTd8NQ==\"}`},\n\t\t},\n\t\t// https://github.com/kubecost/poc-common-configurations/blob/d626a48824a104e3089fc66ef57029f1e2212f6a/keys.txt#L18\n\t\t{\n\t\t\tname: `storage_account_2`,\n\t\t\tinput: `AZ_cloud_integration_subscriptionId:0bd50fdf-c923-4e1e-850c-196ddSAMPLE\nAZ_cloud_integration_azureStorageAccount:kubecostexport\nAZ_cloud_integration_azureStorageAccessKey:TP+G/9FTZRP1he1EpKilMercxSbMyqtaI9xTbc/3HqT2/FkxyIk1wVlBdemDFuYKStmlkFqHc7049l8McTd8NQ==\nAZ_cloud_integration_azureStorageContainer:costexports`,\n\t\t\twant: []string{`{\"accountName\":\"kubecostexport\",\"accountKey\":\"TP+G/9FTZRP1he1EpKilMercxSbMyqtaI9xTbc/3HqT2/FkxyIk1wVlBdemDFuYKStmlkFqHc7049l8McTd8NQ==\"}`},\n\t\t},\n\n\t\t// False positives\n\t\t{\n\t\t\tname:  `test_key`,\n\t\t\tinput: `    azureblockblob: TEST_BACKEND=azureblockblob://DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;`,\n\t\t},\n\t\t{\n\t\t\tname: `test_key_multiline`,\n\t\t\tinput: `\nexport const DevelopmentConnectionString = 'DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;\n\t\t\tAccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;\n\t\t\tQueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;';`,\n\t\t},\n\t\t{\n\t\t\tname:  `invalid_key_1`,\n\t\t\tinput: `        docs::examples = \"DefaultEndpointsProtocol=https;AccountName=mylogstorage;AccountKey=storageaccountkeybase64encoded;EndpointSuffix=core.windows.net\"`,\n\t\t},\n\t\t{\n\t\t\tname:  `invalid_key_2`,\n\t\t\tinput: `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`,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azureapimanagement/repositorykey/repositorykey.go",
    "content": "package repositorykey\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\turlPat      = regexp.MustCompile(detectors.PrefixRegex([]string{\"azure\", \"url\"}) + `([a-z0-9][a-z0-9-]{0,48}[a-z0-9]\\.scm\\.azure-api\\.net)`)\n\tpasswordPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"azure\", \"password\"}) + `\\b(git&[0-9]{12}&[a-zA-Z0-9\\/+]{85}[a-zA-Z0-9]==)`)\n\n\tinvalidHosts  = simple.NewCache[struct{}]()\n\tnoSuchHostErr = errors.New(\"Could not resolve host\")\n)\n\nconst (\n\tazureGitUsername = \"apim\"\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"azure\", \".scm.azure-api.net\"}\n}\n\n// FromData will find and optionally verify AzureDevopsPersonalAccessToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tlogger := logContext.AddLogger(ctx).Logger().WithName(\"azurecr\")\n\tdataStr := string(data)\n\n\t// Deduplicate matches.\n\tuniqueUrlsMatches := make(map[string]struct{})\n\tuniquePasswordMatches := make(map[string]struct{})\n\n\tfor _, matches := range urlPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueUrlsMatches[strings.TrimSpace(matches[1])] = struct{}{}\n\t}\n\n\tfor _, matches := range passwordPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniquePasswordMatches[strings.TrimSpace(matches[1])] = struct{}{}\n\t}\n\nEndpointLoop:\n\tfor urlMatch := range uniqueUrlsMatches {\n\t\tfor passwordMatch := range uniquePasswordMatches {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AzureApiManagementRepositoryKey,\n\t\t\t\tRaw:          []byte(passwordMatch),\n\t\t\t\tRawV2:        []byte(urlMatch + passwordMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tif invalidHosts.Exists(urlMatch) {\n\t\t\t\t\tlogger.V(3).Info(\"Skipping invalid registry\", \"url\", urlMatch)\n\t\t\t\t\tcontinue EndpointLoop\n\t\t\t\t}\n\n\t\t\t\tisVerified, err := verifyUrlPassword(ctx, urlMatch, azureGitUsername, passwordMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\tif err != nil {\n\t\t\t\t\tif errors.Is(err, noSuchHostErr) {\n\t\t\t\t\t\tinvalidHosts.Set(urlMatch, struct{}{})\n\t\t\t\t\t\tcontinue EndpointLoop\n\t\t\t\t\t}\n\t\t\t\t\ts1.SetVerificationError(err, urlMatch)\n\t\t\t\t}\n\t\t\t}\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AzureApiManagementRepositoryKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc gitCmdCheck() error {\n\tif errors.Is(exec.Command(\"git\").Run(), exec.ErrNotFound) {\n\t\treturn fmt.Errorf(\"'git' command not found in $PATH. Make sure git is installed and included in $PATH\")\n\t}\n\n\t// Check the version is greater than or equal to 2.20.0\n\tout, err := exec.Command(\"git\", \"--version\").Output()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to check git version: %w\", err)\n\t}\n\n\t// Extract the version string using a regex to find the version numbers\n\tvar regex = regexp.MustCompile(`\\d+\\.\\d+\\.\\d+`)\n\n\tversionStr := regex.FindString(string(out))\n\tversionParts := strings.Split(versionStr, \".\")\n\n\t// Parse version numbers\n\tmajor, _ := strconv.Atoi(versionParts[0])\n\tminor, _ := strconv.Atoi(versionParts[1])\n\n\t// Compare with version 2.20.0<=x<3.0.0\n\tif major == 2 && minor >= 20 {\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"git version is %s, but must be greater than or equal to 2.20.0, and less than 3.0.0\", versionStr)\n}\n\nfunc verifyUrlPassword(_ context.Context, repoUrl, user, password string) (bool, error) {\n\tif err := gitCmdCheck(); err != nil {\n\t\treturn false, err\n\t}\n\n\tparsedURL, err := url.Parse(repoUrl)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif parsedURL.User == nil {\n\t\tparsedURL.User = url.UserPassword(user, password)\n\t}\n\tparsedURL.Scheme = \"https\" // Force HTTPS\n\n\tfakeRef := \"TRUFFLEHOG_CHECK_GIT_REMOTE_URL_REACHABILITY\"\n\tgitArgs := []string{\"ls-remote\", parsedURL.String(), \"--quiet\", fakeRef}\n\tcmd := exec.Command(\"git\", gitArgs...)\n\toutput, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\toutputString := string(output)\n\t\tif strings.Contains(outputString, \"Authentication failed\") {\n\t\t\treturn false, nil\n\t\t} else if strings.Contains(outputString, \"Could not resolve host\") {\n\t\t\treturn false, noSuchHostErr\n\t\t}\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n"
  },
  {
    "path": "pkg/detectors/azureapimanagement/repositorykey/repositorykey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage repositorykey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAxonaut_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\turl := testSecrets.MustGetField(\"AZURE_API_MGMT_REPOSITORY_KEY_URL\")\n\tinactiveUrl := testSecrets.MustGetField(\"AZURE_API_MGMT_REPOSITORY_KEY_URL_INACTIVE\")\n\tpassword := testSecrets.MustGetField(\"AZURE_API_MGMT_REPOSITORY_KEY_PASSWORD\")\n\tinactivePassword := testSecrets.MustGetField(\"AZURE_API_MGMT_REPOSITORY_KEY_PASSWORD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure repository url %s password %s\", url, password)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureApiManagementRepositoryKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure repository url %s password %s but unverified\", url, inactivePassword)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureApiManagementRepositoryKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, host not resolved\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure repository url %s password %s but unverified\", inactiveUrl, inactivePassword)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AzureApiManagementRepositoryKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"ExtraData\", \"AnalysisInfo\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"AzureApiManagementRepositoryKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azureapimanagement/repositorykey/repositorykey_test.go",
    "content": "package repositorykey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAzureAPIManagementRepositoryKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: `valid pattern`,\n\t\t\tinput: `\n\t\t\t\tAZURE_URL=https://test.scm.azure-api.net\n\t\t\t\tPASSWORD=git&202503251200&R2xlVEmqi+OW130dxWIDhfw1K6XKw/gxc5P9te3cwWBtnK2XkZq5k+VUAdnuX1Y0T/I5CRK9fJyBJr31SmFEYw==\n\t\t\t`,\n\t\t\twant: []string{\"test.scm.azure-api.netgit&202503251200&R2xlVEmqi+OW130dxWIDhfw1K6XKw/gxc5P9te3cwWBtnK2XkZq5k+VUAdnuX1Y0T/I5CRK9fJyBJr31SmFEYw==\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{url 726o3.scm.azure-api.net}</id>\n  \t\t\t\t\t<secret>{password AQAAABAAA git&303102631708&ZidF02ZVakrtuWcW00cgvhZ6YUiZbIsZ84bE3u01jOXdKv7VXr0t6DE9OtdJnUTaBAz843vSDvVpCjRFEYSJq3==}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"726o3.scm.azure-api.netgit&303102631708&ZidF02ZVakrtuWcW00cgvhZ6YUiZbIsZ84bE3u01jOXdKv7VXr0t6DE9OtdJnUTaBAz843vSDvVpCjRFEYSJq3==\"},\n\t\t},\n\t\t{\n\t\t\tname: `invalid host pattern`,\n\t\t\tinput: `\n\t\t\t\tAZURE_URL=https://test.scm.azure.net\n\t\t\t\tPASSWORD=git&202503251200&R2xlVEmqi+OW130dxWIDhfw1K6XKw/gxc5P9te3cwWBtnK2XkZq5k+VUAdnuX1Y0T/I5CRK9fJyBJr31SmFEYw==\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t\t{\n\t\t\tname: `invalid password pattern without ==`,\n\t\t\tinput: `\n\t\t\t\tAZURE_URL=https://test.scm.azure-api.net\n\t\t\t\tPASSWORD=git&202503251200&R2xlVEmqi+OW130dxWIDhfw1K6XKw/gxc5P9te3cwWBtnK2XkZq5k+VUAdnuX1Y0T/I5CRK9fJyBJr31SmFEYw=\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t\t{\n\t\t\tname: `invalid password pattern with wrong expiry date`,\n\t\t\tinput: `\n\t\t\t\tAZURE_URL=https://test.scm.azure-api.net\n\t\t\t\tPASSWORD=git&20250325&R2xlVEmqi+OW130dxWIDhfw1K6XKw/gxc5P9te3cwWBtnK2XkZq5k+VUAdnuX1Y0T/I5CRK9fJyBJr31SmFEYw==\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azureapimanagementsubscriptionkey/azureapimanagementsubscriptionkey.go",
    "content": "package azureapimanagementsubscriptionkey\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\turlPat        = 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/\n\tkeyPat        = regexp.MustCompile(detectors.PrefixRegex([]string{\"azure\", \".azure-api.net\", \"subscription\", \"key\"}) + `([a-zA-Z-0-9]{32})`) // pattern for both Primary and secondary key\n\n\tinvalidHosts  = simple.NewCache[struct{}]()\n\tnoSuchHostErr = errors.New(\"no such host\")\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\".azure-api.net\"}\n}\n\n// FromData will find and optionally verify Azure Subscription keys in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tlogger := logContext.AddLogger(ctx).Logger().WithName(\"azureapimanagementsubscriptionkey\")\n\tdataStr := string(data)\n\n\turlMatchesUnique := make(map[string]struct{})\n\tfor _, urlMatch := range urlPat.FindAllStringSubmatch(dataStr, -1) {\n\t\turlMatchesUnique[urlMatch[0]] = struct{}{}\n\t}\n\tkeyMatchesUnique := make(map[string]struct{})\n\tfor _, keyMatch := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tkeyMatchesUnique[strings.TrimSpace(keyMatch[1])] = struct{}{}\n\t}\n\nEndpointLoop:\n\tfor baseUrl := range urlMatchesUnique {\n\t\tfor key := range keyMatchesUnique {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AzureAPIManagementSubscriptionKey,\n\t\t\t\tRaw:          []byte(baseUrl),\n\t\t\t\tRawV2:        []byte(baseUrl + \":\" + key),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tif invalidHosts.Exists(baseUrl) {\n\t\t\t\t\tlogger.V(3).Info(\"Skipping invalid registry\", \"baseUrl\", baseUrl)\n\t\t\t\t\tcontinue EndpointLoop\n\t\t\t\t}\n\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, verificationErr := s.verifyMatch(ctx, client, baseUrl, key)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\tif verificationErr != nil {\n\t\t\t\t\tif errors.Is(verificationErr, noSuchHostErr) {\n\t\t\t\t\t\tinvalidHosts.Set(baseUrl, struct{}{})\n\t\t\t\t\t\tcontinue EndpointLoop\n\t\t\t\t\t}\n\t\t\t\t\ts1.SetVerificationError(verificationErr, baseUrl)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AzureAPIManagementSubscriptionKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Azure API Management provides a direct management REST API for performing operations on selected entities, such as users, groups, products, and subscriptions.\"\n}\n\nfunc (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {\n\treturn false, \"\"\n}\n\nfunc (s Scanner) verifyMatch(ctx context.Context, client *http.Client, baseUrl, key string) (bool, error) {\n\turl := baseUrl + \"/echo/resource\" // default testing endpoint for api management services\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", url, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Ocp-Apim-Subscription-Key\", key)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\tdefer resp.Body.Close()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azureapimanagementsubscriptionkey/azureapimanagementsubscriptionkey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage azureapimanagementsubscriptionkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAzureAPIManagementSubscriptionKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\turl := testSecrets.MustGetField(\"AZUREAPIMANAGEMENTSUBSCRIPTIONKEY_URL\")\n\tsecret := testSecrets.MustGetField(\"AZUREDIRECTMANAGEMENTAPISUBSCRIPTIONKEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"AZUREDIRECTMANAGEMENTAPISUBSCRIPTIONKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure api management gateway url %s and subscription key %s within\", url, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureAPIManagementSubscriptionKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureAPIManagementSubscriptionKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AzureDirectManagementAPIKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"Redacted\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"AzureDirectManagementAPIKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azureapimanagementsubscriptionkey/azureapimanagementsubscriptionkey_test.go",
    "content": "package azureapimanagementsubscriptionkey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAzureAPIManagementSubscriptionKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tAZURE_API_MANAGEMENT_GATEWAY_URL=https://trufflesecuritytest.azure-api.net\n\t\t\t\tAZURE_API_MANAGEMENT_SUBSCRIPTION_KEY=2c69j0dc327c4929b74d3a832a04266b\n\t\t\t`,\n\t\t\twant: []string{\"https://trufflesecuritytest.azure-api.net:2c69j0dc327c4929b74d3a832a04266b\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{https://dffe5e2teoezcct050ch-2au74tmls8jm1p.azure-api.net}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA uEDFd7-zSeH6dwwzLbGjVrAlfgXoV1Xv}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"https://dffe5e2teoezcct050ch-2au74tmls8jm1p.azure-api.net:uEDFd7-zSeH6dwwzLbGjVrAlfgXoV1Xv\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tAZURE_API_MANAGEMENT_GATEWAY_URL=https://trufflesecuritytest.azure-api.net\n\t\t\t\tAZURE_API_MANAGEMENT_SUBSCRIPTION_KEY=2c69j2dc3f7c4929b74d3a832a042\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azureappconfigconnectionstring/azureappconfigconnectionstring.go",
    "content": "package azureappconfigconnectionstring\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient       = common.SaneHttpClient()\n\tconnectionStringPat = regexp.MustCompile(`Endpoint=(https:\\/\\/[a-zA-Z0-9-]+\\.azconfig\\.io);Id=([a-zA-Z0-9+\\/=]+);Secret=([a-zA-Z0-9+\\/=]+)`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\".azconfig.io\"}\n}\n\n// FromData will find and optionally verify Azure Management API keys in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatchesUnique := make(map[string][]string)\n\tfor _, keyMatch := range connectionStringPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tkeyMatchesUnique[strings.TrimSpace(keyMatch[0])] = keyMatch // keep all the matched groups for verification\n\t}\n\n\tfor connectionString, connectionInfo := range keyMatchesUnique {\n\t\tendpoint := connectionInfo[1] // Endpoint\n\t\tid := connectionInfo[2]       //\tId\n\t\tsecret := connectionInfo[3]   // Secret\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_AzureAppConfigConnectionString,\n\t\t\tRaw:          []byte(id),\n\t\t\tRawV2:        []byte(connectionString),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := s.verifyMatch(ctx, client, endpoint, id, secret)\n\t\t\ts1.Verified = isVerified\n\n\t\t\tif verificationErr != nil && !strings.Contains(verificationErr.Error(), \"no such host\") { // ignore no such host errors\n\t\t\t\ts1.SetVerificationError(verificationErr, connectionString)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AzureAppConfigConnectionString\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// generateHMACSignature creates the HMAC-SHA256 signature\nfunc generateHMACSignature(secret, stringToSign string) (string, error) {\n\tdecodedSecret, err := base64.StdEncoding.DecodeString(secret)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to decode secret: %w\", err)\n\t}\n\n\th := hmac.New(sha256.New, decodedSecret)\n\th.Write([]byte(stringToSign))\n\tsignature := base64.StdEncoding.EncodeToString(h.Sum(nil))\n\treturn signature, nil\n}\n\n// verifyMatch sends a request to the Azure App Configuration REST API to verify the provided credentials\n// https://learn.microsoft.com/en-us/azure/azure-app-configuration/rest-api-authentication-hmac\nfunc (s Scanner) verifyMatch(ctx context.Context, client *http.Client, endpoint, id, secret string) (bool, error) {\n\tapiVersion := \"1.0\"\n\trequestPath := \"/kv\"\n\tquery := fmt.Sprintf(\"?api-version=%s\", apiVersion)\n\turl := fmt.Sprintf(\"%s%s%s\", endpoint, requestPath, query)\n\n\t// Prepare request\n\treq, err := http.NewRequest(http.MethodGet, url, nil)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to create request: %w\", err)\n\t}\n\n\t// Set required headers\n\thost := strings.TrimPrefix(strings.TrimPrefix(endpoint, \"https://\"), \"http://\")\n\tdate := time.Now().UTC().Format(http.TimeFormat)\n\tcontentHash := base64.StdEncoding.EncodeToString(sha256.New().Sum(nil)) // SHA256 hash of an empty body\n\n\treq.Header.Set(\"Host\", host)\n\treq.Header.Set(\"Date\", date)\n\treq.Header.Set(\"x-ms-content-sha256\", contentHash)\n\n\t// Create the string to sign\n\tstringToSign := fmt.Sprintf(\"%s\\n%s%s\\n%s;%s;%s\",\n\t\thttp.MethodGet,\n\t\trequestPath,\n\t\tquery,\n\t\tdate,\n\t\thost,\n\t\tcontentHash,\n\t)\n\n\t// Generate the HMAC signature\n\tsignature, err := generateHMACSignature(secret, stringToSign)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to generate HMAC signature: %w\", err)\n\t}\n\n\t// Set the Authorization header\n\tauthorizationHeader := fmt.Sprintf(\n\t\t\"HMAC-SHA256 Credential=%s&SignedHeaders=date;host;x-ms-content-sha256&Signature=%s\",\n\t\tid,\n\t\tsignature,\n\t)\n\treq.Header.Set(\"Authorization\", authorizationHeader)\n\n\t// Send the request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"request failed: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\t// Check the response status\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"got unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azureappconfigconnectionstring/azureappconfigconnectionstring_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage azureappconfigconnectionstring\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAzureAppConfigConnectionString_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AZURE_APP_CONFIGURATION_CONNECTION_STRING\")\n\tinactiveSecret := testSecrets.MustGetField(\"AZURE_APP_CONFIGURATION_CONNECTION_STRING_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azureappconfigconnectionstring secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureAppConfigConnectionString,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azureappconfigconnectionstring secret %s but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureAppConfigConnectionString,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AzureAppConfigConnectionString.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"RawV2\", \"Raw\", \"Redacted\", \"verificationError\")\n\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"AzureAppConfigConnectionString.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azureappconfigconnectionstring/azureappconfigconnectionstring_test.go",
    "content": "package azureappconfigconnectionstring\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAzureAppConfigConnectionString_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: `Endpoint=https://trufflesecurity.azconfig.io;Id=u+De;Secret=80DtxZkndXpM2mV2J1JjX2vL1x4gm1hHn8Y3JeFJ4N0PPLSO5D70JQQJ99BBAC1i4FpQkb5wAAACAAZC26dr`,\n\t\t\twant:  []string{\"Endpoint=https://trufflesecurity.azconfig.io;Id=u+De;Secret=80DtxZkndXpM2mV2J1JjX2vL1x4gm1hHn8Y3JeFJ4N0PPLSO5D70JQQJ99BBAC1i4FpQkb5wAAACAAZC26dr\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{connectionstring}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA Endpoint=https://iTHzRfnepCddRiYoBbPj-drVzUjwTNduwb3EUOTsuSAgg1e83Q7bw.azconfig.io;Id=eO04L+/m9rYn;Secret=G4jQ3GmcsYqlLkkG8uoIVbx08PZIJSdfB/7}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"Endpoint=https://iTHzRfnepCddRiYoBbPj-drVzUjwTNduwb3EUOTsuSAgg1e83Q7bw.azconfig.io;Id=eO04L+/m9rYn;Secret=G4jQ3GmcsYqlLkkG8uoIVbx08PZIJSdfB/7\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: `Endpoint=https://trufflesecurity.azconfig.io;Secret=80DtxZkndXpMTmV2J3JjX2vL1x4gm1hHn8Y3KeFV4N0PPLSO5D70JQQJ79BBAC1i4FpRkb5wAAACAAZC26dr`,\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azurecontainerregistry/azurecontainerregistry.go",
    "content": "package azurecontainerregistry\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\turlPat      = regexp.MustCompile(`([a-z0-9][a-z0-9-]{1,100}[a-z0-9])\\.azurecr\\.io`)\n\tpasswordPat = regexp.MustCompile(`\\b[a-zA-Z0-9+/]{42}\\+ACR[a-zA-Z0-9]{6}\\b`)\n\n\tinvalidHosts = simple.NewCache[struct{}]()\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\".azurecr.io\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AzureContainerRegistry\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// FromData will find and optionally verify Azurecontainerregistry secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tlogger := logContext.AddLogger(ctx).Logger().WithName(\"azurecr\")\n\tdataStr := string(data)\n\n\t// Deduplicate matches.\n\tregistryMatches := make(map[string]struct{})\n\tfor _, matches := range urlPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tu := matches[1]\n\t\t// Ignore https://learn.microsoft.com/en-us/azure/container-registry/container-registry-private-link\n\t\tif u == \"privatelink\" || u == \"myacr\" {\n\t\t\tcontinue\n\t\t}\n\t\tregistryMatches[u] = struct{}{}\n\t}\n\tpasswordMatches := make(map[string]struct{})\n\tfor _, matches := range passwordPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tp := matches[0]\n\t\tif detectors.StringShannonEntropy(p) < 4 {\n\t\t\tcontinue\n\t\t}\n\t\tpasswordMatches[p] = struct{}{}\n\t}\n\nEndpointLoop:\n\tfor username := range registryMatches {\n\t\tfor password := range passwordMatches {\n\t\t\tr := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AzureContainerRegistry,\n\t\t\t\tRaw:          []byte(password),\n\t\t\t\tRawV2:        []byte(`{\"username\":\"` + username + `\",\"password\":\"` + password + `\"}`),\n\t\t\t\tRedacted:     username,\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tif invalidHosts.Exists(username) {\n\t\t\t\t\tlogger.V(3).Info(\"Skipping invalid registry\", \"username\", username)\n\t\t\t\t\tcontinue EndpointLoop\n\t\t\t\t}\n\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, username, password)\n\t\t\t\tif isVerified {\n\t\t\t\t\tdelete(passwordMatches, password)\n\t\t\t\t\tr.Verified = true\n\t\t\t\t}\n\t\t\t\tif verificationErr != nil {\n\t\t\t\t\tif errors.Is(verificationErr, noSuchHostErr) {\n\t\t\t\t\t\tinvalidHosts.Set(username, struct{}{})\n\t\t\t\t\t\tcontinue EndpointLoop\n\t\t\t\t\t}\n\t\t\t\t\tr.SetVerificationError(verificationErr, password)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, r)\n\t\t\tif r.Verified {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {\n\treturn false, \"\"\n}\n\nvar noSuchHostErr = errors.New(\"no such host\")\n\nfunc verifyMatch(ctx context.Context, client *http.Client, username string, password string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"https://%s.azurecr.io/v2/\", username), nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.SetBasicAuth(username, password)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\t// lookup foo.azurecr.io: no such host\n\t\tif strings.Contains(err.Error(), \"no such host\") {\n\t\t\treturn false, noSuchHostErr\n\t\t}\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\t// The secret is determinately not verified.\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azurecontainerregistry/azurecontainerregistry_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage azurecontainerregistry\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAzureContainerRegistry_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tazureHost := testSecrets.MustGetField(\"AZURE_CR_HOST\")\n\tpassword := testSecrets.MustGetField(\"AZURE_CR_PASSWORD\")\n\tpasswordInactive := testSecrets.MustGetField(\"AZURE_CR_PASSWORD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azurecontainerregistry secret %s and %s within\", azureHost, password)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureContainerRegistry,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureContainerRegistry,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AzureContainerRegistry.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"RawV2\", \"Raw\", \"Redacted\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"AzureContainerRegistry.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azurecontainerregistry/azurecontainerregistry_test.go",
    "content": "package azurecontainerregistry\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAzureContainerRegistry_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"pwd\",\n\t\t\tinput: `source storage.env\n\t\t\t\tACR=smpldev.azurecr.io\n\t\t\t\tACRUSER=smpldev\n\t\t\t\tACRPWD=Cw8xeDNK6Bub3p61aq5ij/TiVvtBicpTj5rverVezj+ACRBPkEcx\n\t\t\t\tCONTAINER=storage-svc:latest`,\n\t\t\twant: []string{`{\"username\":\"smpldev\",\"password\":\"Cw8xeDNK6Bub3p61aq5ij/TiVvtBicpTj5rverVezj+ACRBPkEcx\"}`},\n\t\t},\n\t\t{\n\t\t\tname: \"password\",\n\t\t\tinput: `    - name: Deploy to ARC\n\t\t\t\t\t\tuses: azure/docker-login@v1\n\t\t\t\t\t\twith:\n\t\t\t\t\t\t\t\tlogin-server: crmshopacr.azurecr.io\n\t\t\t\t\t\t\t\tusername: crmshopacr\n\t\t\t\t\t\t\t\tpassword: o9uXSjWlUdRwAeGP2xGSfGy+25vetsONo3Mq13fksa+ACRBXyFsY\n\t\t\t\t\t\t- run: |`,\n\t\t\twant: []string{`{\"username\":\"crmshopacr\",\"password\":\"o9uXSjWlUdRwAeGP2xGSfGy+25vetsONo3Mq13fksa+ACRBXyFsY\"}`},\n\t\t},\n\t\t{\n\t\t\tname: \"docker cli login\",\n\t\t\tinput: `docker login dvacr00.azurecr.io -u dvacr00 -p Ljc+1lq0U0+c3jHlMHxSxAhCipKt6zU43HfMle/Ymj+ACRAKcPHy\n\t\t\t\t\tdocker push dvacr00.azurecr.io/foo-alpine:3.18`,\n\t\t\twant: []string{`{\"username\":\"dvacr00\",\"password\":\"Ljc+1lq0U0+c3jHlMHxSxAhCipKt6zU43HfMle/Ymj+ACRAKcPHy\"}`},\n\t\t},\n\t\t{\n\t\t\tname:  \"request body\",\n\t\t\tinput: `\"registries\":[{\"identity\":\"\",\"passwordSecretRef\":\"registry-password\",\"server\":\"cr2bxwtqgom2oo.azurecr.io\",\"username\":\"cr2bxwtqgom2oo\"}],\"secrets\":[{\"name\":\"registry-password\",\"value\":\"VP2rvkuld42mr3jNjM+rVbvIzVuZxwncKWyVU5UIad+ACRBivL0B\"}]}`,\n\t\t\twant:  []string{`{\"username\":\"cr2bxwtqgom2oo\",\"password\":\"VP2rvkuld42mr3jNjM+rVbvIzVuZxwncKWyVU5UIad+ACRBivL0B\"}`},\n\t\t},\n\t\t{\n\t\t\tname: \"README\",\n\t\t\tinput: `# AZURE-CICD-Deployment-with-Github-Actions\n\t\t\t\t\t## Save pass:\n\n\t\t\t\t\ts3cEZKH3yytiVnJ3h+eI3qhhzf9l1vNwEi1+q+WGdd+ACRCZ7JD6\n\n\n\t\t\t\t\t## Run from terminal:\n\n\t\t\t\t\tdocker build -t testapp.azurecr.io/chicken:latest .\n\t\t\t\t\t`,\n\t\t\twant: []string{`{\"username\":\"testapp\",\"password\":\"s3cEZKH3yytiVnJ3h+eI3qhhzf9l1vNwEi1+q+WGdd+ACRCZ7JD6\"}`},\n\t\t},\n\t\t// TODO:\n\t\t//{\n\t\t//\tname:  \"az cli login\",\n\t\t//\tinput: `az acr login --name tstcopilotacr --username tstcopilotacr --password 9iZkJiOTKeEsQDfgoobtCYU47EEDs9UvU4L8NErLV+ACRACptmc`,\n\t\t//\twant:  []string{},\n\t\t//},\n\t\t//{\n\t\t//\tname:  \"\",\n\t\t//\tinput: ``,\n\t\t//\twant:  []string{},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tazure:\n\t\t\t\t\turl: http://invalid.azurecr.io.azure.com\n\t\t\t\t\tsecret: BXIMbhBlC3=5hIbqCEKvq7op!V2ZfO0XWbcnasZmPm/AJfQqdcnt/+2Ytxc1hDq1m/\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azuredevopspersonalaccesstoken/azuredevopspersonalaccesstoken.go",
    "content": "package azuredevopspersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"azure\"}) + `\\b([0-9a-z]{52})\\b`)\n\torgPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"azure\"}) + `\\b([0-9a-zA-Z][0-9a-zA-Z-]{5,48}[0-9a-zA-Z])\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"azure\"}\n}\n\n// FromData will find and optionally verify AzureDevopsPersonalAccessToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\torgMatches := orgPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, orgMatch := range orgMatches {\n\t\t\tresOrgMatch := strings.TrimSpace(orgMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AzureDevopsPersonalAccessToken,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resOrgMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://dev.azure.com/\"+resOrgMatch+\"/_apis/projects\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.SetBasicAuth(\"\", resMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\thasVerifiedRes, _ := common.ResponseContainsSubstring(res.Body, \"lastUpdateTime\")\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && hasVerifiedRes {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AzureDevopsPersonalAccessToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/azuredevopspersonalaccesstoken/azuredevopspersonalaccesstoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage azuredevopspersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAzureDevopsPersonalAccessToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AZURE_DEVOPS_PAT\")\n\tinactiveSecret := testSecrets.MustGetField(\"AZURE_DEVOPS_PAT_INACTIVE\")\n\torg := testSecrets.MustGetField(\"AZURE_DEVOPS_PAT_ORG\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure secret %s azure organization %s within\", secret, org)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureDevopsPersonalAccessToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRawV2:        []byte(secret + org),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureDevopsPersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(inactiveSecret + org),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure secret %s azure organization %s within\", secret, org)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureDevopsPersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(secret + org),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure secret %s azure organization %s within\", secret, org)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureDevopsPersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(secret + org),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AzureDevopsPersonalAccessToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no rawV2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"AzureDevopsPersonalAccessToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azuredevopspersonalaccesstoken/azuredevopspersonalaccesstoken_test.go",
    "content": "package azuredevopspersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAzureDevopsPersonalAccessToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t\tazure:\n\t\t\t\t\t\tazure_key: uie5tff7m5h5lqnqjhaltetqli90a08p6dhv9rn59uo30jgzw8un\n\t\t\t\t\t\tazure_org_id: WOkQXnjSxCyioEJRa8R6J39cN4Xfyy8CWl1BZksHYsevxVBFzG\n\t\t\t\t\t`,\n\t\t\twant: []string{\"uie5tff7m5h5lqnqjhaltetqli90a08p6dhv9rn59uo30jgzw8unWOkQXnjSxCyioEJRa8R6J39cN4Xfyy8CWl1BZksHYsevxVBFzG\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{azure dlMR9GIBfqCgAPr8qfkBa072OfaP6NbBhCwkPBX0cuHd}</id>\n  \t\t\t\t\t<secret>{azure AQAAABAAA h0wpgbusyba8acyaec1uxxcbxlucgr490c6nvrvd8rylfocwkpg5}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"h0wpgbusyba8acyaec1uxxcbxlucgr490c6nvrvd8rylfocwkpg5dlMR9GIBfqCgAPr8qfkBa072OfaP6NbBhCwkPBX0cuHd\",\n\t\t\t\t\"h0wpgbusyba8acyaec1uxxcbxlucgr490c6nvrvd8rylfocwkpg5AQAAABAAA\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t\tazure:\n\t\t\t\t\t\tazure_key: uie5tff7m5H5lqnqjhaltetqli90a08p6dhv9rn59uo30jgzw8un\n\t\t\t\t\t\tazure_org_id: LOKi\n\t\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azuredirectmanagementkey/azuredirectmanagementkey.go",
    "content": "package azuredirectmanagementkey\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha512\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nconst RFC3339WithoutMicroseconds = \"2006-01-02T15:04:05\"\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\turlPat        = 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/\n\tkeyPat        = 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\n\n\tinvalidHosts  = simple.NewCache[struct{}]()\n\tnoSuchHostErr = errors.New(\"no such host\")\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\".management.azure-api.net\"}\n}\n\n// FromData will find and optionally verify Azure Management API keys in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tlogger := logContext.AddLogger(ctx).Logger().WithName(\"azuredirectmanagementkey\")\n\tdataStr := string(data)\n\n\turlMatchesUnique := make(map[string]string)\n\tfor _, urlMatch := range urlPat.FindAllStringSubmatch(dataStr, -1) {\n\t\turlMatchesUnique[urlMatch[0]] = urlMatch[1] // urlMatch[0] is the full url, urlMatch[1] is the service name\n\t}\n\tkeyMatchesUnique := make(map[string]struct{})\n\tfor _, keyMatch := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tkeyMatchesUnique[strings.TrimSpace(keyMatch[1])] = struct{}{}\n\t}\n\nEndpointLoop:\n\tfor baseUrl, serviceName := range urlMatchesUnique {\n\t\tfor key := range keyMatchesUnique {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AzureDirectManagementKey,\n\t\t\t\tRaw:          []byte(baseUrl),\n\t\t\t\tRawV2:        []byte(baseUrl + \":\" + key),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tif invalidHosts.Exists(baseUrl) {\n\t\t\t\t\tlogger.V(3).Info(\"Skipping invalid registry\", \"baseUrl\", baseUrl)\n\t\t\t\t\tcontinue EndpointLoop\n\t\t\t\t}\n\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, verificationErr := s.verifyMatch(ctx, client, baseUrl, serviceName, key)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\tif verificationErr != nil {\n\t\t\t\t\tif errors.Is(verificationErr, noSuchHostErr) {\n\t\t\t\t\t\tinvalidHosts.Set(baseUrl, struct{}{})\n\t\t\t\t\t\tcontinue EndpointLoop\n\t\t\t\t\t}\n\t\t\t\t\ts1.SetVerificationError(verificationErr, baseUrl)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AzureDirectManagementKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Azure API Management provides a direct management REST API for performing operations on selected entities, such as users, groups, products, and subscriptions.\"\n}\n\nfunc (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {\n\treturn false, \"\"\n}\n\nfunc (s Scanner) verifyMatch(ctx context.Context, client *http.Client, baseUrl, serviceName, key string) (bool, error) {\n\turl := fmt.Sprintf(\n\t\t\"%s/subscriptions/default/resourceGroups/default/providers/Microsoft.ApiManagement/service/%s/apis?api-version=2024-05-01\",\n\t\tbaseUrl, serviceName,\n\t)\n\taccessToken, err := generateAccessToken(key)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", url, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"SharedAccessSignature %s\", accessToken))\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\tdefer resp.Body.Close()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", resp.StatusCode)\n\t}\n}\n\n// https://learn.microsoft.com/en-us/rest/api/apimanagement/apimanagementrest/azure-api-management-rest-api-authentication\nfunc generateAccessToken(key string) (string, error) {\n\texpiry := time.Now().UTC().Add(5 * time.Second).Format(RFC3339WithoutMicroseconds) // expire in 5 seconds\n\texpiry = expiry + \".0000000Z\"                                                      // 7 decimals microsecond's precision is must for access token\n\n\t// Construct the string-to-sign\n\tstringToSign := fmt.Sprintf(\"integration\\n%s\", expiry)\n\n\t// Generate HMAC-SHA512 signature\n\th := hmac.New(sha512.New, []byte(key))\n\th.Write([]byte(stringToSign))\n\tsignature := h.Sum(nil)\n\n\t// Base64 encode the signature\n\tencodedSignature := base64.StdEncoding.EncodeToString(signature)\n\n\t// Create the access token\n\taccessToken := fmt.Sprintf(\"uid=integration&ex=%s&sn=%s\", expiry, encodedSignature)\n\treturn accessToken, nil\n}\n"
  },
  {
    "path": "pkg/detectors/azuredirectmanagementkey/azuredirectmanagementkey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage azuredirectmanagementkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAzureDirectManagementAPIKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\turl := testSecrets.MustGetField(\"AZUREDIRECTMANAGEMENTAPI_URL\")\n\tsecret := testSecrets.MustGetField(\"AZUREDIRECTMANAGEMENTAPI_KEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"AZUREDIRECTMANAGEMENTAPI_KEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure management api url %s and key %s within\", url, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureDirectManagementKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureDirectManagementKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AzureDirectManagementAPIKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"Redacted\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"AzureDirectManagementAPIKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azuredirectmanagementkey/azuredirectmanagementkey_test.go",
    "content": "package azuredirectmanagementkey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAzureDirectManagementAPIKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tAZURE_MANGEMENT_API_KEY=UJh1Wn7txjls2GPK1YxO9+3tpqQffSfxb+97PmT8j3cSQoXvGa74lCKpBqPeppTHCharbaMeKqKs/H4gA/go1w==\n\t\t\t\tAZURE_MANAGEMENT_API_URL=https://trufflesecuritytest.management.azure-api.net\n\t\t\t\t`,\n\t\t\twant: []string{\"https://trufflesecuritytest.management.azure-api.net:UJh1Wn7txjls2GPK1YxO9+3tpqQffSfxb+97PmT8j3cSQoXvGa74lCKpBqPeppTHCharbaMeKqKs/H4gA/go1w==\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{https://0q66287uqx.management.azure-api.net}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA Ub4yMRDBBdEX/BNyNFM6i6Odj25TB0Zd1BRNx57ZeMGpqzkeokXheNpkkTBtvPQb692id65yc2xLKhZ183rg==}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"https://0q66287uqx.management.azure-api.net:Ub4yMRDBBdEX/BNyNFM6i6Odj25TB0Zd1BRNx57ZeMGpqzkeokXheNpkkTBtvPQb692id65yc2xLKhZ183rg==\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tAZURE_MANGEMENT_API_KEY=UJh1Wn7txjls2GPK1YxO9+3tpqQffSfxb+97PmT8j3cSQoXvGa74lCKp\n\t\t\t\tAZURE_MANAGEMENT_API_URL=https://trufflesecuritytest.management.azure-api.net\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azurefunctionkey/azurefunctionkey.go",
    "content": "package azurefunctionkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat      = regexp.MustCompile(detectors.PrefixRegex([]string{\"azure\"}) + `\\b([a-zA-Z0-9_-]{20,56})\\b={0,2}`)\n\tazureUrlPat = regexp.MustCompile(`\\bhttps:\\/\\/([a-zA-Z0-9-]{2,30})\\.azurewebsites\\.net\\/api\\/([a-zA-Z0-9-]{2,30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"azure\"}\n}\n\n// FromData will find and optionally verify azure secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\turlMatches := azureUrlPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresTrim := strings.Split(strings.TrimSpace(match[0]), \" \")\n\t\tresMatch := resTrim[len(resTrim)-1]\n\t\tfor _, urlMatch := range urlMatches {\n\t\t\tresUrl := strings.TrimSpace(urlMatch[0])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AzureFunctionKey,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resUrl),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", resUrl+\"?code=\"+resMatch, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AzureFunctionKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/azurefunctionkey/azurefunctionkey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage azurefunctionkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAzureFunctionKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AZURE_FUNCTION_KEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"AZURE_FUNCTION_KEY_INACTIVE\")\n\turl := testSecrets.MustGetField(\"AZURE_FUNCTION_URL\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure secret %s azure url %s\", secret, url)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureFunctionKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRawV2:        []byte(secret + url),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureFunctionKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(inactiveSecret + url),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure secret %s azure url %s\", secret, url)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureFunctionKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(secret + url),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure secret %s azure url %s\", secret, url)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureFunctionKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(secret + url),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AzureFunctionKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no rawV2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"AzureFunctionKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azurefunctionkey/azurefunctionkey_test.go",
    "content": "package azurefunctionkey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAzureFunctionKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tazure:\n\t\t\t\t\tazureURL: https://z1dUSi5T.azurewebsites.net/api/W8anB5J4uQi-v3Dcd6p7ySE0E\n\t\t\t\t\tazureFunctionkey: B8sm0KyfL1y8vPH3IDTdefevHBCGK33-=\n\t\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"azurewebsites.net/api/W8anB5J4uQi-v3Dcd6p7ySE0Ehttps://z1dUSi5T.azurewebsites.net/api/W8anB5J4uQi-v3Dcd6p7ySE0E\",\n\t\t\t\t\"B8sm0KyfL1y8vPH3IDTdefevHBCGK33https://z1dUSi5T.azurewebsites.net/api/W8anB5J4uQi-v3Dcd6p7ySE0E\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{https://yaeuxPA9-H.azurewebsites.net/api/Hwy5K}</id>\n  \t\t\t\t\t<secret>{azure AQAAABAAA Ijbql3DKRyIZNQIddzCYKICr}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"Ijbql3DKRyIZNQIddzCYKICrhttps://yaeuxPA9-H.azurewebsites.net/api/Hwy5K\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tazure:\n\t\t\t\t\tazureURL: http://invalid.azurecr.io.azure.com\n\t\t\t\t\tazureFunctionkey: BXIMbhBlC3=5hIbqCEKvq7op!V2ZfO0XWbcnasZmPm/AJfQqdcnt/+2Ytxc1hDq1m/\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azuresastoken/azuresastoken.go",
    "content": "package azuresastoken\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// 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\n\turlPat = 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._-]+)*`)\n\n\tkeyPat = regexp.MustCompile(\n\t\tdetectors.PrefixRegex([]string{\"azure\", \"sas\", \"token\", \"blob\", \".blob.core.windows.net\"}) +\n\t\t\t`(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,})`)\n\n\tinvalidStorageAccounts = simple.NewCache[struct{}]()\n\n\tnoSuchHostErr = errors.New(\"no such host\")\n)\n\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\n\t\t\"azure\",\n\t\t\".blob.core.windows.net\",\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AzureSasToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tlogger := logContext.AddLogger(ctx).Logger().WithName(\"azuresas\")\n\n\tdataStr := string(data)\n\n\t// deduplicate urlMatches\n\turlMatchesUnique := make(map[string]string)\n\tfor _, urlMatch := range urlPat.FindAllStringSubmatch(dataStr, -1) {\n\t\turlMatchesUnique[urlMatch[0]] = urlMatch[1]\n\t}\n\n\t// deduplicate keyMatches\n\tkeyMatchesUnique := make(map[string]struct{})\n\tfor _, keyMatch := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tkeyMatchesUnique[keyMatch[1]] = struct{}{}\n\t}\n\n\t// Check results.\nUrlLoop:\n\tfor url, storageAccount := range urlMatchesUnique {\n\t\tfor key := range keyMatchesUnique {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AzureSasToken,\n\t\t\t\tRaw:          []byte(url),\n\t\t\t\tRawV2:        []byte(url + key),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tif invalidStorageAccounts.Exists(storageAccount) {\n\t\t\t\t\tlogger.V(3).Info(\"Skipping invalid storage account\", \"storage account\", storageAccount)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, url, key, true)\n\t\t\t\ts1.Verified = isVerified\n\n\t\t\t\tif verificationErr != nil {\n\t\t\t\t\tif errors.Is(verificationErr, noSuchHostErr) {\n\t\t\t\t\t\tinvalidStorageAccounts.Set(storageAccount, struct{}{})\n\t\t\t\t\t\tcontinue UrlLoop\n\t\t\t\t\t}\n\t\t\t\t\ts1.SetVerificationError(verificationErr, key)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {\n\treturn false, \"\"\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, url, key string, retryOn403 bool) (bool, error) {\n\turlWithToken := url + \"?\" + key\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, urlWithToken, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"no such host\") {\n\t\t\treturn false, noSuchHostErr\n\t\t}\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\tbodyBytes, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusForbidden:\n\t\tif retryOn403 && strings.Contains(string(bodyBytes), \"Signature did not match\") {\n\t\t\t// need to add additional query parameters for container urls\n\t\t\t// https://stackoverflow.com/questions/25038429/azure-shared-access-signature-signature-did-not-match\n\t\t\treturn verifyMatch(ctx, client, url, key+\"&comp=list&restype=container\", false)\n\t\t}\n\t\tif strings.Contains(string(bodyBytes), \"AuthorizationFailure\") && strings.Contains(key, \"&sip=\") {\n\t\t\treturn false, fmt.Errorf(\"SAS token is restricted to specific IP addresses\")\n\t\t}\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azuresastoken/azuresastoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage azuresastoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAzureSasToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\turl := testSecrets.MustGetField(\"AZURESASTOKEN_URL\")\n\tsecret := testSecrets.MustGetField(\"AZURESASTOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"AZURESASTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure sas url %s and token %s within\", url, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureSasToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureSasToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AzureSasToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"Redacted\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"AzureSasToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azuresastoken/azuresastoken_test.go",
    "content": "package azuresastoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAzureSASToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\tAZURE_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\n\tAZURE_BLOB_SAS_URL=https://trufflesecurity.blob.core.windows.net/trufflesecurity\n\t`,\n\t\t\twant: []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\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern with ip\",\n\t\t\tinput: `\n\tAZURE_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\n\tAZURE_BLOB_SAS_URL=https://trufflesecurity.blob.core.windows.net/trufflesecurity\n\t`,\n\t\t\twant: []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\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern with ip range\",\n\t\t\tinput: `\n\tAZURE_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\n\tAZURE_BLOB_SAS_URL=https://trufflesecurity.blob.core.windows.net/trufflesecurity\n\t`,\n\t\t\twant: []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\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern without https\",\n\t\t\tinput: `\n\tAZURE_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\n\tAZURE_BLOB_SAS_URL=https://trufflesecurity.blob.core.windows.net/trufflesecurity\n\t`,\n\t\t\twant: []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\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern with blob url\",\n\t\t\tinput: `\n\tAZURE_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\n\tAZURE_BLOB_SAS_URL=https://trufflesecurity.blob.core.windows.net/trufflesecurity/test_blob.txt\n\t`,\n\t\t\twant: []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\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\tAZURE_BLOB_SAS_TOKEN=st=2025-03-04T07:24:52Z&se=2025-04-04T15:24:52Z&spr=https&sv=2022-11-02&sr=c\n\tAZURE_BLOB_SAS_URL=https://trufflesecurity.blob.core.windows.net/12trufflesecurity\n\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern with invalid permission\",\n\t\t\tinput: `\n\tAZURE_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\n\tAZURE_BLOB_SAS_URL=https://trufflesecurity.blob.core.windows.net/12trufflesecurity\n\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern with invalid ip\",\n\t\t\tinput: `\n\tAZURE_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\n\tAZURE_BLOB_SAS_URL=https://trufflesecurity.blob.core.windows.net/trufflesecurity\n\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azuresearchadminkey/azuresearchadminkey.go",
    "content": "package azuresearchadminkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"azure\"}) + `\\b([0-9a-zA-Z]{52})\\b`)\n\tservicePat = regexp.MustCompile(detectors.PrefixRegex([]string{\"azure\"}) + `\\b([0-9a-zA-Z]{7,40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"azure\"}\n}\n\n// FromData will find and optionally verify AzureSearchAdminKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tserviceMatches := servicePat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, serviceMatch := range serviceMatches {\n\t\t\tresServiceMatch := strings.TrimSpace(serviceMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AzureSearchAdminKey,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resServiceMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://\"+resServiceMatch+\".search.windows.net/servicestats?api-version=2023-10-01-Preview\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"api-key\", resMatch)\n\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else if res.StatusCode == 401 || res.StatusCode == 403 {\n\t\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AzureSearchAdminKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/azuresearchadminkey/azuresearchadminkey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage azuresearchadminkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAzureSearchAdminKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AZURE_SEARCH_ADMIN_KEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"AZURE_SEARCH_ADMIN_KEY_INACTIVE\")\n\tservice := testSecrets.MustGetField(\"AZURE_SEARCH_ADMIN_KEY_SERVICE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure secret %s and azure service %s within\", secret, service)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureSearchAdminKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRawV2:        []byte(secret + service),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureSearchAdminKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(inactiveSecret + service),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure secret %s and azure service %s within\", secret, service)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureSearchAdminKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(secret + service),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure secret %s and azure service %s within\", secret, service)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureSearchAdminKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(secret + service),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AzureSearchAdminKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no rawV2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"AzureSearchAdminKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azuresearchadminkey/azuresearchadminkey_test.go",
    "content": "package azuresearchadminkey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAzureSearchAdminKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tazure:\n\t\t\t\t\tazureKey: wRRPyhjv8m6JGRujUUrPKa8d3rJ0mrGAxhmqf3A68OgZmlWUJyma\n\t\t\t\t\tazureService: TestingService01\n\t\t\t\t`,\n\t\t\twant: []string{\"wRRPyhjv8m6JGRujUUrPKa8d3rJ0mrGAxhmqf3A68OgZmlWUJymaTestingService01\", \"wRRPyhjv8m6JGRujUUrPKa8d3rJ0mrGAxhmqf3A68OgZmlWUJymaazureKey\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{azure bhIIhGTLlW7gLxy4rM93gLPaPFwdRajJX}</id>\n  \t\t\t\t\t<secret>{azure AQAAABAAA Pntv3pDD31oczaYT99OanBBZyYlnKGUpQb4WEFnK6uUsKiR0Mc09}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"Pntv3pDD31oczaYT99OanBBZyYlnKGUpQb4WEFnK6uUsKiR0Mc09bhIIhGTLlW7gLxy4rM93gLPaPFwdRajJX\",\n\t\t\t\t\"Pntv3pDD31oczaYT99OanBBZyYlnKGUpQb4WEFnK6uUsKiR0Mc09AQAAABAAA\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tazure:\n\t\t\t\t\tKey: wRRPyhjv8m6JGRujUUr-PK#a8d3rJ0mrGAxhmqf3A68OgZmlWUJyma\n\t\t\t\t\tService: TS01\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azuresearchquerykey/azuresearchquerykey.go",
    "content": "package azuresearchquerykey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"azure\"}) + `\\b([0-9a-zA-Z]{52})\\b`)\n\turlPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"azure\"}) + `https:\\/\\/([0-9a-z]{5,40})\\.search\\.windows\\.net\\/indexes\\/([0-9a-z]{5,40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"azure\"}\n}\n\n// FromData will find and optionally verify AzureSearchQueryKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\turlMatches := urlPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, urlMatch := range urlMatches {\n\t\t\tresTrim := strings.Split(strings.TrimSpace(urlMatch[0]), \" \")\n\t\t\tresUrlMatch := resTrim[len(resTrim)-1]\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_AzureSearchQueryKey,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resUrlMatch),\n\t\t\t}\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", resUrlMatch+\"/docs/$count?api-version=2023-10-01-Preview\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"api-key\", resMatch)\n\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else if res.StatusCode == 401 || res.StatusCode == 403 {\n\t\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_AzureSearchQueryKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/azuresearchquerykey/azuresearchquerykey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage azuresearchquerykey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAzureSearchQueryKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"AZURE_SEARCH_QUERY_KEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"AZURE_SEARCH_QUERY_KEY_INACTIVE\")\n\turl := testSecrets.MustGetField(\"AZURE_SEARCH_QUERY_KEY_URL\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure secret %s and azure url %s within\", secret, url)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureSearchQueryKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRawV2:        []byte(secret + url),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureSearchQueryKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(inactiveSecret + url),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure secret %s and azure url %s within\", secret, url)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureSearchQueryKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(secret + url),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a azure secret %s and azure url %s within\", secret, url)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_AzureSearchQueryKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(secret + url),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AzureSearchQueryKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no rawV2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"AzureSearchQueryKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/azuresearchquerykey/azuresearchquerykey_test.go",
    "content": "package azuresearchquerykey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestAzureSearchQueryKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tazure:\n\t\t\t\t\tazure_url: https://tzyexx2ktdfhha8w1cktqzbrgv37ywtu.search.windows.net/indexes/n81wg81jogjfq93cyxfi67vy2g7vwlcqfgi\n\t\t\t\t\tazure_key: OKalbM5EBt5hloqU46phTUCZqvNAlZ4S2Jd2gFUCOQ3HG0vQ2uEp\n\t\t\t\t`,\n\t\t\twant: []string{\"OKalbM5EBt5hloqU46phTUCZqvNAlZ4S2Jd2gFUCOQ3HG0vQ2uEphttps://tzyexx2ktdfhha8w1cktqzbrgv37ywtu.search.windows.net/indexes/n81wg81jogjfq93cyxfi67vy2g7vwlcqfgi\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{azure https://w3fsj4c22rdn7mhkf1yxbt7orrvzd720a.search.windows.net/indexes/5934qi40xctuhmzba7ty}</id>\n  \t\t\t\t\t<secret>{azure AQAAABAAA C3idqCYnGa1cTx7iEFJ684QCbSDcEz1jq4s7iRxDDPWYKoK3h3Lr}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"C3idqCYnGa1cTx7iEFJ684QCbSDcEz1jq4s7iRxDDPWYKoK3h3Lrhttps://w3fsj4c22rdn7mhkf1yxbt7orrvzd720a.search.windows.net/indexes/5934qi40xctuhmzba7ty\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tazure:\n\t\t\t\t\turl: http://invalid.azurecr.io.azure.com\n\t\t\t\t\tazure_key: BXIMbhBlC3=5hIbqCEKvq7op!V2ZfO0XWbcnasZmPm/AJfQqdcnt/+2Ytxc1hDq1m/\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bannerbear/v1/bannerbear.go",
    "content": "package bannerbear\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\nfunc (s Scanner) Version() int { return 1 }\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"bannerbear\"}) + `\\b([0-9a-zA-Z]{22}tt)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"bannerbear\"}\n}\n\n// FromData will find and optionally verify Bannerbear secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Bannerbear,\n\t\t\tRaw:          []byte(resMatch),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"version\": fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBannerBear(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Bannerbear\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Bannerbear is an API for generating dynamic images, videos, and GIFs. Bannerbear API keys can be used to access and manipulate these resources.\"\n}\n\n// docs: https://developers.bannerbear.com/\nfunc verifyBannerBear(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.bannerbear.com/v2/auth\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bannerbear/v1/bannerbear_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage bannerbear\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBannerbear_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BANNERBEAR\")\n\tinactiveSecret := testSecrets.MustGetField(\"BANNERBEAR_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bannerbear secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bannerbear,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bannerbear,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Bannerbear.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"verificationError\", \"primarySecret\", \"ExtraData\")\n\t\t\tif diff := cmp.Diff(tt.want, got, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"BannerbearV1.FromData() %s - diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bannerbear/v1/bannerbear_test.go",
    "content": "package bannerbear\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBannerBear_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"POST\", url, bytes.NewBuffer([]byte(\"{}\")))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbannerBearToken := \"Bearer yvxpthLIcYpZweFpPOVeCOtt\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", bannerBearToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"yvxpthLIcYpZweFpPOVeCOtt\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{bannerbear}</id>\n  \t\t\t\t\t<secret>{bannerbear AQAAABAAA Y5UbXOT1Xh1ZOCxztUvGqltt}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"Y5UbXOT1Xh1ZOCxztUvGqltt\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"POST\", url, bytes.NewBuffer([]byte(\"{}\")))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbannerBearToken := \"Bearer yvxpthLIcYpZweFpPOVeCOtot\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", bannerBearToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bannerbear/v2/bannerbear.go",
    "content": "package bannerbear\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\nfunc (s Scanner) Version() int { return 2 }\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(bb_(?:pr|ma)_[a-f0-9]{30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"bb_pr_\", \"bb_ma_\"}\n}\n\n// FromData will find and optionally verify Bannerbear secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tuniqueMatches := make(map[string]struct{}, len(matches))\n\n\tfor _, match := range matches {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Bannerbear,\n\t\t\tRaw:          []byte(match),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"version\": fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, extraData, verificationErr := s.verifyBannerBear(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Bannerbear\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Bannerbear is an API for generating dynamic images, videos, and GIFs. Bannerbear API keys can be used to access and manipulate these resources.\"\n}\n\n// docs: https://developers.bannerbear.com/\nfunc (s Scanner) verifyBannerBear(ctx context.Context, client *http.Client, key string) (bool, map[string]string, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.bannerbear.com/v2/auth\", http.NoBody)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\textraData := map[string]string{\"version\": fmt.Sprintf(\"%d\", s.Version())}\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\textraData[\"key_type\"] = \"Project API Key\"\n\t\treturn true, extraData, nil\n\tcase http.StatusBadRequest:\n\t\tbodyBytes, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false, extraData, err\n\t\t}\n\n\t\t// According to Bannerbear API docs (https://developers.bannerbear.com/#authentication), the /auth endpoint\n\t\t// expects us to add a project_id parameter to the payload, when using a Full Access Master API Key.\n\t\t// otherwise, it returns a 400 Bad Request with \"Error: When using a Master API Key you must set a project_id parameter\"\n\t\t// 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\"\n\t\tvalidResponse := bytes.Contains(bodyBytes, []byte(\"When using a Master API Key\")) || bytes.Contains(bodyBytes, []byte(\"Master Key is Limited Access\"))\n\t\tif validResponse {\n\t\t\textraData[\"key_type\"] = \"Master API Key\"\n\t\t\treturn true, extraData, nil\n\t\t} else {\n\t\t\treturn false, extraData, fmt.Errorf(\"bad request: %s, body: %s\", resp.Status, string(bodyBytes))\n\t\t}\n\tcase http.StatusUnauthorized:\n\t\treturn false, extraData, nil\n\tdefault:\n\t\treturn false, extraData, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bannerbear/v2/bannerbear_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage bannerbear\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBannerbear_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BANNERBEARV2\")\n\tinactiveSecret := testSecrets.MustGetField(\"BANNERBEARV2_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bannerbear secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bannerbear,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bannerbear,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Bannerbear.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"verificationError\", \"primarySecret\", \"ExtraData\")\n\t\t\tif diff := cmp.Diff(tt.want, got, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"BannerbearV2.FromData() %s - diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bannerbear/v2/bannerbear_test.go",
    "content": "package bannerbear\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBannerBear_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"POST\", url, bytes.NewBuffer([]byte(\"{}\")))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbannerBearToken := \"Bearer bb_pr_abcdc2b40ef44abcd8cbf3739aabcd\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", bannerBearToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"bb_pr_abcdc2b40ef44abcd8cbf3739aabcd\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA bb_ma_900063380acef4c7e24c5bcee8af22}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"bb_ma_900063380acef4c7e24c5bcee8af22\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"POST\", url, bytes.NewBuffer([]byte(\"{}\")))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbannerBearToken := \"Bearer bb_ma_abcdc2b40ef44abcd8cbf3739aabcq\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", bannerBearToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/baremetrics/baremetrics.go",
    "content": "package baremetrics\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\t/*\n\t\tBaremetrics has two type of keys:\n\t\t- Sandbox: starts with `sk_`\n\t\t- Production: starts with `lk_`\n\t\tThe length of key is not fixed and can range between 18 to 25 characters.\n\t*/\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"baremetrics\"}) + `\\b((?:sk|lk)_[a-zA-Z0-9]{18,25})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"baremetrics\"}\n}\n\n// FromData will find and optionally verify Baremetrics secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Baremetrics,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBaremetrics(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Baremetrics\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Baremetrics is a subscription analytics and insights tool. Baremetrics API keys can be used to access and analyze subscription data.\"\n}\n\n// docs: https://developers.baremetrics.com/reference/authentication\nfunc verifyBaremetrics(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.baremetrics.com/v1/account\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/baremetrics/baremetrics_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage baremetrics\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBaremetrics_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BAREMETRICS\")\n\tinactiveSecret := testSecrets.MustGetField(\"BAREMETRICS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a baremetrics secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Baremetrics,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Baremetrics,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Baremetrics.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Baremetrics.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/baremetrics/baremetrics_test.go",
    "content": "package baremetrics\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBareMetrics_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbaremetricsToken := \"Bearer sk_nGDJWCkPiFAKE5XFTzUUA\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", baremetricsToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"sk_nGDJWCkPiFAKE5XFTzUUA\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{baremetrics}</id>\n  \t\t\t\t\t<secret>{baremetrics AQAAABAAA lk_JcWYJEi80ZzQA1nRXD}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"lk_JcWYJEi80ZzQA1nRXD\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbaremetricsToken := \"Bearer sk_nGDJWC_io8Q025XFTzUUA\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", baremetricsToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/beamer/beamer.go",
    "content": "package beamer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"beamer\"}) + `\\b([a-zA-Z0-9_+/]{45}=)`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"beamer\"}\n}\n\n// FromData will find and optionally verify Beamer secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Beamer,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBeamer(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Beamer\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc verifyBeamer(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.getbeamer.com/v0/url\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Beamer-Api-Key\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/beamer/beamer_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage beamer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBeamer_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BEAMER_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"BEAMER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a beamer secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Beamer,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Beamer,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Beamer.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Beamer.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/beamer/beamer_test.go",
    "content": "package beamer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBeamer_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\treq.Header.Set(\"Beamer-Api-Key\", \"DyVdf7+cAXw4MH9gT1CPotU31RMl__aLKbrABRWvT7TyO=\")\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"DyVdf7+cAXw4MH9gT1CPotU31RMl__aLKbrABRWvT7TyO=\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{beamer}</id>\n  \t\t\t\t\t<secret>{beamer AQAAABAAA _FXYx2kyyNv6n_CBb9LrMHZPXa_S8iaj89zYn9mICmkB4=}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"_FXYx2kyyNv6n_CBb9LrMHZPXa_S8iaj89zYn9mICmkB4=\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\treq.Header.Set(\"Beamer-Api-Key\", \"DyVdf7%c^AXw4MH9gT1CPotU31RMl__aLKbrABRWvT7TyO\")\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/beebole/beebole.go",
    "content": "package beebole\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"beebole\"}) + `\\b([0-9a-z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"beebole\"}\n}\n\n// FromData will find and optionally verify Beebole secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Beebole,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBeebole(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Beebole\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// docs: https://beebole.com/help/api/\nfunc verifyBeebole(ctx context.Context, client *http.Client, key string) (bool, error) {\n\tpayload := strings.NewReader(`{\"service\": \"custom_field.list\"}`)\n\n\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://beebole-apps.com/api/v2\", payload)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.SetBasicAuth(key, \"x\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/beebole/beebole_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage beebole\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBeebole_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BEEBOLE\")\n\tinactiveSecret := testSecrets.MustGetField(\"BEEBOLE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a beebole secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Beebole,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Beebole,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Beebole.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Beebole.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/beebole/beebole_test.go",
    "content": "package beebole\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBeeBole_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbeeboleAuth := bn6htprmfpukfalts4muwalxh9j15ucvnrfdme8t\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Basic \" + beeboleAuth) // beebole authorization header\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"bn6htprmfpukfalts4muwalxh9j15ucvnrfdme8t\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{beebole}</id>\n  \t\t\t\t\t<secret>{beebole AQAAABAAA rtwtgvvvekkik48t08tvf659hvyb5w8u4xnueh3u}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"rtwtgvvvekkik48t08tvf659hvyb5w8u4xnueh3u\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbeeboleAuth := DyVdf7%c^AXw4MH9gT1CPotU31RMl__aLKbrABRWvT7TyO\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Basic \" + beeboleAuth) // beebole authorization header\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/besnappy/besnappy.go",
    "content": "package besnappy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"besnappy\"}) + `\\b([a-f0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"besnappy\"}\n}\n\n// FromData will find and optionally verify Besnappy secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Besnappy,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBesnappy(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Besnappy\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Besnappy is a customer service platform. The detected key can be used to access Besnappy's API, potentially exposing sensitive customer service data.\"\n}\n\n// docs: https://github.com/BeSnappy/api-docs\nfunc verifyBesnappy(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.besnappy.com/api/v1/accounts\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.SetBasicAuth(key, \"x\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/besnappy/besnappy_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage besnappy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBesnappy_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BESNAPPY\")\n\tinactiveSecret := testSecrets.MustGetField(\"BESNAPPY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a besnappy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Besnappy,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Besnappy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Besnappy.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Besnappy.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/besnappy/besnappy_test.go",
    "content": "package besnappy\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBeSnappy_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"POST\", url, bytes.NewBuffer([]byte(\"{}\")))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbeSnappyToken := f58c5d37d7876d32cfdd823f8fe4ded364a8d483b5dbfadcc55ad801b3be8523\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Basic \" + beSnappyToken) // authorization header\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"f58c5d37d7876d32cfdd823f8fe4ded364a8d483b5dbfadcc55ad801b3be8523\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{besnappy}</id>\n  \t\t\t\t\t<secret>{besnappy AQAAABAAA da5a2e65d83a40d6cebaac60ef01803f8c1a612baa428992ad4c7301df2759ba}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"da5a2e65d83a40d6cebaac60ef01803f8c1a612baa428992ad4c7301df2759ba\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"POST\", url, bytes.NewBuffer([]byte(\"{}\")))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbeSnappyToken := f58c5d37d7876d32cf__f8fe4ded364a8d483b5db+adcc55ad801b3be8523\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Basic \" + beSnappyToken) // authorization header\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/besttime/besttime.go",
    "content": "package besttime\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"besttime\"}) + `\\b(pri_[a-f0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"besttime\"}\n}\n\n// FromData will find and optionally verify Besttime secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Besttime,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBesttime(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Besttime\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// docs: https://documentation.besttime.app/#api-reference\nfunc verifyBesttime(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://besttime.app/api/v1/keys/\"+key, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tbodyBytes, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tbody := string(bodyBytes)\n\n\t\tif strings.Contains(body, `\"status\": \"OK\"`) {\n\t\t\treturn true, nil\n\t\t} else if strings.Contains(body, `\"message\": \"Invalid api_key_private`) {\n\t\t\treturn false, nil\n\t\t}\n\n\t\treturn false, fmt.Errorf(\"unexpected response body: %s\", body)\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/besttime/besttime_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage besttime\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBesttime_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BESTTIME\")\n\tinactiveSecret := testSecrets.MustGetField(\"BESTTIME_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a besttime secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Besttime,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Besttime,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Besttime.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Besttime.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/besttime/besttime_test.go",
    "content": "package besttime\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBestTime_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/besttime/keys/pri_099889f14d114dfaae476569b395eade\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"pri_099889f14d114dfaae476569b395eade\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{besttime}</id>\n  \t\t\t\t\t<secret>{besttime AQAAABAAA pri_cffe0fa1b281feeb01216ec73e149b00}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"pri_cffe0fa1b281feeb01216ec73e149b00\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/besttime/keys/4K1WTb2ysVeg^jHD*wtwhH68K9MuOjiTtXQCS\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/betterstack/betterstack.go",
    "content": "package betterstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"betterstack\"}) + `\\b([0-9a-zA-Z]{24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"betterstack\"}\n}\n\n// FromData will find and optionally verify Betterstack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_BetterStack,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyBetterStack(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_BetterStack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// docs: https://betterstack.com/docs/uptime/api/list-all-existing-monitors/\nfunc verifyBetterStack(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://uptime.betterstack.com/api/v2/monitors\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/betterstack/betterstack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage betterstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBetterstack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BETTERSTACK\")\n\tinactiveSecret := testSecrets.MustGetField(\"BETTERSTACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a betterstack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BetterStack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BetterStack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Betterstack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Betterstack.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/betterstack/betterstack_test.go",
    "content": "package betterstack\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBetterStack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Bearer \" + getbetterStackToken())\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\tfunc getBetterStackToken() string{ return \"ntJD0ER8QpuT0O1WqsclApO2\" }\n\t\t\t\t`,\n\t\t\twant: []string{\"ntJD0ER8QpuT0O1WqsclApO2\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{betterstack}</id>\n  \t\t\t\t\t<secret>{betterstack AQAAABAAA RtSmhl4GkEcFS84Oyi0zlYbE}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"RtSmhl4GkEcFS84Oyi0zlYbE\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Bearer \" + getbetterStackToken())\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\tfunc getBetterStackToken() string{ return \"DyntJD0ER8QpuT0O1WqsclApO2\" }\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/billomat/billomat.go",
    "content": "package billomat\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"billomat\"}) + `\\b([0-9a-z]{4,20})\\b`) // the Billomat ID must be between 4 and 20 characters long.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"billomat\"}) + `\\b([0-9a-f]{32})\\b`)\n\n\terrAccountIDNotFound = errors.New(\"account id not found\")\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"billomat\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Billomat\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Billomat is an online invoicing software. Billomat API keys can be used to access and manage invoices, clients, and other related data.\"\n}\n\n// FromData will find and optionally verify Billomat secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueIDs, uniqueAPIKeys = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, match := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueIDs[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueAPIKeys[match[1]] = struct{}{}\n\t}\n\n\tfor apiKey := range uniqueAPIKeys {\n\t\tfor id := range uniqueIDs {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Billomat,\n\t\t\t\tRaw:          []byte(apiKey),\n\t\t\t\tRawV2:        []byte(apiKey + id),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, verificationErr := verifyBillomat(ctx, client, id, apiKey)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\tif verificationErr != nil {\n\t\t\t\t\t// remove the account ID if not found to prevent reuse during other API key checks.\n\t\t\t\t\tif errors.Is(verificationErr, errAccountIDNotFound) {\n\t\t\t\t\t\tdelete(uniqueIDs, id)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\ts1.SetVerificationError(verificationErr, apiKey)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\n// docs: https://www.billomat.com/en/api/basics/authentication/\nfunc verifyBillomat(ctx context.Context, client *http.Client, id, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"https://%s.billomat.net/api/v2/clients/myself\", id), http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"X-BillomatApiKey\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tcase http.StatusNotFound: // billomat api returns 404 if account id does not exist\n\t\t// read the full response body\n\t\tbodyBytes, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false, nil\n\t\t}\n\n\t\t/*\n\t\t\tThe regex for capturing a Billomat ID is prone to false positives.\n\t\t\tTo minimize incorrect matches, we return an error if the captured account ID does not exist,\n\t\t\tas this likely indicates the match was invalid.\n\t\t*/\n\t\tif strings.Contains(string(bodyBytes), \"account not found\") {\n\t\t\treturn false, errAccountIDNotFound\n\t\t}\n\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/billomat/billomat_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage billomat\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBillomat_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BILLOMAT\")\n\tid := testSecrets.MustGetField(\"BILLOMAT_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"BILLOMAT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a billomat secret %s within billomat id %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Billomat,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Billomat,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Billomat.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Billomat.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/billomat/billomat_test.go",
    "content": "package billomat\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBilloMat_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t\tfunc main() {\n\t\t\t\t\t\turl := \"https://api.billomat.net/v2/id/truffletest\"\n\n\t\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\treq.Header.Set(\"X-BillomatApiKey\", \"c09761f99f39f79ae28eaaf8df20d7c9\")\n\n\t\t\t\t\t\t// Perform the request\n\t\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\t\tdefer resp.Body.Close()\n\n\t\t\t\t\t\t// Check response status\n\t\t\t\t\t\tif resp.StatusCode == http.StatusOK {\n\t\t\t\t\t\t\tfmt.Println(\"Request successful!\")\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfmt.Println(\"Request failed with status:\", resp.Status)\n\t\t\t\t\t\t}\n\t\t\t\t\t}`,\n\t\t\twant: []string{\n\t\t\t\t\"c09761f99f39f79ae28eaaf8df20d7c9truffletest\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{billomat m2o8fqf8}</id>\n  \t\t\t\t\t<secret>{billomat AQAAABAAA 36a584c280b5b617e8eb25dae6b64d63}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"36a584c280b5b617e8eb25dae6b64d63m2o8fqf8\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\treq.Header.Set(\"X-BillomatApiKey\", \"c09761h99f39f79ae28eaaf8df20d7c9\")\n\t\t\t\tbillomatID := truffle-test\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bingsubscriptionkey/bingsubscriptionkey.go",
    "content": "package bingsubscriptionkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"bing\"}) + `\\b([a-fA-F0-9]{32})\\b`)\n)\n\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"bing\"}\n}\n\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_BingSubscriptionKey,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, subscriptionKey string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.bing.microsoft.com/v7.0/search?q=trufflehog\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Ocp-Apim-Subscription-Key\", subscriptionKey)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_BingSubscriptionKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Bing Subscription Key is a key used to access the Bing Web Search API.\"\n}\n"
  },
  {
    "path": "pkg/detectors/bingsubscriptionkey/bingsubscriptionkey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage bingsubscriptionkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBingsubscriptionkey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BING_SUBSCRIPTION_KEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"BING_SUBSCRIPTION_KEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bing subscription key %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BingSubscriptionKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bing subscription key %s within but not valid\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BingSubscriptionKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the key within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bing subscription key %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BingSubscriptionKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bing subscription key %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BingSubscriptionKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Bingsubscriptionkey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Bingsubscriptionkey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bingsubscriptionkey/bingsubscriptionkey_test.go",
    "content": "package bingsubscriptionkey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBingsubscriptionkey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t\tfunc main() {\n\t\t\t\t\t\turl := \"https://api.example.net/v2/api\"\n\n\t\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\t// set bing subscription key\n\t\t\t\t\t\tbingKey := \"89017d414ed64edb9c776d4a52102b9a\"\n\t\t\t\t\t\treq.Header.Set(\"Ocp-Apim-Subscription-Key\", bingKey)\n\n\t\t\t\t\t\t// Perform the request\n\t\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t\t}`,\n\t\t\twant: []string{\"89017d414ed64edb9c776d4a52102b9a\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{bing}</id>\n  \t\t\t\t\t<secret>{bing AQAAABAAA dB963b030A1DafB02d8299F04A00a306}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"dB963b030A1DafB02d8299F04A00a306\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t\tfunc main() {\n\t\t\t\t\t\turl := \"https://api.example.net/v2/api\"\n\n\t\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\t// set bing subscription key\n\t\t\t\t\t\tbingKey := \"89017d414ed64edb9c776d4J52102b9\"\n\t\t\t\t\t\treq.Header.Set(\"Ocp-Apim-Subscription-Key\", bingKey)\n\n\t\t\t\t\t\t// Perform the request\n\t\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t\t}`,\n\t\t\twant: []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bitbar/bitbar.go",
    "content": "package bitbar\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"bitbar\"}) + `\\b([0-9a-zA-Z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"bitbar\"}\n}\n\n// FromData will find and optionally verify Bitbar secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Bitbar,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBitBar(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Bitbar\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Bitbar provides a cloud-based mobile app testing platform. Bitbar API keys can be used to access and manage testing resources and data.\"\n}\n\n// docs: https://support.smartbear.com/bitbar/docs/en/use-rest-apis-with-bitbar.html\nfunc verifyBitBar(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://cloud.bitbar.com/api/me\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.SetBasicAuth(key, \"\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bitbar/bitbar_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage bitbar\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBitbar_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BITBAR\")\n\tinactiveSecret := testSecrets.MustGetField(\"BITBAR_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bitbar secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bitbar,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bitbar,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Bitbar.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Bitbar.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bitbar/bitbar_test.go",
    "content": "package bitbar\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBitBar_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbitBarSecret := os.GetEnv(\"BITBAR_SECRET\")\n\t\t\t\t\tif bitBarSecret == \"\"{\n\t\t\t\t\t\tbitBarSecret = \"64pq66z15thg8fh3acd00l35lpyg7c82\"\n\t\t\t\t\t}\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Basic \" + bitBarSecret)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"64pq66z15thg8fh3acd00l35lpyg7c82\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{bitbar}</id>\n  \t\t\t\t\t<secret>{bitbar AQAAABAAA EJEpftl3MtqwEvE9nwiJhw2rWgjrhP1q}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"EJEpftl3MtqwEvE9nwiJhw2rWgjrhP1q\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbitBarSecret := os.GetEnv(\"BITBAR_SECRET\")\n\t\t\t\t\tif bitBarSecret == \"\"{\n\t\t\t\t\t\tbitBarSecret = \"DyV64pq66z15thg8fh3&cd00l35lpyg7c82$\"\n\t\t\t\t\t}\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Basic \" + bitBarSecret)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bitbucketapppassword/bitbucketapppassword.go",
    "content": "package bitbucketapppassword\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"regexp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\n// Scanner is a stateless struct that implements the detector interface.\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\n// Keywords are used for efficiently pre-filtering chunks.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"bitbucket\", \"ATBB\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_BitbucketAppPassword\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Bitbucket is a Git repository hosting service by Atlassian. Bitbucket App Passwords are used to authenticate to the Bitbucket API.\"\n}\n\nconst bitbucketAPIUserURL = \"https://api.bitbucket.org/2.0/user\"\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n)\n\nvar (\n\t// credentialPatterns uses named capture groups (?P<name>...) for readability and robustness.\n\tcredentialPatterns = []*regexp.Regexp{\n\t\t// Explicitly define the boundary as (start of string) or (a non-username character).\n\t\tregexp.MustCompile(`(?:^|[^A-Za-z0-9-_])(?P<username>[A-Za-z0-9-_]{1,30}):(?P<password>ATBB[A-Za-z0-9_=.-]+)\\b`),\n\t\t// Catches 'https://username:password@bitbucket.org' pattern\n\t\tregexp.MustCompile(`https://(?P<username>[A-Za-z0-9-_]{1,30}):(?P<password>ATBB[A-Za-z0-9_=.-]+)@bitbucket\\.org`),\n\t\t// Catches '(\"username\", \"password\")' pattern, used for HTTP Basic Auth\n\t\tregexp.MustCompile(`\"(?P<username>[A-Za-z0-9-_]{1,30})\",\\s*\"(?P<password>ATBB[A-Za-z0-9_=.-]+)\"`),\n\t}\n)\n\n// FromData will find and optionally verify Bitbucket App Password secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) ([]detectors.Result, error) {\n\tdataStr := string(data)\n\n\tuniqueCredentials := make(map[string]string)\n\n\tfor _, pattern := range credentialPatterns {\n\t\tfor _, match := range pattern.FindAllStringSubmatch(dataStr, -1) {\n\t\t\t// Extract credentials using named capture groups for readability.\n\t\t\tnamedMatches := make(map[string]string)\n\t\t\tfor i, name := range pattern.SubexpNames() {\n\t\t\t\tif i != 0 && name != \"\" {\n\t\t\t\t\tnamedMatches[name] = match[i]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tusername := namedMatches[\"username\"]\n\t\t\tpassword := namedMatches[\"password\"]\n\n\t\t\tif username != \"\" && password != \"\" {\n\t\t\t\tuniqueCredentials[username] = password\n\t\t\t}\n\t\t}\n\t}\n\n\tvar results []detectors.Result\n\tfor username, password := range uniqueCredentials {\n\t\tresult := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_BitbucketAppPassword,\n\t\t\tRaw:          fmt.Appendf(nil, \"%s:%s\", username, password),\n\t\t}\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\tvar vErr error\n\t\t\tresult.Verified, vErr = verifyCredential(ctx, client, username, password)\n\t\t\tif vErr != nil {\n\t\t\t\tresult.SetVerificationError(vErr, username, password)\n\t\t\t}\n\t\t}\n\t\tresults = append(results, result)\n\t}\n\n\treturn results, nil\n}\n\n// verifyCredential checks if a given username and app password are valid by making a request to the Bitbucket API.\nfunc verifyCredential(ctx context.Context, client *http.Client, username, password string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, bitbucketAPIUserURL, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Accept\", \"application/json\")\n\tauth := base64.StdEncoding.EncodeToString(fmt.Appendf(nil, \"%s:%s\", username, password))\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", auth))\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK, http.StatusForbidden:\n\t\t// A 403 can indicate a valid credential with insufficient scope, which is still a finding.\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bitbucketapppassword/bitbucketapppassword_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage bitbucketapppassword\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBitbucketAppPassword_FromData_Integration(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*10)\n\tdefer cancel()\n\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tusername := testSecrets.MustGetField(\"USERNAME\")\n\tvalidPassword := testSecrets.MustGetField(\"BITBUCKETAPPPASSWORD\")\n\tinvalidPassword := \"ATBB123abcDEF456ghiJKL789mnoPQR\" // An invalid but correctly formatted password\n\n\ttests := []struct {\n\t\tname    string\n\t\tinput   string\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:  \"valid credential\",\n\t\t\tinput: fmt.Sprintf(\"https://%s:%s@bitbucket.org\", username, validPassword),\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BitbucketAppPassword,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRaw:          []byte(fmt.Sprintf(\"%s:%s\", username, validPassword)),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid credential\",\n\t\t\tinput: fmt.Sprintf(\"https://%s:%s@bitbucket.org\", username, invalidPassword),\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BitbucketAppPassword,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(fmt.Sprintf(\"%s:%s\", username, invalidPassword)),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"no credential found\",\n\t\t\tinput: \"this string has no credentials\",\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ts := &Scanner{}\n\t\t\tgot, err := s.FromData(ctx, true, []byte(tc.input))\n\n\t\t\tif (err != nil) != tc.wantErr {\n\t\t\t\tt.Fatalf(\"FromData() error = %v, wantErr %v\", err, tc.wantErr)\n\t\t\t}\n\t\t\t// Normalizing results for comparison by removing fields that are not relevant for the test\n\t\t\tfor i := range got {\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tt.Logf(\"verification error: %s\", got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(tc.want, got, cmp.Comparer(func(x, y detectors.Result) bool {\n\t\t\t\treturn x.Verified == y.Verified && string(x.Raw) == string(y.Raw) && x.DetectorType == y.DetectorType\n\t\t\t})); diff != \"\" {\n\t\t\t\tt.Errorf(\"FromData() mismatch (-want +got):\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := &Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bitbucketapppassword/bitbucketapppassword_test.go",
    "content": "package bitbucketapppassword\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBitbucketAppPassword_FromData(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pair\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the bitbucket API\n\t\t\t\t[DEBUG] Using autodesk Key=myuser:ATBB123abcDEF456ghiJKL789mnoPQR\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"myuser:ATBB123abcDEF456ghiJKL789mnoPQR\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA https://trufflesec:ATBBa9iO-tyg7u_op@bitbucket.org}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"trufflesec:ATBBa9iO-tyg7u_op\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid app password by itself (should not be found)\",\n\t\t\tinput: \"ATBB123abcDEF456ghiJKL789mnoPQR\",\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"pair with invalid username\",\n\t\t\tinput: \"my-very-long-username-that-is-over-thirty-characters:ATBB123abcDEF456ghiJKL789mnoPQR\",\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"url pattern\",\n\t\t\tinput: `https://anotheruser:ATBB123abcDEF456ghiJKL789mnoPQR@bitbucket.org`,\n\t\t\twant:  []string{\"anotheruser:ATBB123abcDEF456ghiJKL789mnoPQR\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"http basic auth pattern\",\n\t\t\tinput: `(\"basicauthuser\", \"ATBB123abcDEF456ghiJKL789mnoPQR\")`,\n\t\t\twant:  []string{\"basicauthuser:ATBB123abcDEF456ghiJKL789mnoPQR\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"multiple matches\",\n\t\t\tinput: `user1:ATBB123abcDEF456ghiJKL789mnoPQR and then also user2:ATBBzyxwvUT987srqPON654mlkJIH`,\n\t\t\twant:  []string{\"user1:ATBB123abcDEF456ghiJKL789mnoPQR\", \"user2:ATBBzyxwvUT987srqPON654mlkJIH\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bitcoinaverage/bitcoinaverage.go",
    "content": "package bitcoinaverage\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"bitcoinaverage\"}) + `\\b([a-zA-Z0-9]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"bitcoinaverage\"}\n}\n\ntype response struct {\n\tMsg     string `json:\"msg\"`\n\tSuccess bool   `json:\"success\"`\n}\n\n// FromData will find and optionally verify BitcoinAverage secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_BitcoinAverage,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBitcoinAverage(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_BitcoinAverage\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"BitcoinAverage is a service that provides cryptocurrency market data. BitcoinAverage API keys can be used to access and retrieve this market data.\"\n}\n\n// docs: https://apiv2.bitcoinaverage.com/#authentication\nfunc verifyBitcoinAverage(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://apiv2.bitcoinaverage.com/websocket/v3/get_ticket\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"x-ba-key\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tapiResponse := &response{}\n\t\tif err = json.NewDecoder(resp.Body).Decode(apiResponse); err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tif apiResponse.Success {\n\t\t\treturn true, nil\n\t\t}\n\n\t\treturn false, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bitcoinaverage/bitcoinaverage_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage bitcoinaverage\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBitcoinAverage_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BITCOINAVERAGE\")\n\tinactiveSecret := testSecrets.MustGetField(\"BITCOINAVERAGE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bitcoinaverage secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BitcoinAverage,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BitcoinAverage,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"BitcoinAverage.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"BitcoinAverage.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bitcoinaverage/bitcoinaverage_test.go",
    "content": "package bitcoinaverage\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBitCoinAverage_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tsecret := os.GetEnv(\"BITCOINAVERAGE\")\n\t\t\t\t\tif secret == \"\"{\n\t\t\t\t\t\t// bitcoinaverage secret\n\t\t\t\t\t\tsecret = \"WZizqeWvRnhZmFlpc5pMc92NP1Du19wxxpd5zjsYY8F\"\n\t\t\t\t\t}\n\t\t\t\t\treq.Header.Set(\"x-ba-key\", secret)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"WZizqeWvRnhZmFlpc5pMc92NP1Du19wxxpd5zjsYY8F\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{bitcoinaverage}</id>\n  \t\t\t\t\t<secret>{bitcoinaverage AQAAABAAA gVXtVKIj5CO3b0F12XjibnE2TvwS5rL5nJ0kQ2NZkso}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"gVXtVKIj5CO3b0F12XjibnE2TvwS5rL5nJ0kQ2NZkso\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tsecret := os.GetEnv(\"BITCOINAVERAGE\")\n\t\t\t\t\tif secret == \"\"{\n\t\t\t\t\t\t// bitcoinaverage secret\n\t\t\t\t\t\tsecret = \"DyV64pq66z15thg8fh3&cd00l35lpyg7c82$\"\n\t\t\t\t\t}\n\t\t\t\t\treq.Header.Set(\"x-ba-key\", secret)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bitfinex/bitfinex.go",
    "content": "package bitfinex\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha512\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// related resource https://medium.com/@Bitfinex/api-development-update-april-65fe52f84124\n\tapiKeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"bitfinex\"}) + `\\b([A-Za-z0-9_-]{43})\\b`)\n\tapiSecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"bitfinex\"}) + `\\b([A-Za-z0-9_-]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"bitfinex\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Bitfinex\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Bitfinex is a cryptocurrency exchange offering various trading options. Bitfinex API keys can be used to access and manage trading accounts.\"\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Bitfinex secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueAPIKeys, uniqueAPISecrets = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, apiKey := range apiKeyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueAPIKeys[apiKey[1]] = struct{}{}\n\t}\n\n\tfor _, apiSecret := range apiSecretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueAPISecrets[apiSecret[1]] = struct{}{}\n\t}\n\n\tfor apiKey := range uniqueAPIKeys {\n\t\tfor apiSecret := range uniqueAPISecrets {\n\t\t\t// as both patterns are same, avoid verifying same string for both\n\t\t\tif apiKey == apiSecret {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Bitfinex,\n\t\t\t\tRaw:          []byte(apiKey),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, verificationErr := verifyBitfinex(ctx, s.getClient(), apiKey, apiSecret)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\n// docs: https://docs.bitfinex.com/docs/introduction\nfunc verifyBitfinex(ctx context.Context, client *http.Client, apiKey, apiSecret string) (bool, error) {\n\tbaseURL := \"https://api.bitfinex.com\"\n\trequestPath := \"/v2/auth/r/wallets\"\n\tsignaturePath := \"/api\" + requestPath\n\tnonce := fmt.Sprintf(\"%d\", time.Now().UnixNano()/int64(time.Microsecond))\n\tbody := \"{}\"\n\tsignaturePayload := signaturePath + nonce + body\n\tsignature, err := sign(signaturePayload, apiSecret)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+requestPath, bytes.NewBuffer([]byte(body)))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Accept\", \"application/json\")\n\treq.Header.Set(\"bfx-apikey\", apiKey)\n\treq.Header.Set(\"bfx-signature\", signature)\n\treq.Header.Set(\"bfx-nonce\", nonce)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusInternalServerError:\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tif strings.Contains(string(body), \"apikey: digest invalid\") || strings.Contains(string(body), \"apikey: invalid\") {\n\t\t\treturn false, nil\n\t\t} else {\n\t\t\treturn false, fmt.Errorf(\"failed to verify Bitfinex API key, error: %s\", string(body))\n\t\t}\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", resp.StatusCode)\n\t}\n}\n\nfunc sign(msg, apiSecret string) (string, error) {\n\tsig := hmac.New(sha512.New384, []byte(apiSecret))\n\t_, err := sig.Write([]byte(msg))\n\tif err != nil {\n\t\treturn \"\", nil\n\t}\n\treturn hex.EncodeToString(sig.Sum(nil)), nil\n}\n"
  },
  {
    "path": "pkg/detectors/bitfinex/bitfinex_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage bitfinex\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBitfinex_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tapiKey := testSecrets.MustGetField(\"BITFINEX_API_KEY\")\n\tinactiveApiKey := testSecrets.MustGetField(\"BITFINEX_API_KEY_INACTIVE\")\n\tapiSecret := testSecrets.MustGetField(\"BITFINEX_API_SECRET\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bitfinex api key %s within bitfinex api secret %s\", apiKey, apiSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t// will try to scan (apiKey, apiSecret) which will verify then (apiSecret, apiKey) which will not since both parameters have equal length\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bitfinex,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bitfinex,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bitfinex api key %s within bitfinex api secret %s\", inactiveApiKey, apiSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bitfinex,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bitfinex,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Bitfinex.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Bitfinex.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bitfinex/bitfinex_test.go",
    "content": "package bitfinex\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBitFinex_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\tbitfinexKey := \"HxfuG198amaeCcYkASkto5VuIO-oXcplDV6JZ7OIEQZ\"\n\t\t\t\t\tbitfinexSecret := \"Pf3-3v989gPbJT54D3oDBiFZmJoLpWoTHGvF8xuSBPP\"\n\t\t\t\t\thttp.DefaultClient = client\n\t\t\t\t\tc := rest.NewClientWithURL(*api).Credentials(key, secret)\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"HxfuG198amaeCcYkASkto5VuIO-oXcplDV6JZ7OIEQZ\", \"Pf3-3v989gPbJT54D3oDBiFZmJoLpWoTHGvF8xuSBPP\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{bitfinex bdiODwPukXLKUjSLvfeTlKVEwm89zqOhQ2a9chacKcr}</id>\n  \t\t\t\t\t<secret>{bitfinex AQAAABAAA MTvK78juiZmddv3eEyoz1gqRwP89OHreiX6fnXkfbce}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"bdiODwPukXLKUjSLvfeTlKVEwm89zqOhQ2a9chacKcr\",\n\t\t\t\t\"MTvK78juiZmddv3eEyoz1gqRwP89OHreiX6fnXkfbce\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\tbitfinexKey := \"HxfuG198amaeCcYkASkto5VuIO-oXcplDV6JZ7OIEQZ\"\n\t\t\t\t\tbitfinexSecret := \"kASkto5VuIO%c^HxfuG198amaeCcYkASkto5VuIO\"\n\t\t\t\t\thttp.DefaultClient = client\n\t\t\t\t\tc := rest.NewClientWithURL(*api).Credentials(key, secret)\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bitlyaccesstoken/bitlyaccesstoken.go",
    "content": "package bitlyaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"bitly\"}) + `\\b([a-zA-Z-0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"bitly\"}\n}\n\n// FromData will find and optionally verify BitLyAccessToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueTokens = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokens[matches[1]] = struct{}{}\n\t}\n\n\tfor token := range uniqueTokens {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_BitLyAccessToken,\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBitlyAccessToken(ctx, client, token)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_BitLyAccessToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc verifyBitlyAccessToken(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api-ssl.bitly.com/v4/user\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bitlyaccesstoken/bitlyaccesstoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage bitlyaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBitLyAccessToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BITLYACCESSTOKEN_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"BITLYACCESSTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bitlyaccesstoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BitLyAccessToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BitLyAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"BitLyAccessToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"BitLyAccessToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bitlyaccesstoken/bitlyaccesstoken_test.go",
    "content": "package bitlyaccesstoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBitlyAccessToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbitlyToken := \"2xN7puShxzNf5fZleQthTg305lKr7KrbW95D3gSD\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Bearer \" + bitlyToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"2xN7puShxzNf5fZleQthTg305lKr7KrbW95D3gSD\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{bitly}</id>\n  \t\t\t\t\t<secret>{bitly AQAAABAAA TKymDGZ62qKyWXsq00Nyp-w1bTJn7bFlXWTaH-2i}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"TKymDGZ62qKyWXsq00Nyp-w1bTJn7bFlXWTaH-2i\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbitlyToken := \"2xN7puShxzNf5fZleQthTg305l95D3gSD%c^\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Bearer \" + bitlyToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bitmex/bitmex.go",
    "content": "package bitmex\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"bitmex\"}) + `([ \\r\\n]{1}[0-9a-zA-Z\\-\\_]{24}[ \\r\\n]{1})`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"bitmex\"}) + `([ \\r\\n]{1}[0-9a-zA-Z\\-\\_]{48}[ \\r\\n]{1})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"bitmex\"}\n}\n\n// FromData will find and optionally verify Bitmex secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, secretMatch := range secretMatches {\n\t\t\tresSecretMatch := strings.TrimSpace(secretMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Bitmex,\n\t\t\t\tRaw:          []byte(resSecretMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resSecretMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, verificationErr := verifyBitmex(ctx, client, resMatch, resSecretMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Bitmex\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Bitmex is a cryptocurrency exchange and derivative trading platform. Bitmex API keys can be used to access and trade on the platform programmatically.\"\n}\n\n// docs: https://www.bitmex.com/app/apiKeysUsage\nfunc verifyBitmex(ctx context.Context, client *http.Client, key, secret string) (bool, error) {\n\ttimestamp := strconv.FormatInt(time.Now().Unix()+5, 10)\n\taction := \"GET\"\n\tpath := \"/api/v1/user\"\n\tpayload := url.Values{}\n\n\tsignature := getBitmexSignature(timestamp, secret, action, path, payload.Encode())\n\n\treq, err := http.NewRequestWithContext(ctx, action, \"https://www.bitmex.com\"+path, strings.NewReader(payload.Encode()))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"api-expires\", timestamp)\n\treq.Header.Add(\"api-key\", key)\n\treq.Header.Add(\"api-signature\", signature)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\nfunc getBitmexSignature(timeStamp string, secret string, action string, path string, payload string) string {\n\tmac := hmac.New(sha256.New, []byte(secret))\n\tmac.Write([]byte(action + path + timeStamp + payload))\n\tmacsum := mac.Sum(nil)\n\treturn hex.EncodeToString(macsum)\n}\n"
  },
  {
    "path": "pkg/detectors/bitmex/bitmex_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage bitmex\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBitmex_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tkey := testSecrets.MustGetField(\"BITMEX_KEY\")\n\tinactiveKey := testSecrets.MustGetField(\"BITMEX_KEY_INACTIVE\")\n\tsecret := testSecrets.MustGetField(\"BITMEX\")\n\tinactiveSecret := testSecrets.MustGetField(\"BITMEX_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bitmex key %s with bitmex secret %s within\", key, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bitmex,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bitmex,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Bitmex.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw v2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Bitmex.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bitmex/bitmex_test.go",
    "content": "package bitmex\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBitmex_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbitmexKey := \" EPwUIxOIveS463D_2O9LFgkz \"\n\t\t\t\t\tbitmexSecret := \" W_HlMtrmELzXm4bSlWv49JLcgvg5hvu467WbbnpmoEA-RjrY \"\n\n\t\t\t\t\tsignature, err := generateSecretSignature(bitmexKey, bitmexSecret)\n\t\t\t\t\tif err != nil{\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\treq.Header.Set(\"api-signature\", signature)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"EPwUIxOIveS463D_2O9LFgkzW_HlMtrmELzXm4bSlWv49JLcgvg5hvu467WbbnpmoEA-RjrY\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{bitmex EPwUIxOIveS463D_2O9LFgkz }</id>\n  \t\t\t\t\t<secret>{bitmex AQAAABAAA W_HlMtrmELzXm4bSlWv49JLcgvg5hvu467WbbnpmoEA-RjrY }</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"EPwUIxOIveS463D_2O9LFgkzW_HlMtrmELzXm4bSlWv49JLcgvg5hvu467WbbnpmoEA-RjrY\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbitmexKey := \"mELzXm4bSlWv49JLc%c^\"\n\t\t\t\t\tbitmexSecret := \"IXpH-fJJiLFn--Wo7rnlXE\"\n\n\t\t\t\t\tsignature, err := generateSecretSignature(bitmexKey, bitmexSecret)\n\t\t\t\t\tif err != nil{\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\treq.Header.Set(\"api-signature\", signature)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/blazemeter/blazemeter.go",
    "content": "package blazemeter\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"blazemeter\", \"runscope\"}\n}\n\n// FromData will find and optionally verify Blazemeter secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Blazemeter,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBlazeMeter(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Blazemeter\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Blazemeter is a continuous testing platform for DevOps. The keys can be used to access and manage performance tests and other resources.\"\n}\n\n// docs: https://help.blazemeter.com/apidocs/api-monitoring/account.htm?tocpath=API%20Monitoring%7C_____12\nfunc verifyBlazeMeter(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.runscope.com/account\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/blazemeter/blazemeter_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage blazemeter\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBlazemeter_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BLAZEMETER\")\n\tinactiveSecret := testSecrets.MustGetField(\"BLAZEMETER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a blazemeter secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Blazemeter,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Blazemeter,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Blazemeter.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Blazemeter.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/blazemeter/blazemeter_test.go",
    "content": "package blazemeter\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBlazeMeter_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tblazemeterToken := \"sjbuxa3m-vs4n-ykl8-8jpv-i09hdidciolp\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Bearer \" + blazemeterToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"sjbuxa3m-vs4n-ykl8-8jpv-i09hdidciolp\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{runscope}</id>\n  \t\t\t\t\t<secret>{runscope AQAAABAAA vzn9dy84-mnvd-alqd-4pbf-cn618kvo26le}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"vzn9dy84-mnvd-alqd-4pbf-cn618kvo26le\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tblazemeterToken := \"sjbuxa3m-vs4n- ykl8-8jpv#i09hdidciolp\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Bearer \" + blazemeterToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/blitapp/blitapp.go",
    "content": "package blitapp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"blitapp\"}) + `\\b([a-zA-Z0-9_-]{39})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"blitapp\"}\n}\n\n// FromData will find and optionally verify BlitApp secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_BlitApp,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBlitApp(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_BlitApp\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"BlitApp is a service used for managing applications. BlitApp API keys can be used to access and modify application data.\"\n}\n\n// docs: https://blitapp.com/api/#/App/get_apps_all\nfunc verifyBlitApp(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://blitapp.com/api/apps/all\", nil)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\n\treq.Header.Add(\"API-Key\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/blitapp/blitapp_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage blitapp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBlitApp_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BLITAPP\")\n\tinactiveSecret := testSecrets.MustGetField(\"BLITAPP_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a blitapp secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BlitApp,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BlitApp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"BlitApp.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"BlitApp.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/blitapp/blitapp_test.go",
    "content": "package blitapp\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBlitApp_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tblitAppKey := \"I_MncTA8nlFcqlBCakI5vwkwFD4_zRUYZKt8hyd\"\n\t\t\t\t\treq.Header.Set(\"API-Key\", blitAppKey)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"I_MncTA8nlFcqlBCakI5vwkwFD4_zRUYZKt8hyd\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{blitapp}</id>\n  \t\t\t\t\t<secret>{blitapp AQAAABAAA 188hN_78_V86WbCBVJd6OLMQJTHva7cbSf8HDFo}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"188hN_78_V86WbCBVJd6OLMQJTHva7cbSf8HDFo\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tblitAppKey := \"I_Mn%^&*qlBCakI5vwkwFD4_zRUY\"\n\t\t\t\t\treq.Header.Set(\"API-Key\", blitAppKey)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/blocknative/blocknative.go",
    "content": "package blocknative\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"blocknative\"}\n}\n\n// FromData will find and optionally verify Blocknative secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_BlockNative,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBlocknative(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_BlockNative\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// docs: https://docs.blocknative.com/gas-prediction/gas-platform#api-endpoint\nfunc verifyBlocknative(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.blocknative.com/gasprices/blockprices\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\t// Right now the blocknative API logic is broken and return 200 for invalid key as well\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusTooManyRequests:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/blocknative/blocknative_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage blocknative\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBlocknative_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BLOCKNATIVE\")\n\tinactiveSecret := testSecrets.MustGetField(\"BLOCKNATIVE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a blocknative secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BlockNative,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BlockNative,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Blocknative.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Blocknative.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/blocknative/blocknative_test.go",
    "content": "package blocknative\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBlockNative_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tblocknativeSecret := \"76e50995-059f-3d1a-af8e-cc85fc05eb03\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", blocknativeSecret)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"76e50995-059f-3d1a-af8e-cc85fc05eb03\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{blocknative}</id>\n  \t\t\t\t\t<secret>{blocknative AQAAABAAA 7b15f7f8-52a8-849d-384e-20b4c0de82dd}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"7b15f7f8-52a8-849d-384e-20b4c0de82dd\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tblocknativeSecret := \"2xN7puShxzNf5fZleQthTg305l95D3gSD%c^\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", blocknativeSecret)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/blogger/blogger.go",
    "content": "package blogger\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"blogger\"}) + `\\b([0-9A-Za-z-]{39})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"blogger\"}\n}\n\n// FromData will find and optionally verify Blogger secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Blogger,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBlogger(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Blogger\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Blogger API keys can be used to access and manage blogs on the Blogger platform.\"\n}\n\n// docs: https://developers.google.com/blogger/docs/3.0/using\nfunc verifyBlogger(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.googleapis.com/blogger/v3/blogs/2399953?key=\"+key, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusBadRequest, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/blogger/blogger_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage blogger\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBlogger_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BLOGGER\")\n\tinactiveSecret := testSecrets.MustGetField(\"BLOGGER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a blogger secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Blogger,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Blogger,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Blogger.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Blogger.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/blogger/blogger_test.go",
    "content": "package blogger\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBlogger_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", \"https://api.example.com/v1/blogger/blogs?key=fnWLw7pz1tc6uCzq6qocQZIxRF6SqUaOOkLqePY\", http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\n\t\t\t\t\t// Check response status\n\t\t\t\t\tif resp.StatusCode == http.StatusOK {\n\t\t\t\t\t\tfmt.Println(\"Request successful!\")\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfmt.Println(\"Request failed with status:\", resp.Status)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"fnWLw7pz1tc6uCzq6qocQZIxRF6SqUaOOkLqePY\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{blogger}</id>\n  \t\t\t\t\t<secret>{blogger AQAAABAAA mtkwpygpNROxOgLZCnEvl7gNme1IuFiQm9oxPzJ}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"mtkwpygpNROxOgLZCnEvl7gNme1IuFiQm9oxPzJ\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", \"https://api.example.com/v1/blogger/blogs?key=fnWL(w7pz1t)6uCz-q6qocQZIxRF6S/UqePY\", http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\n\t\t\t\t\t// Check response status\n\t\t\t\t\tif resp.StatusCode == http.StatusOK {\n\t\t\t\t\t\tfmt.Println(\"Request successful!\")\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfmt.Println(\"Request failed with status:\", resp.Status)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bombbomb/bombbomb.go",
    "content": "package bombbomb\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"bombbomb\"}) + common.BuildRegexJWT(\"0,140\", \"0,419\", \"0,171\"))\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"bombbomb\"}\n}\n\n// FromData will find and optionally verify BombBomb secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_BombBomb,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBombBomb(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_BombBomb\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// docs: https://developer.bombbomb.com/api#operations-Users-UserInfo\nfunc verifyBombBomb(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.bombbomb.com/v2/user/\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", \"Bearer \"+key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bombbomb/bombbomb_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage bombbomb\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBombBomb_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BOMBBOMB\")\n\tinactiveSecret := testSecrets.MustGetField(\"BOMBBOMB_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bombbomb secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BombBomb,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BombBomb,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"BombBomb.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"BombBomb.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bombbomb/bombbomb_test.go",
    "content": "package bombbomb\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBombBomb_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t\tbombbombToken := \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", bombbombToken)\n\t\t\t\t`,\n\t\t\twant: []string{\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{bombbomb}</id>\n  \t\t\t\t\t<secret>{bombbomb AQAAABAAA eyJioGciOiJIU9I1NiIsInR5cCI6IkpXVCJ9.eyJJdWIiOiIxMjM0NTY3ODkwIiwibmFtZSJ6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5d}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"eyJioGciOiJIU9I1NiIsInR5cCI6IkpXVCJ9.eyJJdWIiOiIxMjM0NTY3ODkwIiwibmFtZSJ6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5d\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t\tbombbombToken := \"eyJhbGciOiJIUzI1N^iIsInRkpXVCJ9.ey$JzdWIiOiIxMjM0NTY3ODkwIiwibmFtZwiaWF0IjoxNTE2MjM5MDIyfQ.S&flKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", bombbombToken)\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/boostnote/boostnote.go",
    "content": "package boostnote\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"boostnote\"}) + `\\b([0-9a-f]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"boostnote\"}\n}\n\n// FromData will find and optionally verify BoostNote secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_BoostNote,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBoostnote(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_BoostNote\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"BoostNote is a note-taking application. The secret detected here is likely an API key or token used to access BoostNote services.\"\n}\n\n// docs: https://boostnote.io/features/public-api\nfunc verifyBoostnote(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://boostnote.io/api/docs\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/boostnote/boostnote_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage boostnote\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBoostNote_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BOOSTNOTE\")\n\tinactiveSecret := testSecrets.MustGetField(\"BOOSTNOTE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a boostnote secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BoostNote,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BoostNote,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"BoostNote.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"BoostNote.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/boostnote/boostnote_test.go",
    "content": "package boostnote\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBoostNote_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tboostnoteKey := \"fb1026ac5994e3ad01799fe040289317ba2594a20e9e45307a143be82b49d213\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Bearer \" + boostnoteKey)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"fb1026ac5994e3ad01799fe040289317ba2594a20e9e45307a143be82b49d213\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{boostnote}</id>\n  \t\t\t\t\t<secret>{boostnote AQAAABAAA a546e80a8018e1c5e37e4a3366a20aa363489691d2ca335e3a082550d8a92120}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"a546e80a8018e1c5e37e4a3366a20aa363489691d2ca335e3a082550d8a92120\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tboostnoteKey := \"#^fb1026ac59=4e3ad01799fe04028931___4a20e9e45307a143be82b49d213$\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Bearer \" + boostnoteKey)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/borgbase/borgbase.go",
    "content": "package borgbase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"borgbase\"}) + `\\b([a-zA-Z0-9/_.-]{148,152})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"borgbase\"}\n}\n\n// FromData will find and optionally verify Borgbase secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Borgbase,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBorgbase(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Borgbase\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Borgbase is a service for hosting Borg repositories. Borgbase API keys can be used to manage and access these repositories.\"\n}\n\n// docs: https://docs.borgbase.com/api\nfunc verifyBorgbase(ctx context.Context, client *http.Client, key string) (bool, error) {\n\ttimeout := 10 * time.Second\n\tclient.Timeout = timeout\n\n\tpayload := strings.NewReader(`{\"query\":\"{ sshList {id, name}}\"}`)\n\n\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.borgbase.com/graphql\", payload)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tbodyBytes, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tbodyString := string(bodyBytes)\n\t\tvalidResponse := strings.Contains(bodyString, `\"sshList\":[]`)\n\t\tif validResponse {\n\t\t\treturn true, nil\n\t\t}\n\n\t\treturn false, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/borgbase/borgbase_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage borgbase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBorgbase_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BORGBASE\")\n\tinactiveSecret := testSecrets.MustGetField(\"BORGBASE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a borgbase secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Borgbase,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Borgbase,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Borgbase.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Borgbase.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/borgbase/borgbase_test.go",
    "content": "package borgbase\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBorgBase_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\tpayload := '{\"query\":\"{ sshList {id, name}}\"}'\n\t\t\t\t\treq, err := http.NewRequest(\"POST\", url, payload)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tborgbaseToken := \"FoHclCFSi_aV09jowJQ4RUF_MiqW6ioqq6_OcyB0PFlV-mQ1yoFjk5JLlxbzRUzKTA6vsfR8wq6TNc83rtNKlkD092Sj1c9CbPVBXlHksy.sT2I/so6bMGdPcqxzbjrxYgAUiORgqJDeTet4gKOQlZpt\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Bearer \" + borgbaseToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"FoHclCFSi_aV09jowJQ4RUF_MiqW6ioqq6_OcyB0PFlV-mQ1yoFjk5JLlxbzRUzKTA6vsfR8wq6TNc83rtNKlkD092Sj1c9CbPVBXlHksy.sT2I/so6bMGdPcqxzbjrxYgAUiORgqJDeTet4gKOQlZpt\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{borgbase}</id>\n  \t\t\t\t\t<secret>{borgbase AQAAABAAA KtSE0ggsVsvvDQPHau2ItXW8yi7YsFTho4wHTTjCDShrWgYA421GzfXMwkOYklS6psQd1W8459NvmcZSmr7_LKqQffBGYAVvexM1D4JxRcQS49H3rnFlwDYspB5_m7AxvmbPrpWj8TfNm7zKCa2Ed}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"KtSE0ggsVsvvDQPHau2ItXW8yi7YsFTho4wHTTjCDShrWgYA421GzfXMwkOYklS6psQd1W8459NvmcZSmr7_LKqQffBGYAVvexM1D4JxRcQS49H3rnFlwDYspB5_m7AxvmbPrpWj8TfNm7zKCa2Ed\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\tpayload := '{\"query\":\"{ sshList {id, name}}\"}'\n\t\t\t\t\treq, err := http.NewRequest(\"POST\", url, payload)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tborgbaseToken := \"mQ1yoFjk5JLlxbzRUzKTA6vsfR8wq,6TNc83rtNKlkD092Sj1c9CbPVBXlHksy%c^so6bMGdPcqxzbjrxYgAUiORgqJDeTet4gKOQlZpt\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Bearer \" + borgbaseToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/box/box.go",
    "content": "package box\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"box\"}) + `\\b([0-9a-zA-Z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"box\"}\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// FromData will find and optionally verify Box secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Box,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\turl := \"https://api.box.com/2.0/users/me\"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header = http.Header{\"Authorization\": []string{\"Bearer \" + token}}\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\t{\n\t\t\tvar u user\n\t\t\tif err := json.NewDecoder(res.Body).Decode(&u); err != nil {\n\t\t\t\treturn false, nil, err\n\t\t\t}\n\t\t\treturn true, bakeExtraDataFromUser(u), nil\n\t\t}\n\tcase http.StatusUnauthorized:\n\t\t// 401 access token not found\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Box\n}\n\nfunc bakeExtraDataFromUser(u user) map[string]string {\n\treturn map[string]string{\n\t\t\"user_id\":     u.ID,\n\t\t\"username\":    u.Login,\n\t\t\"user_status\": u.Status,\n\t}\n}\n\n// struct to represent a Box user.\ntype user struct {\n\tID     string `json:\"id\"`\n\tLogin  string `json:\"login\"`\n\tStatus string `json:\"status\"`\n}\n"
  },
  {
    "path": "pkg/detectors/box/box_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage box\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBox_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\ttoken := testSecrets.MustGetField(\"BOX_ACCESS_TOKEN\")\n\tinactiveToken := testSecrets.MustGetField(\"BOX_ACCESS_TOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a box token %s within\", token)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Box,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRaw:          []byte(token),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Box,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(inactiveToken),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a box token %s within\", token)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Box,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(token),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a box secret %s within\", token)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Box,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(token),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Box.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"verificationError\", \"ExtraData\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Box.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/box/box_test.go",
    "content": "package box\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBox_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] request received to fetch box data\n\t\t\t\t[INFO] sending API request to box API\n\t\t\t\t[DEBUG] using Key=Ogowv5cj5AJJjO5F3daNHbKJDdPud0CZ\n\t\t\t\t[DEBUG] request sent successfully\n\t\t\t\t[INFO] response received: 200 OK\n\t\t\t\t[DEBUG] fetch data from the database for ID Qje1HjJmgrNzOQpQZROEeYjmHbD2qdFF\n\t\t\t\t[INFO] data returned\n\t\t\t`,\n\t\t\twant: []string{\"Ogowv5cj5AJJjO5F3daNHbKJDdPud0CZ\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{box}</id>\n  \t\t\t\t\t<secret>{box AQAAABAAA Dxb2zNdFF2QTSMwrZJnoeD54Dc4zZAIW}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"Dxb2zNdFF2QTSMwrZJnoeD54Dc4zZAIW\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] request received to fetch box data\n\t\t\t\t[INFO] sending API request to box API\n\t\t\t\t[DEBUG] using Key=Ogow-v5cj-5AJJ-jO5F-3daN-HbKJ-DdPu-d0CZ\n\t\t\t\t[DEBUG] request sent successfully\n\t\t\t\t[ERROR] response received: 401 UnAuthorized\n\t\t\t\t[INFO] nothing to return\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/boxoauth/boxoauth.go",
    "content": "package boxoauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tclientIdPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"id\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n\tclientSecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"secret\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"box\"}\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// FromData will find and optionally verify Box secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueIdMatches := make(map[string]struct{})\n\tfor _, match := range clientIdPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueIdMatches[match[1]] = struct{}{}\n\t}\n\n\tuniqueSecretMatches := make(map[string]struct{})\n\tfor _, match := range clientSecretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueSecretMatches[match[1]] = struct{}{}\n\t}\n\n\tfor resIdMatch := range uniqueIdMatches {\n\t\tfor resSecretMatch := range uniqueSecretMatches {\n\n\t\t\t// ignore if the id and secret are the same\n\t\t\tif resIdMatch == resSecretMatch {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_BoxOauth,\n\t\t\t\tRaw:          []byte(resIdMatch),\n\t\t\t\tRawV2:        []byte(resIdMatch + resSecretMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, resIdMatch, resSecretMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.ExtraData = extraData\n\t\t\t\ts1.SetVerificationError(verificationErr, resIdMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\n\t\t\t// box client supports only one client id and secret pair\n\t\t\tif s1.Verified {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, id string, secret string) (bool, map[string]string, error) {\n\turl := \"https://api.box.com/oauth2/token\"\n\tpayload := strings.NewReader(\"grant_type=client_credentials&client_id=\" + id + \"&client_secret=\" + secret)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, payload)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header = http.Header{\"content-type\": []string{\"application/x-www-form-urlencoded\"}}\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\t// We are using malformed request to check if the client id and secret are valid.\n\t// In this case, the Box OAuth API returns a 400 status code even if the credentials are valid.\n\t//\n\t// - If the client ID/secret are valid, the response contains \"unauthorized_client\"\n\t// - If the credentials are invalid, the response contains \"invalid_client\"\n\t//\n\t// So we check the response body for one of these keywords.\n\tswitch res.StatusCode {\n\tcase http.StatusBadRequest:\n\t\t{\n\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\tif err != nil {\n\t\t\t\treturn false, nil, err\n\t\t\t}\n\t\t\tbody := string(bodyBytes)\n\t\t\tif strings.Contains(body, \"unauthorized_client\") {\n\t\t\t\treturn true, nil, nil\n\t\t\t} else if strings.Contains(body, \"invalid_client\") {\n\t\t\t\treturn false, nil, nil\n\t\t\t} else {\n\t\t\t\treturn false, nil, fmt.Errorf(\"response body missing expected keyword\")\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_BoxOauth\n}\n"
  },
  {
    "path": "pkg/detectors/boxoauth/boxoauth_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage boxoauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBoxOauth_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tid := testSecrets.MustGetField(\"BOXOAUTH_ID\")\n\tsecret := testSecrets.MustGetField(\"BOXOAUTH_SECRET\")\n\tinvalidSecret := testSecrets.MustGetField(\"BOXOAUTH_INVALID_SECRET\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a box id %s with secret %s\", id, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BoxOauth,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRaw:          []byte(id),\n\t\t\t\t\tRawV2:        []byte(id + secret),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a box id %s with secret %s\", id, invalidSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BoxOauth,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(id),\n\t\t\t\t\tRawV2:        []byte(id + invalidSecret),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"BoxOauth.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no rawV2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"BoxOauth.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/boxoauth/boxoauth_test.go",
    "content": "package boxoauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tclientId            = common.GenerateRandomPassword(true, true, true, false, 32)\n\tclientSecret        = common.GenerateRandomPassword(true, true, true, false, 32)\n\tinvalidClientSecret = common.GenerateRandomPassword(true, true, true, true, 32)\n)\n\nfunc TestBoxOauth_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: fmt.Sprintf(\"box id = '%s' box secret = '%s'\", clientId, clientSecret),\n\t\t\twant:  []string{clientId + clientSecret},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"box id = '%s' box secret = '%s'\", clientId, invalidClientSecret),\n\t\t\twant:  nil,\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"box = '%s|%s'\", clientId, invalidClientSecret),\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/braintreepayments/braintreepayments.go",
    "content": "package braintreepayments\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient     *http.Client\n\tuseTestURL bool\n}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nconst (\n\tverifyURL     = \"https://payments.braintree-api.com/graphql\"\n\tverifyTestURL = \"https://payments.sandbox.braintree-api.com/graphql\"\n)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"braintree\"}) + `\\b([0-9a-f]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"braintree\"}) + `\\b([0-9a-z]{16})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"braintree\"}\n}\n\n// FromData will find and optionally verify BraintreePayments secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_BraintreePayments,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.getClient()\n\t\t\t\turl := s.getBraintreeURL()\n\t\t\t\tisVerified, verificationErr := verifyBraintree(ctx, client, url, resIdMatch, resMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) getBraintreeURL() string {\n\tif s.useTestURL {\n\t\treturn verifyTestURL\n\t}\n\treturn verifyURL\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\nfunc verifyBraintree(ctx context.Context, client *http.Client, url, pubKey, privKey string) (bool, error) {\n\tpayload := strings.NewReader(`{\"query\": \"query { ping }\"}`)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, payload)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Braintree-Version\", \"2019-01-01\")\n\treq.SetBasicAuth(pubKey, privKey)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tbodyBytes, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tbodyString := string(bodyBytes)\n\tif !(res.StatusCode == http.StatusOK) {\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n\n\tvalidResponse := `\"data\":{`\n\tif strings.Contains(bodyString, validResponse) {\n\t\treturn true, nil\n\t}\n\n\treturn false, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_BraintreePayments\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/braintreepayments/braintreepayments_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage braintreepayments\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBraintreePayments_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BRAINTREEPAYMENTS\")\n\tid := testSecrets.MustGetField(\"BRAINTREEPAYMENTS_USER\")\n\tinactiveSecret := testSecrets.MustGetField(\"BRAINTREEPAYMENTS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{useTestURL: true},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a braintree secret %s within braintree %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BraintreePayments,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts: Scanner{\n\t\t\t\tclient: common.ConstantResponseHttpClient(404, \"\"),\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a braintree secret %s within braintree %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BraintreePayments,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts: Scanner{\n\t\t\t\tclient: common.SaneHttpClientTimeOut(1 * time.Microsecond),\n\t\t\t},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a braintree secret %s within braintree %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BraintreePayments,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{useTestURL: true},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BraintreePayments,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"BraintreePayments.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"BraintreePayments.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/braintreepayments/braintreepayments_test.go",
    "content": "package braintreepayments\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBrainTreePayments_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbraintreeKey := \"f7b3cb83a7fdb915a71ce17ab8a903cc\"\n\t\t\t\t\tbraintreeId := \"kmajpm4h1pqoqxyo\"\n\t\t\t\t\treq.SetBasicAuth(braintreeKey, braintreeId)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"f7b3cb83a7fdb915a71ce17ab8a903cc\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{braintree jvbs4thxyzhh8n00}</id>\n  \t\t\t\t\t<secret>{braintree AQAAABAAA 7d1ab9c76bea2cfb80a29fef8f1e0b12}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"7d1ab9c76bea2cfb80a29fef8f1e0b12\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbraintreeKey := \"f7b3cb83a7fdb915a71ce17ab8a903cckmajpm4h1pqoqxyo\"\n\t\t\t\t\tbraintreeId := \"kmajpm4h1pqoqxyo\"\n\t\t\t\t\treq.SetBasicAuth(braintreeKey, braintreeId)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/brandfetch/v1/brandfetch.go",
    "content": "package brandfetch\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strconv\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\tv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/brandfetch/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nfunc (s Scanner) Version() int { return 1 }\n\nvar (\n\t// Ensure the Scanner satisfies the interface at compile time.\n\t_             detectors.Detector  = (*Scanner)(nil)\n\t_             detectors.Versioner = (*Scanner)(nil)\n\tdefaultClient                     = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"brandfetch\"}) + `\\b([0-9A-Za-z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"brandfetch\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Brandfetch\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Brandfetch is a service that provides brand data, including logos, colors, fonts, and more. Brandfetch API keys can be used to access this data.\"\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Brandfetch secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueTokenMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokenMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueTokenMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Brandfetch,\n\t\t\tRaw:          []byte(match),\n\t\t\tExtraData:    map[string]string{\"version\": strconv.Itoa(s.Version())},\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := v2.VerifyMatch(ctx, s.getClient(), match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "pkg/detectors/brandfetch/v1/brandfetch_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage brandfetch\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBrandfetch_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BRANDFETCH\")\n\tinactiveSecret := testSecrets.MustGetField(\"BRANDFETCH_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a brandfetch secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Brandfetch,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Brandfetch,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Brandfetch.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].ExtraData = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Brandfetch.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/brandfetch/v1/brandfetch_test.go",
    "content": "package brandfetch\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBrandFetch_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbrandfetchAPIKey := \"uHOAdwfQ7sD2yOpur72UqyUeIqnFwILOIlEPyBtJ\"\n\t\t\t\t\treq.Header.Set(\"x-api-key\", brandfetchAPIKey) // brandfetch secret\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"uHOAdwfQ7sD2yOpur72UqyUeIqnFwILOIlEPyBtJ\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{uSiXZ-NMpDW-ZJQFSN-5wkT7SqQ8-mDbr9K2pl}</id>\n  \t\t\t\t\t<secret>{brandfetch AQAAABAAA uSiXZNMpDWWhZJQFSNkE5wkT7SqQ8B3mDbr9K2pl}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"uSiXZNMpDWWhZJQFSNkE5wkT7SqQ8B3mDbr9K2pl\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbrandfetchAPIKey := \"yUeIqnFwILOIlEPyBt+=JOAdwfQ7sD2uHOAdwf2U[qy]UeIqnFwILOIlEPyBtJ^\"\n\t\t\t\t\treq.Header.Set(\"x-api-key\", brandfetchAPIKey) // brandfetch secret\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/brandfetch/v2/brandfetch.go",
    "content": "package brandfetch\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nfunc (s Scanner) Version() int { return 2 }\n\nvar (\n\t// Ensure the Scanner satisfies the interface at compile time.\n\t_             detectors.Detector  = (*Scanner)(nil)\n\t_             detectors.Versioner = (*Scanner)(nil)\n\tdefaultClient                     = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"brandfetch\"}) + `([a-zA-Z0-9=+/\\-_!@#$%^&*()]{43}=)`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"brandfetch\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Brandfetch\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Brandfetch is a service that provides brand data, including logos, colors, fonts, and more. Brandfetch API keys can be used to access this data.\"\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Brandfetch secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Brandfetch,\n\t\t\tRaw:          []byte(match),\n\t\t\tExtraData:    map[string]string{\"version\": strconv.Itoa(s.Version())},\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := VerifyMatch(ctx, s.getClient(), match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\n// verifyMatch checks if the provided Brandfetch token is valid by making a request to the Brandfetch API.\n// https://docs.brandfetch.com/docs/getting-started\nfunc VerifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.brandfetch.io/v2/brands/google.com\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Authorization\", \"Bearer \"+token)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer resp.Body.Close()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/brandfetch/v2/brandfetch_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage brandfetch\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBrandfetch_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BRANDFETCH_V2\")\n\tinactiveSecret := testSecrets.MustGetField(\"BRANDFETCH_V2_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a brandfetch secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Brandfetch,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Brandfetch,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Brandfetch.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].ExtraData = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Brandfetch.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/brandfetch/v2/brandfetch_test.go",
    "content": "package brandfetch\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBrandFetch_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: \"brandfetch credentials: ZUfake+eKo3qNxLDfake/6vqjOtr4fa6u5wShfakes8=\",\n\t\t\twant:  []string{\"ZUfake+eKo3qNxLDfake/6vqjOtr4fa6u5wShfakes8=\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - assignment format\",\n\t\t\tinput: \"BRANDFETCH_API_KEY=msCwufakeod43s2ad/D0em/LbIBpZqFAKE9P+H3UTno=\",\n\t\t\twant:  []string{\"msCwufakeod43s2ad/D0em/LbIBpZqFAKE9P+H3UTno=\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - complex\",\n\t\t\tinput: `\n\t\t\tfunc main() {\n\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tbrandfetchAPIKey := \"0mWrufake4X1dRfake0mxS+E48ofakesTlyl55raNOs=\"\n\t\t\t\treq.Header.Set(\"x-api-key\", brandfetchAPIKey) // brandfetch secret\n\n\t\t\t\t// Perform the request\n\t\t\t\tclient := &http.Client{}\n\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\tdefer resp.Body.Close()\n\n\t\t\t\t// Check response status\n\t\t\t\tif resp.StatusCode == http.StatusOK {\n\t\t\t\t\tfmt.Println(\"Request successful!\")\n\t\t\t\t} else {\n\t\t\t\t\tfmt.Println(\"Request failed with status:\", resp.Status)\n\t\t\t\t}\n\t\t\t}\n\t\t\t`,\n\t\t\twant: []string{\"0mWrufake4X1dRfake0mxS+E48ofakesTlyl55raNOs=\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{uSiXZ-NMpDW-ZJQFSN-5wkT7SqQ8-mDbr9K2pl}</id>\n  \t\t\t\t\t<secret>{brandfetch AQAAABAAA 0mWrufake4X1dRfake0mxS+E48ofakesTlyl55rfake=}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"0mWrufake4X1dRfake0mxS+E48ofakesTlyl55rfake=\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern - wrong length\",\n\t\t\tinput: \"brandfetch credentials: yUeIqnFwILOIlEPyBt+=JOAdwfQ7sD2uHOAdwf2U\",\n\t\t\twant:  nil,\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern - invalid characters\",\n\t\t\tinput: \"brandfetch credentials: yUeIqnFwILOIlEPyBt+=JOAdwfQ7sD2uHOAdwf2U[qy]UeIqnFwILOIlEPyBtJ^fakes=\",\n\t\t\twant:  nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbrandfetchAPIKey := \"yUeIqnFwILOIlEPyBt+=JOAdwfQ7sD2uHOAdwf2U[qy]UeIqnFwILOIlEPyBtJ^\"\n\t\t\t\t\treq.Header.Set(\"x-api-key\", brandfetchAPIKey) // brandfetch secret\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/browserstack/browserstack.go",
    "content": "package browserstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/cookiejar\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"golang.org/x/net/publicsuffix\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nconst browserStackAPIURL = \"https://www.browserstack.com/automate/plan.json\"\n\nvar (\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat  = 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`)\n\tuserPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"browserstack\"}\n}\n\nfunc (s Scanner) getClient(cookieJar *cookiejar.Jar) *http.Client {\n\tif s.client != nil {\n\t\ts.client.Jar = cookieJar\n\t\treturn s.client\n\t}\n\t// Using custom HTTP client instead of common.SaneHttpClient() here because, for unknown reasons, browserstack blocks those requests even with cookie jar attached\n\treturn &http.Client{\n\t\tJar: cookieJar,\n\t}\n}\n\n// FromData will find and optionally verify BrowserStack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tuserMatches := userPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, userMatch := range userMatches {\n\n\t\t\tresUserMatch := strings.TrimSpace(userMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_BrowserStack,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resUserMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\t// browserstack (via cloudflare) requires cookies to be enabled\n\t\t\t\tjar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tclient := s.getClient(jar)\n\n\t\t\t\tisVerified, verificationErr := verifyBrowserStackCredentials(ctx, client, resUserMatch, resMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyBrowserStackCredentials(ctx context.Context, client *http.Client, username, accessKey string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, browserStackAPIURL, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"User-Agent\", common.UserAgent())\n\n\treq.SetBasicAuth(username, accessKey)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\tif res.StatusCode == http.StatusOK {\n\t\treturn true, nil\n\t} else if res.StatusCode == http.StatusForbidden {\n\t\t// Sometimes browserstack (via Cloudflare) will block requests for security\n\t\tbody, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tif strings.Contains(string(body), \"blocked\") {\n\t\t\treturn false, fmt.Errorf(\"blocked by browserstack\")\n\t\t}\n\t} else if res.StatusCode != http.StatusUnauthorized {\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n\n\treturn false, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_BrowserStack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"BrowserStack is a cloud web and mobile testing platform. BrowserStack credentials can be used to access and manage testing environments.\"\n}\n"
  },
  {
    "path": "pkg/detectors/browserstack/browserstack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage browserstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBrowserStack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecretUser := testSecrets.MustGetField(\"BROWSERSTACK_USER\")\n\tsecret := testSecrets.MustGetField(\"BROWSERSTACK\")\n\tinactiveSecret := testSecrets.MustGetField(\"BROWSERSTACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a BROWSERSTACK_ACCESS_KEY %s within BROWSERSTACK_USERNAME %s\", secret, secretUser)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BrowserStack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s%s\", secret, secretUser)),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BrowserStack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s%s\", inactiveSecret, secretUser)),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a BROWSERSTACK_ACCESS_KEY %s within BROWSERSTACK_USERNAME %s\", secret, secretUser)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BrowserStack,\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s%s\", secret, secretUser)),\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"context deadline exceeded\"), secret)\n\t\t\t\tresults := []detectors.Result{r}\n\t\t\t\treturn results\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a BROWSERSTACK_ACCESS_KEY %s within BROWSERSTACK_USERNAME %s\", secret, secretUser)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BrowserStack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s%s\", secret, secretUser)),\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"unexpected HTTP response status 404\"), secret)\n\t\t\t\tresults := []detectors.Result{r}\n\t\t\t\treturn results\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but blocked by browserstack\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(403, \"blocked\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a BROWSERSTACK_ACCESS_KEY %s within BROWSERSTACK_USERNAME %s\", secret, secretUser)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BrowserStack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s%s\", secret, secretUser)),\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"blocked by browserstack\"), secret)\n\t\t\t\tresults := []detectors.Result{r}\n\t\t\t\treturn results\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"BrowserStack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"BrowserStack.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/browserstack/browserstack_test.go",
    "content": "package browserstack\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBrowserStack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tif browserstackKey, _ := os.GetEnv(\"ACCESS_KEY\"); browserstackKey != \"cK1bq7JREJtMf1meaGgs\" {\n\t\t\t\t\t\treturn fmt.Errorf(\"invalid accessKey: %v expected: %v\", browserstackKey, \"1YZazUAPFOiaIFljWDhC\")\n\t\t\t\t\t}\n\n\t\t\t\t\tif browserstackUser, _ := os.GetEnv(\"USER_NAME\"); browserstackUser != \"truffle-security91\" {\n\t\t\t\t\t\treturn fmt.Errorf(\"invalid userName: %v\", \"truffle-security91\")\n\t\t\t\t\t}\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"cK1bq7JREJtMf1meaGgstruffle-security91\",\n\t\t\t\t\"1YZazUAPFOiaIFljWDhCbrowserstackUser\",\n\t\t\t\t\"1YZazUAPFOiaIFljWDhCtruffle-security91\",\n\t\t\t\t\"cK1bq7JREJtMf1meaGgsbrowserstackUser\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{BS_USERNAME Q8fo0ADq_-_Cj4HtE4Gr}</id>\n  \t\t\t\t\t<secret>{BROWSERSTACK_ACCESS_KEY AQAAABAAA 25IQfQKfEm26vKV96nao}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"25IQfQKfEm26vKV96naoQ8fo0ADq_-_Cj4HtE4Gr\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tif browserstackKey, _ := os.GetEnv(\"ACCESS_KEY\"); browserstackKey != \"RxLVnOlvj3#V4bh4RBwOd\" {\n\t\t\t\t\t\treturn fmt.Errorf(\"invalid accessKey: %v expected: %v\", browserstackKey, \"RxLVnOlvj3#V4bh4RBwOd\")\n\t\t\t\t\t}\n\n\t\t\t\t\tif browserstackUser, _ := os.GetEnv(\"USER_NAME\"); browserstackUser != \"test\" {\n\t\t\t\t\t\treturn fmt.Errorf(\"invalid userName: %v\", browserstackUser)\n\t\t\t\t\t}\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/browshot/browshot.go",
    "content": "package browshot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"browshot\"}) + `\\b([a-zA-Z-0-9]{28})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"browshot\"}\n}\n\n// FromData will find and optionally verify Browshot secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Browshot,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBrowshot(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Browshot\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// docs: https://browshot.com/api/documentation#instance_list\nfunc verifyBrowshot(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.browshot.com/api/v1/instance/list?key=\"+key, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusBadRequest, http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/browshot/browshot_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage browshot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBrowshot_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BROWSHOT\")\n\tinactiveSecret := testSecrets.MustGetField(\"BROWSHOT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a browshot secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Browshot,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Browshot,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Browshot.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Browshot.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/browshot/browshot_test.go",
    "content": "package browshot\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBrowShot_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.browshot.com/v1/instances/list?key=AemQ06R35S1Y8rXnOzYvT8I4-a7u\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\n\t\t\t\t\t// Check response status\n\t\t\t\t\tif resp.StatusCode == http.StatusOK {\n\t\t\t\t\t\tfmt.Println(\"Request successful!\")\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfmt.Println(\"Request failed with status:\", resp.Status)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"AemQ06R35S1Y8rXnOzYvT8I4-a7u\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{browshot}</id>\n  \t\t\t\t\t<secret>{browshot AQAAABAAA SyGuw6JXLULnOEDZjiicnTtQ4FA3}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"SyGuw6JXLULnOEDZjiicnTtQ4FA3\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.browshot.com/v1/instances/list?key=2xN7puShxzNf5fZleQt#hTg305l95D3gSD-c^\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bscscan/bscscan.go",
    "content": "package bscscan\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"bscscan\"}) + `\\b([0-9A-Z]{34})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"bscscan\"}\n}\n\n// FromData will find and optionally verify Bscscan secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_BscScan,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBscScan(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_BscScan\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// docs: https://docs.bscscan.com/api-endpoints/accounts\nfunc verifyBscScan(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.bscscan.com/api?module=account&action=balance&address=0x70F657164e5b75689b64B7fd1fA275F334f28e18&apikey=\"+key, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tbodyBytes, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tbody := string(bodyBytes)\n\n\t\tif !strings.Contains(body, \"NOTOK\") {\n\t\t\treturn true, nil\n\t\t}\n\n\t\treturn false, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bscscan/bscscan_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage bscscan\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBscscan_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BSCSCAN\")\n\tinactiveSecret := testSecrets.MustGetField(\"BSCSCAN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bscscan secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BscScan,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BscScan,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Bscscan.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Bscscan.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bscscan/bscscan_test.go",
    "content": "package bscscan\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBscScan_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.bscscan.com/v1/resource?apikey=HYZHPP4PBYXCOZAVK4FH55W4MRHYLALPU1\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"HYZHPP4PBYXCOZAVK4FH55W4MRHYLALPU1\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{bscscan}</id>\n  \t\t\t\t\t<secret>{bscscan AQAAABAAA SLQOD6LO36MN446N44L98FDJR1AS5PYPTR}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"SLQOD6LO36MN446N44L98FDJR1AS5PYPTR\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.bscscan.com/v1/resource?apikey=2xHYZHPP4PBYXCOZAVK4FH55W4MRHYLALPU1thTg303gSD%c^\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/buddyns/buddyns.go",
    "content": "package buddyns\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"buddyns\"}) + `\\b([0-9a-z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"buddyns\"}\n}\n\n// FromData will find and optionally verify Buddyns secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_BuddyNS,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBuddyns(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_BuddyNS\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"BuddyNS is a DNS hosting service. BuddyNS API keys can be used to manage DNS zones and records.\"\n}\n\n// docs: https://www.buddyns.com/support/api/v2/\nfunc verifyBuddyns(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.buddyns.com/api/v2/zone/\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Token %s\", key))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/buddyns/buddyns_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage buddyns\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBuddyns_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BUDDYNS\")\n\tinactiveSecret := testSecrets.MustGetField(\"BUDDYNS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a buddyns secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BuddyNS,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_BuddyNS,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Buddyns.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Buddyns.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/buddyns/buddyns_test.go",
    "content": "package buddyns\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBuddyNs_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbuddynsToken := \"kkmvdiolccw4v0tue4lu7l7kmnnb4ao8z25ezink\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Token \" + buddynsToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"kkmvdiolccw4v0tue4lu7l7kmnnb4ao8z25ezink\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{buddyns}</id>\n  \t\t\t\t\t<secret>{buddyns AQAAABAAA jqcayapqh1soy2zlfdbs1j4ytn0mpgmeffzsu2yt}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"jqcayapqh1soy2zlfdbs1j4ytn0mpgmeffzsu2yt\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbuddynsToken := \"diolccw4v0tue4lu7l7kmnnb4ao8z25ezink305l95D3gSD%c^\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Token \" + buddynsToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/budibase/budibase.go",
    "content": "package budibase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"budibase\"}) + `\\b([a-f0-9]{32}-[a-f0-9]{78,80})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"budibase\"}\n}\n\n// FromData will find and optionally verify Budibase secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Budibase,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyBudibase(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Budibase\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// docs: https://docs.budibase.com/docs/rest\nfunc verifyBudibase(ctx context.Context, client *http.Client, key string) (bool, error) {\n\t// URL: https://docs.budibase.com/reference/appsearch\n\t// API searches for the app with given name, since we only need to check api key, sending any appname will work.\n\tpayload := strings.NewReader(`{\"name\":\"qwerty\"}`)\n\n\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://budibase.app/api/public/v1/applications/search\", payload)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"x-budibase-api-key\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/budibase/budibase_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage budibase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBudibase_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BUDIBASE\")\n\tinactiveSecret := testSecrets.MustGetField(\"BUDIBASE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a budibase secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Budibase,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Budibase,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"unexpected HTTP response status 403\"))\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Budibase.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Budibase.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/budibase/budibase_test.go",
    "content": "package budibase\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBudiBase_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\treq.Header.Set(\"x-budibase-api-key\", \"b256def166fcdf4a429a1e83175105d5-fd36f3da1e934bf533cd0e68dbb80ed6a42e1178bd4200428d83e876e7d05e40b21e3a68888f826d\")\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"b256def166fcdf4a429a1e83175105d5-fd36f3da1e934bf533cd0e68dbb80ed6a42e1178bd4200428d83e876e7d05e40b21e3a68888f826d\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{budibase}</id>\n  \t\t\t\t\t<secret>{budibase AQAAABAAA eb72aa19dafbd0166e16299e0bea6a35-96ab88e1b2691be47aa15b343e8e2b5a3be0564b704db9f2812b6e4decde312038c2f3ba00102e}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"eb72aa19dafbd0166e16299e0bea6a35-96ab88e1b2691be47aa15b343e8e2b5a3be0564b704db9f2812b6e4decde312038c2f3ba00102e\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\treq.Header.Set(\"x-budibase-api-key\", \"diolccw4v0tue4lu7l7kmnnb4ao8z25ezink305l95D3gSD%c^\")\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bugherd/bugherd.go",
    "content": "package bugherd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"bugherd\"}) + `\\b([0-9a-z]{22})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"bugherd\"}\n}\n\n// FromData will find and optionally verify Bugherd secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Bugherd,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBugherd(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Bugherd\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// docs: https://www.bugherd.com/api_v2\nfunc verifyBugherd(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.bugherd.com/api_v2/projects.json\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.SetBasicAuth(key, \"x\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bugherd/bugherd_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage bugherd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBugherd_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BUGHERD\")\n\tinactiveSecret := testSecrets.MustGetField(\"BUGHERD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bugherd secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bugherd,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bugherd,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Bugherd.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Bugherd.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bugherd/bugherd_test.go",
    "content": "package bugherd\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBugHerd_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbugherdToken := \"fisy6bbu6il4x96bekx587\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Basic \" + buddynsToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"fisy6bbu6il4x96bekx587\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{bugherd}</id>\n  \t\t\t\t\t<secret>{bugherd AQAAABAAA mx2rxr8ztizo8kytvx1kan}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"mx2rxr8ztizo8kytvx1kan\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbugherdToken := \"fisy6bbu+6il4x()96bekx587-7l7kmnnb4ao8z25ezink305l95D3gSD%c^\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Basic \" + buddynsToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bugsnag/bugsnag.go",
    "content": "package bugsnag\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"bugsnag\"}\n}\n\n// FromData will find and optionally verify Bugsnag secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Bugsnag,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBugsnag(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Bugsnag\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Bugsnag is an error monitoring service for web and mobile applications. Bugsnag API keys can be used to report and manage errors.\"\n}\n\n// docs: https://docs.bugsnag.com/api/\nfunc verifyBugsnag(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.bugsnag.com/user/organizations?admin\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"token %s\", key))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bugsnag/bugsnag_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage bugsnag\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBugsnag_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BUGSNAG\")\n\tinactiveSecret := testSecrets.MustGetField(\"BUGSNAG_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bugsnag secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bugsnag,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bugsnag,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Bugsnag.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Bugsnag.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bugsnag/bugsnag_test.go",
    "content": "package bugsnag\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBugSnag_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbugsnagToken := \"wz9450iu-iewm-jonx-eab8-0ibxwadddm8i\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"token \" + bugsnagToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"wz9450iu-iewm-jonx-eab8-0ibxwadddm8i\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{bugsnag}</id>\n  \t\t\t\t\t<secret>{bugsnag AQAAABAAA heatep16-k3fw-dflj-ucc1-ay1lu0in3p7k}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"heatep16-k3fw-dflj-ucc1-ay1lu0in3p7k\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbugsnagToken := \"%c^wz9450iu-iewm-jonx-eab8-\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"token \" + bugsnagToken)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/buildkite/v1/buildkite.go",
    "content": "package buildkite\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\ntype APIResponse struct {\n\tScopes []string `json:\"scopes\"`\n}\n\nfunc (s Scanner) Version() int { return 1 }\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"buildkite\"}) + `\\b([a-z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"buildkite\"}\n}\n\n// FromData will find and optionally verify Buildkite secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Buildkite,\n\t\t\tRaw:          []byte(resMatch),\n\t\t\tExtraData:    make(map[string]string),\n\t\t}\n\n\t\tif verify {\n\t\t\textraData, isVerified, verificationErr := VerifyBuildKite(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\ts1.ExtraData = extraData\n\n\t\t\tif isVerified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\"key\": resMatch,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Buildkite\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc VerifyBuildKite(ctx context.Context, client *http.Client, secret string) (map[string]string, bool, error) {\n\t// create a request\n\t// api doc: https://buildkite.com/docs/apis/rest-api/access-token#get-the-current-token\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.buildkite.com/v2/access-token\", nil)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\n\t// add authorization header\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", secret))\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tvar response APIResponse\n\n\t\tif err := json.NewDecoder(res.Body).Decode(&response); err != nil {\n\t\t\treturn nil, false, err\n\t\t}\n\n\t\textraData := make(map[string]string)\n\n\t\textraData[\"scopes\"] = strings.Join(response.Scopes, \", \")\n\t\treturn extraData, true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn nil, false, nil\n\tdefault:\n\t\treturn nil, false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/buildkite/v1/buildkite_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage buildkite\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBuildkite_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BUILDKITE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"BUILDKITE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a buildkite secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Buildkite,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Buildkite,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Buildkite.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].ExtraData = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Buildkite.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/buildkite/v1/buildkite_test.go",
    "content": "package buildkite\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBuildKite_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbuildkite_secret := \"kimu4axq3jxxdj8un0kpo3ua2ucr05zmhh4de0r6\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Bearer \" + buildkite_secret)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"kimu4axq3jxxdj8un0kpo3ua2ucr05zmhh4de0r6\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{buildkite}</id>\n  \t\t\t\t\t<secret>{buildkite AQAAABAAA ssoj8umx032r2f6sintvtw582bwvxymxgifu6gmk}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"ssoj8umx032r2f6sintvtw582bwvxymxgifu6gmk\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbuildkite_secret := \"%c^wz9450iu-buildkite_secret-jonx-eab8\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Bearer \" + buildkite_secret)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/buildkite/v2/buildkite.go",
    "content": "package buildkitev2\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\tv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/buildkite/v1\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\nfunc (s Scanner) Version() int { return 2 }\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(bkua_[a-z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"bkua_\"}\n}\n\n// FromData will find and optionally verify Buildkite secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Buildkite,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\textraData, isVerified, verificationErr := v1.VerifyBuildKite(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\ts1.ExtraData = extraData\n\n\t\t\tif isVerified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\"key\": resMatch,\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Buildkite\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/buildkite/v2/buildkite_test.go",
    "content": "package buildkitev2\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBuildKiteV2_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbuildkite_secret := \"bkua_hqlh73m51jtho0jh12wcf2758c8fcdbv05z023ly\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Bearer \" + buildkite_secret)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"bkua_hqlh73m51jtho0jh12wcf2758c8fcdbv05z023ly\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA bkua_j8cqyoaodi7z1fzo8u5albtyw4x9gh83yx1m6ien}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"bkua_j8cqyoaodi7z1fzo8u5albtyw4x9gh83yx1m6ien\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbuildkite_secret := \"bkua_hqlh73m51jtho0jh12wcf27v05z023ly-jonx-eab8\"\n\t\t\t\t\treq.Header.Set(\"Authorization\", \"Bearer \" + buildkite_secret)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/buildkite/v2/buildkitev2_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage buildkitev2\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBuildkite_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BUILDKITEV2_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"BUILDKITEV2_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a buildkite secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Buildkite,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Buildkite,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Buildkite.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].ExtraData = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Buildkite.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bulbul/bulbul.go",
    "content": "package bulbul\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"bulbul\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"bulbul\"}\n}\n\n// FromData will find and optionally verify Bulbul secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Bulbul,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyBulbul(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Bulbul\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Bulbul is an API service. Bulbul API keys can be used to access and modify data within the service.\"\n}\n\n// docs: https://docs.jungleworks.com/bulbul/bulbul-api-details\nfunc verifyBulbul(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://prod-api.bulbul.io/view_all_users?api_key=%s\", key), nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tbodyBytes, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tbodyString := string(bodyBytes)\n\n\t\tif strings.Contains(bodyString, `\"message\":\"Successful\",`) {\n\t\t\treturn true, nil\n\t\t} else {\n\t\t\treturn false, nil\n\t\t}\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bulbul/bulbul_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage bulbul\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBulbul_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BULBUL\")\n\tinactiveSecret := testSecrets.MustGetField(\"BULBUL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bulbul secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bulbul,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bulbul,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Bulbul.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Bulbul.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bulbul/bulbul_test.go",
    "content": "package bulbul\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBulBul_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.bulbul.com/v1/users?key=3kx19qpx748ldb75lsjicbs6ipit6ssm\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"3kx19qpx748ldb75lsjicbs6ipit6ssm\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{bulbul}</id>\n  \t\t\t\t\t<secret>{bulbul AQAAABAAA r9gk8o0ctd4xq4r66d3reahu9ku4i4ht}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"r9gk8o0ctd4xq4r66d3reahu9ku4i4ht\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.bulbul.com/v1/users?key=%c^wz9450iu-3kx19qcbs6ipit6ssm-jonx-eab8\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bulksms/bulksms.go",
    "content": "package bulksms\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"bulksms\"}) + `\\b([a-zA-Z0-9!@#$%^&*()]{29})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"bulksms\"}) + `\\b([A-F0-9-]{37})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"bulksms\"}\n}\n\n// FromData will find and optionally verify Bulksms secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueIds = make(map[string]struct{})\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, match := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueIds[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[match[1]] = struct{}{}\n\t}\n\n\tfor id := range uniqueIds {\n\t\tfor key := range uniqueKeys {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Bulksms,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tRawV2:        []byte(key + id),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, verificationErr := verifyBulksms(ctx, client, id, key)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Bulksms\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// docs: https://www.bulksms.com/developer/json/v1/\nfunc verifyBulksms(ctx context.Context, client *http.Client, id, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.bulksms.com/v1/messages\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.SetBasicAuth(id, key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bulksms/bulksms_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage bulksms\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestBulksms_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BULKSMS\")\n\tinactiveSecret := testSecrets.MustGetField(\"BULKSMS_INACTIVE\")\n\ttoken := testSecrets.MustGetField(\"BULKSMS_TOKEN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a bulksms secret %s within bulksms %s\", secret, token)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bulksms,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Bulksms,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Bulksms.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Bulksms.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/bulksms/bulksms_test.go",
    "content": "package bulksms\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestBulkSMS_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbulksmsKey := \"(QGxPqRyzvt%xEKcV&#ePJGn)k0d9a\"\n\t\t\t\t\tbulksmsID := \"381A26C47380B85F2DB572314-ACBDC267B-8\"\n\n\t\t\t\t\treq.SetBasicAuth(bulksmsKey, bulksmsID)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"QGxPqRyzvt%xEKcV&#ePJGn)k0d9a381A26C47380B85F2DB572314-ACBDC267B-8\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{bulksms fXHnHK&cN8H!1r5ersTDIe6ZJ8j51}</id>\n  \t\t\t\t\t<secret>{bulksms AQAAABAAA 04A1ED4D90D3E17-3968-66B6A571D--2134E}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"fXHnHK&cN8H!1r5ersTDIe6ZJ8j5104A1ED4D90D3E17-3968-66B6A571D--2134E\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tbulksmsKey := \"(QGxPqRyzvt%xEKcV&#ePJGn)k0d9a\"\n\t\t\t\t\tbulksmsID := \"%c^wz9450iu-iewm-jonx-eab8-/F2DB572314-ACBDC267B-8\"\n\n\t\t\t\t\treq.SetBasicAuth(bulksmsKey, bulksmsID)\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/buttercms/buttercms.go",
    "content": "package buttercms\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"buttercms\"}) + `\\b([a-z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"buttercms\"}\n}\n\n// FromData will find and optionally verify ButterCMS secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ButterCMS,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyButterCMS(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ButterCMS\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// docs: https://buttercms.com/docs/api/#introduction\nfunc verifyButterCMS(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.buttercms.com/v2/posts/?auth_token=\"+key, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/buttercms/buttercms_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage buttercms\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestButterCMS_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"BUTTERCMS_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"BUTTERCMS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a buttercms secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ButterCMS,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ButterCMS,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ButterCMS.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ButterCMS.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/buttercms/buttercms_test.go",
    "content": "package buttercms\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestButterCMS_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.buttercms.com/v2/posts?auth_token=l7psk7wkedkpiyp4jrx5fjdnno8c89243of6yde8\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: []string{\"l7psk7wkedkpiyp4jrx5fjdnno8c89243of6yde8\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>api-key</id>\n  \t\t\t\t\t<secret>{buttercms AQAAABAAA xjndr06i2jiaoqs2plf8x0cgfz976blm1dctjqv9}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"xjndr06i2jiaoqs2plf8x0cgfz976blm1dctjqv9\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\tfunc main() {\n\t\t\t\t\turl := \"https://api.buttercms.com/v2/posts?auth_token=l7psk7wkedkpiyp4j(rx5fjdnn)\"\n\n\t\t\t\t\t// Create a new request with the secret as a header\n\t\t\t\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// Perform the request\n\t\t\t\t\tclient := &http.Client{}\n\t\t\t\t\tresp, _ := client.Do(req)\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t}\n\t\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/caflou/caflou.go",
    "content": "package caflou\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClientTimeOut(time.Second * 10)\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(eyJhbGciOiJIUzI1NiJ9[a-zA-Z0-9._-]{135})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"caflou\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Caflou\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// FromData will find and optionally verify Caflou secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Caflou,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyCaflou(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyCaflou(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://app.caflou.com/api/v1/accounts\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/caflou/caflou_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage caflou\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCaflou_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CAFLOU\")\n\tinactiveSecret := testSecrets.MustGetField(\"CAFLOU_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a caflou secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Caflou,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Caflou,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Caflou.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Caflou.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/caflou/caflou_test.go",
    "content": "package caflou\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestCaflou_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t\t# Configuration File: config.yaml\n\t\t\t\t\tdatabase:\n\t\t\t\t\t\thost: $DB_HOST\n\t\t\t\t\t\tport: $DB_PORT\n\t\t\t\t\t\tusername: $DB_USERNAME\n\t\t\t\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\t\t\t\tapi:\n\t\t\t\t\t\tbase_url: \"https://api.example.com/instances\"\n\t\t\t\t\t\tapi_key: $API_KEY\n\t\t\t\t\t\tcaflou_auth_token: \"Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX9lkIjo1OTQ5MCwianCpIjoiOTQwZjBlODkxNPhhZjM4OTQ1OGQwMDIxIiziZXhwIjoxGzU1MTk4MDAwfQ.EMNGCPX7aNIvriX360oLFAgMwHeXxKD7N4kdcJtPqTI\"\n\n\t\t\t\t\t# Notes:\n\t\t\t\t\t# - Remember to rotate the secret every 90 days.\n\t\t\t\t\t# - The above credentials should only be used in a secure environment.\n\t\t\t\t`,\n\t\t\twant: []string{\"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX9lkIjo1OTQ5MCwianCpIjoiOTQwZjBlODkxNPhhZjM4OTQ1OGQwMDIxIiziZXhwIjoxGzU1MTk4MDAwfQ.EMNGCPX7aNIvriX360oLFAgMwHeXxKD7N4kdcJtPqTI\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/calendarific/calendarific.go",
    "content": "package calendarific\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"calendarific\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"calendarific\"}\n}\n\n// FromData will find and optionally verify Calendarific secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Calendarific,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://calendarific.com/api/v2/holidays?&api_key=\"+resMatch+\"&country=US&year=2019\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Calendarific\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Calendarific provides a public API for obtaining holiday information. The API key can be used to access holiday data for various countries and years.\"\n}\n"
  },
  {
    "path": "pkg/detectors/calendarific/calendarific_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage calendarific\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCalendarific_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CALENDARIFIC_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"CALENDARIFIC_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a calendarific secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Calendarific,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Calendarific,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Calendarific.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Calendarific.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/calendarific/calendarific_test.go",
    "content": "package calendarific\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tcalendarific_api_key: \"M9qT0uymrS2FgJ78p9CpLIlFOBLUtmao\"\n\t\t\tbase_url: \"https://api.calendarific.com/v1/holidays?api_key=$calendarific_api_key\"\n\t\t\tauth_token: \"\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"M9qT0uymrS2FgJ78p9CpLIlFOBLUtmao\"\n)\n\nfunc TestCalendarific_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/calendlyapikey/calendlyapikey.go",
    "content": "package calendlyapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"calendly\"}\n}\n\n// FromData will find and optionally verify CalendlyApiKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_CalendlyApiKey,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.calendly.com/users/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CalendlyApiKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/calendlyapikey/calendlyapikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage calendlyapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCalendlyApiKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CALENDLYAPIKEY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"CALENDLYAPIKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a calendlyapikey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CalendlyApiKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CalendlyApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CalendlyApiKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CalendlyApiKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/calendlyapikey/calendlyapikey_test.go",
    "content": "package calendlyapikey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tapi_key: $API_KEY\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcalendly_auth_token: \"Bearer eyJuL_8UF5AiQVfO2xlr_HaoSluHz9u-Q-s1qWDWvycQhR11J9wZTmYfFpKnawuIbKjA4i340DSpYI3d3E-oEZZdcHW4cLd_OASWu-H.eyJuOUikPwjw1RKXYXfcjjeqQwdWzA4uooei_ADIUX3of4UzwTjSaaEzLWMGopW4n9Fma0nINBD1qUp_OtbhuH6dmHyv94IeX-hUYla.A0rTdrx3sJ\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"eyJuL_8UF5AiQVfO2xlr_HaoSluHz9u-Q-s1qWDWvycQhR11J9wZTmYfFpKnawuIbKjA4i340DSpYI3d3E-oEZZdcHW4cLd_OASWu-H.eyJuOUikPwjw1RKXYXfcjjeqQwdWzA4uooei_ADIUX3of4UzwTjSaaEzLWMGopW4n9Fma0nINBD1qUp_OtbhuH6dmHyv94IeX-hUYla.A0rTdrx3sJ\"\n)\n\nfunc TestCalendlyAPIKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/calorieninja/calorieninja.go",
    "content": "package calorieninja\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"calorieninja\"}) + `\\b([0-9A-Za-z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"calorieninja\"}\n}\n\n// FromData will find and optionally verify Calorieninja secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_CalorieNinja,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.calorieninjas.com/v1/nutrition?query\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"X-Api-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CalorieNinja\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"CalorieNinja is a service that provides nutritional information for various foods. CalorieNinja API keys can be used to access this nutritional data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/calorieninja/calorieninja_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage calorieninja\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCalorieninja_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CALORIENINJA\")\n\tinactiveSecret := testSecrets.MustGetField(\"CALORIENINJA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a calorieninja secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CalorieNinja,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CalorieNinja,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Calorieninja.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Calorieninja.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/calorieninja/calorieninja_test.go",
    "content": "package calorieninja\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tcalorieninja_api_key: \"ix1aaifujilTcGEjB67e1EBBRXcr7r9cdChAR5hb\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tauth_token: \"\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"ix1aaifujilTcGEjB67e1EBBRXcr7r9cdChAR5hb\"\n)\n\nfunc TestCalorieNinja_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/campayn/campayn.go",
    "content": "package campayn\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"campayn\"}) + `\\b([a-z0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"campayn\"}\n}\n\n// FromData will find and optionally verify Campayn secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Campayn,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://campayn.com/api/v1/lists\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", \"TRUEREST apikey=\"+resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Campayn\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/campayn/campayn_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage campayn\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCampayn_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CAMPAYN_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"CAMPAYN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a campayn secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Campayn,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Campayn,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Campayn.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Campayn.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/campayn/campayn_test.go",
    "content": "package campayn\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tcampayn_api_key: \"z6q8z47eu46wri18ygu6pc68vsizprt83jrlu5ustliyhktzoxbzhf1ycdaka978\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tauth_token: \"\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"z6q8z47eu46wri18ygu6pc68vsizprt83jrlu5ustliyhktzoxbzhf1ycdaka978\"\n)\n\nfunc TestCampayn_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cannyio/cannyio.go",
    "content": "package cannyio\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"canny\"}\n}\n\n// FromData will find and optionally verify CannyIo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_CannyIo,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(\"apiKey=\" + resMatch)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://canny.io/api/v1/boards/list\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CannyIo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/cannyio/cannyio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage cannyio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCannyIo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CANNYIO_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"CANNYIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cannyio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CannyIo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CannyIo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CannyIo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CannyIo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cannyio/cannyio_test.go",
    "content": "package cannyio\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tcannyio_api_key: \"faiiahli-goke-db0r-oxli-s20dgab9a0iv\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tauth_token: \"\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"faiiahli-goke-db0r-oxli-s20dgab9a0iv\"\n)\n\nfunc TestCannyio_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/capsulecrm/capsulecrm.go",
    "content": "package capsulecrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"capsulecrm\"}) + `\\b([a-zA-Z0-9-._+=]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"capsulecrm\"}\n}\n\n// FromData will find and optionally verify CapsuleCRM secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_CapsuleCRM,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.capsulecrm.com/api/v2/users\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CapsuleCRM\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"CapsuleCRM is a customer relationship management (CRM) platform. CapsuleCRM API keys can be used to access and manage customer data and interactions.\"\n}\n"
  },
  {
    "path": "pkg/detectors/capsulecrm/capsulecrm_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage capsulecrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCapsuleCRM_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CAPSULECRM\")\n\tinactiveSecret := testSecrets.MustGetField(\"CAPSULECRM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a capsulecrm secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CapsuleCRM,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CapsuleCRM,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CapsuleCRM.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CapsuleCRM.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/capsulecrm/capsulecrm_test.go",
    "content": "package capsulecrm\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tapi_key: \"\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcapsulecrm_auth_token: \"Bearer ULeIqU-4ss+YImZYsyjPLSsm.9H.SZJ1v.KxT1D-zbaW6sg5b0R5g=koBH4X62hC\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"ULeIqU-4ss+YImZYsyjPLSsm.9H.SZJ1v.KxT1D-zbaW6sg5b0R5g=koBH4X62hC\"\n)\n\nfunc TestCapsulecrm_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/captaindata/v1/captaindata.go",
    "content": "package captaindata\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\nfunc (s Scanner) Version() int { return 1 }\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"captaindata\"}) + `\\b([0-9a-f]{64})\\b`)\n\tprojIdPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"captaindata\"}\n}\n\n// FromData will find and optionally verify CaptainData secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tprojIdMatches := projIdPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, projIdMatch := range projIdMatches {\n\t\tresProjIdMatch := strings.TrimSpace(projIdMatch[1])\n\n\t\tfor _, match := range matches {\n\t\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_CaptainData,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resProjIdMatch + resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.captaindata.co/v2/\"+resProjIdMatch, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"x-api-key\", resMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CaptainData\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"CaptainData is a service for automating data extraction and processing. The API keys can be used to access and control these automation processes.\"\n}\n"
  },
  {
    "path": "pkg/detectors/captaindata/v1/captaindata_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage captaindata\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCaptainData_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tprojId := testSecrets.MustGetField(\"CAPTAINDATA_PROJID\")\n\tsecret := testSecrets.MustGetField(\"CAPTAINDATA\")\n\tinactiveSecret := testSecrets.MustGetField(\"CAPTAINDATA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a captaindata project %s with captaindata secret %s within\", projId, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CaptainData,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CaptainData,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CaptainData.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CaptainData.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/captaindata/v1/captaindata_test.go",
    "content": "package captaindata\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestCaptainData_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: \"captaindata_project = '12345678-1234-1234-1234-123456789012' captaindata_api_key = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'\",\n\t\t\twant:  []string{\"12345678-1234-1234-1234-1234567890121234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\"},\n\t\t},\n\t\t{\n\t\t\tname: \"finds all matches\",\n\t\t\tinput: `captaindata_project1 = '12345678-1234-1234-1234-123456789012' captaindata_api_key1 = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'\ncaptaindata_project2 = '87654321-4321-4321-4321-210987654321' captaindata_api_key2 = 'fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321'`,\n\t\t\twant: []string{\n\t\t\t\t\"12345678-1234-1234-1234-1234567890121234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\",\n\t\t\t\t\"12345678-1234-1234-1234-123456789012fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321\",\n\t\t\t\t\"87654321-4321-4321-4321-210987654321fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321\",\n\t\t\t\t\"87654321-4321-4321-4321-2109876543211234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: \"captaindata_project = '123456' captaindata_api_key = '1234567890'\",\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/captaindata/v2/captaindata.go",
    "content": "package captaindata\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nfunc (Scanner) Version() int { return 2 }\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"captaindata\"}) + `\\b([0-9a-f]{64})\\b`)\n\tprojIdPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"captaindata\"}\n}\n\n// FromData will find and optionally verify CaptainData secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tuniqueProjIdMatches := make(map[string]struct{})\n\tfor _, match := range projIdPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueProjIdMatches[match[1]] = struct{}{}\n\t}\n\n\tfor projId := range uniqueProjIdMatches {\n\t\tfor apiKey := range uniqueMatches {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_CaptainData,\n\t\t\t\tRaw:          []byte(apiKey),\n\t\t\t\tRawV2:        []byte(projId + apiKey),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, projId, apiKey)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.ExtraData = extraData\n\t\t\t\ts1.SetVerificationError(verificationErr, apiKey)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, projId, apiKey string) (bool, map[string]string, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.captaindata.co/v3/workspace\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\treq.Header.Set(\"Authorization\", \"x-api-key \"+apiKey)\n\treq.Header.Set(\"x-project-id\", projId)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CaptainData\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"CaptainData is a service for automating data extraction and processing. The API keys can be used to access and control these automation processes.\"\n}\n"
  },
  {
    "path": "pkg/detectors/captaindata/v2/captaindata_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage captaindata\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCaptainData_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tprojId := testSecrets.MustGetField(\"CAPTAINDATA_PROJID\")\n\tsecret := testSecrets.MustGetField(\"CAPTAINDATA\")\n\tinactiveSecret := testSecrets.MustGetField(\"CAPTAINDATA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a captaindata project %s with captaindata secret %s within\", projId, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CaptainData,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a captaindata project %s with captaindata secret %s within but not valid\", projId, inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CaptainData,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CaptainData.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"ExtraData\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"CaptainData.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/captaindata/v2/captaindata_test.go",
    "content": "package captaindata\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestCaptainData_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: \"captaindata_project = '12345678-1234-1234-1234-123456789012' captaindata_api_key = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'\",\n\t\t\twant:  []string{\"12345678-1234-1234-1234-1234567890121234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\"},\n\t\t},\n\t\t{\n\t\t\tname: \"finds all matches\",\n\t\t\tinput: `captaindata_project1 = '12345678-1234-1234-1234-123456789012' captaindata_api_key1 = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'\ncaptaindata_project2 = '87654321-4321-4321-4321-210987654321' captaindata_api_key2 = 'fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321'`,\n\t\t\twant: []string{\n\t\t\t\t\"12345678-1234-1234-1234-1234567890121234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\",\n\t\t\t\t\"12345678-1234-1234-1234-123456789012fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321\",\n\t\t\t\t\"87654321-4321-4321-4321-210987654321fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321\",\n\t\t\t\t\"87654321-4321-4321-4321-2109876543211234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: \"captaindata_project = '123456' captaindata_api_key = '1234567890'\",\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/carboninterface/carboninterface.go",
    "content": "package carboninterface\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"carboninterface\"}) + `\\b([a-zA-Z0-9]{21})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"carboninterface\"}\n}\n\n// FromData will find and optionally verify CarbonInterface secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_CarbonInterface,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\"type\":\"flight\",\"passengers\":2,\"legs\":[{\"departure_airport\":\"sfo\",\"destination_airport\":\"yyz\"},{\"departure_airport\":\"yyz\",\"destination_airport\":\"sfo\"}]}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://www.carboninterface.com/api/v1/estimates\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\treq.Header.Add(\"Content-type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CarbonInterface\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"CarbonInterface provides an API for estimating carbon emissions for various activities. The API keys can be used to access and utilize this service.\"\n}\n"
  },
  {
    "path": "pkg/detectors/carboninterface/carboninterface_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage carboninterface\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCarbonInterface_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CARBONINTERFACE\")\n\tinactiveSecret := testSecrets.MustGetField(\"CARBONINTERFACE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a carboninterface secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CarbonInterface,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CarbonInterface,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CarbonInterface.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CarbonInterface.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/carboninterface/carboninterface_test.go",
    "content": "package carboninterface\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tapi_key: \"\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcarboninterface_auth_token: \"Bearer PkN3gWaSHSIjz188TjD4h\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"PkN3gWaSHSIjz188TjD4h\"\n)\n\nfunc TestCarbonInterface_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cashboard/cashboard.go",
    "content": "package cashboard\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"cashboard\"}) + `\\b([0-9A-Z]{3}-[0-9A-Z]{3}-[0-9A-Z]{3}-[0-9A-Z]{3})\\b`)\n\tuserPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"cashboard\"}) + `\\b([0-9a-z]{1,})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"cashboard\"}\n}\n\n// FromData will find and optionally verify Cashboard secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tuserMatches := userPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, userMatch := range userMatches {\n\t\t\tresUser := strings.TrimSpace(userMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Cashboard,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resUser),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tdata := fmt.Sprintf(\"%s:%s\", resUser, resMatch)\n\t\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.cashboardapp.com/account.xml\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Cashboard\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Cashboard is a financial management service. Cashboard credentials can be used to access and manage financial data and accounts.\"\n}\n"
  },
  {
    "path": "pkg/detectors/cashboard/cashboard_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage cashboard\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCashboard_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CASHBOARD\")\n\tuser := testSecrets.MustGetField(\"SCANNER_USERNAME\")\n\tinactiveSecret := testSecrets.MustGetField(\"CASHBOARD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cashboard secret %s within %s\", secret, user)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Cashboard,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Cashboard,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Cashboard.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Cashboard.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cashboard/cashboard_test.go",
    "content": "package cashboard\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tcashboard_key: \"F1A-NEI-HY4-PZK\"\n\t\t\tcashboard_user: \"ts7z\"\n\t\t\tauth_type: Basic\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tauth_token: \"\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"F1A-NEI-HY4-PZKts7z\"\n)\n\nfunc TestCashBoard_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/caspio/caspio.go",
    "content": "package caspio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"caspio\"}) + `\\b([a-z0-9]{50})\\b`)\n\tidPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"caspio\"}) + `\\b([a-z0-9]{50})\\b`)\n\tdomainPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"caspio\"}) + `\\b([a-z0-9]{8})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"caspio\"}\n}\n\n// FromData will find and optionally verify Caspio secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\tdomainMatches := domainPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\tfor _, domainMatch := range domainMatches {\n\n\t\t\t\tresDomainMatch := strings.TrimSpace(domainMatch[1])\n\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Caspio,\n\t\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\t\tRawV2:        []byte(resMatch + resIdMatch + resDomainMatch),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tpayload := strings.NewReader(fmt.Sprintf(`grant_type=client_credentials&client_id=%s&client_secret=%s`, resIdMatch, resMatch))\n\t\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", fmt.Sprintf(\"https://%s.caspio.com/oauth/token\", resDomainMatch), payload)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\treq.Header.Add(\"Content-Type\", \"text/plain\")\n\t\t\t\t\tres, err := client.Do(req)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Caspio\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Caspio is a cloud platform for building custom database applications. Caspio credentials can be used to access and manage these applications.\"\n}\n"
  },
  {
    "path": "pkg/detectors/caspio/caspio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage caspio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCaspio_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CASPIO\")\n\tid := testSecrets.MustGetField(\"CASPIO_ID\")\n\tsubdomain := testSecrets.MustGetField(\"CASPIO_SUBDOMAIN\")\n\tinactiveSecret := testSecrets.MustGetField(\"CASPIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a caspio secret %s within caspio %s and caspio subdomain %s\", secret, id, subdomain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Caspio,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Caspio,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Caspio.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Caspio.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/caspio/caspio_test.go",
    "content": "package caspio\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tcaspio_id: \"qye6c979b55w55hz7nqrdgax3pufdsigwvhofsrxalgk01asty\"\n\t\t\tcaspio_secret: \"x5vzi1o2fmnjywilv3obwx5n041f56v54lgral6znyccet8ibe\"\n\t\t\tcaspio_domain: xlo0xo2s\n\t\t\tauth_type: Client Credentials\n\t\t\tbase_url: \"https://$caspio_domain.caspio.com/v1/user\"\n\t\t\tauth_token: \"\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecrets = []string{\n\t\t\"qye6c979b55w55hz7nqrdgax3pufdsigwvhofsrxalgk01astyqye6c979b55w55hz7nqrdgax3pufdsigwvhofsrxalgk01astyxlo0xo2s\",\n\t\t\"qye6c979b55w55hz7nqrdgax3pufdsigwvhofsrxalgk01astyx5vzi1o2fmnjywilv3obwx5n041f56v54lgral6znyccet8ibexlo0xo2s\",\n\t\t\"x5vzi1o2fmnjywilv3obwx5n041f56v54lgral6znyccet8ibeqye6c979b55w55hz7nqrdgax3pufdsigwvhofsrxalgk01astyxlo0xo2s\",\n\t\t\"x5vzi1o2fmnjywilv3obwx5n041f56v54lgral6znyccet8ibex5vzi1o2fmnjywilv3obwx5n041f56v54lgral6znyccet8ibexlo0xo2s\",\n\t}\n)\n\nfunc TestCaspio_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  secrets,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/censys/censys.go",
    "content": "package censys\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"censys\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"censys\"}) + `\\b([a-z0-9-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"censys\"}\n}\n\n// FromData will find and optionally verify Censys secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\ttokenPatMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\t\t\tuserPatMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Censys,\n\t\t\t\tRaw:          []byte(tokenPatMatch),\n\t\t\t\tRawV2:        []byte(tokenPatMatch + userPatMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://search.censys.io/api/v1/account\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.SetBasicAuth(userPatMatch, tokenPatMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Censys\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/censys/censys_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage censys\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCensys_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CENSYS\")\n\tid := testSecrets.MustGetField(\"CENSYS_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"CENSYS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a censys secret %s within censys %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Censys,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Censys,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Censys.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw v2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Censys.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/censys/censys_test.go",
    "content": "package censys\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tcensys_key: \"roLtT01S6znCRhgoSwNNKY8O7AELn8e4\"\n\t\t\tcensys_user: \"p4cuaz9fuonwmbfkc4di5uqsizp4yyttpu-q\"\n\t\t\tauth_type: Basic\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tauth_token: \"\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"roLtT01S6znCRhgoSwNNKY8O7AELn8e4p4cuaz9fuonwmbfkc4di5uqsizp4yyttpu-q\"\n)\n\nfunc TestCensys_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/centralstationcrm/centralstationcrm.go",
    "content": "package centralstationcrm\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"centralstation\"}) + `\\b([a-z0-9]{30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"centralstationcrm\"}\n}\n\n// FromData will find and optionally verify CentralStationCRM secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_CentralStationCRM,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.centralstationcrm.net/api/users.json\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-apikey\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CentralStationCRM\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"CentralStationCRM is a customer relationship management service. The API keys can be used to access and manage customer data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/centralstationcrm/centralstationcrm_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage centralstationcrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCentralStationCRM_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CENTRALSTATIONCRM\")\n\tinactiveSecret := testSecrets.MustGetField(\"CENTRALSTATIONCRM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a centralstationcrm secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CentralStationCRM,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CentralStationCRM,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CentralStationCRM.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CentralStationCRM.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/centralstationcrm/centralstationcrm_test.go",
    "content": "package centralstationcrm\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tcentralstationcrm_api_key: \"gyeyy7soy4lxx7yenw56iba4szfu1f\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tauth_token: \"\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"gyeyy7soy4lxx7yenw56iba4szfu1f\"\n)\n\nfunc TestCentralStationCRM_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cexio/cexio.go",
    "content": "package cexio\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"cexio\", \"cex.io\"}) + `\\b([0-9A-Za-z]{24,27})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"cexio\", \"cex.io\"}) + `\\b([0-9A-Za-z]{24,27})\\b`)\n\tuserIdPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"cexio\", \"cex.io\"}) + `\\b([a-z]{2}[0-9]{9})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"cexio\", \"cex.io\"}\n}\n\n// FromData will find and optionally verify CexIO secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\tuserIdMatches := userIdPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, userIdMatch := range userIdMatches {\n\t\tresUserIdMatch := strings.TrimSpace(userIdMatch[1])\n\n\t\tfor _, keyMatch := range keyMatches {\n\t\t\tresKeyMatch := strings.TrimSpace(keyMatch[1])\n\n\t\t\tfor _, secretMatch := range secretMatches {\n\t\t\t\tresSecretMatch := strings.TrimSpace(secretMatch[1])\n\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CexIO,\n\t\t\t\t\tRaw:          []byte(resKeyMatch),\n\t\t\t\t\tRawV2:        []byte(resUserIdMatch + resSecretMatch),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\n\t\t\t\t\ttimestamp := strconv.FormatInt(time.Now().Unix()*1000, 10)\n\n\t\t\t\t\tsignature := getCexIOPassphrase(resSecretMatch, resKeyMatch, timestamp, resUserIdMatch)\n\n\t\t\t\t\tpayload := url.Values{}\n\t\t\t\t\tpayload.Add(\"key\", resKeyMatch)\n\t\t\t\t\tpayload.Add(\"signature\", signature)\n\t\t\t\t\tpayload.Add(\"nonce\", timestamp)\n\n\t\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://cex.io/api/balance/\", strings.NewReader(payload.Encode()))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\t\t\tres, err := client.Do(req)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tdefer res.Body.Close()\n\n\t\t\t\t\t\tbody, err := io.ReadAll(res.Body)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbodyString := string(body)\n\t\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `timestamp`)\n\n\t\t\t\t\t\tvar responseObject Response\n\t\t\t\t\t\tif err := json.Unmarshal(body, &responseObject); err != nil {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\ntype Response struct {\n\tError string `json:\"error\"`\n}\n\nfunc getCexIOPassphrase(apiSecret string, apiKey string, nonce string, userId string) string {\n\n\tmsg := nonce + userId + apiKey\n\tmac := hmac.New(sha256.New, []byte(apiSecret))\n\tmac.Write([]byte(msg))\n\tmacsum := mac.Sum(nil)\n\treturn strings.ToUpper(hex.EncodeToString(macsum))\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CexIO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"CexIO is a cryptocurrency exchange platform. CexIO API keys can be used to access and manage cryptocurrency accounts and transactions.\"\n}\n"
  },
  {
    "path": "pkg/detectors/cexio/cexio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage cexio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCexIO_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tuserId := testSecrets.MustGetField(\"CEXIO_USERID\")\n\tkey := testSecrets.MustGetField(\"CEXIO_KEY\")\n\tinactiveKey := testSecrets.MustGetField(\"CEXIO_KEY_INACTIVE\")\n\tsecret := testSecrets.MustGetField(\"CEXIO\")\n\tinactiveSecret := testSecrets.MustGetField(\"CEXIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cexio userId %s with cexio key %s and cexio secret %s within\", userId, key, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CexIO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CexIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CexIO.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CexIO.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cexio/cexio_test.go",
    "content": "package cexio\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tcexio_key: \"bVI24QF2B8omVr9ddfxSHtkb18D\"\n\t\t\tcexio_secret: \"2m2pEr2OLi48y2NCpSbPVwJqb\"\n\t\t\tcex.io_userID: \"zd930167221\"\n\t\t\tauth_type: Signature\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tauth_token: \"\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecrets = []string{\n\t\t\"zd9301672212m2pEr2OLi48y2NCpSbPVwJqb\",\n\t\t\"zd9301672212m2pEr2OLi48y2NCpSbPVwJqb\",\n\t\t\"zd930167221bVI24QF2B8omVr9ddfxSHtkb18D\",\n\t\t\"zd930167221bVI24QF2B8omVr9ddfxSHtkb18D\",\n\t}\n)\n\nfunc TestCexio_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  secrets,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/chartmogul/chartmogul.go",
    "content": "package chartmogul\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"chartmogul\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"chartmogul\"}\n}\n\n// FromData will find and optionally verify Chartmogul secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Chartmogul,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tdata := fmt.Sprintf(\"%s:\", resMatch)\n\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.chartmogul.com/v1/ping\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Chartmogul\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/chartmogul/chartmogul_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage chartmogul\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestChartmogul_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CHARTMOGUL\")\n\tinactiveSecret := testSecrets.MustGetField(\"CHARTMOGUL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a chartmogul secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Chartmogul,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Chartmogul,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Chartmogul.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Chartmogul.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/chartmogul/chartmogul_test.go",
    "content": "package chartmogul\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tchartmogul_key: \"e8hwspf91879g0u267yq1bkoxquvwndk\"\n\t\t\tauth_type: Basic\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tauth_token: \"\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"e8hwspf91879g0u267yq1bkoxquvwndk\"\n)\n\nfunc TestChartMogul_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/chatbot/chatbot.go",
    "content": "package chatbot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"chatbot\"}) + `\\b([a-zA-Z0-9_]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"chatbot\"}\n}\n\n// FromData will find and optionally verify Chatbot secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Chatbot,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.chatbot.com/stories\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Chatbot\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Chatbot API keys are used to interact with the Chatbot service, allowing access to create, modify, and retrieve chatbot stories and other resources.\"\n}\n"
  },
  {
    "path": "pkg/detectors/chatbot/chatbot_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage chatbot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestChatbot_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CHATBOT_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"CHATBOT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a chatbot secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Chatbot,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Chatbot,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Chatbot.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Chatbot.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/chatbot/chatbot_test.go",
    "content": "package chatbot\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: Bearer\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tchatbot_auth_token: \"Bearer 5RzDGrpFKkrA_90yM_BFmyxKKAQkgu0B\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"5RzDGrpFKkrA_90yM_BFmyxKKAQkgu0B\"\n)\n\nfunc TestChatBot_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/chatfule/chatfule.go",
    "content": "package chatfule\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"chatfuel\"}) + `\\b([a-zA-Z0-9]{128})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"chatfuel\"}\n}\n\n// FromData will find and optionally verify Chatfule secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Chatfule,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://dashboard.chatfuel.com/api/bots\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Chatfule\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/chatfule/chatfule_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage chatfule\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestChatfule_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CHATFULE\")\n\tinactiveSecret := testSecrets.MustGetField(\"CHATFULE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a chatfuel secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Chatfule,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Chatfule,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Chatfuel.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Chatfuel.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/chatfule/chatfule_test.go",
    "content": "package chatfule\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: Bearer\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tchatfuel_auth_token: \"Bearer 22ZEyoBNMXpT6rHmbuBlIJ8n19vo3tHzNTQDUku00WhHuBCAlkfkn8kQkXslseKEHARZthTrm8QfErQ5auXEr8teFIt6stHYi9sfJXM7IK0vEsezKFQwADCvMhX202eL\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"22ZEyoBNMXpT6rHmbuBlIJ8n19vo3tHzNTQDUku00WhHuBCAlkfkn8kQkXslseKEHARZthTrm8QfErQ5auXEr8teFIt6stHYi9sfJXM7IK0vEsezKFQwADCvMhX202eL\"\n)\n\nfunc TestChatFule_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/checio/checio.go",
    "content": "package checio\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"checio\"}) + `\\b(pk_[a-z0-9]{45})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"checio\"}\n}\n\n// FromData will find and optionally verify ChecIO secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ChecIO,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.chec.io/v1/products?limit=25\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-Authorization\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ChecIO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/checio/checio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage checio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestChecIO_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CHECIO\")\n\tinactiveSecret := testSecrets.MustGetField(\"CHECIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a checio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ChecIO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ChecIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ChecIO.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ChecIO.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/checio/checio_test.go",
    "content": "package checio\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: N/A\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tchecio_auth_token: \"X-Authorization pk_k64v4e7f5vfun5efk7kscnvuwiuo9ioxbvxjq8qrdga0p\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"pk_k64v4e7f5vfun5efk7kscnvuwiuo9ioxbvxjq8qrdga0p\"\n)\n\nfunc TestChecio_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/checklyhq/checklyhq.go",
    "content": "package checklyhq\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"checklyhq\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"checklyhq\"}\n}\n\n// FromData will find and optionally verify ChecklyHQ secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ChecklyHQ,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.checklyhq.com/v1/checks\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ChecklyHQ\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ChecklyHQ is a monitoring service for API and browser checks. ChecklyHQ API keys can be used to access and manage these checks.\"\n}\n"
  },
  {
    "path": "pkg/detectors/checklyhq/checklyhq_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage checklyhq\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestChecklyHQ_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CHECKLYHQ\")\n\tinactiveSecret := testSecrets.MustGetField(\"CHECKLYHQ_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a checklyhq secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ChecklyHQ,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ChecklyHQ,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ChecklyHQ.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ChecklyHQ.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/checklyhq/checklyhq_test.go",
    "content": "package checklyhq\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: Bearer\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tchecklyhq_auth_token: \"Bearer r3sd5apfe7p3eg1318qpbtxo36gwcct2\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"r3sd5apfe7p3eg1318qpbtxo36gwcct2\"\n)\n\nfunc TestChecklyhq_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/checkout/checkout.go",
    "content": "package checkout\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\t// Tokens starting with sk_test are used for the app's sandbox environment while tokens starting with sk only are for production environment\n\tkeyPat = 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`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"checkout\"}) + `\\b(cus_[0-9a-zA-Z]{26})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"checkout\"}\n}\n\n// FromData will find and optionally verify Checkout secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Checkout,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resIdMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\t// Used the app's sandbox environment for this case since I can't create a live account.\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.sandbox.checkout.com/customers/\"+resIdMatch, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", resMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Checkout\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Checkout is a global payment solution provider. Checkout API keys can be used to process payments and manage customer data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/checkout/checkout_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage checkout\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCheckout_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CHECKOUT\")\n\tinactiveSecret := testSecrets.MustGetField(\"CHECKOUT_INACTIVE\")\n\tsecretId := testSecrets.MustGetField(\"CHECKOUT_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a checkout secret %s with checkout id %s within\", secret, secretId)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Checkout,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Checkout,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Checkout.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Checkout.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/checkout/checkout_test.go",
    "content": "package checkout\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: API-Key\n\t\t\tbase_url: \"https://api.checkout.com/v1/customers/cus_DaZoK0ioakAfFaj6fyqSFQZatk\"\n\t\t\tcheckout_api_key: \"sk_test_14a67eEd-21Fd-B18d-2B8D-275697febE7D\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"sk_test_14a67eEd-21Fd-B18d-2B8D-275697febE7Dcus_DaZoK0ioakAfFaj6fyqSFQZatk\"\n)\n\nfunc TestCheckout_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/checkvist/checkvist.go",
    "content": "package checkvist\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"checkvist\"}) + `\\b([0-9a-zA-Z]{14})\\b`)\n\temailPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"checkvist\"}) + common.EmailPattern)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"checkvist\"}\n}\n\n// FromData will find and optionally verify Checkvist secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tuniqueEmailMatches := make(map[string]struct{})\n\tfor _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor emailMatch := range uniqueEmailMatches {\n\t\tfor _, match := range matches {\n\t\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Checkvist,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + emailMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tpayload := url.Values{}\n\t\t\t\tpayload.Add(\"username\", emailMatch)\n\t\t\t\tpayload.Add(\"remote_key\", resMatch)\n\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://checkvist.com/auth/login.json?version=2\", strings.NewReader(payload.Encode()))\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Checkvist\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Checkvist is an online task management tool. The credentials found can be used to access and manage tasks and data within Checkvist.\"\n}\n"
  },
  {
    "path": "pkg/detectors/checkvist/checkvist_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage checkvist\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCheckvist_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tuser := testSecrets.MustGetField(\"CHECKVIST_EMAIL\")\n\tsecret := testSecrets.MustGetField(\"CHECKVIST\")\n\tinactiveSecret := testSecrets.MustGetField(\"CHECKVIST_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a checkvist user %s with checkvist secret %s within\", user, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Checkvist,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Checkvist,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Checkvist.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Checkvist.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/checkvist/checkvist_test.go",
    "content": "package checkvist\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"wdvnusa87afxYn / testuser1005@example.com\"\n\tinvalidPattern = \"wdvn-usa87a-fxp9ioasQQsstestUsQQ@example\"\n)\n\nfunc TestCheckvist_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: fmt.Sprintf(\"checkvist: %s\", validPattern),\n\t\t\twant:  []string{\"wdvnusa87afxYntestuser1005@example.com\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"checkvist keyword is not close to the real key and id = %s\", validPattern),\n\t\t\twant:  nil,\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"checkvist: %s\", invalidPattern),\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 && test.want != nil {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"expected %d results, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cicero/cicero.go",
    "content": "package cicero\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"cicero\"}) + `\\b([0-9a-z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"cicero\"}\n}\n\n// FromData will find and optionally verify Cicero secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Cicero,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://cicero.azavea.com/v3.1/account/credits_remaining?key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Cicero\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/cicero/cicero_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage cicero\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCicero_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CICERO\")\n\tinactiveSecret := testSecrets.MustGetField(\"CICERO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cicero secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Cicero,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Cicero,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Cicero.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Cicero.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cicero/cicero_test.go",
    "content": "package cicero\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"\"\n\t\t\tbase_url: \"https://api.cicero.com/v1/user?key=sbxod2yo56quitbyujhkig3mgtu6z49f4hh56va6\"\n\t\t\tauth_token: \"\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"sbxod2yo56quitbyujhkig3mgtu6z49f4hh56va6\"\n)\n\nfunc TestCicero_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/circleci/v1/circleci.go",
    "content": "package circleci\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"circle\"}) + `([a-fA-F0-9]{40})`)\n)\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\nfunc (Scanner) Version() int { return 1 }\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"circle\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Circle\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// FromData will find and optionally verify Circle secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueTokens = make(map[string]struct{})\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokens[match[1]] = struct{}{}\n\t}\n\n\tfor token := range uniqueTokens {\n\t\tresult := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Circle,\n\t\t\tRaw:          []byte(token),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"Version\": strconv.Itoa(s.Version()),\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\t// https://circleci.com/docs/api/#authentication\n\t\t\tisVerified, verificationErr := VerifyCircleCIToken(ctx, s.getClient(), token)\n\t\t\tresult.Verified = isVerified\n\t\t\tresult.SetVerificationError(verificationErr, token)\n\t\t}\n\n\t\tresults = append(results, result)\n\t}\n\n\treturn\n}\n\nfunc VerifyCircleCIToken(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://circleci.com/api/v2/me\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Accept\", \"application/json;\")\n\treq.Header.Add(\"Circle-Token\", token)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/circleci/v1/circleci_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage circleci\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCircleCI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CIRCLECI\")\n\tsecretInactive := testSecrets.MustGetField(\"CIRCLECI_INACTIVE\")\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a circle secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Circle,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a circle secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Circle,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CircleCI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif os.Getenv(\"FORCE_PASS_DIFF\") == \"true\" {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CircleCI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/circleci/v1/circleci_test.go",
    "content": "package circleci\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: API-Key\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcircleci_api_key: \"4a4fFEA0Cb7760ee42Bb1Dc82b1E4E5eDCacB9E7\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"4a4fFEA0Cb7760ee42Bb1Dc82b1E4E5eDCacB9E7\"\n)\n\nfunc TestCircleCI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/circleci/v2/circleci.go",
    "content": "package circleci\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strconv\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\tv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/circleci/v1\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(`(CCIPAT_[a-zA-Z0-9]{22}_[a-fA-F0-9]{40})`)\n)\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\nfunc (Scanner) Version() int { return 2 }\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"CCIPAT_\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Circle\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// FromData will find and optionally verify Circle secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueTokens = make(map[string]struct{})\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokens[match[1]] = struct{}{}\n\t}\n\n\tfor token := range uniqueTokens {\n\t\tresult := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Circle,\n\t\t\tRaw:          []byte(token),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"Version\": strconv.Itoa(s.Version()),\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := v1.VerifyCircleCIToken(ctx, s.getClient(), token)\n\t\t\tresult.Verified = isVerified\n\t\t\tresult.SetVerificationError(verificationErr, token)\n\t\t}\n\n\t\tresults = append(results, result)\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "pkg/detectors/circleci/v2/circleci_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage circleci\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCircleCI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CIRCLECI\")\n\tsecretInactive := testSecrets.MustGetField(\"CIRCLECI_INACTIVE\")\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a circle secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Circle,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a circle secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Circle,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CircleCI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif os.Getenv(\"FORCE_PASS_DIFF\") == \"true\" {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].ExtraData = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CircleCI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/circleci/v2/circleci_test.go",
    "content": "package circleci\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestCircleCI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t# Configuration File: config.yaml\n\t\t\t\tdatabase:\n\t\t\t\t\thost: $DB_HOST\n\t\t\t\t\tport: $DB_PORT\n\t\t\t\t\tusername: $DB_USERNAME\n\t\t\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\t\t\tapi:\n\t\t\t\t\tauth_type: API-Key\n\t\t\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\t\t\tapi_key: \"CCIPAT_FAKEd5qPreGFAKEaQxBi6i_914bf0042f4f2d34e1d2ef6615c051a5caf70172\"\n\n\t\t\t\t# Notes:\n\t\t\t\t# - Remember to rotate the secret every 90 days.\n\t\t\t\t# - The above credentials should only be used in a secure environment.\n\t\t\t`,\n\t\t\twant: []string{\"CCIPAT_FAKEd5qPreGFAKEaQxBi6i_914bf0042f4f2d34e1d2ef6615c051a5caf70172\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clarifai/clarifai.go",
    "content": "package clarifai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\tkeyPat = 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)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"clarifai\"}\n}\n\n// FromData will find and optionally verify Clarifai secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Clarifai,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\t// test for api key\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.clarifai.com/v2/inputs\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Key %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !s1.Verified {\n\t\t\t\t// test for pat\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.clarifai.com/v2/users/me\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Key %s\", resMatch))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Clarifai\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Clarifai is an AI platform for visual recognition. Clarifai API keys can be used to access and manage visual recognition models and data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/clarifai/clarifai_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage clarifai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestClarifai_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tapiKey := testSecrets.MustGetField(\"CLARIFAI_API_KEY\")\n\tinactiveApiKey := testSecrets.MustGetField(\"CLARIFAI_API_KEY_INACTIVE\")\n\tpat := testSecrets.MustGetField(\"CLARIFAI_PAT\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a clarifai api key %s within\", apiKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Clarifai,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a clarifai pat %s within\", pat)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Clarifai,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a clarifai secret %s within but unverified\", inactiveApiKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Clarifai,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Clarifai.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Clarifai.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clarifai/clarifai_test.go",
    "content": "package clarifai\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: API-Key\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tclarifai_key: \"WCFcfUCsl2P3vCJuFgDuLeUXduycoZli\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"WCFcfUCsl2P3vCJuFgDuLeUXduycoZli\"\n)\n\nfunc TestClarifai_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clearbit/clearbit.go",
    "content": "package clearbit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"clearbit\"}) + `\\b([0-9a-z_]{35})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"clearbit\"}\n}\n\n// FromData will find and optionally verify Clearbit secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Clearbit,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://person.clearbit.com/v1/people/email/alex@alexmaccaw.com\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Clearbit\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/clearbit/clearbit_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage clearbit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestClearbit_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CLEARBIT\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLEARBIT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a clearbit secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Clearbit,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Clearbit,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Clearbit.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Clearbit.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clearbit/clearbit_test.go",
    "content": "package clearbit\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: Bearer\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tclearbit_auth_token: \"Bearer 9itvicgfiyq3ry6g03qwhwc_0s309dy8woh\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"9itvicgfiyq3ry6g03qwhwc_0s309dy8woh\"\n)\n\nfunc TestClearBit_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clickhelp/clickhelp.go",
    "content": "package clickhelp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tportalPat = regexp.MustCompile(`\\b([0-9A-Za-z-]{3,20}\\.(?:try\\.)?clickhelp\\.co)\\b`)\n\temailPat  = regexp.MustCompile(common.EmailPattern)\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"clickhelp\", \"key\", \"token\", \"api\", \"secret\"}) + `\\b([0-9A-Za-z]{24})\\b`)\n)\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ClickHelp\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"clickhelp.co\"}\n}\n\n// FromData will find and optionally verify Clickhelp secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniquePortalLinks, uniqueEmails, uniqueAPIKeys = make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, match := range portalPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniquePortalLinks[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueEmails[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueAPIKeys[match[1]] = struct{}{}\n\t}\n\n\tfor portalLink := range uniquePortalLinks {\n\t\tfor email := range uniqueEmails {\n\t\t\tfor apiKey := range uniqueAPIKeys {\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ClickHelp,\n\t\t\t\t\tRaw:          []byte(portalLink),\n\t\t\t\t\tRawV2:        []byte(portalLink + email),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tisVerified, verificationErr := verifyClickHelp(ctx, client, portalLink, email, apiKey)\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t\t\ts1.SetPrimarySecretValue(apiKey) // line number will point to api key\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyClickHelp(ctx context.Context, client *http.Client, portalLink, email, apiKey string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"https://%s/api/v1/projects\", portalLink), http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.SetBasicAuth(email, apiKey)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clickhelp/clickhelp_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage clickhelp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestClickhelp_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\temail := testSecrets.MustGetField(\"SCANNERS_EMAIL\")\n\tserver := testSecrets.MustGetField(\"CLICKHELP_SERVER\")\n\tkey := testSecrets.MustGetField(\"CLICKHELP\")\n\tinactiveKey := testSecrets.MustGetField(\"CLICKHELP_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a clickhelp secret %s within clickhelp email %s and clickhelp server %s\", key, email, server)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ClickHelp,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ClickHelp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Clickhelp.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"AdafruitIO.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clickhelp/clickhelp_test.go",
    "content": "package clickhelp\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestClickHelp_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t# Configuration File: config.yaml\n\t\t\t\tdatabase:\n\t\t\t\t\thost: $DB_HOST\n\t\t\t\t\tport: $DB_PORT\n\t\t\t\t\tusername: $DB_USERNAME\n\t\t\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\t\t\tapi:\n\t\t\t\t\tuser_email: \"test-user@clickhelp.co\"\n\t\t\t\t\tkey: \"XzUkp562BtmjfRGoOGBiLLNu\"\n\t\t\t\t\tportal: testingdev.try.clickhelp.co\n\t\t\t\t\tauth_type: Basic\n\t\t\t\t\tbase_url: \"https://testing-dev.try.clickhelp.co/v1/user\"\n\t\t\t\t\tauth_token: \"\"\n\n\t\t\t\t# Notes:\n\t\t\t\t# - Remember to rotate the secret every 90 days.\n\t\t\t\t# - The above credentials should only be used in a secure environment.\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"testing-dev.try.clickhelp.cotest-user@clickhelp.co\",\n\t\t\t\t\"testingdev.try.clickhelp.cotest-user@clickhelp.co\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{test-user01@clickhelp.co}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA XzUkp562BtmjfRGoOGBiLLNu}</secret>\n\t\t\t\t\t<portal>company-prod.clickhelp.co</portal>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"company-prod.clickhelp.cotest-user01@clickhelp.co\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clicksendsms/clicksendsms.go",
    "content": "package clicksendsms\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(common.UUIDPatternUpperCase)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"sms\"}) + common.EmailPattern)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"clicksendsms\"}\n}\n\n// FromData will find and optionally verify ClickSendsms secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, idMatch := range idMatches {\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[0][strings.LastIndex(idMatch[0], \" \")+1:])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_ClickSendsms,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resIdMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\n\t\t\t\tdata := fmt.Sprintf(\"%s:%s\", resIdMatch, resMatch)\n\t\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://rest.clicksend.com/v3/account\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ClickSendsms\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/clicksendsms/clicksendsms_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage clicksendsms\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestClickSendsms_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CLICKSENDSMS_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLICKSENDSMS_INACTIVE\")\n\temail := testSecrets.MustGetField(\"CLICKSENDSMS_EMAIL\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a clicksendsms secret %s within clicksendsmsemail %s\", secret, email)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ClickSendsms,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ClickSendsms,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ClickSendsms.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw v2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ClickSendsms.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clicksendsms/clicksendsms_test.go",
    "content": "package clicksendsms\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File for clicksendsms: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: Basic\n\t\t\tbase_url: \"https://api.example.com/v1/sms\"\n\t\t\tsms_id: G9TXU2YD-NOYB-LLSX-21NU-5CX5SIA330Z7\n\t\t\tsms_email: user-test@clicksend.sms\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"G9TXU2YD-NOYB-LLSX-21NU-5CX5SIA330Z7user-test@clicksend.sms\"\n)\n\nfunc TestClickSendSMS_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clickuppersonaltoken/clickuppersonaltoken.go",
    "content": "package clickuppersonaltoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"clickup\"}) + `\\b(pk_[0-9]{7,9}_[0-9A-Z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"clickup\"}\n}\n\n// FromData will find and optionally verify ClickupPersonalToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor resMatch := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ClickupPersonalToken,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\tisVerified, err := verifyToken(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ClickupPersonalToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ClickUp is a project management tool. Personal tokens can be used to access and modify data within ClickUp on behalf of a user.\"\n}\n\nfunc verifyToken(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.clickup.com/api/v2/user\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Authorization\", token)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code %d\", res.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clickuppersonaltoken/clickuppersonaltoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage clickuppersonaltoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestClickupPersonalToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CLICKUPPERSONALTOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLICKUPPERSONALTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a clickuppersonaltoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ClickupPersonalToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ClickupPersonalToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found verifiable secret, verification failed due to unexpected API response\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ClickupPersonalToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ClickupPersonalToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"ClickupPersonalToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clickuppersonaltoken/clickuppersonaltoken_test.go",
    "content": "package clickuppersonaltoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tclickup_token: \"pk_7043602_WIKY22PAKCVC1S5Q2X6119IK7N1UL8VY\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"pk_7043602_WIKY22PAKCVC1S5Q2X6119IK7N1UL8VY\"\n)\n\nfunc TestClickupPersonalToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cliengo/cliengo.go",
    "content": "package cliengo\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"cliengo\"}\n}\n\n// FromData will find and optionally verify Cliengo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Cliengo,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.cliengo.com/1.0/account?api_key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Cliengo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/cliengo/cliengo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage cliengo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCliengo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CLIENGO\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLIENGO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cliengo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Cliengo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Cliengo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Cliengo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Cliengo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cliengo/cliengo_test.go",
    "content": "package cliengo\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tbase_url: \"https://api.cliengo.com/v1/user?key=9e4635bc-28dc-25d3-8546-2b30115d9a7b\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"9e4635bc-28dc-25d3-8546-2b30115d9a7b\"\n)\n\nfunc TestCliengo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clientary/clientary.go",
    "content": "/*\nRoninApp rebranded to Clientary\n\nArticle: https://www.clientary.com/articles/a-new-brand/\n*/\npackage clientary\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ronin\", \"clientary\"}) + `\\b([0-9a-zA-Z]{24,26})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"ronin\", \"clientary\"}) + `\\b([0-9Aa-zA-Z-]{4,25})\\b`)\n\n\terrAccountNotFound = errors.New(\"account not found\")\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ronin\", \"clientary\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Clientary\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// FromData will find and optionally verify RoninApp secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueIDs, uniqueAPIKeys = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, match := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueIDs[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueAPIKeys[match[1]] = struct{}{}\n\t}\n\n\tfor apiKey := range uniqueAPIKeys {\n\t\tfor id := range uniqueIDs {\n\t\t\t// since regex matches can overlap, continue only if both apiKey and id are the same.\n\t\t\tif apiKey == id {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Clientary,\n\t\t\t\tRaw:          []byte(apiKey),\n\t\t\t\tRawV2:        []byte(apiKey + \":\" + id),\n\t\t\t\tExtraData:    make(map[string]string),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, verificationErr := verifyClientaryAPIKey(ctx, client, id, apiKey)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\tif verificationErr != nil {\n\t\t\t\t\t// remove the account ID if not found to prevent reuse during other API key checks.\n\t\t\t\t\tif errors.Is(verificationErr, errAccountNotFound) {\n\t\t\t\t\t\tdelete(uniqueIDs, id)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\ts1.SetVerificationError(verificationErr, apiKey)\n\t\t\t\t}\n\n\t\t\t\t// If a verified result is found, attach rebranding documentation to inform the user about the RoninApp rebranding to Clientary.\n\t\t\t\tif s1.Verified {\n\t\t\t\t\ts1.ExtraData[\"Rebrading Docs\"] = \"https://www.clientary.com/articles/a-new-brand/\"\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\n// docs: https://www.clientary.com/api\nfunc verifyClientaryAPIKey(ctx context.Context, client *http.Client, id, apiKey string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://\"+id+\".clientary.com/api/v2/invoices\", http.NoBody)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\n\treq.SetBasicAuth(apiKey, apiKey)\n\treq.Header.Add(\"Accept\", \"application/json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusForbidden, http.StatusUnauthorized:\n\t\treturn false, nil\n\tcase http.StatusNotFound:\n\t\t// API return 404 if the account id does not exist\n\t\treturn false, errAccountNotFound\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clientary/clientary_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage clientary\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRoninApp_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"RONINAPP\")\n\tinactiveSecret := testSecrets.MustGetField(\"RONINAPP_INACTIVE\")\n\tdomain := testSecrets.MustGetField(\"RONINAPP_DOMAIN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a clientary secret %s and clientaryDomain %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Clientary,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Clientary,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"Rebrading Docs\": \"https://www.clientary.com/articles/a-new-brand/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Clientary,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"RoninApp.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"RoninApp.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clientary/clientary_test.go",
    "content": "package clientary\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestRoninApp_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern - with keyword ronin\",\n\t\t\tinput: `\n\t\t\t\t# some random code\n\t\t\t\tdata := getIDFromDatabase(ctx)\n\t\t\t\troninAPIKey := ZycQ0G6IBgNsBWytwzwVKixyz\n\t\t\t\troninDomain := truffle-dev.roninapp.com\n\t\t\t`,\n\t\t\twant: []string{\"ZycQ0G6IBgNsBWytwzwVKixyz:truffle-dev\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - with keyword clientary\",\n\t\t\tinput: `\n\t\t\t\t# some random code\n\t\t\t\tdata := getIDFromDatabase(ctx)\n\t\t\t\tclientaryAPIKey := ZycQ0G6IBgNsBWytwzwVKixyz\n\t\t\t\tclientaryDomain := truffle-dev.clientary.com\n\t\t\t`,\n\t\t\twant: []string{\"ZycQ0G6IBgNsBWytwzwVKixyz:truffle-dev\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t# some random code\n\t\t\t\tdata := getIDFromDatabase(ctx)\n\t\t\t\troninAPIKey := ZycQ0G6IBg-NsBWytwzwVKixyz\n\t\t\t\trominDomain := t_de.roninapp.com\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clinchpad/clinchpad.go",
    "content": "package clinchpad\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"clinchpad\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"clinchpad\"}\n}\n\n// FromData will find and optionally verify Clinchpad secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Clinchpad,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tdata := fmt.Sprintf(\"api-key:%s\", resMatch)\n\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.clinchpad.com/api/v1/pipelines\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Clinchpad\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Clinchpad is a CRM tool. Clinchpad API keys can be used to access and modify data within Clinchpad.\"\n}\n"
  },
  {
    "path": "pkg/detectors/clinchpad/clinchpad_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage clinchpad\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestClinchpad_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CLINCHPAD_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLINCHPAD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a clinchpad secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Clinchpad,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Clinchpad,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Clinchpad.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Clinchpad.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clinchpad/clinchpad_test.go",
    "content": "package clinchpad\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Basic\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tclinchpad_key: \"3v1xo5r03ghc538iwzbzeddwqulnun8h\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"3v1xo5r03ghc538iwzbzeddwqulnun8h\"\n)\n\nfunc TestClinchPad_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clockify/clockify.go",
    "content": "package clockify\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"clockify\"}) + `\\b([a-zA-Z0-9]{48})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"clockify\"}\n}\n\n// FromData will find and optionally verify Clockify secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Clockify,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.clockify.me/api/v1/user\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"content-type\", \"application/json\")\n\t\t\treq.Header.Add(\"X-Api-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Clockify\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Clockify is a time tracking software. Clockify API keys can be used to access and modify time tracking data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/clockify/clockify_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage clockify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestClockify_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CLOCKIFY\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLOCKIFY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a clockify secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Clockify,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Clockify,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Clockify.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Clockify.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clockify/clockify_test.go",
    "content": "package clockify\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tclockify_key: \"kfJkRn7Knahh6pyDOL82NqNq5c4VLUNulVe5CMyJpIK9NXQC\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"kfJkRn7Knahh6pyDOL82NqNq5c4VLUNulVe5CMyJpIK9NXQC\"\n)\n\nfunc TestClockify_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clockworksms/clockworksms.go",
    "content": "package clockworksms\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tuserKeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"clockwork\", \"textanywhere\"}) + `\\b([0-9]{5})\\b`)\n\ttokenPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"clockwork\", \"textanywhere\"}) + `\\b([0-9a-zA-Z]{24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"clockworksms\", \"textanywhere\"}\n}\n\n// FromData will find and optionally verify Clockworksms secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuserKeyMatches := userKeyPat.FindAllStringSubmatch(dataStr, -1)\n\ttokenMatches := tokenPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range userKeyMatches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, tokenMatch := range tokenMatches {\n\t\t\ttokenRes := strings.TrimSpace(tokenMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_ClockworkSMS,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + tokenRes),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.textanywhere.com/API/v1.0/REST/status\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"user_key\", resMatch)\n\t\t\t\treq.Header.Add(\"access_token\", tokenRes)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ClockworkSMS\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/clockworksms/clockworksms_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage clockworksms\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestClockworksms_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tuserKey := testSecrets.MustGetField(\"CLOCKWORKSMS_USERKEY\")\n\ttoken := testSecrets.MustGetField(\"CLOCKWORKSMS_TOKEN\")\n\tinactiveToken := testSecrets.MustGetField(\"CLOCKWORKSMS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a clockworksms secret %s and clockworksms token %s within\", userKey, token)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ClockworkSMS,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ClockworkSMS,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Clockworksms.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw v2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Clockworksms.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clockworksms/clockworksms_test.go",
    "content": "package clockworksms\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"\"\n\t\t\tbase_url: \"https://api.textanywhere.com/v1/user\"\n\t\t\tclockwork_key: \"84473\"\n\t\t\tclockwork_token: \"YROh7NZbZxHwiSc9pMNIAGYs\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"84473YROh7NZbZxHwiSc9pMNIAGYs\"\n)\n\nfunc TestClockWorkSMS_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/closecrm/close.go",
    "content": "package closecrm\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(api_[a-z0-9A-Z.]{45})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"close\"}\n}\n\n// FromData will find and optionally verify Close secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Close,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tdata := fmt.Sprintf(\"%s:\", resMatch)\n\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.close.com/api/v1/me/\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Close\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/closecrm/close_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage closecrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestClose_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CLOSE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLOSE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a close secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Close,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Close,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Close.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Close.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/closecrm/close_test.go",
    "content": "package closecrm\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Basic\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tclose_key: \"api_3cyEW8syFEmeND561qJ9Sj8mT6E0VyWqY7h6cjJIBtc2e\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"api_3cyEW8syFEmeND561qJ9Sj8mT6E0VyWqY7h6cjJIBtc2e\"\n)\n\nfunc TestCloseCRM_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudconvert/cloudconvert.go",
    "content": "package cloudconvert\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.MaxSecretSizeProvider = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"cloudconvert\"}) + common.BuildRegexJWT(\"30,34\", \"200,500\", \"600,700\"))\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"cloudconvert\"}\n}\n\nconst maxJWTSize = 1300\n\n// MaxSecretSize returns the maximum size of a secret that this detector can find.\nfunc (s Scanner) MaxSecretSize() int64 { return maxJWTSize }\n\n// FromData will find and optionally verify CloudConvert secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_CloudConvert,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.cloudconvert.com/v2/users/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.cloudconvert+json; version=3\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CloudConvert\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"CloudConvert is a file conversion service. CloudConvert API keys can be used to access and manage file conversion operations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/cloudconvert/cloudconvert_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage cloudconvert\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCloudConvert_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CLOUDCONVERT\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLOUDCONVERT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cloudconvert secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CloudConvert,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CloudConvert,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CloudConvert.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CloudConvert.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudconvert/cloudconvert_test.go",
    "content": "package cloudconvert\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcloudconvert_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\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"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\"\n)\n\nfunc TestCloudConvert_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudelements/cloudelements.go",
    "content": "package cloudelements\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"cloudelements\"}) + `\\b([a-zA-Z0-9]{43})\\b`)\n\torgPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"cloudelements\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"cloudelements\"}\n}\n\n// FromData will find and optionally verify CloudElements secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\torgMatches := orgPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, orgMatch := range orgMatches {\n\n\t\t\tresOrgMatch := strings.TrimSpace(orgMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_CloudElements,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resOrgMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://staging.cloud-elements.com/elements/api-v2/accounts\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"User %s, Organization %s\", resMatch, resOrgMatch))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CloudElements\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/cloudelements/cloudelements_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage cloudelements\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCloudElements_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CLOUDELEMENTS\")\n\torg := testSecrets.MustGetField(\"CLOUDELEMENTS_ORG\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLOUDELEMENTS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cloudelements secret %s within cloudelements %s\", secret, org)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CloudElements,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CloudElements,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CloudElements.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CloudElements.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudelements/cloudelements_test.go",
    "content": "package cloudelements\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcloudelements_key: \"4NloL5EzH3PLvNzjCMikofUfKXYOsYOeJBopEyDScIL\"\n\t\t\tcloudelements_org: \"inz9qofvjwnx59hgefq9sy5v64ilqrnu\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"4NloL5EzH3PLvNzjCMikofUfKXYOsYOeJBopEyDScILinz9qofvjwnx59hgefq9sy5v64ilqrnu\"\n)\n\nfunc TestCloudElements_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudflareapitoken/cloudflareapitoken.go",
    "content": "package cloudflareapitoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"cloudflare\"}) + `\\b([A-Za-z0-9_-]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"cloudflare\"}\n}\n\n// FromData will find and optionally verify CloudflareApiToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_CloudflareApiToken,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.cloudflare.com/client/v4/user/tokens/verify\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CloudflareApiToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/cloudflareapitoken/cloudflareapitoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage cloudflareapitoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCloudflareApiToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CLOUDFLARE_API_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLOUDFLARE_API_INACTIVE\")\n\tsecret2 := testSecrets.MustGetField(\"CLOUDFLARE_API_TOKEN2\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cloudflareapitoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CloudflareApiToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cloudflareapitoken secret %s within\", secret2)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CloudflareApiToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cloudflareapitoken secret %s within but unverified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CloudflareApiToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CloudflareApiToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CloudflareApiToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudflareapitoken/cloudflareapitoken_test.go",
    "content": "package cloudflareapitoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcloudflare_token: \"kOjD1yceduu2jxL2uuwT9dkOIudU3_54sLCEud6j\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"kOjD1yceduu2jxL2uuwT9dkOIudU3_54sLCEud6j\"\n)\n\nfunc TestCloudFlareAPIToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudflarecakey/cloudflarecakey.go",
    "content": "package cloudflarecakey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// origin ca keys documentation: https://developers.cloudflare.com/fundamentals/api/get-started/ca-keys/\n\tkeyPat = regexp.MustCompile(`\\b(v1\\.0-[A-Za-z0-9-]{171})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"cloudflare\"}\n}\n\n// FromData will find and optionally verify CloudflareCaKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[matches[1]] = struct{}{}\n\t}\n\n\tfor caKey := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_CloudflareCaKey,\n\t\t\tRaw:          []byte(caKey),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyCloudFlareCAKey(ctx, client, caKey)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, caKey)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CloudflareCaKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Cloudflare is a web infrastructure and website security company. Cloudflare CA keys can be used to manage SSL/TLS certificates and other security settings.\"\n}\n\nfunc verifyCloudFlareCAKey(ctx context.Context, client *http.Client, caKey string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.cloudflare.com/client/v4/certificates?zone_id=a\", nil)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.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\n\n\treq.Header.Add(\"X-Auth-User-Service-Key\", caKey)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudflarecakey/cloudflarecakey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage cloudflarecakey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCloudflareCaKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CLOUDFLARE_ORIGIN_CA_KEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLOUDFLARE_ORIGIN_CA_KEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cloudflarecakey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CloudflareCaKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cloudflarecakey secret %s within but unverified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CloudflareCaKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CloudflareCaKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CloudflareCaKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudflarecakey/cloudflarecakey_test.go",
    "content": "package cloudflarecakey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tbase_url: \"https://api.cloudflare.com/v1/user\"\n\t\t\tca_key: \"v1.0-13vvv5141b975834504fc75b-a670d21e1e012816c3c8d9745e2693adc2d2ec7c402f607dbf7f2bd5de3bdb490cce4420ef13179957c5651e1ee5d952b1e03bd0271e2b43a9847f0713f4d3942cde4a7bc2e4770615\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"v1.0-13vvv5141b975834504fc75b-a670d21e1e012816c3c8d9745e2693adc2d2ec7c402f607dbf7f2bd5de3bdb490cce4420ef13179957c5651e1ee5d952b1e03bd0271e2b43a9847f0713f4d3942cde4a7bc2e4770615\"\n)\n\nfunc TestCloudFlareCAKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudflareglobalapikey/cloudflareglobalapikey.go",
    "content": "package cloudflareglobalapikey\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\tapiKeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"cloudflare\"}) + `\\b([A-Za-z0-9_-]{37})\\b`)\n\n\temailPat = regexp.MustCompile(common.EmailPattern)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"cloudflare\"}\n}\n\n// FromData will find and optionally verify CloudflareGlobalApiKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tapiKeyMatches := apiKeyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tuniqueEmailMatches := make(map[string]struct{})\n\tfor _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor _, apiKeyMatch := range apiKeyMatches {\n\t\tapiKeyRes := strings.TrimSpace(apiKeyMatch[1])\n\n\t\tfor emailMatch := range uniqueEmailMatches {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_CloudflareGlobalApiKey,\n\t\t\t\tRedacted:     emailMatch,\n\t\t\t\tRaw:          []byte(apiKeyRes),\n\t\t\t\tRawV2:        []byte(apiKeyRes + emailMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.cloudflare.com/client/v4/user\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"X-Auth-Email\", emailMatch)\n\t\t\t\treq.Header.Add(\"X-Auth-Key\", apiKeyRes)\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CloudflareGlobalApiKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/cloudflareglobalapikey/cloudflareglobalapikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage cloudflareglobalapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCloudflareGlobalApiKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tglobalApiKey := testSecrets.MustGetField(\"CLOUDFLARE_GLOBAL_API_KEY\")\n\tglobalApiKeyEmail := testSecrets.MustGetField(\"CLOUDFLARE_GLOBAL_API_KEY_EMAIL\")\n\tinactiveglobalApiKey := testSecrets.MustGetField(\"CLOUDFLARE_GLOBAL_API_KEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cloudflare globalapikey secret %s within with email %s\", globalApiKey, globalApiKeyEmail)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CloudflareGlobalApiKey,\n\t\t\t\t\tRedacted:     globalApiKeyEmail,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cloudflare globalapikey secret %s with email %s within but unverified\", inactiveglobalApiKey, globalApiKeyEmail)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CloudflareGlobalApiKey,\n\t\t\t\t\tRedacted:     globalApiKeyEmail,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CloudflareGlobalApiKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CloudflareGlobalApiKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudflareglobalapikey/cloudflareglobalapikey_test.go",
    "content": "package cloudflareglobalapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"abcD123efg456HIJklmn789OPQ_rstUVWxYZ-012 / testuser1005@example.com\"\n\tinvalidPattern = \"abcD123efg456HIJklmn789OPQ_rstUVWxYZ-012/testing@go\"\n)\n\nfunc TestCloudFlareGlobalAPIKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: fmt.Sprintf(\"cloudflare: %s\", validPattern),\n\t\t\twant:  []string{\"abcD123efg456HIJklmn789OPQ_rstUVWxYZ-testuser1005@example.com\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"cloudflare keyword is not close to the real key and id = %s\", validPattern),\n\t\t\twant:  nil,\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"cloudflare: %s\", invalidPattern),\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 && test.want != nil {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"expected %d results, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudimage/cloudimage.go",
    "content": "package cloudimage\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"cloudimage\"}) + `\\b([a-z0-9_]{30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"cloudimage\"}\n}\n\n// FromData will find and optionally verify CloudImage secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_CloudImage,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\"scope\":\"urls\",\"urls\":[\"/sample.li/paris.jpg?width=400\",\"/sample.li/flat.jpg?width=400\"]}\n\t\t\t`)\n\t\t\ttimeout := 10 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.cloudimage.com/invalidate\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"X-Client-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CloudImage\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"CloudImage is a service that provides image optimization and delivery. CloudImage API keys can be used to access and modify image data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/cloudimage/cloudimage_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage cloudimage\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCloudImage_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CLOUDIMAGE\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLOUDIMAGE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cloudimage secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CloudImage,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CloudImage,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CloudImage.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CloudImage.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudimage/cloudimage_test.go",
    "content": "package cloudimage\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcloudimage: \"d__9rvli8sm4jo18v5q0q4n7vhkwbv\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"d__9rvli8sm4jo18v5q0q4n7vhkwbv\"\n)\n\nfunc TestCloudImage_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudmersive/cloudmersive.go",
    "content": "package cloudmersive\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"cloudmersive\"}) + `\\b([a-z0-9-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"cloudmersive\"}\n}\n\n// FromData will find and optionally verify Cloudmersive secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Cloudmersive,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\"AddressString\":\"string\",\"CapitalizationMode\":\"string\"}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.cloudmersive.com/validate/address/parse\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Apikey\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Cloudmersive\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Cloudmersive provides a suite of APIs for data validation, conversion, and security. Cloudmersive API keys can be used to access these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/cloudmersive/cloudmersive_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage cloudmersive\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCloudmersive_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CLOUDMERSIVE\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLOUDMERSIVE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cloudmersive secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Cloudmersive,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Cloudmersive,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Cloudmersive.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Cloudmersive.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudmersive/cloudmersive_test.go",
    "content": "package cloudmersive\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcloudmersive: \"sxk5k1nfra8jak0mjjc6afr6v-6gsf7dr9o1\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"sxk5k1nfra8jak0mjjc6afr6v-6gsf7dr9o1\"\n)\n\nfunc TestCloudMersive_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudplan/cloudplan.go",
    "content": "package cloudplan\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"cloudplan\"}) + `\\b([A-Z0-9-]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"cloudplan\"}\n}\n\n// FromData will find and optionally verify Cloudplan secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Cloudplan,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.cloudplan.biz/api/user/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"session_id\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Cloudplan\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Cloudplan is a service that offers cloud-based business solutions. Cloudplan session IDs can be used to access and manage user sessions and data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/cloudplan/cloudplan_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage cloudplan\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCloudplan_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CLOUDPLAN\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLOUDPLAN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cloudplan secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Cloudplan,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Cloudplan,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Cloudplan.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Cloudplan.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudplan/cloudplan_test.go",
    "content": "package cloudplan\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcloudplan_session_key: \"Y6D1FIS3XZXIJLKD82P6U8IXYV4UEYPP\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"Y6D1FIS3XZXIJLKD82P6U8IXYV4UEYPP\"\n)\n\nfunc TestCloudPlan_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudsmith/cloudsmith.go",
    "content": "package cloudsmith\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"cloudsmith\"}) + `\\b([0-9a-f]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"cloudsmith\"}\n}\n\ntype response struct {\n\tAuthenticated bool `json:\"authenticated\"`\n}\n\n// FromData will find and optionally verify Cloudsmith secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Cloudsmith,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.cloudsmith.io/v1/user/self/\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"X-Api-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\tvar r response\n\t\t\t\t\tif err := json.NewDecoder(res.Body).Decode(&r); err != nil {\n\t\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif r.Authenticated {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Cloudsmith\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Cloudsmith is a cloud-native package management service. Cloudsmith API keys can be used to manage and distribute packages.\"\n}\n"
  },
  {
    "path": "pkg/detectors/cloudsmith/cloudsmith_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage cloudsmith\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCloudsmith_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CLOUDSMITH\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLOUDSMITH_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cloudsmith secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Cloudsmith,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Cloudsmith,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Cloudsmith.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Cloudsmith.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloudsmith/cloudsmith_test.go",
    "content": "package cloudsmith\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"X-API-Key\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcloudsmith: \"6fd00a2cfd7bbc51e1c4db6ac2f29d59629afd22\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"6fd00a2cfd7bbc51e1c4db6ac2f29d59629afd22\"\n)\n\nfunc TestCloudSmith_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloverly/cloverly.go",
    "content": "package cloverly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"cloverly\"}) + `\\b([a-z0-9:_]{28})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"cloverly\"}\n}\n\n// FromData will find and optionally verify Cloverly secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Cloverly,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.cloverly.com/2019-03-beta/account\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Cloverly\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/cloverly/cloverly_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage cloverly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCloverly_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CLOVERLY\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLOVERLY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cloverly secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Cloverly,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Cloverly,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Cloverly.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Cloverly.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloverly/cloverly_test.go",
    "content": "package cloverly\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcloverly_token: \"564i_0a9_v58bn:p9st3r3cgi95_\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"564i_0a9_v58bn:p9st3r3cgi95_\"\n)\n\nfunc TestCloverly_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloze/cloze.go",
    "content": "package cloze\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"cloze\"}) + `\\b([0-9a-f]{32})\\b`)\n\temailPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"cloze\"}) + common.EmailPattern)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"cloze\"}\n}\n\n// FromData will find and optionally verify Cloze secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tuniqueEmailMatches := make(map[string]struct{})\n\tfor _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor emailMatch := range uniqueEmailMatches {\n\t\tfor _, match := range matches {\n\t\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Cloze,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tpayload := url.Values{}\n\t\t\t\tpayload.Add(\"user\", emailMatch)\n\t\t\t\tpayload.Add(\"api_key\", resMatch)\n\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.cloze.com/v1/profile?\"+payload.Encode(), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Cloze\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/cloze/cloze_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage cloze\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCloze_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\temail := testSecrets.MustGetField(\"CLOZE_EMAIL\")\n\tsecret := testSecrets.MustGetField(\"CLOZE\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLOZE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cloze user %s with cloze secret %s within\", email, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Cloze,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Cloze,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Cloze.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Cloze.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cloze/cloze_test.go",
    "content": "package cloze\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d / testuser1005@example.com\"\n\tinvalidPattern = \"abcD123efg456HIJklmn789OPQ_rstUVWxYZ-012/testing@go\"\n)\n\nfunc TestCloze_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: fmt.Sprintf(\"cloze: %s\", validPattern),\n\t\t\twant:  []string{\"1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"cloze keyword is not close to the real key and id = %s\", validPattern),\n\t\t\twant:  nil,\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"cloze: %s\", invalidPattern),\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 && test.want != nil {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"expected %d results, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clustdoc/clustdoc.go",
    "content": "package clustdoc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"clustdoc\"}) + `\\b([0-9a-zA-Z]{60})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"clustdoc\"}\n}\n\n// FromData will find and optionally verify ClustDoc secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ClustDoc,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://clustdoc.com/api/users\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ClustDoc\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ClustDoc is a document management platform. ClustDoc API keys can be used to access and manage documents and workflows within the ClustDoc platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/clustdoc/clustdoc_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage clustdoc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestClustDoc_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CLUSTDOC\")\n\tinactiveSecret := testSecrets.MustGetField(\"CLUSTDOC_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a clustdoc secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ClustDoc,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ClustDoc,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ClustDoc.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ClustDoc.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/clustdoc/clustdoc_test.go",
    "content": "package clustdoc\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tclustdoc_token: \"yQ7mTTO4eJ4I9GHDEdzF3wq0KVowNKjPMud3q0ZqaEIuoR1qCrARUyLwknNP\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"yQ7mTTO4eJ4I9GHDEdzF3wq0KVowNKjPMud3q0ZqaEIuoR1qCrARUyLwknNP\"\n)\n\nfunc TestClustDoc_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/coda/coda.go",
    "content": "package coda\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"coda\"}\n}\n\n// FromData will find and optionally verify Coda secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Coda,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://coda.io/apis/v1/whoami\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Coda\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/coda/coda_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage coda\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCoda_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CODA\")\n\tinactiveSecret := testSecrets.MustGetField(\"CODA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a coda secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Coda,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Coda,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a coda secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Coda,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a coda secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Coda,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Coda.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Coda.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/coda/coda_test.go",
    "content": "package coda\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcoda_token: \"64ukni4l-zub4-3coe-html-9byb40oi5i87\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"64ukni4l-zub4-3coe-html-9byb40oi5i87\"\n)\n\nfunc TestCoda_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/codacy/codacy.go",
    "content": "package codacy\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"codacy\"}) + `\\b([0-9A-Za-z]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"codacy\"}\n}\n\n// FromData will find and optionally verify Codacy secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Codacy,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.codacy.com/api/v3/user\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"api-token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Codacy\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/codacy/codacy_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage codacy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCodacy_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CODACY\")\n\tinactiveSecret := testSecrets.MustGetField(\"CODACY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a codacy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Codacy,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Codacy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Codacy.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Codacy.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/codacy/codacy_test.go",
    "content": "package codacy\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcodacy_token: \"g73RSmTIzTU1wUA5BXYI\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"g73RSmTIzTU1wUA5BXYI\"\n)\n\nfunc TestCodacy_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/codeclimate/codeclimate.go",
    "content": "package codeclimate\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"codeclimate\"}) + `\\b([a-f0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"codeclimate\"}\n}\n\ntype response struct {\n\tData struct {\n\t\tId string `json:\"id\"`\n\t} `json:\"data\"`\n}\n\n// FromData will find and optionally verify Codeclimate secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Codeclimate,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.codeclimate.com/v1/user\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.api+json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Token token=%s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\tvar r response\n\t\t\t\t\tif err := json.NewDecoder(res.Body).Decode(&r); err != nil {\n\t\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif r.Data.Id != \"\" {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Codeclimate\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Codeclimate is a tool for automated code review and analysis. Codeclimate tokens can be used to access and manage repositories and their analysis results.\"\n}\n"
  },
  {
    "path": "pkg/detectors/codeclimate/codeclimate_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage codeclimate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCodeclimate_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CODECLIMATE\")\n\tinactiveSecret := testSecrets.MustGetField(\"CODECLIMATE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a codeclimate secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Codeclimate,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Codeclimate,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Codeclimate.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Codeclimate.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/codeclimate/codeclimate_test.go",
    "content": "package codeclimate\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcodeclimate_token: \"efbc069555c703d31c3bcc6fbd426cec5f21eb43\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"efbc069555c703d31c3bcc6fbd426cec5f21eb43\"\n)\n\nfunc TestCodeClimate_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/codemagic/codemagic.go",
    "content": "package codemagic\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"codemagic\"}) + common.BuildRegex(common.AlphaNumPattern, \"_\", 43))\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"codemagic\"}\n}\n\n// FromData will find and optionally verify Codemagic secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Codemagic,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.codemagic.io/apps\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"x-auth-token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Codemagic\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/codemagic/codemagic_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage codemagic\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCodemagic_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CODEMAGIC\")\n\tinactiveSecret := testSecrets.MustGetField(\"CODEMAGIC_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a codemagic secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Codemagic,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Codemagic,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Codemagic.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Codemagic.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/codemagic/codemagic_test.go",
    "content": "package codemagic\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Auth-Key\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcodemagic_key: \"PSIYbVgfkbEPQoqJfzHpACTtihONkQ_cKmOpNDPNiCU\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"PSIYbVgfkbEPQoqJfzHpACTtihONkQ_cKmOpNDPNiCU\"\n)\n\nfunc TestCodeMagic_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/codequiry/codequiry.go",
    "content": "package codequiry\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"codequiry\"}) + `\\b([a-zA-Z-0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"codequiry\"}\n}\n\n// FromData will find and optionally verify Codequiry secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Codequiry,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://codequiry.com/api/v1/checks\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treq.Header.Add(\"apikey\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Codequiry\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Codequiry is a plagiarism detection service. Codequiry API keys can be used to access and utilize their plagiarism detection features.\"\n}\n"
  },
  {
    "path": "pkg/detectors/codequiry/codequiry_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage codequiry\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCodequiry_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CODEQUIRY\")\n\tinactiveSecret := testSecrets.MustGetField(\"CODEQUIRY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a codequiry secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Codequiry,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Codequiry,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Codequiry.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Codequiry.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/codequiry/codequiry_test.go",
    "content": "package codequiry\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcodequiry_key: \"7cA6eb3AvmlVSqLMP4iKvp7fXtEAADQud11KidjPzbSLcvntAD8CK6P7uGaGdlit\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"7cA6eb3AvmlVSqLMP4iKvp7fXtEAADQud11KidjPzbSLcvntAD8CK6P7uGaGdlit\"\n)\n\nfunc TestCodeQuiry_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/coinapi/coinapi.go",
    "content": "package coinapi\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"coinapi\"}) + `\\b([A-Z0-9-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"coinapi\"}\n}\n\n// FromData will find and optionally verify CoinApi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_CoinApi,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://rest.coinapi.io/v1/exchanges\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-CoinAPI-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CoinApi\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"CoinApi provides a RESTful API to access cryptocurrency market data. CoinApi keys can be used to fetch real-time and historical cryptocurrency data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/coinapi/coinapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage coinapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCoinApi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"COINAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"COINAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a coinapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CoinApi,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CoinApi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CoinApi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CoinApi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/coinapi/coinapi_test.go",
    "content": "package coinapi\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcoinapi_key: \"6D8B5AUIRDQCB3NRKSMZZL9RCV9G07GTHUR3\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"6D8B5AUIRDQCB3NRKSMZZL9RCV9G07GTHUR3\"\n)\n\nfunc TestCoinAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/coinbase/coinbase.go",
    "content": "package coinbase\n\nimport (\n\t\"context\"\n\t\"crypto/ecdsa\"\n\t\"crypto/rand\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\n\t\"github.com/golang-jwt/jwt/v5\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Reference: https://docs.cdp.coinbase.com/coinbase-app/docs/auth/api-key-authentication\n\tkeyNamePat    = 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`)\n\tprivateKeyPat = 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)?)`)\n\n\tapiHost              = \"api.coinbase.com\"\n\tverificationEndpoint = \"/v2/user\"\n\tverificationMethod   = http.MethodGet\n\tverificationURI      = fmt.Sprintf(\"https://%s%s\", apiHost, verificationEndpoint)\n\n\tnameReplacer = strings.NewReplacer(\"\\\\\", \"\")\n\tkeyReplacer  = strings.NewReplacer(\n\t\t\"\\r\\n\", \"\\n\",\n\t\t\"\\\\r\\\\n\", \"\\n\",\n\t\t\"\\\\n\", \"\\n\",\n\t\t\"\\\\r\", \"\\n\",\n\t)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"begin ec\"}\n}\n\nfunc isValidECPrivateKey(pemKey []byte) bool {\n\tblock, _ := pem.Decode(pemKey)\n\tif block == nil {\n\t\treturn false\n\t}\n\n\tkey, err := x509.ParseECPrivateKey(block.Bytes)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\t// Check the key type\n\t_, ok := key.Public().(*ecdsa.PublicKey)\n        return ok\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Coinbase secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueKeyNames, uniquePrivateKeys := map[string]struct{}{}, map[string]struct{}{}\n\n\tfor _, keyNameMatch := range keyNamePat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeyNames[keyNameMatch[1]] = struct{}{}\n\t}\n\n\tfor _, privateKeyMatch := range privateKeyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniquePrivateKeys[privateKeyMatch[1]] = struct{}{}\n\t}\n\n\tfor keyName := range uniqueKeyNames {\n\t\tfor privateKey := range uniquePrivateKeys {\n\t\t\tclient := s.getClient()\n\t\t\tresKeyName := nameReplacer.Replace(strings.TrimSpace(keyName))\n\t\t\tresPrivateKey := keyReplacer.Replace(strings.TrimSpace(privateKey))\n\n\t\t\tif !isValidECPrivateKey([]byte(resPrivateKey)) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Coinbase,\n\t\t\t\tRaw:          []byte(resPrivateKey),\n\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s\", resKeyName, resPrivateKey)),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, verificationErr := s.verifyMatch(ctx, client, resKeyName, resPrivateKey)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, resPrivateKey)\n\t\t\t}\n\t\t\tresults = append(results, s1)\n\n\t\t\t// 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.\n\t\t\tif s1.Verified {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) verifyMatch(ctx context.Context, client *http.Client, keyName, privateKey string) (bool, error) {\n\tjwtToken, err := buildJWT(verificationMethod, apiHost, verificationEndpoint, keyName, privateKey)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq, err := http.NewRequestWithContext(ctx, verificationMethod, verificationURI, http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", jwtToken))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code %d\", res.StatusCode)\n\t}\n}\n\n// Coinbase API requires the credentials encoded in a JWT token\n// The JWT token is signed with the private key and expires in 2 minutes\nfunc buildJWT(method, host, endpoint, keyName, key string) (string, error) {\n\t// Decode the PEM key\n\tpemStr := strings.ReplaceAll(key, `\\n`, \"\\n\")\n\tblock, _ := pem.Decode([]byte(pemStr))\n\tif block == nil || block.Type != \"EC PRIVATE KEY\" {\n\t\treturn \"\", fmt.Errorf(\"failed to decode PEM block containing EC private key\")\n\t}\n\n\tprivateKey, err := x509.ParseECPrivateKey(block.Bytes)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to parse EC private key: %v\", err)\n\t}\n\n\tnow := time.Now().Unix()\n\tclaims := jwt.MapClaims{\n\t\t\"sub\": keyName,\n\t\t\"iss\": \"cdp\",\n\t\t\"nbf\": now,\n\t\t\"exp\": now + 120,\n\t\t\"uri\": fmt.Sprintf(\"%s %s%s\", method, host, endpoint),\n\t}\n\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodES256, claims)\n\ttoken.Header[\"kid\"] = keyName\n\ttoken.Header[\"nonce\"] = fmt.Sprintf(\"%x\", makeNonce())\n\n\tsignedToken, err := token.SignedString(privateKey)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to sign JWT: %v\", err)\n\t}\n\n\treturn signedToken, nil\n}\n\nfunc makeNonce() []byte {\n\tnonce := make([]byte, 16) // 128-bit nonce\n\t_, _ = rand.Read(nonce)\n\treturn nonce\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Coinbase\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/coinbase/coinbase_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage coinbase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCoinbase_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tkeyName := testSecrets.MustGetField(\"COINBASE_KEY_NAME\")\n\tprivateKey := testSecrets.MustGetField(\"COINBASE_PRIVATE_KEY\")\n\tinactiveKeyName := testSecrets.MustGetField(\"COINBASE_INACTIVE_KEY_NAME\")\n\tinactivePrivateKey := testSecrets.MustGetField(\"COINBASE_INACTIVE_PRIVATE_KEY\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a coinbase secret %s %s within\", keyName, privateKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Coinbase,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Coinbase,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a coinbase secret %s %s within\", keyName, privateKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Coinbase,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(http.StatusInternalServerError, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a coinbase secret %s %s within\", keyName, privateKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Coinbase,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Coinbase.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Coinbase.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/coinbase/coinbase_test.go",
    "content": "package coinbase\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\nfunc TestCoinbase_Pattern(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tdata        string\n\t\tshouldMatch bool\n\t\tmatch       string\n\t}{\n\t\t// True positives\n\t\t// https://github.com/coinbase/waas-client-library-go/issues/41\n\t\t{\n\t\t\tname:        \"valid_result1\",\n\t\t\tdata:        `{ \"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\" }`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       \"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\",\n\t\t},\n\t\t// https://github.com/coinbase/waas-client-library-go/pull/32#issuecomment-1666415017\n\t\t{\n\t\t\tname: \"valid_result2_name_slashes\",\n\t\t\tdata: `{\n\t\t\t\t\"name\": \"organizations\\/d3f266dc-0d36-4cd0-91c3-e3a292b0b4b3\\/apiKeys\\/032c4fdf-d763-4b0c-9ed3-ff41a873bcc8\",\n\t\t\t\t\"principal\": \"5d5c9f00-3224-52a7-a1f7-9e6ce3ada40c\",\n\t\t\t\t\"principalType\": \"USER\",\n\t\t\t\t\"publicKey\": \"-----BEGIN EC PUBLIC KEY-----\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAjw43hwOqS2PF4gAFbhoxIJqCHAP\\niqLdg5GFVn9QAS/0oY4/fJGrCn9rpQGOvHxHf1mtQ6j4bIWN1AtHvA/3uw==\\n-----END EC PUBLIC KEY-----\\n\",\n\t\t\t\t\"privateKey\": \"-----BEGIN EC PRIVATE KEY-----\\nMHcCAQEEIFkA1kU4DlNu36wTTHycWy6n1rsUH0UT8mfAKNtOukXHoAoGCCqGSM49\\nAwEHoUQDQgAEAjw43hwOqS2PF4gAFbhoxIJqCHAPiqLdg5GFVn9QAS/0oY4/fJGr\\nCn9rpQGOvHxHf1mtQ6j4bIWN1AtHvA/3uw==\\n-----END EC PRIVATE KEY-----\\n\",\n\t\t\t\t\"createTime\": \"2023-08-05T06:34:40.265235553Z\",\n\t\t\t\t\"projectId\": \"64b3f391-c69d-4c59-91a2-75816c1a0738\"\n\t\t\t\t}`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       \"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\",\n\t\t},\n\t\t{\n\t\t\tname: \"valid_result3\",\n\t\t\tdata: `name: \"organizations/7eead2d5-fa48-4423-8f40-c70d8ce398ae/apiKeys/7b9516b6-d82e-44e8-bed5-89b160452ed9\",\n\t\tdescription: \"principal\": \"775fb863-004f-5412-8e4c-e9449c612563\" and install dependencies\n\t\t\n\t\truns:    \"principalType\": \"USER\",\n\t\t using: composite\n\t\t steps:\"publicKey\": \"-----BEGIN EC PUBLIC KEY-----\\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvHsvI08kox+n/8wSMFwCbK5hEf5b\\n/g82Lmz3HpATKFmrICcOBX2lRHo99JWRrupmjUGxnD8i4sj4mZafTEokhA==\\n-----END EC PUBLIC KEY-----\\n\",\n\t\t   - name: Setup Node.js\n\t\t     uses: actions/setup-node@v3\n\t\t     with: \"privateKey\": \"-----BEGIN EC PRIVATE KEY-----\\nMHcCAQEEIKOQ7lvGL0EiUzZ23pmH/NBPRwVV8yZsqofds5bSR9qFoAoGCCqGSM49\\nAwEHoUQDQgAEvHsvI08kox+n/8wSMFwCbK5hEf5b/g82Lmz3HpATKFmrICcOBX2l\\nRHo99JWRrupmjUGxnD8i4sj4mZafTEokhA==\\n-----END EC PRIVATE KEY-----\\n\",\n\t\t       node-version-file: .nvmrc\n\t\t\n\t\t   - name: Cache dependencies`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       \"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\",\n\t\t},\n\t\t{\n\t\t\tname: \"valid_result_ecdsa\",\n\t\t\tdata: `{\n    \"name\": \"organizations/7eead2d5-fa48-4423-8f40-c70d8ce398ae/apiKeys/7b9516b6-d82e-44e8-bed5-89b160452ed9\",\n    \"privateKey\": \"-----BEGIN ECDSA PRIVATE KEY-----\\nMHcCAQEEINQdZMbF2r07KF0mxfLYt9Y1PNaC0C6UpZ31MxD4NEE8oAoGCCqGSM49\\nAwEHoUQDQgAEeRFgMrQEHI/APWaziRH90jN7EozjdbPVxvzc1F4zqWTeCtLASwqA\\nqnMugYX2epqsFhGn82xNXu2NwgORc6embQ==\\n-----END ECDSA PRIVATE KEY-----\\n\"\n}`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       \"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\",\n\t\t},\n\t\t// TODO: Is it worth supporting case-insensitive headers?\n\t\t// https://github.com/coinbase/waas-sdk-react-native/blob/bbaf597e73d02ecaf64161061e71b85d9eeeb9d6/example/src/.coinbase_cloud_api_key.json#L4\n\t\t//\t\t{\n\t\t//\t\t\tname: \"valid_result_case_insensitive\",\n\t\t//\t\t\tdata: `{\n\t\t//    \"name\": \"organizations/7eead2d5-fa48-4423-8f40-c70d8ce398ae/apiKeys/7b9516b6-d82e-44e8-bed5-89b160452ed9\",\n\t\t//    \"privateKey\": \"-----BEGIN ECDSA private key-----\\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg8id7yCfmNp0ppczu\\nDhjB1pesdDB6Uwuz6KxARrenNfyhRANCAASI6DBntdr+XSOaK55J++x8ORuDxn81\\nENa0RmGFjTwu4vQcWcx5rrIWNh6b7FPxy6mrZl0n3rswEtZmUci8Y5HX\\n-----END ECDSA PRIVATE KEY-----\\n\"\n\t\t//}`,\n\t\t//\t\t\tshouldMatch: true,\n\t\t//\t\t\tmatch:       \"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\",\n\t\t//\t\t},\n\n\t\t// False positives\n\t\t// https://github.com/coinbase/waas-client-library-go/blob/main/example.go\n\t\t{\n\t\t\tname: `invalid_key_name1`,\n\t\t\tdata: `const (\n\t// apiKeyName is the name of the API Key to use. Fill this out before running the main function.\n\tapiKeyName = \"organizations/my-organization/apiKeys/my-api-key\"\n\n\t// privKeyTemplate is the private key of the API Key to use. Fill this out before running the main function.\n\tprivKeyTemplate = \"-----BEGIN EC PRIVATE KEY-----\\nmy-private-key\\n-----END EC PRIVATE KEY-----\\n\"\n)`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t// https://github.com/coinbase/waas-sdk-react-native/blob/bbaf597e73d02ecaf64161061e71b85d9eeeb9d6/example/src/.coinbase_cloud_api_key.json#L4\n\t\t{\n\t\t\tname: `invalid_key_name2`,\n\t\t\tdata: `{\n    \"name\": \"organizations/organizationID/apiKeys/apiKeyName\",\n    \"privateKey\": \"-----BEGIN ECDSA Private Key-----ExamplePrivateKey-----END ECDSA Private Key-----\\n\"\n}`,\n\t\t},\n\t\t{\n\t\t\tname: `invalid_private_key`,\n\t\t\tdata: `{ \"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\" }`,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\n\t\t\tresults, err := s.FromData(context.Background(), false, []byte(test.data))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Coinbase.FromData() error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif test.shouldMatch {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"%s: did not receive a match for '%v' when one was expected\", test.name, test.data)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\texpected := test.data\n\t\t\t\tif test.match != \"\" {\n\t\t\t\t\texpected = test.match\n\t\t\t\t}\n\t\t\t\tresult := results[0]\n\t\t\t\tresultData := string(result.RawV2)\n\t\t\t\tif resultData != expected {\n\t\t\t\t\tt.Errorf(\"%s: did not receive expected match.\\n\\texpected: '%s'\\n\\t  actual: '%s'\", test.name, expected, resultData)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif len(results) > 0 {\n\t\t\t\t\tt.Errorf(\"%s: received a match for '%v' when one wasn't wanted\", test.name, test.data)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/coinlayer/coinlayer.go",
    "content": "package coinlayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"coinlayer\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"coinlayer\"}\n}\n\n// FromData will find and optionally verify Coinlayer secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Coinlayer,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.coinlayer.com/api/livelive?access_key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `\"success\": true`) || strings.Contains(bodyString, `\"info\":\"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption.\"`)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Coinlayer\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Coinlayer provides real-time and historical cryptocurrency exchange rates. The API key can be used to access this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/coinlayer/coinlayer_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage coinlayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCoinlayer_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"COINLAYER\")\n\tinactiveSecret := testSecrets.MustGetField(\"COINLAYER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a coinlayer secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Coinlayer,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Coinlayer,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Coinlayer.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Coinlayer.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/coinlayer/coinlayer_test.go",
    "content": "package coinlayer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tbase_url: \"https://api.coinlayer.com/v1/user?key=gg2einqoe3zxu0ju7c3wqg9vql662vdj\"\n\t\t\tkey: \"\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"gg2einqoe3zxu0ju7c3wqg9vql662vdj\"\n)\n\nfunc TestCoinLayer_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/coinlib/coinlib.go",
    "content": "package coinlib\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"coinlib\"}) + `\\b([a-z0-9]{16})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"coinlib\"}\n}\n\n// FromData will find and optionally verify Coinlib secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Coinlib,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://coinlib.io/api/v1/global?key=%s&pref=EUR\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Coinlib\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Coinlib is a cryptocurrency data provider. Coinlib API keys can be used to access and retrieve cryptocurrency data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/coinlib/coinlib_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage coinlib\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCoinlib_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"COINLIB\")\n\tinactiveSecret := testSecrets.MustGetField(\"COINLIB_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a coinlib secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Coinlib,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Coinlib,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Coinlib.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Coinlib.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/coinlib/coinlib_test.go",
    "content": "package coinlib\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tbase_url: \"https://api.coinlib.com/v1/user?key=seugeupfknprstoe\"\n\t\t\tkey: \"\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"seugeupfknprstoe\"\n)\n\nfunc TestCoinLib_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/collect2/collect2.go",
    "content": "package collect2\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"collect2\"}\n}\n\n// FromData will find and optionally verify Collect2 secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Collect2,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://collect2.com/api/%s/datarecord/\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Collect2\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"An API to Collect, Modify, Filter and Export Data using webhooks. API keys can create read update and delete data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/collect2/collect2_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage collect2\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCollect2_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"COLLECT2\")\n\tinactiveSecret := testSecrets.MustGetField(\"COLLECT2_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a collect2 secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Collect2,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Collect2,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Collect2.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Collect2.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/collect2/collect2_test.go",
    "content": "package collect2\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tbase_url: \"https://api.collect2.com/v1/user?key=22f39f53-3bd4-d84b-8e29-00402d5c316f\"\n\t\t\tkey: \"\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"22f39f53-3bd4-d84b-8e29-00402d5c316f\"\n)\n\nfunc TestCollect2_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/column/column.go",
    "content": "package column\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"column\"}) + `\\b((?:test|live)_[a-zA-Z0-9]{27})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"column\"}\n}\n\n// FromData will find and optionally verify Column secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Column,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.column.com/entities\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// req.SetBasicAuth(resMatch, \"\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", base64.StdEncoding.EncodeToString([]byte(resMatch))))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else {\n\t\t\t\t\ts1.Verified = false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Column\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Column is a service used for managing entity data. Column keys can be used to access and modify this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/column/column_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage column\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestColumn_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"COLUMN_ACTIVE\")\n\tinactiveSecret := testSecrets.MustGetField(\"COLUMN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a column secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Column,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Column,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Column.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Column.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/column/column_test.go",
    "content": "package column\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Basic\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcolumn_key: \"live_ID8Jxlu0QRsV7rKkWI9CUDpkrUv\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"live_ID8Jxlu0QRsV7rKkWI9CUDpkrUv\"\n)\n\nfunc TestColumn_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/commercejs/commercejs.go",
    "content": "package commercejs\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"commercejs\"}) + `\\b([a-z0-9_]{48})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"commercejs\"}\n}\n\n// FromData will find and optionally verify CommerceJS secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_CommerceJS,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.chec.io/v1/categories\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-Authorization\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CommerceJS\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/commercejs/commercejs_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage commercejs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCommerceJS_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"COMMERCEJS\")\n\tinactiveSecret := testSecrets.MustGetField(\"COMMERCEJS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a commercejs secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CommerceJS,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CommerceJS,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CommerceJS.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CommerceJS.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/commercejs/commercejs_test.go",
    "content": "package commercejs\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"\"\n\t\t\tin: \"Header\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\t\t\tcommercejs_key: \"g6cl4jt_2noyibalgbqid4h58jxivqdgyyxovepbvqmbl7wq\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"g6cl4jt_2noyibalgbqid4h58jxivqdgyyxovepbvqmbl7wq\"\n)\n\nfunc TestCommerceJS_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/commodities/commodities.go",
    "content": "package commodities\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"commodities\"}) + `\\b([a-zA-Z0-9]{60})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"commodities\"}\n}\n\n// FromData will find and optionally verify Commodities secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Commodities,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient.Timeout = 5 * time.Second\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://commodities-api.com/api/latest?access_key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\tvalidResponse := strings.Contains(bodyString, `\"success\":true`)\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\tif validResponse {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Commodities\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Commodities API keys can be used to access and modify commodity data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/commodities/commodities_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage commodities\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCommodities_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"COMMODITIES\")\n\tinactiveSecret := testSecrets.MustGetField(\"COMMODITIES_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a commodities secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Commodities,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Commodities,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Commodities.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Commodities.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/commodities/commodities_test.go",
    "content": "package commodities\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tcommodities_key: \"5aJKBqkCyGCT9FIWNUTmowbzqgcm9DUCi60mHwgPQRBSt7dFahv9eY329Dn9\"\n\t\t\tbase_url: \"https://api.example.com/v1/user?access_key=$commodities_key\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"5aJKBqkCyGCT9FIWNUTmowbzqgcm9DUCi60mHwgPQRBSt7dFahv9eY329Dn9\"\n)\n\nfunc TestCommodities_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/companyhub/companyhub.go",
    "content": "package companyhub\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"companyhub\"}) + `\\b([0-9a-zA-Z]{20})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"companyhub\"}) + `\\b([a-zA-Z0-9$%^=-]{4,32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"companyhub\"}\n}\n\n// FromData will find and optionally verify CompanyHub secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_CompanyHub,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resIdMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.companyhub.com/v1/me\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"%s %s\", resIdMatch, resMatch))\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CompanyHub\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"CompanyHub is a CRM tool used to manage customer relationships. CompanyHub keys can be used to access and manipulate CRM data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/companyhub/companyhub_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage companyhub\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCompanyHub_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"COMPANYHUB_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"COMPANYHUB_INACTIVE\")\n\tuser := testSecrets.MustGetField(\"COMPANYHUB_USER\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a companyhub secret %s within companyhubuser %s\", secret, user)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CompanyHub,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CompanyHub,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CompanyHub.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CompanyHub.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/companyhub/companyhub_test.go",
    "content": "package companyhub\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"\"\n\t\t\tin: \"Header\"\n\t\t\tcompanyhub_key: \"A5zrYt9xY4X1Q9mG6IX6\"\n\t\t\tcompanyhub_id: \"xzAMMncOTR7^5d5NS6\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecrets = []string{\n\t\t\"A5zrYt9xY4X1Q9mG6IX6xzAMMncOTR7^5d5NS6\",\n\t\t\"A5zrYt9xY4X1Q9mG6IX6A5zrYt9xY4X1Q9mG6IX6\",\n\t}\n)\n\nfunc TestCompanyHub_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  secrets,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/confluent/confluent.go",
    "content": "package confluent\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"confluent\"}) + `\\b([a-zA-Z0-9]{16})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"confluent\"}) + `\\b([a-zA-Z0-9\\+\\/]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"confluent\"}\n}\n\n// FromData will find and optionally verify Confluent secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, secret := range secretMatches {\n\t\t\tresSecret := strings.TrimSpace(secret[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Confluent,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resSecret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tdata := fmt.Sprintf(\"%s:%s\", resMatch, resSecret)\n\t\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.confluent.cloud/iam/v2/api-keys/\"+resMatch, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Confluent\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/confluent/confluent_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage confluent\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestConfluent_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CONFLUENT_TOKEN\")\n\tkey := testSecrets.MustGetField(\"CONFLUENT_KEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"CONFLUENT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a confluent secret %s within confluent %s\", secret, key)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Confluent,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Confluent,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Confluent.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Confluent.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/confluent/confluent_test.go",
    "content": "package confluent\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Basic\"\n\t\t\tin: \"Header\"\n\t\t\tconfluent_key: \"CVAJHB4RAZboV3Od\"\n\t\t\tconfluent_secret: \"pIsdFuG0oJuyiir3GWqpC4pv7xpKFodCNh6PYN4XdE8EtyIwYtzEer0KtHQ8kofs\"\n\t\t\tbase_url: \"https://api.example.com/v1/user?api-key=$confluent_key\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"CVAJHB4RAZboV3OdpIsdFuG0oJuyiir3GWqpC4pv7xpKFodCNh6PYN4XdE8EtyIwYtzEer0KtHQ8kofs\"\n)\n\nfunc TestConfluent_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/contentfulpersonalaccesstoken/contentfulpersonalaccesstoken.go",
    "content": "package contentfulpersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\tkeyPat = regexp.MustCompile(`\\b(CFPAT-[a-zA-Z0-9_\\-]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"CFPAT-\"}\n}\n\n// FromData will find and optionally verify ContentfulDelivery secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range keyMatches {\n\t\tkeyRes := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ContentfulPersonalAccessToken,\n\t\t\tRaw:          []byte(keyRes),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.contentful.com/organizations\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", keyRes))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ContentfulPersonalAccessToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/contentfulpersonalaccesstoken/contentfulpersonalaccesstoken_test.go",
    "content": "package contentfulpersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tin: \"Header\"\n\t\t\tcontentful_access_token: \"CFPAT-\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n)\n\nfunc TestContentfulPersonalAccessToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - not found\",\n\t\t\tinput: validPattern,\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/contentfulpersonalaccesstoken/contentfulpersonalacesstoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage contentfulpersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestContentfulPersonalAccessToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CONTENTFULPERSONALACCESSTOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"CONTENTFULPERSONALACCESSTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a contentful secret %s within contentful https://api.contentful.com/organizations\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ContentfulPersonalAccessToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a contentful secret %s within but unverified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ContentfulPersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ContentfulPersonalAccessToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\n\t\t\t\tt.Errorf(\"ContentfulPersonalAccessToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/conversiontools/conversiontools.go",
    "content": "package conversiontools\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"conversiontools\"}) + `\\b(ey[a-zA-Z0-9_.]{157,165})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"conversiontools\"}\n}\n\n// FromData will find and optionally verify ConversionTools secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ConversionTools,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{  \"type\": \"convert.website_to_jpg\",  \"options\": {    \"url\": \"http://google.com\",    \"images\": \"yes\"  }}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.conversiontools.io/v1/tasks\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ConversionTools\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ConversionTools is a service used for various data conversion tasks. The API keys can be used to access and perform these tasks.\"\n}\n"
  },
  {
    "path": "pkg/detectors/conversiontools/conversiontools_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage conversiontools\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestConversionTools_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CONVERSIONTOOLS\")\n\tinactiveSecret := testSecrets.MustGetField(\"CONVERSIONTOOLS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a conversiontools secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ConversionTools,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ConversionTools,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ConversionTools.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ConversionTools.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/conversiontools/conversiontools_test.go",
    "content": "package conversiontools\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tin: \"Header\"\n\t\t\tconversiontools_key: \"ey5g5C73ichf2TWwQQfuPNG2SW1xdTmCHFgS6zsUjRz3kkiEofoa8X7SVGjwAMkhrv5KyOFqunP29gQpKq9A4sPF_Ps4B4IkTtgUG9cgP5A5ygAkuSR2rsOC.SIDSLIy4jZiL7L8ZHyAhyR8msV7JzxlI6YsNqmmj\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"ey5g5C73ichf2TWwQQfuPNG2SW1xdTmCHFgS6zsUjRz3kkiEofoa8X7SVGjwAMkhrv5KyOFqunP29gQpKq9A4sPF_Ps4B4IkTtgUG9cgP5A5ygAkuSR2rsOC.SIDSLIy4jZiL7L8ZHyAhyR8msV7JzxlI6YsNqmmj\"\n)\n\nfunc TestConversionTools_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/convertapi/convertapi.go",
    "content": "package convertapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"convertapi\"}) + `\\b(secret_[0-9a-zA-Z]{16})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"convertapi\"}\n}\n\n// FromData will find and optionally verify ConvertApi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ConvertApi,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://v2.convertapi.com/user?auth=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ConvertApi\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ConvertAPI is a service that provides file conversion capabilities via API. ConvertAPI keys can be used to access and perform file conversions.\"\n}\n"
  },
  {
    "path": "pkg/detectors/convertapi/convertapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage convertapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestConvertApi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CONVERTAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"CONVERTAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a convertapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ConvertApi,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ConvertApi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ConvertApi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ConvertApi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/convertapi/convertapi_test.go",
    "content": "package convertapi\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tconvertapi_key: \"secret_H9ZGTfAERfN5W0AX\"\n\t\t\tbase_url: \"https://api.example.com/v1/user?auth=$convertapi_key\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"secret_H9ZGTfAERfN5W0AX\"\n)\n\nfunc TestConvertAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/convertkit/convertkit.go",
    "content": "package convertkit\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"convertkit\"}) + `\\b([a-z0-9A-Z_]{22})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"convertkit\"}\n}\n\n// FromData will find and optionally verify Convertkit secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Convertkit,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.convertkit.com/v3/forms?api_key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Convertkit\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Convertkit is an email marketing service provider. API keys can be used to access and manage email marketing campaigns and subscriber data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/convertkit/convertkit_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage convertkit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestConvertkit_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CONVERTKIT_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"CONVERTKIT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a convertkit secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Convertkit,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Convertkit,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Convertkit.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Convertkit.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/convertkit/convertkit_test.go",
    "content": "package convertkit\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tconvertkit_key: \"hfCnuVcYOgiRjlDEmAoRbN\"\n\t\t\tbase_url: \"https://api.example.com/v1/forms?api-key=$convertapi_key\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"hfCnuVcYOgiRjlDEmAoRbN\"\n)\n\nfunc TestConvertKit_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/convier/convier.go",
    "content": "package convier\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"convier\"}) + `\\b([0-9]{2}\\|[a-zA-Z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"convier\"}\n}\n\n// FromData will find and optionally verify Convier secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Convier,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := 10 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://convier.me/api/event\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\tvalidResponse := strings.Contains(bodyString, `\"error\":false`)\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\tif validResponse {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Convier\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/convier/convier_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage convier\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestConvier_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CONVIER\")\n\tinactiveSecret := testSecrets.MustGetField(\"CONVIER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a convier secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Convier,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Convier,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Convier.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Convier.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/convier/convier_test.go",
    "content": "package convier\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tin: \"Header\"\n\t\t\tconvier_key: \"49|07KJBwfPzF2ESyNui5yBw9OVB6eWj0iXkssEKC7b\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"49|07KJBwfPzF2ESyNui5yBw9OVB6eWj0iXkssEKC7b\"\n)\n\nfunc TestConvier_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/copper/copper.go",
    "content": "package copper\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"copper\"}) + `\\b([a-z0-9]{32})\\b`)\n\tidPat  = regexp.MustCompile(`\\b([a-z0-9]{4,25}@[a-zA-Z0-9]{2,12}.[a-zA-Z0-9]{2,6})\\b`)\n)\n\ntype UserApiResponse struct {\n\tId    int    `json:\"id\"`\n\tEmail string `json:\"email\"`\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"copper\"}\n}\n\n// FromData will find and optionally verify Copper secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Copper,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resIdMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, verificationErr := verifyCopper(ctx, client, resIdMatch, resMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyCopper(ctx context.Context, client *http.Client, email, apiKey string) (bool, error) {\n\treq, err := http.NewRequestWithContext(\n\t\tctx,\n\t\thttp.MethodGet,\n\t\t\"https://api.copper.com/developer_api/v1/users/me\",\n\t\thttp.NoBody,\n\t)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"X-PW-AccessToken\", apiKey)\n\treq.Header.Add(\"X-PW-Application\", \"developer_api\")\n\treq.Header.Add(\"X-PW-UserEmail\", email)\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\trespBytes, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tvar respBody UserApiResponse\n\t\tif err := json.Unmarshal(respBytes, &respBody); err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\t// strict verification with email in credentials\n\t\tif respBody.Email == email {\n\t\t\treturn true, nil\n\t\t}\n\n\t\treturn false, fmt.Errorf(\"email mismatch in verification response\")\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code :%d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Copper\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/copper/copper_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage copper\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCopper_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"COOPER_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"COOPER_INACTIVE\")\n\tid := testSecrets.MustGetField(\"COOPER_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a copper secret %s within copperid %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Copper,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Copper,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Copper.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Abstract.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/copper/copper_test.go",
    "content": "package copper\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"\"\n\t\t\tin: \"Header\"\n\t\t\tcopper_email: \"s0ovh@P8I~p3\"\n\t\t\tcopper_token: \"noqs39jzqaegbam2k6mai9ov1uwsl21y\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"noqs39jzqaegbam2k6mai9ov1uwsl21ys0ovh@P8I~p3\"\n)\n\nfunc TestCopper_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/copy_metadata_test.go",
    "content": "package detectors\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nfunc TestCopyMetadata_ChunkDataFromOriginalData(t *testing.T) {\n\tchunk := &sources.Chunk{\n\t\tData:         []byte(\"decoded-data\"),\n\t\tOriginalData: []byte(\"original-source-data\"),\n\t\tSourceName:   \"test-source\",\n\t}\n\tresult := Result{\n\t\tDetectorType: 1,\n\t\tRaw:          []byte(\"secret\"),\n\t}\n\n\trwm := CopyMetadata(chunk, result)\n\n\tassert.Equal(t, \"original-source-data\", string(rwm.ChunkData))\n}\n\nfunc TestCopyMetadata_ChunkDataFallsBackToData(t *testing.T) {\n\tchunk := &sources.Chunk{\n\t\tData:       []byte(\"only-data\"),\n\t\tSourceName: \"test-source\",\n\t}\n\tresult := Result{\n\t\tDetectorType: 1,\n\t\tRaw:          []byte(\"secret\"),\n\t}\n\n\trwm := CopyMetadata(chunk, result)\n\n\tassert.Equal(t, \"only-data\", string(rwm.ChunkData))\n}\n"
  },
  {
    "path": "pkg/detectors/couchbase/couchbase.go",
    "content": "package couchbase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\t\"unicode\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/couchbase/gocb/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tconnectionStringPat = regexp.MustCompile(`\\b(cb\\.[a-z0-9]+\\.cloud\\.couchbase\\.com)\\b`)\n\tusernamePat         = common.UsernameRegexCheck(`?()/\\+=\\s\\n`)\n\tpasswordPat         = common.PasswordRegexCheck(`^<>;.*&|£\\n\\s`)\n)\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Couchbase\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Couchbase is a distributed NoSQL cloud database. Couchbase credentials can be used to access and modify data within the Couchbase database.\"\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"couchbase://\", \"couchbases://\"}\n}\n\n// FromData will find and optionally verify Couchbase secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueConnStrings, uniqueUsernames, uniquePasswords = make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, match := range connectionStringPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueConnStrings[\"couchbases://\"+match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range usernamePat.Matches(data) {\n\t\tuniqueUsernames[match] = struct{}{}\n\t}\n\n\tfor _, match := range passwordPat.Matches(data) {\n\t\tuniquePasswords[match] = struct{}{}\n\t}\n\n\tfor connString := range uniqueConnStrings {\n\t\tfor username := range uniqueUsernames {\n\t\t\tfor password := range uniquePasswords {\n\t\t\t\tif !isValidCouchbasePassword(password) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Couchbase,\n\t\t\t\t\tRaw:          fmt.Appendf([]byte(\"\"), \"%s:%s@%s\", username, password, connString),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tisVerified, verificationErr := verifyCouchBase(username, password, connString)\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t\t\ts1.SetPrimarySecretValue(connString)\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc verifyCouchBase(username, password, connString string) (bool, error) {\n\toptions := gocb.ClusterOptions{\n\t\tAuthenticator: gocb.PasswordAuthenticator{\n\t\t\tUsername: username,\n\t\t\tPassword: password,\n\t\t},\n\t}\n\n\t// Sets a pre-configured profile called \"wan-development\" to help avoid latency issues\n\t// when accessing Capella from a different Wide Area Network\n\t// or Availability Zone (e.g. your laptop).\n\tif err := options.ApplyProfile(gocb.ClusterConfigProfileWanDevelopment); err != nil {\n\t\treturn false, err\n\t}\n\n\t// Initialize the Connection\n\tcluster, err := gocb.Connect(connString, options)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// We'll ping the KV nodes in our cluster.\n\tpings, err := cluster.Ping(&gocb.PingOptions{\n\t\tTimeout: time.Second * 5,\n\t})\n\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tfor _, ping := range pings.Services {\n\t\tfor _, pingEndpoint := range ping {\n\t\t\tif pingEndpoint.State == gocb.PingStateOk {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false, nil\n}\n\nfunc isValidCouchbasePassword(password string) bool {\n\tvar hasLower, hasUpper, hasNumber, hasSpecialChar bool\n\n\tfor _, r := range password {\n\t\tswitch {\n\t\tcase unicode.IsLower(r):\n\t\t\thasLower = true\n\t\tcase unicode.IsUpper(r):\n\t\t\thasUpper = true\n\t\tcase unicode.IsNumber(r):\n\t\t\thasNumber = true\n\t\tcase unicode.IsPunct(r), unicode.IsSymbol(r):\n\t\t\thasSpecialChar = true\n\t\t}\n\t}\n\n\treturn hasLower && hasUpper && hasNumber && hasSpecialChar\n}\n"
  },
  {
    "path": "pkg/detectors/couchbase/couchbase_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage couchbase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCouchbase_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tendpoint := testSecrets.MustGetField(\"COUCHBASE_ENDPOINT\")\n\tusername := testSecrets.MustGetField(\"COUCHBASE_USERNAME\")\n\tpassword := testSecrets.MustGetField(\"COUCHBASE_PASSWORD\")\n\tinactiveSecret := testSecrets.MustGetField(\"COUCHBASE_INACTIVE_PASSWORD\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"db uri: %s \\n username = %s \\n password = %s\", endpoint, username, password)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Couchbase,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Couchbase,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Couchbase.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Couchbase.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/couchbase/couchbase_test.go",
    "content": "package couchbase\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestCouchBase_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t# Configuration File: config.yaml\n\t\t\t\tdatabase:\n\t\t\t\t\thost: $DB_HOST\n\t\t\t\t\tport: $DB_PORT\n\t\t\t\t\tusername: $DB_USERNAME\n\t\t\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\t\t\tapi:\n\t\t\t\t\tauth_type: \"Password\"\n\t\t\t\t\tin: \"Configuration\"\n\t\t\t\t\tcouchbase_domain: \"couchbases://cb.testing.cloud.couchbase.com\"\n\t\t\t\t\tcouchbase_username: \"usrpS@d>p\"\n\t\t\t\t\tcouchbase_password: \"passwordU+2028 rf\\@V[4,L/?2}\"\n\t\t\t\t\tbase_url: \"https://$couchbase_domain/v1/user\"\n\n\t\t\t\t# Notes:\n\t\t\t\t# - Remember to rotate the secret every 90 days.\n\t\t\t\t# - The above credentials should only be used in a secure environment.\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"usrpS@d>p:passwordU+2028@couchbases://cb.testing.cloud.couchbase.com\",\n\t\t\t\t\"$DB_USERNAME:passwordU+2028@couchbases://cb.testing.cloud.couchbase.com\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/countrylayer/countrylayer.go",
    "content": "package countrylayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"countrylayer\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"countrylayer\"}\n}\n\n// FromData will find and optionally verify CountryLayer secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_CountryLayer,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.countrylayer.com/v2/all?access_key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CountryLayer\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"CountryLayer is a service that provides information about countries. CountryLayer API keys can be used to access this information.\"\n}\n"
  },
  {
    "path": "pkg/detectors/countrylayer/countrylayer_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage countrylayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCountryLayer_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"COUNTRYLAYER\")\n\tinactiveSecret := testSecrets.MustGetField(\"COUNTRYLAYER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a countrylayer secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CountryLayer,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CountryLayer,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CountryLayer.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CountryLayer.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/countrylayer/countrylayer_test.go",
    "content": "package countrylayer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tcountrylayer_key: \"031eiaqplnq39py5ppsctxo6n2xj5t10\"\n\t\t\tbase_url: \"https://api.example.com/v1/user?access_key=$countrylayer_key\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"031eiaqplnq39py5ppsctxo6n2xj5t10\"\n)\n\nfunc TestCountryLayer_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/courier/courier.go",
    "content": "package courier\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"courier\"}) + `\\b(pk\\_[a-zA-Z0-9]{1,}\\_[a-zA-Z0-9]{28})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"courier\"}\n}\n\n// FromData will find and optionally verify Courier secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Courier,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.courier.com/preferences\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Courier\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/courier/courier_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage courier\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCourier_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"COURIER\")\n\tinactiveSecret := testSecrets.MustGetField(\"COURIER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a courier secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Courier,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Courier,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Courier.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Courier.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/courier/courier_test.go",
    "content": "package courier\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tin: \"Header\"\n\t\t\tcourier_key: \"pk_iHWk6NqTne0QthfSVF7uixZpa3OTYpA8hC6bIIavhluXfxz37FB_rHPqJNWh06HpNIOuokET4dfFzXS1\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"pk_iHWk6NqTne0QthfSVF7uixZpa3OTYpA8hC6bIIavhluXfxz37FB_rHPqJNWh06HpNIOuokET4dfFzXS1\"\n)\n\nfunc TestCourier_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/coveralls/coveralls.go",
    "content": "package coveralls\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"coveralls\"}) + `\\b([a-zA-Z0-9-]{37})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"coveralls\"}\n}\n\n// FromData will find and optionally verify Coveralls secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Coveralls,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://coveralls.io/api/repos/github/secretscanner02/scanner\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"token %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Coveralls\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/coveralls/coveralls_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage coveralls\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCoveralls_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"COVERALLS_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"COVERALLS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a coveralls secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Coveralls,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Coveralls,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Coveralls.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Coveralls.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/coveralls/coveralls_test.go",
    "content": "package coveralls\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"\"\n\t\t\tin: \"Header\"\n\t\t\tcoveralls_token: \"tPfhjkzKJyWtUdxDLYMjNDEfP7Yn9WvWb2-K3\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"tPfhjkzKJyWtUdxDLYMjNDEfP7Yn9WvWb2-K3\"\n)\n\nfunc TestCoveralls_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/craftmypdf/craftmypdf.go",
    "content": "package craftmypdf\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"craftmypdf\"}) + `\\b([0-9a-zA-Z]{35})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"craftmypdf\"}\n}\n\n// FromData will find and optionally verify CraftMyPDF secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_CraftMyPDF,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.craftmypdf.com/v1/get-account-info\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"X-API-KEY\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CraftMyPDF\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"CraftMyPDF is a service for generating PDFs from templates and data. CraftMyPDF API keys can be used to access and manage PDF generation tasks.\"\n}\n"
  },
  {
    "path": "pkg/detectors/craftmypdf/craftmypdf_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage craftmypdf\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCraftMyPDF_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CRAFTMYPDF\")\n\tinactiveSecret := testSecrets.MustGetField(\"CRAFTMYPDF_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a craftmypdf secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CraftMyPDF,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CraftMyPDF,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CraftMyPDF.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CraftMyPDF.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/craftmypdf/craftmypdf_test.go",
    "content": "package craftmypdf\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Header\"\n\t\t\tcraftmypdf_key: \"GuTSS3XQdT6fx00mxudKq7oj2CsieZCGmEc\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"GuTSS3XQdT6fx00mxudKq7oj2CsieZCGmEc\"\n)\n\nfunc TestCraftMyPDF_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/crowdin/crowdin.go",
    "content": "package crowdin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"crowdin\"}) + `\\b([0-9A-Za-z]{80})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"crowdin\"}\n}\n\n// FromData will find and optionally verify Crowdin secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Crowdin,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.crowdin.com/api/v2/storages\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Crowdin\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Crowdin is a cloud-based localization management platform. Crowdin API keys can be used to access and manage localization projects and resources.\"\n}\n"
  },
  {
    "path": "pkg/detectors/crowdin/crowdin_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage crowdin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCrowdin_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CROWDIN\")\n\tinactiveSecret := testSecrets.MustGetField(\"CROWDIN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a crowdin secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Crowdin,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Crowdin,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Crowdin.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Crowdin.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/crowdin/crowdin_test.go",
    "content": "package crowdin\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tin: \"Header\"\n\t\t\tcrowdin_token: \"BiIRgdPvboWwqlhQtlnCsM041zVYCJ5yMfgltWesDiu9bv1nuRtCEPewsDL3vgRFcp2qLemaPMa8L9g7\"\n\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"BiIRgdPvboWwqlhQtlnCsM041zVYCJ5yMfgltWesDiu9bv1nuRtCEPewsDL3vgRFcp2qLemaPMa8L9g7\"\n)\n\nfunc TestCrowDin_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cryptocompare/cryptocompare.go",
    "content": "package cryptocompare\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"cryptocompare\"}) + `\\b([a-z-0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"cryptocompare\"}\n}\n\n// FromData will find and optionally verify CryptoCompare secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_CryptoCompare,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://min-api.cryptocompare.com/data/blockchain/list?api_key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\terrCode := strings.Contains(bodyString, `\"Response\":\"Success\"`)\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\tif errCode {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CryptoCompare\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"CryptoCompare is a cryptocurrency market data provider. CryptoCompare API keys can be used to access and retrieve market data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/cryptocompare/cryptocompare_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage cryptocompare\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCryptoCompare_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CRYPTOCOMPARE\")\n\tinactiveSecret := testSecrets.MustGetField(\"CRYPTOCOMPARE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a cryptocompare secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CryptoCompare,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CryptoCompare,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CryptoCompare.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CryptoCompare.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/cryptocompare/cryptocompare_test.go",
    "content": "package cryptocompare\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Header\"\n\t\t\tcryptocompare_key: \"lx8zzovs5h15zl15mj224zks2v25re59965gz0l1z4jsc0bng33a75m5pf52-bvd\"\n\t\t\tbase_url: \"https://api.example.com/v1/user?api_key=$cryptocompare_key\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"lx8zzovs5h15zl15mj224zks2v25re59965gz0l1z4jsc0bng33a75m5pf52-bvd\"\n)\n\nfunc TestCryptoCompare_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/currencycloud/currencycloud.go",
    "content": "package currencycloud\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"currencycloud\"}) + `\\b([0-9a-z]{64})\\b`)\n\temailPat = regexp.MustCompile(common.EmailPattern)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"currencycloud\"}\n}\n\n// FromData will find and optionally verify Currencycloud secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tuniqueEmailMatches := make(map[string]struct{})\n\tfor _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor emailmatch := range uniqueEmailMatches {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_CurrencyCloud,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\t\t\tenvironments := []string{\"devapi\", \"api\"}\n\t\t\tif verify {\n\t\t\t\tfor _, env := range environments {\n\t\t\t\t\t// Get authentication token\n\t\t\t\t\tpayload := strings.NewReader(`{\"login_id\":\"` + emailmatch + `\",\"api_key\":\"` + resMatch + `\"`)\n\t\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://\"+env+\".currencycloud.com/v2/authenticate/api\", payload)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\t\tres, err := client.Do(req)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbody := string(bodyBytes)\n\t\t\t\t\t\tif strings.Contains(body, \"auth_token\") {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t\ts1.ExtraData = map[string]string{\"environment\": fmt.Sprintf(\"https://%s.currencycloud.com\", env)}\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CurrencyCloud\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/currencycloud/currencycloud_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage currencycloud\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCurrencycloud_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CURRENCYCLOUD\")\n\temail := testSecrets.MustGetField(\"SCANNERS_EMAIL\")\n\tinactiveSecret := testSecrets.MustGetField(\"CURRENCYCLOUD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a currencycloud secret %s within %s\", secret, email)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CurrencyCloud,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CurrencyCloud,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Currencycloud.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].ExtraData = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Currencycloud.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/currencycloud/currencycloud_test.go",
    "content": "package currencycloud\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b / testuser1005@example.com\"\n\tinvalidPattern = \"abcD123efg456HIJklmn789OPQ_rstUVWxYZ-012/testing@go\"\n)\n\nfunc TestCurrencyCloud_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: fmt.Sprintf(\"currencycloud: %s\", validPattern),\n\t\t\twant:  []string{\"1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"currencycloud keyword is not close to the real key and id = %s\", validPattern),\n\t\t\twant:  nil,\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"currencycloud: %s\", invalidPattern),\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 && test.want != nil {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"expected %d results, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/currencyfreaks/currencyfreaks.go",
    "content": "package currencyfreaks\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"currencyfreaks\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"currencyfreaks\"}\n}\n\n// FromData will find and optionally verify Currencyfreaks secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Currencyfreaks,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.currencyfreaks.com/latest?apikey=\"+resMatch+\"&format=xml\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Currencyfreaks\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Currencyfreaks provides exchange rates and currency conversion API services. The API keys can be used to access and retrieve exchange rate data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/currencyfreaks/currencyfreaks_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage currencyfreaks\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCurrencyfreaks_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CURRENCYFREAKS\")\n\tinactiveSecret := testSecrets.MustGetField(\"CURRENCYFREAKS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a currencyfreaks secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Currencyfreaks,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Currencyfreaks,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Currencyfreaks.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Currencyfreaks.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/currencyfreaks/currencyfreaks_test.go",
    "content": "package currencyfreaks\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Header\"\n\t\t\tcurrencyfreaks_key: \"6zlrpo4u8z4s72b2nqr54m9ehmqvwe8p\"\n\t\t\tbase_url: \"https://api.example.com/v1/user?apiKey=$currencyfreaks_key\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"6zlrpo4u8z4s72b2nqr54m9ehmqvwe8p\"\n)\n\nfunc TestCurrencyFreaks_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/currencylayer/currencylayer.go",
    "content": "package currencylayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"currencylayer\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"currencylayer\"}\n}\n\n// FromData will find and optionally verify Currencylayer secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Currencylayer,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.currencylayer.com/live?access_key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err2 := io.ReadAll(res.Body)\n\t\t\t\tif err2 == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `\"success\": true`) || strings.Contains(bodyString, `\"info\":\"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption.\"`)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Currencylayer\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"An API for converting and exchanging currencies. API keys can read currency data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/currencylayer/currencylayer_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage currencylayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCurrencylayer_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CURRENCYLAYER\")\n\tinactiveSecret := testSecrets.MustGetField(\"CURRENCYLAYER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a currencylayer secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Currencylayer,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Currencylayer,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Currencylayer.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Currencylayer.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/currencylayer/currencylayer_test.go",
    "content": "package currencylayer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tcurrencylayer_key: \"sxthwp257vpusfe4gr4d4awc794lkxvh\"\n\t\t\tbase_url: \"https://api.example.com/v1/user?access_key=$currencylayer_key\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"sxthwp257vpusfe4gr4d4awc794lkxvh\"\n)\n\nfunc TestCurrencyLayer_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/currencyscoop/currencyscoop.go",
    "content": "package currencyscoop\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"currencyscoop\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"currencyscoop\"}\n}\n\n// FromData will find and optionally verify Currencyscoop secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_CurrencyScoop,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.currencyscoop.com/v1/latest?api_key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CurrencyScoop\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"CurrencyScoop is a currency data service providing real-time and historical exchange rates. CurrencyScoop API keys can be used to access currency data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/currencyscoop/currencyscoop_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage currencyscoop\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCurrencyscoop_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CURRENCYSCOOP\")\n\tinactiveSecret := testSecrets.MustGetField(\"CURRENCYSCOOP_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a currencyscoop secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CurrencyScoop,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CurrencyScoop,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Currencyscoop.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Currencyscoop.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/currencyscoop/currencyscoop_test.go",
    "content": "package currencyscoop\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tcurrencyscoop_key: \"70x6tezndca5dqlm5tnn7s03bm6c27jt\"\n\t\t\tbase_url: \"https://api.example.com/v1/user?api_key=$currencylayer_key\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"70x6tezndca5dqlm5tnn7s03bm6c27jt\"\n)\n\nfunc TestCurrencyScoop_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/currentsapi/currentsapi.go",
    "content": "package currentsapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"currentsapi\"}) + `([a-zA-Z0-9_-]{48})`)\n)\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CurrentsAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"CurrentsAPI provides access to the latest news and trends. CurrentsAPI keys can be used to authenticate requests and retrieve news data.\"\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"currentsapi\"}\n}\n\n// FromData will find and optionally verify CurrentsAPI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueTokens = make(map[string]struct{})\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokens[match[1]] = struct{}{}\n\t}\n\n\tfor token := range uniqueTokens {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_CurrentsAPI,\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyCurrentsAPI(ctx, client, token)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, token)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyCurrentsAPI(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.currentsapi.services/v1/latest-news\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", token)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/currentsapi/currentsapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage currentsapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCurrentsAPI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CURRENTSAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"CURRENTSAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a currentsapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CurrentsAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CurrentsAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CurrentsAPI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CurrentsAPI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/currentsapi/currentsapi_test.go",
    "content": "package currentsapi\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestCurrentsAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t\t# Configuration File: config.yaml\n\t\t\t\t\tdatabase:\n\t\t\t\t\t\thost: $DB_HOST\n\t\t\t\t\t\tport: $DB_PORT\n\t\t\t\t\t\tusername: $DB_USERNAME\n\t\t\t\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\t\t\t\tapi:\n\t\t\t\t\t\tauth_type: \"\"\n\t\t\t\t\t\tin: \"Header\"\n\t\t\t\t\t\tcurrentsapi_key: \"P1ctBOMKKnSnc43K6z5E1IiPp0Q46BTrf62UHJBTcC2qkCGE\"\n\t\t\t\t\t\tbase_url: \"https://api.example.com/v1/user\"\n\n\t\t\t\t\t# Notes:\n\t\t\t\t\t# - Remember to rotate the secret every 90 days.\n\t\t\t\t\t# - The above credentials should only be used in a secure environment.\n\t\t\t\t`,\n\t\t\twant: []string{\"P1ctBOMKKnSnc43K6z5E1IiPp0Q46BTrf62UHJBTcC2qkCGE\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n  \t\t\t\t\t<scope>GLOBAL</scope>\n  \t\t\t\t\t<id>{currentsapi}</id>\n  \t\t\t\t\t<secret>{AQAAABAAA -WE1-BwePKJJwiRN0lZ_qBe4WpZpgeAeYy281o5nImlhqaxG}</secret>\n  \t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n  \t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"-WE1-BwePKJJwiRN0lZ_qBe4WpZpgeAeYy281o5nImlhqaxG\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/customerguru/customerguru.go",
    "content": "package customerguru\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"guru\"}) + `\\b([a-z0-9A-Z]{30})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"guru\"}) + `\\b([a-z0-9A-Z]{50})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"customerguru\"}\n}\n\n// FromData will find and optionally verify CustomerGuru secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_CustomerGuru,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resIdMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://customer.guru/export/customers?api_secret=\"+resIdMatch+\"&api_token=\"+resMatch, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CustomerGuru\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/customerguru/customerguru_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage customerguru\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCustomerGuru_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CUSTOMERGURU_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"CUSTOMERGURU_INACTIVE\")\n\tkey := testSecrets.MustGetField(\"CUSTOMERGURU_KEY\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a customerguru secret %s within customergurukey %s\", secret, key)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CustomerGuru,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CustomerGuru,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CustomerGuru.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CustomerGuru.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/customerguru/customerguru_test.go",
    "content": "package customerguru\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tguru_key: \"WWj2zAK0tMkVJqc28Itfu6THQycyfT\"\n\t\t\tguru_id: \"Ic53IHpPK71wIacbCgEkIlFbw0VIMcsz6ir2i2DJ0XDRdirf2K\"\n\t\t\tbase_url: \"https://api.customerguru.com/v1/user?api_secret=$guru_id&api_token=guru_key\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"WWj2zAK0tMkVJqc28Itfu6THQycyfTIc53IHpPK71wIacbCgEkIlFbw0VIMcsz6ir2i2DJ0XDRdirf2K\"\n)\n\nfunc TestCustomerGuru_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/customerio/customerio.go",
    "content": "package customerio\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"customer\"}) + `\\b([a-z0-9A-Z]{20})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"customer\"}) + `\\b([a-z0-9A-Z]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"customerio\"}\n}\n\n// FromData will find and optionally verify CustomerIO secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_CustomerIO,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resIdMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tpayload := strings.NewReader(\"name=purchase&data%5Bprice%5D=23.45&data%5Bproduct%5D=socks\")\n\n\t\t\t\tdata := fmt.Sprintf(\"%s:%s\", resIdMatch, resMatch)\n\t\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://track.customer.io/api/v1/customers/5/events\", payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_CustomerIO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/customerio/customerio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage customerio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestCustomerIO_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"CUSTOMERIO_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"CUSTOMERIO_INACTIVE\")\n\tid := testSecrets.MustGetField(\"CUSTOMERIO_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a customerio secret %s within customerid %s \", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CustomerIO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_CustomerIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CustomerIO.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"CustomerIO.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/customerio/customerio_test.go",
    "content": "package customerio\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Basic\"\n\t\t\tin: \"Header\"\n\t\t\tcustomerio_key: \"bXQLU0kcl0A7kxCErc3L\"\n\t\t\tcustomerio_id: \"tM2JFc8pmKHUmkdwhmgG\"\n\t\t\tbase_url: \"https://api.example.com/v1/user?access_key=$currencylayer_key\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecrets = []string{\n\t\t\"bXQLU0kcl0A7kxCErc3LbXQLU0kcl0A7kxCErc3L\",\n\t\t\"bXQLU0kcl0A7kxCErc3LtM2JFc8pmKHUmkdwhmgG\",\n\t\t\"tM2JFc8pmKHUmkdwhmgGbXQLU0kcl0A7kxCErc3L\",\n\t\t\"tM2JFc8pmKHUmkdwhmgGtM2JFc8pmKHUmkdwhmgG\",\n\t}\n)\n\nfunc TestCustomerio_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  secrets,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/d7network/d7network.go",
    "content": "package d7network\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"d7network\"}) + `\\b([a-zA-Z0-9\\W\\S]{23}\\=)`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"d7network\"}\n}\n\n// FromData will find and optionally verify D7Network secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_D7Network,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://rest-api.d7networks.com/secure/balance\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", \"Basic \"+resMatch)\n\t\t\tres, err := detectors.DetectorHttpClientWithNoLocalAddresses.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_D7Network\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"D7Network provides messaging services through their API. The credentials can be used to send SMS and other types of messages via their platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/d7network/d7network_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage d7network\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestD7Network_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"D7NETWORK_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"D7NETWORK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a d7network secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_D7Network,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_D7Network,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"D7Network.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"D7Network.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/d7network/d7network_test.go",
    "content": "package d7network\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Basic\"\n\t\t\tin: \"Header\"\n\t\t\td7network_secret: \"u@D7GXt)t>8d(LtH^(lvZ<g=\"\n\t\t\tbase_url: \"https://api.example.com/v1/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"u@D7GXt)t>8d(LtH^(lvZ<g=\"\n)\n\nfunc TestD7Network_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dailyco/dailyco.go",
    "content": "package dailyco\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"daily\"}) + `\\b([0-9a-f]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"daily\"}\n}\n\n// FromData will find and optionally verify DailyCO secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_DailyCO,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.daily.co/v1/rooms\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_DailyCO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/dailyco/dailyco_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage dailyco\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDailyCO_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DAILYCO\")\n\tinactiveSecret := testSecrets.MustGetField(\"DAILYCO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a dailyco secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DailyCO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DailyCO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"DailyCO.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"DailyCO.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dailyco/dailyco_test.go",
    "content": "package dailyco\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tin: \"Header\"\n\t\t\tdailyco_secret: \"40842f16899170ffaf4e8ea99c68e748fac5e9ee5d675dd06fbe0c300a8f291a\"\n\t\t\tbase_url: \"https://api.example.com/v1/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"40842f16899170ffaf4e8ea99c68e748fac5e9ee5d675dd06fbe0c300a8f291a\"\n)\n\nfunc TestDailyCo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dandelion/dandelion.go",
    "content": "package dandelion\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"dandelion\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"dandelion\"}\n}\n\n// FromData will find and optionally verify Dandelion secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Dandelion,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.dandelion.eu/datatxt/li/v1/?text=Smart&token=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Dandelion\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Dandelion is a text analysis service. Dandelion tokens can be used to access and analyze text data using their APIs.\"\n}\n"
  },
  {
    "path": "pkg/detectors/dandelion/dandelion_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage dandelion\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDandelion_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DANDELION\")\n\tinactiveSecret := testSecrets.MustGetField(\"DANDELION_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a dandelion secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dandelion,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dandelion,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Dandelion.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Dandelion.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dandelion/dandelion_test.go",
    "content": "package dandelion\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Header\"\n\t\t\tdandelion_secret: \"xccl325526f9cp6qzh89qkgoklje5ds9\"\n\t\t\tbase_url: \"https://api.example.com/v1/example?token=$dandelion_secret\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"xccl325526f9cp6qzh89qkgoklje5ds9\"\n)\n\nfunc TestDandelion_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dareboost/dareboost.go",
    "content": "package dareboost\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"dareboost\"}) + `\\b([0-9a-zA-Z]{60})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"dareboost\"}\n}\n\n// FromData will find and optionally verify Dareboost secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Dareboost,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{    \"token\": \"` + resMatch + `\",    \"location\": \"Paris\"}`)\n\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.dareboost.com/0.8/config\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\tvalidResponse := strings.Contains(bodyString, `\"status\":200`)\n\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\tif validResponse {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Dareboost\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Dareboost is a website performance monitoring tool. Dareboost API keys can be used to access and modify performance monitoring configurations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/dareboost/dareboost_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage dareboost\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDareboost_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DAREBOOST\")\n\tinactiveSecret := testSecrets.MustGetField(\"DAREBOOST_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a dareboost secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dareboost,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dareboost,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Dareboost.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Dareboost.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dareboost/dareboost_test.go",
    "content": "package dareboost\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"\"\n\t\t\tin: \"Body\"\n\t\t\tdareboost_secret: \"fS6aBVkb0qpOje4VED8OhKqGGNdNVUuDhdBi9fTvxwIRMNK2uyd68WlPa1X5\"\n\t\t\tbody: {\"payload\":$dareboost_secret}\n\t\t\tbase_url: \"https://api.example.com/v1/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"fS6aBVkb0qpOje4VED8OhKqGGNdNVUuDhdBi9fTvxwIRMNK2uyd68WlPa1X5\"\n)\n\nfunc TestDareBoost_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/databox/databox.go",
    "content": "package databox\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"databox\"}) + common.BuildRegex(common.RegexPattern, \"\", 21))\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"databox\"}\n}\n\n// FromData will find and optionally verify Databox secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Databox,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\t\tif verify {\n\t\t\tdata := fmt.Sprintf(\"%s:\", resMatch)\n\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\tpayload := strings.NewReader(`{\n\t\t\t\t\"data\":[\n\t\t\t\t  {\n\t\t\t\t\t\"$sales\": 420,\n\t\t\t\t\t\"$visitors\": 123000\n\t\t\t\t  }\n\t\t\t\t]\n\t\t\t  }`)\n\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://push.databox.com\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.databox.v2+json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Databox\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/databox/databox_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage databox\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDatabox_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DATABOX\")\n\tinactiveSecret := testSecrets.MustGetField(\"DATABOX_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a databox secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Databox,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Databox,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Databox.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Databox.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/databox/databox_test.go",
    "content": "package databox\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Basic\"\n\t\t\tin: \"Header\"\n\t\t\tdatabox_secret: \"arjrvgzxx20sivy4rigjs\"\n\t\t\tbase_url: \"https://api.example.com/v1/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"arjrvgzxx20sivy4rigjs\"\n)\n\nfunc TestDataBox_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/databrickstoken/databrickstoken.go",
    "content": "package databrickstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tdomain = regexp.MustCompile(`\\b([a-z0-9-]+(?:\\.[a-z0-9-]+)*\\.(cloud\\.databricks\\.com|gcp\\.databricks\\.com|azuredatabricks\\.net))\\b`)\n\tkeyPat = regexp.MustCompile(`\\b(dapi[0-9a-f]{32}(-\\d)?)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"databricks\", \"dapi\"}\n}\n\n// FromData will find and optionally verify Databrickstoken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueDomains, uniqueTokens = make(map[string]struct{}), make(map[string]struct{})\n\tfor _, match := range domain.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueDomains[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokens[match[1]] = struct{}{}\n\t}\n\n\tfor token := range uniqueTokens {\n\t\tfor domain := range uniqueDomains {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_DatabricksToken,\n\t\t\t\tRaw:          []byte(token),\n\t\t\t\tRawV2:        []byte(token + domain),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, verificationErr := verifyDatabricksToken(client, domain, token)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\n\t\t\t\tif s1.Verified {\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\"token\":  token,\n\t\t\t\t\t\t\"domain\": domain,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_DatabricksToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Databricks is a cloud data platform. Databricks tokens can be used to authenticate and interact with Databricks services and APIs.\"\n}\n\nfunc verifyDatabricksToken(client *http.Client, domain, token string) (bool, error) {\n\treq, err := http.NewRequest(http.MethodGet, \"https://\"+domain+\"/api/2.0/preview/scim/v2/Me\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/databrickstoken/databrickstoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage databrickstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDatabricksToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DATABRICKSTOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"DATABRICKSTOKEN_INACTIVE\")\n\tdomain := testSecrets.MustGetField(\"DATABRICKSTOKEN_DOMAIN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a databrickstoken secret %s within %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DatabricksToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DatabricksToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a databrickstoken secret %s within %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DatabricksToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a databrickstoken secret %s within %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DatabricksToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Databrickstoken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"AnalysisInfo\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"DatabricksToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/databrickstoken/databrickstoken_test.go",
    "content": "package databrickstoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tin: \"Header\"\n\t\t\tsecret: \"dapib8a799e452bf722cb28874cee50a7abf\"\n\t\t\tdomain: \"nonprod-test.cloud.databricks.com\"\n\t\t\tbase_url: \"https://$domain/v1/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"dapib8a799e452bf722cb28874cee50a7abfnonprod-test.cloud.databricks.com\"\n)\n\nfunc TestDataBrickStoken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/datadogapikey/datadogapikey.go",
    "content": "package datadogapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\tregexp \"github.com/wasilibs/go-re2\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.EndpointSetter\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.EndpointCustomizer = (*Scanner)(nil)\nvar _ detectors.CloudProvider = (*Scanner)(nil)\n\nfunc (Scanner) CloudEndpoint() string { return \"https://api.datadoghq.com\" }\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tapiKeyPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"datadog\", \"dd\"}) + `\\b([a-zA-Z0-9-]{32})\\b`)\n\tdatadogURLPat = regexp.MustCompile(`\\b(api(?:\\.[a-z0-9-]+)?\\.(?:datadoghq|ddog-gov)\\.(com|eu))\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"datadog\", \"ddog-gov\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn client\n}\n\n// FromData will find and optionally verify DatadogToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tapiMatches := apiKeyPat.FindAllStringSubmatch(dataStr, -1)\n\tvar uniqueFoundUrls = make(map[string]struct{})\n\tfor _, matches := range datadogURLPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueFoundUrls[matches[1]] = struct{}{}\n\t}\n\tendpoints := make([]string, 0, len(uniqueFoundUrls))\n\tfor endpoint := range uniqueFoundUrls {\n\t\tendpoints = append(endpoints, \"https://\"+endpoint)\n\t}\n\n\tfor _, apiMatch := range apiMatches {\n\t\tresApiMatch := strings.TrimSpace(apiMatch[1])\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_DatadogApikey,\n\t\t\tRaw:          []byte(resApiMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tfor _, baseURL := range s.Endpoints(endpoints...) {\n\t\t\t\tclient := s.getClient()\n\t\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, resApiMatch, baseURL)\n\t\t\t\tif isVerified {\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\"api_key\": resApiMatch, \"endpoint\": baseURL}\n\t\t\t\t\t// break the loop once we've successfully validated the token against a baseURL\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\ts1.SetVerificationError(verificationErr, resApiMatch)\n\t\t\t}\n\t\t}\n\t\tresults = append(results, s1)\n\t}\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, apiKey, baseUrl string) (bool, error) {\n\t// Reference: https://docs.datadoghq.com/api/latest/authentication/\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, baseUrl+\"/api/v1/validate\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"DD-API-KEY\", apiKey)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusForbidden:\n\t\treturn false, nil\n\tcase http.StatusTooManyRequests:\n\t\treturn false, fmt.Errorf(\"too many requests\")\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_DatadogApikey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/datadogapikey/datadogapikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage datadogapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDataDogApiKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tapiKey := testSecrets.MustGetField(\"DATADOGTOKEN_TOKEN\")\n\tinvalidApiKey := \"FKNwdbyfYTmGUm5DK3yHEuK-BBQf0fVG\"\n\tdatdogEndpoint := \"https://api.us5.datadoghq.com\"\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a datadogtoken secret within datadog %s and endpoint is %v\", apiKey, datdogEndpoint)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DatadogApikey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tAnalysisInfo: map[string]string{\n\t\t\t\t\t\t\"api_key\":  apiKey,\n\t\t\t\t\t\t\"endpoint\": datdogEndpoint,\n\t\t\t\t\t},\n\t\t\t\t\tRaw: []byte(apiKey),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a datadogtoken secret within datadog %s and endpoint is %v\", invalidApiKey, datdogEndpoint)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DatadogApikey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(invalidApiKey),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\n\t\t\t// use default cloud endpoint\n\t\t\ts.UseCloudEndpoint(true)\n\t\t\ts.SetCloudEndpoint(s.CloudEndpoint())\n\t\t\ts.UseFoundEndpoints(true)\n\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"DatadogToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"DatadogToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/datadogapikey/datadogapikey_test.go",
    "content": "package datadogapikey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDataDogApiKey_Pattern_WithValidAPIKey(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\tinput := `\n\t\t\tdd_api_secret: \"FKNwdbyfYTmGUm5DK3yHEuK-BBQf0fVG\"\n\t\t\tdd_app: \"iHxNanzZ8vjrmbjXK7NJLrwpGw2czdSh90PKH6VL\"\n\t\t\tbase_url1: \"https://api.us5.datadoghq.com\"\n\t\t\tbase_url2: \"https://api.app.ddog-gov.com\"\n\t`\n\tapiKey := \"FKNwdbyfYTmGUm5DK3yHEuK-BBQf0fVG\"\n\twantedResult := []detectors.Result{\n\t\t{\n\t\t\tDetectorType: detectorspb.DetectorType_DatadogApikey,\n\t\t\tRaw:          []byte(apiKey),\n\t\t},\n\t}\n\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(input))\n\tif len(matchedDetectors) == 0 {\n\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), input)\n\t\treturn\n\t}\n\tresults, err := d.FromData(context.Background(), false, []byte(input))\n\tif err != nil {\n\t\tt.Errorf(\"error = %v\", err)\n\t\treturn\n\t}\n\n\tif diff := cmp.Diff(wantedResult, results, cmpopts.IgnoreFields(detectors.Result{}, \"verificationError\", \"primarySecret\")); diff != \"\" {\n\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", \"TestDataDogApiKey_Pattern_WithValidAPIKeyOnly\", diff)\n\t}\n}\n\nfunc TestDataDogApiKey_NoSecrets(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\tinput := `\n\t\t\tbase_url1: \"https://api.us5.datadoghq.com\"\n\t\t\tbase_url2: \"https://api.app.ddog-gov.com\"\n\t`\n\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(input))\n\tif len(matchedDetectors) == 0 {\n\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), input)\n\t\treturn\n\t}\n\tresults, err := d.FromData(context.Background(), false, []byte(input))\n\tif err != nil {\n\t\tt.Errorf(\"error = %v\", err)\n\t\treturn\n\t}\n\tif len(results) != 0 {\n\t\tt.Errorf(\"expected 0 results, received %d\", len(results))\n\t}\n}\n\nfunc TestDataDogApiKey_InvalidSecrets(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\tinput := `\n\t\t\tdd_api_secret: \"@FKNwdbyfYTmGUm5DK3yHEuK\"\n\t\t\tdd_app: \"iHxNanzZ8vjrmbjXK7NJLrwpGw2czdSh90PKH6VL\"\n\t\t\tbase_url1: \"https://api.us5.datadoghq.com\"\n\t\t\tbase_url2: \"https://api.app.ddog-gov.com\"\n\t`\n\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(input))\n\tif len(matchedDetectors) == 0 {\n\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), input)\n\t\treturn\n\t}\n\tresults, err := d.FromData(context.Background(), false, []byte(input))\n\tif err != nil {\n\t\tt.Errorf(\"error = %v\", err)\n\t\treturn\n\t}\n\tif len(results) != 0 {\n\t\tt.Errorf(\"expected 0 results, received %d\", len(results))\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/datadogtoken/datadogtoken.go",
    "content": "package datadogtoken\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.EndpointSetter\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.EndpointCustomizer = (*Scanner)(nil)\nvar _ detectors.CloudProvider = (*Scanner)(nil)\n\nfunc (Scanner) CloudEndpoint() string { return \"https://api.datadoghq.com\" }\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tappPat        = regexp.MustCompile(detectors.PrefixRegex([]string{\"datadog\", \"dd\"}) + `\\b([a-zA-Z-0-9]{40})\\b`)\n\tapiPat        = regexp.MustCompile(detectors.PrefixRegex([]string{\"datadog\", \"dd\"}) + `\\b([a-zA-Z-0-9]{32})\\b`)\n\tdatadogURLPat = regexp.MustCompile(`\\b(api(?:\\.[a-z0-9-]+)?\\.(?:datadoghq|ddog-gov)\\.(com|eu))\\b`)\n)\n\ntype userServiceResponse struct {\n\tData     []*user    `json:\"data\"`\n\tIncluded []*options `json:\"included\"`\n}\n\ntype user struct {\n\tAttributes userAttributes `json:\"attributes\"`\n}\n\ntype userAttributes struct {\n\tEmail            string `json:\"email\"`\n\tIsServiceAccount bool   `json:\"service_account\"`\n\tVerified         bool   `json:\"verified\"`\n\tDisabled         bool   `json:\"disabled\"`\n}\n\ntype options struct {\n\tType       string          `json:\"type\"`\n\tAttributes optionAttribute `json:\"attributes\"`\n}\n\ntype optionAttribute struct {\n\tUrl      string `json:\"url\"`\n\tName     string `json:\"name\"`\n\tDisabled bool   `json:\"disabled\"`\n}\n\nfunc setUserEmails(data []*user, s1 *detectors.Result) {\n\tvar emails []string\n\tfor _, user := range data {\n\t\t// filter out non verified emails, disabled emails, service accounts\n\t\tif user.Attributes.Verified && !user.Attributes.Disabled && !user.Attributes.IsServiceAccount {\n\t\t\temails = append(emails, user.Attributes.Email)\n\t\t}\n\t}\n\n\tif len(emails) == 0 && len(data) > 0 {\n\t\temails = append(emails, data[0].Attributes.Email)\n\t}\n\n\ts1.ExtraData[\"user_emails\"] = strings.Join(emails, \", \")\n}\n\nfunc setOrganizationInfo(opt []*options, s1 *detectors.Result) {\n\tvar orgs *options\n\tfor _, option := range opt {\n\t\tif option.Type == \"orgs\" && !option.Attributes.Disabled {\n\t\t\torgs = option\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif orgs != nil {\n\t\ts1.ExtraData[\"org_name\"] = orgs.Attributes.Name\n\t\ts1.ExtraData[\"org_url\"] = orgs.Attributes.Url\n\t}\n\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"datadog\", \"ddog-gov\"}\n}\n\n// FromData will find and optionally verify DatadogToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tappMatches := appPat.FindAllStringSubmatch(dataStr, -1)\n\tapiMatches := apiPat.FindAllStringSubmatch(dataStr, -1)\n\n\tvar uniqueFoundUrls = make(map[string]struct{})\n\tfor _, matches := range datadogURLPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueFoundUrls[\"https://\"+matches[1]] = struct{}{}\n\t}\n\tendpoints := make([]string, 0, len(uniqueFoundUrls))\n\tfor endpoint := range uniqueFoundUrls {\n\t\tendpoints = append(endpoints, endpoint)\n\t}\n\n\tfor _, apiMatch := range apiMatches {\n\t\tresApiMatch := strings.TrimSpace(apiMatch[1])\n\t\tfor _, appMatch := range appMatches {\n\t\t\tresAppMatch := strings.TrimSpace(appMatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_DatadogToken,\n\t\t\t\tRaw:          []byte(resAppMatch),\n\t\t\t\tRawV2:        []byte(resAppMatch + resApiMatch),\n\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\"Type\": \"Application+APIKey\",\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tfor _, baseURL := range s.Endpoints(endpoints...) {\n\t\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", baseURL+\"/api/v2/users\", nil)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\t\treq.Header.Add(\"DD-API-KEY\", resApiMatch)\n\t\t\t\t\treq.Header.Add(\"DD-APPLICATION-KEY\", resAppMatch)\n\t\t\t\t\tres, err := client.Do(req)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t\ts1.AnalysisInfo = map[string]string{\"api_key\": resApiMatch, \"app_key\": resAppMatch, \"endpoint\": baseURL}\n\t\t\t\t\t\t\tvar serviceResponse userServiceResponse\n\t\t\t\t\t\t\tif err := json.NewDecoder(res.Body).Decode(&serviceResponse); err == nil {\n\t\t\t\t\t\t\t\t// setup emails\n\t\t\t\t\t\t\t\tif len(serviceResponse.Data) > 0 {\n\t\t\t\t\t\t\t\t\tsetUserEmails(serviceResponse.Data, &s1)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// setup organizations\n\t\t\t\t\t\t\t\tif len(serviceResponse.Included) > 0 {\n\t\t\t\t\t\t\t\t\tsetOrganizationInfo(serviceResponse.Included, &s1)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// break the loop once we've successfully validated the token against a baseURL\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_DatadogToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/datadogtoken/datadogtoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage datadogtoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDatadogToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tapiKey := testSecrets.MustGetField(\"DATADOGTOKEN_TOKEN\")\n\tappKey := testSecrets.MustGetField(\"DATADOGTOKEN_APPKEY\")\n\tinactiveAppKey := testSecrets.MustGetField(\"DATADOGTOKEN_INACTIVE\")\n\tendpoint := \"https://api.us5.datadoghq.com\"\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a datadogtoken secret %s within datadog %s and endpoint %s\", appKey, apiKey, endpoint)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DatadogToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"Type\": \"Application+APIKey\",\n\t\t\t\t\t},\n\t\t\t\t\tAnalysisInfo: map[string]string{\n\t\t\t\t\t\t\"api_key\":  apiKey,\n\t\t\t\t\t\t\"app_key\":  appKey,\n\t\t\t\t\t\t\"endpoint\": endpoint,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DatadogToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"Type\": \"Application+APIKey\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\n\t\t\t// use default cloud endpoint\n\t\t\ts.UseCloudEndpoint(true)\n\t\t\ts.SetCloudEndpoint(s.CloudEndpoint())\n\t\t\ts.UseFoundEndpoints(true)\n\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"DatadogToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t\tdelete(got[i].ExtraData, \"user_emails\")\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"DatadogToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/datadogtoken/datadogtoken_test.go",
    "content": "package datadogtoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestDataDogToken_Pattern_WithValidAPIandAppKey(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\tinput := `\n\t\t\tdd_api_secret: \"FKNwdbyfYTmGUm5DK3yHEuK-BBQf0fVG\"\n\t\t\tdd_app: \"iHxNanzZ8vjrmbjXK7NJLrwpGw2czdSh90PKH6VL\"\n\t\t\tbase_url1: \"https://api.us5.datadoghq.com\"\n\t\t\tbase_url2: \"https://api.app.ddog-gov.com\"\n\t`\n\twant := []string{\"iHxNanzZ8vjrmbjXK7NJLrwpGw2czdSh90PKH6VLFKNwdbyfYTmGUm5DK3yHEuK-BBQf0fVG\"}\n\twantedResultType := \"Application+APIKey\"\n\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(input))\n\tif len(matchedDetectors) == 0 {\n\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), input)\n\t\treturn\n\t}\n\tresults, err := d.FromData(context.Background(), false, []byte(input))\n\tif err != nil {\n\t\tt.Errorf(\"error = %v\", err)\n\t\treturn\n\t}\n\tif len(results) != len(want) {\n\t\tif len(results) == 0 {\n\t\t\tt.Errorf(\"did not receive result\")\n\t\t} else {\n\t\t\tt.Errorf(\"expected %d results, only received %d\", len(want), len(results))\n\t\t}\n\t\treturn\n\t}\n\n\tactual := make(map[string]struct{}, len(results))\n\tfor _, r := range results {\n\t\tif len(r.RawV2) > 0 {\n\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t} else {\n\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t}\n\t\tif r.ExtraData[\"Type\"] != wantedResultType {\n\t\t\tt.Errorf(\"expected result type %s, got %s\", wantedResultType, r.ExtraData[\"Type\"])\n\t\t}\n\t}\n\texpected := make(map[string]struct{}, len(want))\n\tfor _, v := range want {\n\t\texpected[v] = struct{}{}\n\t}\n\n\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", \"TestDataDogToken_Pattern_WithValidAPIandAppKey\", diff)\n\t}\n}\n\nfunc TestDataDogToken_Pattern_WithAPIKeyOnly(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\tinput := `\n\t\t\tdd_api_secret: \"FKNwdbyfYTmGUm5DK3yHEuK-BBQf0fVG\"\n\t\t\tbase_url: \"https://api.us5.datadoghq.com\"\n\t\t\tresponse_code: 200\n\t`\n\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(input))\n\tif len(matchedDetectors) == 0 {\n\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), input)\n\t\treturn\n\t}\n\tresults, err := d.FromData(context.Background(), false, []byte(input))\n\tif err != nil {\n\t\tt.Errorf(\"error = %v\", err)\n\t\treturn\n\t}\n\n\tif len(results) != 0 {\n\t\tt.Errorf(\"expected 0 results, received %d\", len(results))\n\t}\n}\n\nfunc TestDataDogToken_NoSecrets(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\tinput := `\n\t\t\tbase_url1: \"https://api.us5.datadoghq.com\"\n\t\t\tbase_url2: \"https://api.app.ddog-gov.com\"\n\t`\n\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(input))\n\tif len(matchedDetectors) == 0 {\n\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), input)\n\t\treturn\n\t}\n\tresults, err := d.FromData(context.Background(), false, []byte(input))\n\tif err != nil {\n\t\tt.Errorf(\"error = %v\", err)\n\t\treturn\n\t}\n\tif len(results) != 0 {\n\t\tt.Errorf(\"expected 0 results, received %d\", len(results))\n\t}\n}\n\nfunc TestDataDogToken_InvalidSecrets(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\tinput := `\n\t\t\tdd_api_secret: \"@FKNwdbyfYTmGUm5DK3yHEuK\"\n\t\t\tdd_app: \"iHxNanzZ8vjrmbjXK7NJLrwpGw2czdSh90PKH6VL\"\n\t\t\tbase_url1: \"https://api.us5.datadoghq.com\"\n\t\t\tbase_url2: \"https://api.app.ddog-gov.com\"\n\t`\n\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(input))\n\tif len(matchedDetectors) == 0 {\n\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), input)\n\t\treturn\n\t}\n\tresults, err := d.FromData(context.Background(), false, []byte(input))\n\tif err != nil {\n\t\tt.Errorf(\"error = %v\", err)\n\t\treturn\n\t}\n\tif len(results) != 0 {\n\t\tt.Errorf(\"expected 0 results, received %d\", len(results))\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/datagov/datagov.go",
    "content": "package datagov\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"data.gov\"}) + `\\b([a-zA-Z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"data.gov\"}\n}\n\n// FromData will find and optionally verify DataGov secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_DataGov,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.ers.usda.gov/data/arms/state?api_key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_DataGov\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/datagov/datagov_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage datagov\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDataGov_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DATAGOV\")\n\tinactiveSecret := testSecrets.MustGetField(\"DATAGOV_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a data.gov secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DataGov,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DataGov,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"DataGov.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"DataGov.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/datagov/datagov_test.go",
    "content": "package datagov\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Header\"\n\t\t\tdata.gov_secret: \"Ge4R2TmPk1R6NPsXYu0ceRnmawtYfnVeiZ4zztB8\"\n\t\t\tbase_url: \"https://api.example.com/v1/example?api_key=$data.gov_secret\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"Ge4R2TmPk1R6NPsXYu0ceRnmawtYfnVeiZ4zztB8\"\n)\n\nfunc TestDataGov_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/debounce/debounce.go",
    "content": "package debounce\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"debounce\"}) + `\\b([a-zA-Z0-9]{13})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"debounce\"}\n}\n\n// FromData will find and optionally verify Debounce secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Debounce,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.debounce.io/v1/?api=\"+resMatch+\"&email=some@gmail.com\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Debounce\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/debounce/debounce_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage debounce\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDebounce_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DEBOUNCE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"DEBOUNCE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a debounce secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Debounce,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Debounce,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Debounce.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Debounce.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/debounce/debounce_test.go",
    "content": "package debounce\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Header\"\n\t\t\tdebounce_secret: \"OTM0Bp42sFTRB\"\n\t\t\tbase_url: \"https://api.example.com/v1/example?api=$debounce_secret\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"OTM0Bp42sFTRB\"\n)\n\nfunc TestDebounce_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/deepai/deepai.go",
    "content": "package deepai\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"deepai\"}) + `\\b([a-z0-9-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"deepai\"}\n}\n\n// FromData will find and optionally verify DeepAI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_DeepAI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tbody := &bytes.Buffer{}\n\t\t\twriter := multipart.NewWriter(body)\n\t\t\tfw, err := writer.CreateFormField(\"text\")\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t_, err = io.Copy(fw, strings.NewReader(\"test\"))\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\twriter.Close()\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.deepai.org/api/text-tagging\", bytes.NewReader(body.Bytes()))\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", writer.FormDataContentType())\n\t\t\treq.Header.Add(\"api-key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_DeepAI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"DeepAI is an AI service provider offering various machine learning APIs. DeepAI API keys can be used to access and utilize these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/deepai/deepai_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage deepai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDeepAI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DEEPAI\")\n\tinactiveSecret := testSecrets.MustGetField(\"DEEPAI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a deepai secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DeepAI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DeepAI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"DeepAI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"DeepAI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/deepai/deepai_test.go",
    "content": "package deepai\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Header\"\n\t\t\tdeepai_secret: \"ulrouaemk45y6pr8clttmjw8sucqq3skl7g9\"\n\t\t\tbase_url: \"https://api.example.com/v1/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"ulrouaemk45y6pr8clttmjw8sucqq3skl7g9\"\n)\n\nfunc TestDeepAI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/deepgram/deepgram.go",
    "content": "package deepgram\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"deepgram\"}) + `\\b([0-9a-z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"deepgram\"}\n}\n\n// FromData will find and optionally verify Deepgram secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Deepgram,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.deepgram.com/v1/projects\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Token %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Deepgram\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Deepgram is an automatic speech recognition (ASR) service. Deepgram API keys can be used to access and utilize Deepgram's ASR capabilities.\"\n}\n"
  },
  {
    "path": "pkg/detectors/deepgram/deepgram_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage deepgram\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDeepgram_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DEEPGRAM\")\n\tinactiveSecret := testSecrets.MustGetField(\"DEEPGRAM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a deepgram secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Deepgram,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Deepgram,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Deepgram.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Deepgram.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/deepgram/deepgram_test.go",
    "content": "package deepgram\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Token\"\n\t\t\tin: \"Header\"\n\t\t\tdeepgram_secret: \"4y7fjndvwi8bydxfwe0zppeef9n6j44kpizq3zr4\"\n\t\t\tbase_url: \"https://api.example.com/v1/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"4y7fjndvwi8bydxfwe0zppeef9n6j44kpizq3zr4\"\n)\n\nfunc TestDeepGram_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/deepseek/deepseek.go",
    "content": "package deepseek\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"deepseek\"}) + `\\b(sk-[a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"deepseek\"}\n}\n\n// FromData will find and optionally verify DeepSeek secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor token := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_DeepSeek,\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tverified, extraData, verificationErr := verifyToken(ctx, client, token)\n\t\t\ts1.Verified = verified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyToken(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.deepseek.com/user/balance\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json; charset=utf-8\")\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tvar resData response\n\t\tif err = json.NewDecoder(res.Body).Decode(&resData); err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\n\t\textraData := map[string]string{\n\t\t\t\"is_available\": fmt.Sprintf(\"%t\", resData.IsAvailable),\n\t\t}\n\t\treturn true, extraData, nil\n\tcase http.StatusUnauthorized:\n\t\t// Invalid\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_DeepSeek\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"DeepSeek is an artificial intelligence company that develops large language models (LLMs)\"\n}\n\ntype response struct {\n\tIsAvailable bool `json:\"is_available\"`\n}\n"
  },
  {
    "path": "pkg/detectors/deepseek/deepseek_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage deepseek\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestDeepseek_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tapiKey := testSecrets.MustGetField(\"DEEPSEEK\")\n\tinactiveSecret := testSecrets.MustGetField(\"DEEPSEEK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a deepseek secret %s within\", apiKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DeepSeek,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a deepseek secret %s within but not valid\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DeepSeek,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a deepseek secret %s within\", apiKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DeepSeek,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Deepseek.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t\t// Ignore Extra Data for comparison\n\t\t\t\tif tt.want[i].Verified == true {\n\t\t\t\t\tif got[i].ExtraData != nil {\n\t\t\t\t\t\tgot[i].ExtraData = nil\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.Fatalf(\"no extra data\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"AnalysisInfo\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Deepseek.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/deepseek/deepseek_test.go",
    "content": "package deepseek\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestDeepseek_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\tother.code()\n\t\t\t\tdeepseek.Apikey = sk-abc123def456ghi789jkl012mno345pq\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"sk-abc123def456ghi789jkl012mno345pq\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: \"deepseek.key = sk-abc123invalid\",\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/delighted/delighted.go",
    "content": "package delighted\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"delighted\"}) + `\\b([a-z0-9A-Z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"delighted\"}\n}\n\n// FromData will find and optionally verify Delighted secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Delighted,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tdata := fmt.Sprintf(\"%s:\", resMatch)\n\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\tpayload := strings.NewReader(`{\n\t\t\t\t\"email\": \"jony@appleseed.com\",\n\t\t\t\t\"properties\": { \"Purchase Experience\": \"Mobile App\", \"State\": \"CA\" }\n\t\t\t\t}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.delighted.com/v1/people.json\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Delighted\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Delighted is a customer feedback platform. Delighted API keys can be used to access and manage customer feedback data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/delighted/delighted_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage delighted\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDelighted_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DELIGHTED_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"DELIGHTED_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a delighted secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Delighted,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Delighted,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Delighted.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Delighted.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/delighted/delighted_test.go",
    "content": "package delighted\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Basic\"\n\t\t\tin: \"Header\"\n\t\t\tdelighted_secret: \"Vm62eJY7FFguRjYjqIdiLXUEOoRgvQ6W\"\n\t\t\tbase_url: \"https://api.example.com/v1/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"Vm62eJY7FFguRjYjqIdiLXUEOoRgvQ6W\"\n)\n\nfunc TestDelighted_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/demio/demio.go",
    "content": "package demio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"demio\"}) + `\\b([a-z0-9A-Z]{32})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"demio\"}) + `\\b([a-z0-9A-Z]{10,20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"demio\"}\n}\n\n// FromData will find and optionally verify Demio secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, idmatch := range idMatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Demio,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\turl := fmt.Sprintf(\"https://my.demio.com/api/v1/ping/query?api_key=%s&api_secret=%s\", resMatch, resIdMatch)\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", url, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Demio\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/demio/demio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage demio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDemio_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DEMIO\")\n\tinactiveSecret := testSecrets.MustGetField(\"DEMIO_INACTIVE\")\n\tkeySecret := testSecrets.MustGetField(\"DEMIO_SECRET\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a demio secret %s within demio %s\", secret, keySecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Demio,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Demio,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Demio.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Demio.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/demio/demio_test.go",
    "content": "package demio\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Header\"\n\t\t\tdemio_key: \"KL0F0y61VeIixRmn2A4Sha3h0xiLMX7J\"\n\t\t\tdemio_secret: \"PWkiVWEw7s7JjtzR\"\n\t\t\tbase_url: \"https://api.example.com/v1/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"KL0F0y61VeIixRmn2A4Sha3h0xiLMX7J\"\n)\n\nfunc TestDemio_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/deno/denodeploy.go",
    "content": "package denodeploy\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\ttokenPat      = regexp.MustCompile(`\\b(dd[pw]_[a-zA-Z0-9]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ddp_\", \"ddw_\"}\n}\n\ntype userResponse struct {\n\tLogin string `json:\"login\"`\n}\n\n// FromData will find and optionally verify DenoDeploy secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\ttokenMatches := tokenPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, tokenMatch := range tokenMatches {\n\t\ttoken := tokenMatch[1]\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: s.Type(),\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.deno.com/user\", nil)\n\t\t\treq.Header.Set(\"Authorization\", \"Bearer \"+token)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode == 200 {\n\t\t\t\t\ts1.Verified = true\n\n\t\t\t\t\tbody, err := io.ReadAll(res.Body)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\ts1.SetVerificationError(err, token)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvar user userResponse\n\t\t\t\t\t\tif err := json.Unmarshal(body, &user); err != nil {\n\t\t\t\t\t\t\ts1.SetVerificationError(err, token)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.ExtraData = map[string]string{\n\t\t\t\t\t\t\t\t\"login\": user.Login,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, token)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, token)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_DenoDeploy\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"DenoDeploy is a cloud service for deploying JavaScript and TypeScript applications. DenoDeploy tokens can be used to access and manage these deployments.\"\n}\n"
  },
  {
    "path": "pkg/detectors/deno/denodeploy_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage denodeploy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDenoDeploy_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DENODEPLOY\")\n\tinactiveSecret := testSecrets.MustGetField(\"DENODEPLOY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a denodeploy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DenoDeploy,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DenoDeploy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a denodeploy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DenoDeploy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a denodeploy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DenoDeploy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Denodeploy.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"ExtraData\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Denodeploy.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/deno/denodeploy_test.go",
    "content": "package denodeploy\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\nfunc TestDenoDeploy_Pattern(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tdata        string\n\t\tshouldMatch bool\n\t\tmatch       string\n\t}{\n\t\t// True positives\n\t\t{\n\t\t\tname: `valid_deployctl`,\n\t\t\tdata: `  \"tasks\": {\n  \t\"d\": \"deployctl deploy --prod --import-map=import_map.json --project=o88 main.ts --token ddp_eg5DjUmbR5lHZ3LiN9MajMk2tA1GxL2NRdvc\",\n    \"start\": \"deno run -A --unstable --watch=static/,routes/ dev.ts\"\n  },`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       `ddp_eg5DjUmbR5lHZ3LiN9MajMk2tA1GxL2NRdvc`,\n\t\t},\n\t\t{\n\t\t\tname:        `valid_dotenv`,\n\t\t\tdata:        `DENO_KV_ACCESS_TOKEN=ddp_hn029Cl2dIN4Jb0BF0L1V9opokoPVC30ddGk`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       `ddp_hn029Cl2dIN4Jb0BF0L1V9opokoPVC30ddGk`,\n\t\t},\n\t\t{\n\t\t\tname: `valid_dotfile`,\n\t\t\tdata: `# deno\nexport DENO_INSTALL=\"/home/khushal/.deno\"\nexport PATH=\"$DENO_INSTALL/bin:$PATH\"\nexport DENO_DEPLOY_TOKEN=\"ddp_QLbDfRlMKpXSf3oCz20Hp8wVVxThDwlwhFbV\"\"`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       `ddp_QLbDfRlMKpXSf3oCz20Hp8wVVxThDwlwhFbV`,\n\t\t},\n\t\t{\n\t\t\tname:        `valid_webtoken`,\n\t\t\tdata:        `    //     headers: { Authorization: 'Bearer ddw_ebahKKeZqiZVXOad7KJRHskLeP79Lf0OJXlj' }`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       `ddw_ebahKKeZqiZVXOad7KJRHskLeP79Lf0OJXlj`,\n\t\t},\n\n\t\t// False positives\n\t\t{\n\t\t\tname: `invalid_token1`,\n\t\t\tdata: `                \"summoner2Id\": 4,\n                \"summonerId\": \"oljqJ1Ddp_LJm5s6ONPAJXIl97Bi6pcKMywYLG496a58rA\",\n                \"summonerLevel\": 146,`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\tname:        `invalid_token2`,\n\t\t\tdata:        `        \"image_thumbnail_url\": \"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQFq6zzTXpXtRDdP_JbNkS58loAyCvhhZ1WWONaUkJoWbHsgwIJBw\",`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\tname:        `invalid_token3`,\n\t\t\tdata:        `matplotlib/backends/_macosx.cpython-37m-darwin.so,sha256=DDw_KRE5yTUEY5iDBwBW7KvDcTkDmrIu0N18i8I3FvA,90140`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\n\t\t\tresults, err := s.FromData(context.Background(), false, []byte(test.data))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"DenoDeploy.FromData() error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif test.shouldMatch {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"%s: did not receive a match for '%v' when one was expected\", test.name, test.data)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\texpected := test.data\n\t\t\t\tif test.match != \"\" {\n\t\t\t\t\texpected = test.match\n\t\t\t\t}\n\t\t\t\tresult := results[0]\n\t\t\t\tresultData := string(result.Raw)\n\t\t\t\tif resultData != expected {\n\t\t\t\t\tt.Errorf(\"%s: did not receive expected match.\\n\\texpected: '%s'\\n\\t  actual: '%s'\", test.name, expected, resultData)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif len(results) > 0 {\n\t\t\t\t\tt.Errorf(\"%s: received a match for '%v' when one wasn't wanted\", test.name, test.data)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/deputy/deputy.go",
    "content": "package deputy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"deputy\"}) + `\\b([0-9a-z]{32})\\b`)\n\turlPat = regexp.MustCompile(`\\b([0-9a-z]{1,}\\.as\\.deputy\\.com)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"deputy\"}\n}\n\n// FromData will find and optionally verify Deputy secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\turlMatches := urlPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, urlMatch := range urlMatches {\n\t\t\tresURL := strings.TrimSpace(urlMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Deputy,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://%s/api/v1/me\", resURL), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"OAuth %s\", resMatch))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Deputy\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/deputy/deputy_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage deputy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDeputy_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DEPUTY\")\n\turl := testSecrets.MustGetField(\"DEPUTY_URL\")\n\tinactiveSecret := testSecrets.MustGetField(\"DEPUTY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a deputy secret %s within %s\", secret, url)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Deputy,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Deputy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Deputy.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Deputy.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/deputy/deputy_test.go",
    "content": "package deputy\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Header\"\n\t\t\tdeputy_secret: \"puf5nguo090lkrkqxfeqj5ymm0nb26pt\"\n\t\t\tbase_url: \"https://api.nonprodtest.as.deputy.com.com/v1/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"puf5nguo090lkrkqxfeqj5ymm0nb26pt\"\n)\n\nfunc TestDeputy_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/detectify/detectify.go",
    "content": "package detectify\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"detectify\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"detectify\"}\n}\n\n// FromData will find and optionally verify Detectify secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Detectify,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.detectify.com/rest/v2/assets/\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-Detectify-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Detectify\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/detectify/detectify_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage detectify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDetectify_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DETECTIFY\")\n\tinactiveSecret := testSecrets.MustGetField(\"DETECTIFY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a detectify secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Detectify,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Detectify,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Detectify.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Detectify.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/detectify/detectify_test.go",
    "content": "package detectify\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Header\"\n\t\t\tdetectify_secret: \"eg90srff9v6cxk794kr2k56l5q5s9wx2\"\n\t\t\tbase_url: \"https://api.example.com/v1/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"eg90srff9v6cxk794kr2k56l5q5s9wx2\"\n)\n\nfunc TestDetectify_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/detectlanguage/detectlanguage.go",
    "content": "package detectlanguage\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"detectlanguage\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"detectlanguage\"}\n}\n\n// FromData will find and optionally verify DetectLanguage secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_DetectLanguage,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://ws.detectlanguage.com/0.2/user/status\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_DetectLanguage\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"DetectLanguage is a language detection API service. The API key can be used to access the language detection functionalities provided by DetectLanguage.\"\n}\n"
  },
  {
    "path": "pkg/detectors/detectlanguage/detectlanguage_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage detectlanguage\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDetectLanguage_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DETECTLANGUAGE\")\n\tinactiveSecret := testSecrets.MustGetField(\"DETECTLANGUAGE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a detectlanguage secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DetectLanguage,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DetectLanguage,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"DetectLanguage.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"DetectLanguage.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/detectlanguage/detectlanguage_test.go",
    "content": "package detectlanguage\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tin: \"Header\"\n\t\t\tdetectlanguage_secret: \"6esicmhsdpu8blum1wzr8a6bae9s507u\"\n\t\t\tbase_url: \"https://api.example.com/v1/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"6esicmhsdpu8blum1wzr8a6bae9s507u\"\n)\n\nfunc TestDetectLanguage_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/detectors.go",
    "content": "package detectors\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"math/big\"\n\t\"net/url\"\n\t\"strings\"\n\t\"unicode\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\n// Detector defines an interface for scanning for and verifying secrets.\ntype Detector interface {\n\t// FromData will scan bytes for results and optionally verify them.\n\t//\n\t// FromData can be called concurrently from multiple goroutines.\n\t// Any modification to the receiver or to global variables will need to use some kind of synchronization.\n\tFromData(ctx context.Context, verify bool, data []byte) ([]Result, error)\n\n\t// Keywords are used for efficiently pre-filtering chunks using substring operations.\n\t// Use unique identifiers that are part of the secret if you can, or the provider name.\n\t//\n\t// When multiple keywords are provided, they are is treated as a *union* of filtering terms.\n\t// That is, if any of the keywords are found in a chunk, the chunk will be run through the detector.\n\tKeywords() []string\n\n\t// Type returns the DetectorType number from detectors.proto for the given detector.\n\tType() detectorspb.DetectorType\n\n\t// Description returns a description for the result being detected\n\tDescription() string\n}\n\n// CustomResultsCleaner is an optional interface that a detector can implement to customize how its generated results\n// are \"cleaned,\" which is defined as removing superfluous results from those found in a given chunk. The default\n// implementation of this logic removes all unverified results if there are any verified results, and all unverified\n// results except for one otherwise, but this interface allows a detector to specify different logic. (This logic must\n// be implemented outside results generation because there are circumstances under which the engine should not execute\n// it.)\ntype CustomResultsCleaner interface {\n\t// CleanResults removes \"superfluous\" results from a result set (where the definition of \"superfluous\" is detector-\n\t// specific).\n\tCleanResults(results []Result) []Result\n\t// ShouldCleanResultsIrrespectiveOfConfiguration allows a custom cleaner to instruct the engine to ignore\n\t// user-provided configuration that controls whether results are cleaned. (User-provided configuration is not the\n\t// only factor that determines whether the engine runs cleaning logic.)\n\tShouldCleanResultsIrrespectiveOfConfiguration() bool\n}\n\n// Versioner is an optional interface that a detector can implement to\n// differentiate instances of the same detector type.\ntype Versioner interface {\n\tVersion() int\n}\n\n// MaxSecretSizeProvider is an optional interface that a detector can implement to\n// provide a custom max size for the secret it finds.\ntype MaxSecretSizeProvider interface {\n\tMaxSecretSize() int64\n}\n\n// StartOffsetProvider is an optional interface that a detector can implement to\n// provide a custom start offset for the secret it finds.\ntype StartOffsetProvider interface {\n\tStartOffset() int64\n}\n\n// MultiPartCredentialProvider is an optional interface that a detector can implement\n// to indicate its compatibility with multi-part credentials and provide the maximum\n// secret size for the credential it finds.\ntype MultiPartCredentialProvider interface {\n\t// MaxCredentialSpan returns the maximum span or range of characters that the\n\t// detector should consider when searching for a multi-part credential.\n\tMaxCredentialSpan() int64\n}\n\n// EndpointCustomizer is an optional interface that a detector can implement to\n// support verifying against user-supplied endpoints.\ntype EndpointCustomizer interface {\n\tSetConfiguredEndpoints(...string) error\n\tSetCloudEndpoint(string)\n\tUseCloudEndpoint(bool)\n\tUseFoundEndpoints(bool)\n}\n\ntype CloudProvider interface {\n\tCloudEndpoint() string\n}\n\ntype Result struct {\n\t// DetectorType is the type of Detector.\n\tDetectorType detectorspb.DetectorType\n\t// DetectorName is the name of the Detector. Used for custom detectors.\n\tDetectorName string\n\t// Verified indicates whether the result was verified or not.\n\tVerified bool\n\t// VerificationFromCache indicates whether this result's verification result came from the verification cache rather\n\t// than an actual remote request.\n\tVerificationFromCache bool\n\t// Raw contains the raw secret identifier data. Prefer IDs over secrets since it is used for deduping after hashing.\n\tRaw []byte\n\t// RawV2 contains the raw secret identifier that is a combination of both the ID and the secret.\n\t// This is used for secrets that are multi part and could have the same ID. Ex: AWS credentials\n\tRawV2 []byte\n\t// Redacted contains the redacted version of the raw secret identification data for display purposes.\n\t// A secret ID should be used if available.\n\tRedacted       string\n\tExtraData      map[string]string\n\tStructuredData *detectorspb.StructuredData\n\n\t// verificationError should be populated if the verification process itself failed in a way that provides no\n\t// information about the verification status of the candidate secret, such as if the verification request timed out.\n\tverificationError error\n\n\t// AnalysisInfo should be set with information required for credential\n\t// analysis to run. The keys of the map are analyzer specific and\n\t// should match what is expected in the corresponding analyzer.\n\tAnalysisInfo map[string]string\n\n\t// primarySecret is used when a detector has multiple secret patterns.\n\t// This secret is designated to determine the line number.\n\t// If set, the line number will correspond to this secret.\n\tprimarySecret struct {\n\t\tValue string\n\t\tLine  int64\n\t}\n}\n\n// CopyVerificationInfo clones verification info (status and error) from another Result struct. This is used when\n// loading verification info from a verification cache. (A method is necessary because verification errors are not\n// exported, to prevent the accidental storage of sensitive information in them.)\nfunc (r *Result) CopyVerificationInfo(from *Result) {\n\tr.Verified = from.Verified\n\tr.verificationError = from.verificationError\n}\n\n// SetVerificationError is the only way to set a new verification error. Any sensitive values should be passed-in as secrets to be redacted.\nfunc (r *Result) SetVerificationError(err error, secrets ...string) {\n\tif err != nil {\n\t\tr.verificationError = redactSecrets(err, secrets...)\n\t}\n}\n\n// Public accessors for the fields could also be provided if needed.\nfunc (r *Result) VerificationError() error {\n\treturn r.verificationError\n}\n\n// SetPrimarySecretValue set the value passed as primary secret in the result\nfunc (r *Result) SetPrimarySecretValue(value string) {\n\tif value != \"\" {\n\t\tr.primarySecret.Value = value\n\t}\n}\n\n// SetPrimarySecretLine set the passed line number as primary secret line number\nfunc (r *Result) SetPrimarySecretLine(line int64) {\n\t// line number is only set if value is set for primary secret\n\tif r.primarySecret.Value != \"\" {\n\t\tr.primarySecret.Line = line\n\t}\n}\n\n// GetPrimarySecretValue return primary secret match value\nfunc (r *Result) GetPrimarySecretValue() string {\n\treturn r.primarySecret.Value\n}\n\n// redactSecrets replaces all instances of the given secrets with [REDACTED] in the error message.\nfunc redactSecrets(err error, secrets ...string) error {\n\tlastErr := unwrapToLast(err)\n\terrStr := lastErr.Error()\n\tfor _, secret := range secrets {\n\t\terrStr = strings.ReplaceAll(errStr, secret, \"[REDACTED]\")\n\t}\n\treturn errors.New(errStr)\n}\n\n// unwrapToLast returns the last error in the chain of errors.\n// This is added to exclude non-essential details (like URLs) for brevity and security.\n// Also helps us optimize performance in redaction and enhance log clarity.\nfunc unwrapToLast(err error) error {\n\tfor {\n\t\tunwrapped := errors.Unwrap(err)\n\t\tif unwrapped == nil {\n\t\t\t// We've reached the last error in the chain\n\t\t\treturn err\n\t\t}\n\t\terr = unwrapped\n\t}\n}\n\ntype ResultWithMetadata struct {\n\t// IsWordlistFalsePositive indicates whether this secret was flagged as a false positive based on a wordlist check\n\tIsWordlistFalsePositive bool\n\t// SourceMetadata contains source-specific contextual information.\n\tSourceMetadata *source_metadatapb.MetaData\n\t// SourceID is the ID of the source that the API uses to map secrets to specific sources.\n\tSourceID sources.SourceID\n\t// JobID is the ID of the job that the API uses to map secrets to specific jobs.\n\tJobID sources.JobID\n\t// SecretID is the ID of the secret, if it exists.\n\t// Only secrets that are being reverified will have a SecretID.\n\tSecretID int64\n\t// SourceType is the type of Source.\n\tSourceType sourcespb.SourceType\n\t// SourceName is the name of the Source.\n\tSourceName string\n\tResult\n\t// DetectorDescription is the description of the Detector.\n\tDetectorDescription string\n\t// DecoderType is the type of decoder that was used to generate this result's data.\n\tDecoderType detectorspb.DecoderType\n\t// ChunkData holds the original pre-decode source chunk data, preserved\n\t// for secret storage encryption in the dispatcher.\n\tChunkData []byte\n}\n\n// CopyMetadata returns a detector result with included metadata from the source chunk.\nfunc CopyMetadata(chunk *sources.Chunk, result Result) ResultWithMetadata {\n\t// OriginalData may be nil when CopyMetadata is called outside the engine\n\t// pipeline (e.g., in tests or external consumers that construct chunks directly).\n\tchunkData := chunk.OriginalData\n\tif chunkData == nil {\n\t\tchunkData = chunk.Data\n\t}\n\treturn ResultWithMetadata{\n\t\tSourceMetadata: chunk.SourceMetadata,\n\t\tSourceID:       chunk.SourceID,\n\t\tJobID:          chunk.JobID,\n\t\tSecretID:       chunk.SecretID,\n\t\tSourceType:     chunk.SourceType,\n\t\tSourceName:     chunk.SourceName,\n\t\tResult:         result,\n\t\tChunkData:      chunkData,\n\t}\n}\n\n// CleanResults returns all verified secrets, and if there are no verified secrets,\n// just one unverified secret if there are any.\nfunc CleanResults(results []Result) []Result {\n\tif len(results) == 0 {\n\t\treturn results\n\t}\n\n\tvar cleaned = make(map[string]Result, 0)\n\n\tfor _, s := range results {\n\t\tif s.Verified {\n\t\t\tcleaned[s.Redacted] = s\n\t\t}\n\t}\n\n\tif len(cleaned) == 0 {\n\t\treturn results[:1]\n\t}\n\n\tresults = results[:0]\n\tfor _, r := range cleaned {\n\t\tresults = append(results, r)\n\t}\n\n\treturn results\n}\n\n// PrefixRegex ensures that at least one of the given keywords is within\n// 40 characters of the capturing group that follows.\n// This can help prevent false positives.\nfunc PrefixRegex(keywords []string) string {\n\tpre := `(?i:`\n\tmiddle := strings.Join(keywords, \"|\")\n\tpost := `)(?:.|[\\n\\r]){0,40}?`\n\treturn pre + middle + post\n}\n\n// KeyIsRandom is a Low cost check to make sure that 'keys' include a number to reduce FPs.\n// Golang doesn't support regex lookaheads, so must be done in separate calls.\n// TODO improve checks. Shannon entropy did not work well.\nfunc KeyIsRandom(key string) bool {\n\tfor _, ch := range key {\n\t\tif unicode.IsDigit(ch) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc MustGetBenchmarkData() map[string][]byte {\n\tsizes := map[string]int{\n\t\t\"xsmall\":  10,          // 10 bytes\n\t\t\"small\":   100,         // 100 bytes\n\t\t\"medium\":  1024,        // 1KB\n\t\t\"large\":   10 * 1024,   // 10KB\n\t\t\"xlarge\":  100 * 1024,  // 100KB\n\t\t\"xxlarge\": 1024 * 1024, // 1MB\n\t}\n\tdata := make(map[string][]byte)\n\n\tfor key, size := range sizes {\n\t\t// Generating a byte slice of a specific size with random data.\n\t\tcontent := make([]byte, size)\n\t\tfor i := range size {\n\t\t\trandomByte, err := rand.Int(rand.Reader, big.NewInt(256))\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tcontent[i] = byte(randomByte.Int64())\n\t\t}\n\t\tdata[key] = content\n\t}\n\n\treturn data\n}\n\nfunc RedactURL(u url.URL) string {\n\tu.User = url.UserPassword(u.User.Username(), \"********\")\n\treturn strings.TrimSpace(strings.ReplaceAll(u.String(), \"%2A\", \"*\"))\n}\n\nfunc ParseURLAndStripPathAndParams(u string) (*url.URL, error) {\n\tparsedURL, err := url.Parse(u)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tparsedURL.Path = \"\"\n\tparsedURL.RawQuery = \"\"\n\treturn parsedURL, nil\n}\n"
  },
  {
    "path": "pkg/detectors/detectors_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage detectors\n\nimport (\n\t\"testing\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n)\n\nfunc TestPrefixRegex(t *testing.T) {\n\ttests := []struct {\n\t\tkeywords []string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tkeywords: []string{\"securitytrails\"},\n\t\t\texpected: `(?i:securitytrails)(?:.|[\\n\\r]){0,40}?`,\n\t\t},\n\t\t{\n\t\t\tkeywords: []string{\"zipbooks\"},\n\t\t\texpected: `(?i:zipbooks)(?:.|[\\n\\r]){0,40}?`,\n\t\t},\n\t\t{\n\t\t\tkeywords: []string{\"wrike\"},\n\t\t\texpected: `(?i:wrike)(?:.|[\\n\\r]){0,40}?`,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tgot := PrefixRegex(tt.keywords)\n\t\tif got != tt.expected {\n\t\t\tt.Errorf(\"PrefixRegex(%v) got: %v want: %v\", tt.keywords, got, tt.expected)\n\t\t}\n\t}\n}\n\nfunc TestPrefixRegexKeywords(t *testing.T) {\n\tkeywords := []string{\"keyword1\", \"keyword2\", \"keyword3\"}\n\n\ttestCases := []struct {\n\t\tinput    string\n\t\texpected bool\n\t}{\n\t\t{\"keyword1 1234c4aabceeff4444442131444aab44\", true},\n\t\t{\"keyword1 1234567890ABCDEF1234567890ABBBCA\", false},\n\t\t{\"KEYWORD1 1234567890abcdef1234567890ababcd\", true},\n\t\t{\"KEYWORD1 1234567890ABCDEF1234567890ABdaba\", false},\n\t\t{\"keyword2 1234567890abcdef1234567890abeeff\", true},\n\t\t{\"keyword2 1234567890ABCDEF1234567890ABadbd\", false},\n\t\t{\"KEYWORD2 1234567890abcdef1234567890ababca\", true},\n\t\t{\"KEYWORD2 1234567890ABCDEF1234567890ABBBBs\", false},\n\t\t{\"keyword3 1234567890abcdef1234567890abccea\", true},\n\t\t{\"KEYWORD3 1234567890abcdef1234567890abaabb\", true},\n\t\t{\"keyword4 1234567890abcdef1234567890abzzzz\", false},\n\t\t{\"keyword3 1234567890ABCDEF1234567890AB\", false},\n\t\t{\"keyword4 1234567890ABCDEF1234567890AB\", false},\n\t}\n\n\tkeyPat := regexp.MustCompile(PrefixRegex(keywords) + `\\b([0-9a-f]{32})\\b`)\n\n\tfor _, tc := range testCases {\n\t\tmatch := keyPat.MatchString(tc.input)\n\t\tif match != tc.expected {\n\t\t\tt.Errorf(\"Input: %s, Expected: %v, Got: %v\", tc.input, tc.expected, match)\n\t\t}\n\t}\n}\n\nfunc BenchmarkPrefixRegex(b *testing.B) {\n\tkws := []string{\"securitytrails\"}\n\tfor i := 0; i < b.N; i++ {\n\t\tPrefixRegex(kws)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dfuse/dfuse.go",
    "content": "package dfuse\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(web\\_[0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"dfuse\"}\n}\n\n// FromData will find and optionally verify Dfuse secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Dfuse,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\"api_key\":\"` + resMatch + `\"}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://auth.dfuse.io/v1/auth/issue\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Dfuse\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/dfuse/dfuse_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage dfuse\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDfuse_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DFUSE\")\n\tinactiveSecret := testSecrets.MustGetField(\"DFUSE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a dfuse secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dfuse,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dfuse,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Dfuse.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Dfuse.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dfuse/dfuse_test.go",
    "content": "package dfuse\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Header\"\n\t\t\tdfuse_secret: \"web_akqaeqqsrlb5bczdblzgi4g94i3yt2jb\"\n\t\t\tbase_url: \"https://api.example.com/v1/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"web_akqaeqqsrlb5bczdblzgi4g94i3yt2jb\"\n)\n\nfunc TestDfuse_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/diffbot/diffbot.go",
    "content": "package diffbot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"diffbot\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"diffbot\"}\n}\n\n// FromData will find and optionally verify Diffbot secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Diffbot,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := 10 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.diffbot.com/v4/account?token=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `\"token\":`) && strings.Contains(bodyString, `\"planCredits\":`)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Diffbot\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/diffbot/diffbot_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage diffbot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDiffbot_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DIFFBOT\")\n\tinactiveSecret := testSecrets.MustGetField(\"DIFFBOT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a diffbot secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Diffbot,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Diffbot,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Diffbot.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Diffbot.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/diffbot/diffbot_test.go",
    "content": "package diffbot\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tdiffbot_secret: \"un7g0mse9r0i1m2p56832mja133vtysm\"\n\t\t\tbase_url: \"https://api.example.com/v1/example?token=$diffbot_secret\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"un7g0mse9r0i1m2p56832mja133vtysm\"\n)\n\nfunc TestDiffBot_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/diggernaut/diggernaut.go",
    "content": "package diggernaut\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"diggernaut\"}) + `\\b([0-9a-z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"diggernaut\"}\n}\n\n// FromData will find and optionally verify Diggernaut secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Diggernaut,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.diggernaut.com/api/projects\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Token %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Diggernaut\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Diggernaut is a web scraping service. Diggernaut API keys can be used to access and manage scraping projects and data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/diggernaut/diggernaut_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage diggernaut\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDiggernaut_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DIGGERNAUT\")\n\tinactiveSecret := testSecrets.MustGetField(\"DIGGERNAUT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a diggernaut secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Diggernaut,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Diggernaut,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Diggernaut.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Diggernaut.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/diggernaut/diggernaut_test.go",
    "content": "package diggernaut\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Header\"\n\t\t\tdiggernaut_secret: \"vwrclry0t0ttuggr7gjdxarb9yb4td1618nziytp\"\n\t\t\tbase_url: \"https://api.example.com/v1/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"vwrclry0t0ttuggr7gjdxarb9yb4td1618nziytp\"\n)\n\nfunc TestDiggerNaut_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/digitaloceantoken/digitaloceantoken.go",
    "content": "package digitaloceantoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\tkeyPat        = regexp.MustCompile(detectors.PrefixRegex([]string{\"ocean\", \"do\"}) + `\\b([A-Za-z0-9_-]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"digitalocean\"}\n}\n\n// FromData will find and optionally verify DigitalOceanToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tvar uniqueTokens = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokens[matches[1]] = struct{}{}\n\t}\n\n\tfor token := range uniqueTokens {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_DigitalOceanToken,\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\tisVerified, verificationErr := verifyDigitalOceanToken(ctx, client, token)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\tif s1.Verified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\"key\": token,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyDigitalOceanToken(ctx context.Context, client *http.Client, token string) (bool, error) {\n\t// Ref: https://docs.digitalocean.com/reference/api/digitalocean/#tag/Account\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.digitalocean.com/v2/account\", nil)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to create request: %w\", err)\n\t}\n\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to make request: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_DigitalOceanToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/digitaloceantoken/digitaloceantoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage digitaloceantoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDigitalOceanToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DIGITALOCEAN_PERSONAL_ACCESS_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"DIGITALOCEAN_PERSONAL_ACCESS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a digitaloceantoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DigitalOceanToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a digitaloceantoken secret %s within but unverified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DigitalOceanToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found verifiable secret, verification failed due to unexpected API response\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a digitaloceantoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DigitalOceanToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"DigitalOceanToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"AnalysisInfo\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"DigitalOceanToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/digitaloceantoken/digitaloceantoken_test.go",
    "content": "package digitaloceantoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tin: \"Header\"\n\t\t\tdigitalocean_secret: \"wisN3jbppF1dA3vrcB0C40iRlNiXAvEE8ToRFHkfBQS5dt5KIq-E8_vKW7NqrJJO\"\n\t\t\tbase_url: \"https://api.example.com/v1/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"wisN3jbppF1dA3vrcB0C40iRlNiXAvEE8ToRFHkfBQS5dt5KIq-E8_vKW7NqrJJO\"\n)\n\nfunc TestDigitalOceanToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/digitaloceanv2/digitaloceanv2.go",
    "content": "package digitaloceanv2\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b((?:dop|doo|dor)_v1_[a-f0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"dop_v1_\", \"doo_v1_\", \"dor_v1_\"}\n}\n\n// FromData will find and optionally verify DigitalOceanV2 secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueTokens = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokens[matches[0]] = struct{}{}\n\t}\n\n\tfor token := range uniqueTokens {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_DigitalOceanV2,\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\t// Check if the token is a refresh token or an access token\n\t\t\tswitch {\n\t\t\tcase strings.HasPrefix(token, \"dor_v1_\"):\n\t\t\t\tverified, verificationErr, newAccessToken := verifyRefreshToken(ctx, client, token)\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t\ts1.Verified = verified\n\t\t\t\tif s1.Verified {\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\"key\": newAccessToken,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase strings.HasPrefix(token, \"doo_v1_\"), strings.HasPrefix(token, \"dop_v1_\"):\n\t\t\t\tverified, verificationErr := verifyAccessToken(ctx, client, token)\n\t\t\t\ts1.Verified = verified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t\tif s1.Verified {\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\"key\": token,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\n// verifyRefreshToken verifies the refresh token by making a request to the DigitalOcean API.\n// If the token is valid, it returns the new access token and no error.\n// If the token is invalid/expired, it returns an empty string and no error.\n// If an error is encountered, it returns an empty string along and the error.\nfunc verifyRefreshToken(ctx context.Context, client *http.Client, token string) (bool, error, string) {\n\t// Ref: https://docs.digitalocean.com/reference/api/oauth/\n\n\turl := \"https://cloud.digitalocean.com/v1/oauth/token?grant_type=refresh_token&refresh_token=\" + token\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to create request: %w\", err), \"\"\n\t}\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to make request: %w\", err), \"\"\n\t}\n\n\tbodyBytes, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to read response body: %w\", err), \"\"\n\t}\n\tdefer res.Body.Close()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tvar responseMap map[string]interface{}\n\t\tif err := json.Unmarshal(bodyBytes, &responseMap); err != nil {\n\t\t\treturn false, fmt.Errorf(\"failed to parse response body: %w\", err), \"\"\n\t\t}\n\t\t// Extract the access token from the response\n\t\taccessToken, exists := responseMap[\"access_token\"].(string)\n\t\tif !exists {\n\t\t\treturn false, fmt.Errorf(\"access_token not found in response: %s\", string(bodyBytes)), \"\"\n\t\t}\n\t\treturn true, nil, accessToken\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil, \"\"\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode), \"\"\n\t}\n}\n\n// verifyAccessToken verifies the access token by making a request to the DigitalOcean API.\n// If the token is valid, it returns true and no error.\n// If the token is invalid, it returns false and no error.\n// If an error is encountered, it returns false along with the error.\nfunc verifyAccessToken(ctx context.Context, client *http.Client, token string) (bool, error) {\n\t// Ref: https://docs.digitalocean.com/reference/api/digitalocean/#tag/Account\n\n\turl := \"https://api.digitalocean.com/v2/account\"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to create request: %w\", err)\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to make request: %w\", err)\n\t}\n\tdefer res.Body.Close()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_DigitalOceanV2\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"DigitalOcean is a cloud service provider offering scalable compute and storage solutions. DigitalOcean API keys can be used to access and manage these resources.\"\n}\n"
  },
  {
    "path": "pkg/detectors/digitaloceanv2/digitaloceanv2_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage digitaloceanv2\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDigitalOceanV2_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DIGITALOCEANV2\")\n\tinactiveSecret := testSecrets.MustGetField(\"DIGITALOCEANV2_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a digitaloceanv2 secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DigitalOceanV2,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DigitalOceanV2,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found verifiable secret, verification failed due to unexpected API response\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a digitaloceanv2 secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DigitalOceanV2,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"DigitalOceanV2.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"AnalysisInfo\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"DigitalOceanV2.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/digitaloceanv2/digitaloceanv2_test.go",
    "content": "package digitaloceanv2\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tdigitalocean_secret1: \"doo_v1_997deb722a2cf8f14a2eef30e41ed96e08268603b7877a595504e4367ca58e3d\"\n\t\t\tdigitalocean_secret2: \"dop_v1_997deb722a2cf8f14a2eef30e41ed96e08268603b7877a595504e4367ca58e3d\"\n\t\t\tdigitalocean_secret3: \"dor_v1_997deb722a2cf8f14a2eef30e41ed96e08268603b7877a595504e4367ca58e3d\"\n\t\t\tbase_url: \"https://api.example.com/v1/example?refresh_token=$digitalocean_secret1\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecrets = []string{\n\t\t\"doo_v1_997deb722a2cf8f14a2eef30e41ed96e08268603b7877a595504e4367ca58e3d\",\n\t\t\"dop_v1_997deb722a2cf8f14a2eef30e41ed96e08268603b7877a595504e4367ca58e3d\",\n\t\t\"dor_v1_997deb722a2cf8f14a2eef30e41ed96e08268603b7877a595504e4367ca58e3d\",\n\t}\n)\n\nfunc TestDigitalOceanV2_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  secrets,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/discordbottoken/discordbottoken.go",
    "content": "package discordbottoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"discord\"}) + `\\b([0-9]{17})\\b`)\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"discord\"}) + `\\b([A-Za-z0-9_-]{24}\\.[A-Za-z0-9_-]{6}\\.[A-Za-z0-9_-]{27})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"discord\"}\n}\n\n// FromData will find and optionally verify DiscordBotToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatch := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idmatch := range idMatch {\n\t\t\tresId := strings.TrimSpace(idmatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_DiscordBotToken,\n\t\t\t\tRedacted:     resId,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resId),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://discord.com/api/v8/users/\"+resId, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bot %s\", resMatch))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_DiscordBotToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/discordbottoken/discordbottoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage discordbottoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDiscordBotToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DISCORDBOTTOKEN_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"DISCORDBOTTOKEN_INACTIVE\")\n\tidSecret := testSecrets.MustGetField(\"DISCORDBOTTOKEN_USERID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a discordbot secret %s within https://discord.com/api/v8/users/%s\", secret, idSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DiscordBotToken,\n\t\t\t\t\tRedacted:     idSecret,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DiscordBotToken,\n\t\t\t\t\tRedacted:     idSecret,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"DiscordBotToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw v2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"DiscordBotToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/discordbottoken/discordbottoken_test.go",
    "content": "package discordbottoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Token\"\n\t\t\tin: \"Header\"\n\t\t\tdiscord_id: \"17014529625858348\"\n\t\t\tdiscord_secret: \"oHILWmk3qakMYbqAikD9R0nJ.Vhu0LY.FK1U_2L2Of8Bm5ESbD6Cy4VKu2K\"\n\t\t\tbase_url: \"https://api.example.com/v1/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"oHILWmk3qakMYbqAikD9R0nJ.Vhu0LY.FK1U_2L2Of8Bm5ESbD6Cy4VKu2K17014529625858348\"\n)\n\nfunc TestDiscordBotToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/discordwebhook/discordwebhook.go",
    "content": "package discordwebhook\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`(https:\\/\\/discord\\.com\\/api\\/webhooks\\/[0-9]{18,19}\\/[0-9a-zA-Z-]{68})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string { return []string{\"https://discord.com/api/webhooks/\"} }\n\n// FromData will find and optionally verify DiscordWebhook secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_DiscordWebhook,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_DiscordWebhook\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Discord webhooks are used to send messages to a Discord channel. They can be used to automate messages and send data updates.\"\n}\n"
  },
  {
    "path": "pkg/detectors/discordwebhook/discordwebhook_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage discordwebhook\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDiscordWebhook_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DISCORDWEBHOOK\")\n\tinactiveSecret := testSecrets.MustGetField(\"DISCORDWEBHOOK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a discordwebhook secret %s within discordwebhook\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DiscordWebhook,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DiscordWebhook,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"DiscordWebhook.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"DiscordWebhook.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/discordwebhook/discordwebhook_test.go",
    "content": "package discordwebhook\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"\"\n\t\t\tin: \"\"\n\t\t\tdiscord_hook: \"https://discord.com/api/webhooks/144147826297622273/Rz9B09dB7cXxtldzXXfmJY0opIzgeANtGJw08vx5PXrP8BpbOeE5lZ7wx8vVcyacYkEl\"\n\t\t\tbase_url: \"\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"https://discord.com/api/webhooks/144147826297622273/Rz9B09dB7cXxtldzXXfmJY0opIzgeANtGJw08vx5PXrP8BpbOeE5lZ7wx8vVcyacYkEl\"\n\n\tvalidPattern19Digits = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"\"\n\t\t\tin: \"\"\n\t\t\tdiscord_hook: \"https://discord.com/api/webhooks/1369248176954937405/Q7bFGgbEMoZ-tRHuA4QHk3xTNC7nrrSmTm8IPjFvkp-ChRj4gi2C9lzvJiUcVlnE48X2\"\n\t\t\tbase_url: \"\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n    `\n\tsecret19Digits = \"https://discord.com/api/webhooks/1369248176954937405/Q7bFGgbEMoZ-tRHuA4QHk3xTNC7nrrSmTm8IPjFvkp-ChRj4gi2C9lzvJiUcVlnE48X2\"\n)\n\nfunc TestDiscordWebHook_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern with 19-digit ID\",\n\t\t\tinput: validPattern19Digits,\n\t\t\twant:  []string{secret19Digits},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/disqus/disqus.go",
    "content": "package disqus\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"disqus\"}) + `\\b([a-zA-Z0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"disqus\"}\n}\n\n// FromData will find and optionally verify Disqus secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Disqus,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://disqus.com/api/3.0/trends/listThreads.json?api_key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Disqus\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/disqus/disqus_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage disqus\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDisqus_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DISQUS\")\n\tinactiveSecret := testSecrets.MustGetField(\"DISQUS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a disqus secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Disqus,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Disqus,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Disqus.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Disqus.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/disqus/disqus_test.go",
    "content": "package disqus\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tbase_url: \"https://api.disqus.com/v3/example?token=T7YaiuviPyYp8WyWlJ9lqQLI5oPirYMcfDYLPY7NAqxAr3872ovqq9AOVU3RcPUB\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"T7YaiuviPyYp8WyWlJ9lqQLI5oPirYMcfDYLPY7NAqxAr3872ovqq9AOVU3RcPUB\"\n)\n\nfunc TestDisqus_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ditto/ditto.go",
    "content": "package ditto\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ditto\"}\n}\n\n// FromData will find and optionally verify Ditto secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Ditto,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.dittowords.com/variants\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"token %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Ditto\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ditto/ditto_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ditto\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDitto_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DITTO\")\n\tinactiveSecret := testSecrets.MustGetField(\"DITTO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ditto secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ditto,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ditto,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Ditto.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Ditto.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ditto/ditto_test.go",
    "content": "package ditto\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tapi_version: v1\n\t\t\tditto_secret: \"smtkww1b-bpux-6mds-r977-7kr1rb1q8r5o.4jwv35awjadnwzzm4u4kz8otf3lgmns2oazb8f6w\"\n\t\t\tbase_url: \"https://api.ditto.com/$api_version/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"smtkww1b-bpux-6mds-r977-7kr1rb1q8r5o.4jwv35awjadnwzzm4u4kz8otf3lgmns2oazb8f6w\"\n)\n\nfunc TestDitto_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dnscheck/dnscheck.go",
    "content": "package dnscheck\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"dnscheck\"}) + `\\b([a-z0-9A-Z]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"dnscheck\"}) + `\\b([a-z0-9A-Z-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"dnscheck\"}\n}\n\n// FromData will find and optionally verify Dnscheck secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Dnscheck,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resIdMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.dnscheck.co/api/v1/groups/\"+resIdMatch+\"?api_key=\"+resMatch, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Dnscheck\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Dnscheck is a service used to monitor DNS records. The API keys can be used to access and manage DNS monitoring configurations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/dnscheck/dnscheck_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage dnscheck\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDnscheck_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DNSCHECK\")\n\tinactiveSecret := testSecrets.MustGetField(\"DNSCHECK_INACTIVE\")\n\tid := testSecrets.MustGetField(\"DNSCHECK_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a dnscheck secret %s within dnscheckid %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dnscheck,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dnscheck,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Dnscheck.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw v2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Dnscheck.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dnscheck/dnscheck_test.go",
    "content": "package dnscheck\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tapi_version: v1\n\t\t\tdnscheck_secret: \"GaMSE8mJT7evjXg1Tmwz0wAyrY4Yagur\"\n\t\t\tbase_url: \"https://api.dnscheck.com/$api_version/groups/zDTON8dac54pwe1OaCrKhcwC9qptimIdX42K?api_key=$dnscheck_secret\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"GaMSE8mJT7evjXg1Tmwz0wAyrY4YagurzDTON8dac54pwe1OaCrKhcwC9qptimIdX42K\"\n)\n\nfunc TestDnsCheck_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/docker/docker_auth_config.go",
    "content": "package docker\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/go-logr/logr\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ interface {\n\tdetectors.Detector\n\tdetectors.MaxSecretSizeProvider\n} = (*Scanner)(nil)\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Docker\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Docker credentials can be used to pull images from private registries.\"\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{`\"auths\"`, `\\\"auths\\`}\n}\n\nfunc (s Scanner) MaxSecretSize() int64 {\n\treturn 4096\n}\n\nvar (\n\tkeyPat          = 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])*}`)\n\tescapedReplacer = strings.NewReplacer(\n\t\t`\\n`, \"\",\n\t\t`\\r`, \"\",\n\t\t`\\t`, \"\",\n\t\t`\\\\`, ``,\n\t\t`\\\"`, `\"`,\n\t)\n\n\t// Common false-positives used in examples.\n\texampleRegistries = map[string]struct{}{\n\t\t\"https://index.docker.io/v1/\":       {}, // https://github.com/moby/moby/blob/34679e568a22b4f35ff8460f3b5b7bf7089df818/cliconfig/config_test.go#L259\n\t\t\"registry.hostname.com\":             {}, // https://github.com/openshift/machine-config-operator/blob/82011335dbdd3d4c869b959d6048a3fba7742e47/pkg/controller/build/helpers_test.go#L47\n\t\t\"registry.example.com:5000\":         {}, // https://github.com/openshift/cluster-baremetal-operator/blob/f908020b1d46667056f21cf1d79e032c535a41fc/provisioning/baremetal_secrets_test.go#L53\n\t\t\"registry2.example.com:5000\":        {},\n\t\t\"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\n\t}\n)\n\n// FromData will find and optionally verify Docker secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tlogCtx := logContext.AddLogger(ctx)\n\tlogger := logCtx.Logger().WithName(\"docker\")\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[0]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\t// Remove escaped quotes and literal whitespace characters, if present.\n\t\t// It is common for auth to be escaped, however, the json package cannot unmarshal escaped JSON.\n\t\tmatch := escapedReplacer.Replace(match)\n\n\t\t// Unmarshal the config string.\n\t\t// Doing byte->string->byte probably isn't the most efficient.\n\t\tvar auths dockerAuths\n\t\tif err := json.NewDecoder(strings.NewReader(match)).Decode(&auths); err != nil {\n\t\t\tlogger.Error(err, \"Could not parse Docker auth JSON\")\n\t\t\treturn results, err\n\t\t} else if len(auths.Auths) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor registry, auth := range auths.Auths {\n\t\t\t// `docker.io` is a special case, Docker is hard-coded to rewrite it as `index.docker.io`.\n\t\t\t// https://github.com/moby/moby/blob/145a73a36c171b34c196ad780e699b154ddf47b5/registry/config_test.go#L329\n\t\t\tif strings.EqualFold(registry, \"docker.io\") {\n\t\t\t\tregistry = \"index.docker.io\"\n\t\t\t}\n\n\t\t\t// Skip known invalid registries.\n\t\t\tif _, ok := exampleRegistries[registry]; ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Skip configs with no credentials.\n\t\t\t// TODO: Should this be an error? What if it's a logic issue?\n\t\t\tusername, password, b64encoded := parseBasicAuth(logger, auth)\n\t\t\tif username == \"\" && password == \"\" {\n\t\t\t\tlogger.V(2).Info(\"Skipping empty credentials\", \"auth\", auth, \"username\", username, \"password\", password)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tr := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Docker,\n\t\t\t\tRaw:          []byte(b64encoded),\n\t\t\t\tRawV2:        []byte(`{\"registry\":\"` + registry + `\",\"auth\":\"` + b64encoded + `\"}`),\n\t\t\t\tExtraData:    map[string]string{\"Username\": username},\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = common.SaneHttpClient()\n\t\t\t\t}\n\n\t\t\t\tisVerified, verificationErr := verifyMatch(logCtx, client, registry, username, b64encoded)\n\t\t\t\tr.Verified = isVerified\n\t\t\t\tr.SetVerificationError(verificationErr, match)\n\t\t\t}\n\n\t\t\tresults = append(results, r)\n\t\t}\n\t}\n\treturn\n}\n\nfunc verifyMatch(ctx logContext.Context, client *http.Client, registry string, username string, basicAuth string) (bool, error) {\n\t// Build the registry URL path.\n\tvar registryUrl string\n\tregistry, _ = strings.CutSuffix(registry, \"/\")\n\tif strings.HasPrefix(registry, \"http://\") || strings.HasPrefix(registry, \"https://\") {\n\t\tregistryUrl = registry + \"/v2/\"\n\t} else {\n\t\tregistryUrl = \"https://\" + registry + \"/v2/\"\n\t}\n\n\t// Build the request.\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, registryUrl, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Set(\"Authorization\", \"Basic \"+basicAuth)\n\treq.Header.Set(\"Accept\", \"application/json\")\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\t// Send the initial request.\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\t// Handle the initial response.\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tbody, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\treturn json.Valid(body), nil\n\tcase http.StatusUnauthorized:\n\t\t// Some registries do not support basic auth, so we must follow the `Www-Authenticate` header, if present.\n\t\t// https://distribution.github.io/distribution/spec/auth/token/\n\t\th := res.Header.Get(\"Www-Authenticate\")\n\t\tif h == \"\" {\n\t\t\treturn false, nil\n\t\t}\n\n\t\tif !strings.HasPrefix(h, \"Bearer\") {\n\t\t\treturn false, fmt.Errorf(\"unsupported WWW-Authenticate auth scheme: %s\", h)\n\t\t}\n\n\t\tauthParams, err := parseAuthenticateHeader(h)\n\t\tif err != nil {\n\t\t\treturn false, fmt.Errorf(\"failed to parse registry auth header: %w\", err)\n\t\t}\n\t\trealm := authParams[\"realm\"]\n\t\tif realm == \"\" {\n\t\t\treturn false, fmt.Errorf(\"unexpected empty realm for WWW-Authenticate header: %s\", h)\n\t\t}\n\n\t\tauthReq, err := http.NewRequestWithContext(ctx, http.MethodGet, realm, nil)\n\t\tif err != nil {\n\t\t\treturn false, nil\n\t\t}\n\n\t\tauthReq.Header.Set(\"Authorization\", \"Basic \"+basicAuth)\n\t\tauthReq.Header.Set(\"Accept\", \"application/json\")\n\t\tauthReq.Header.Set(\"Content-Type\", \"application/json\")\n\n\t\tparams := url.Values{}\n\t\tparams.Add(\"account\", username)\n\t\tparams.Add(\"service\", authParams[\"service\"])\n\t\tauthReq.URL.RawQuery = params.Encode()\n\n\t\tauthRes, err := client.Do(authReq)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tdefer func() {\n\t\t\t_, _ = io.Copy(io.Discard, authRes.Body)\n\t\t\t_ = authRes.Body.Close()\n\t\t}()\n\n\t\tswitch authRes.StatusCode {\n\t\tcase http.StatusOK:\n\t\t\treturn true, nil\n\t\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\t\t// Auth was rejected.\n\t\t\treturn false, nil\n\t\tdefault:\n\t\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d for '%s'\", authRes.StatusCode, authReq.URL.String())\n\t\t}\n\tdefault:\n\t\terr = fmt.Errorf(\"unexpected HTTP response status %d for '%s'\", res.StatusCode, req.URL.String())\n\t\treturn false, err\n\t}\n}\n\ntype dockerAuths struct {\n\tAuths map[string]dockerAuth `json:\"auths\"`\n}\n\ntype dockerAuth struct {\n\tAuth     string `json:\"auth\"`\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n\tEmail    string `json:\"email\"`\n}\n\n// parseBasicAuth handles cases where configs can have `username` and `password` but no `auth`,\n// or vice-versa.\nfunc parseBasicAuth(logger logr.Logger, auth dockerAuth) (string, string, string) {\n\tvar (\n\t\tusername string\n\t\tpassword string\n\t)\n\n\tif auth.Username != \"\" && auth.Password != \"\" {\n\t\tusername = auth.Username\n\t\tpassword = auth.Password\n\t}\n\n\tif auth.Auth != \"\" {\n\t\tdata, err := base64.StdEncoding.DecodeString(auth.Auth)\n\t\tif err != nil {\n\t\t\tgoto end\n\t\t}\n\n\t\tparts := strings.SplitN(string(data), \":\", 2)\n\t\tif len(parts) != 2 {\n\t\t\tlogger.V(2).Info(\"Skipping invalid parts\", \"length\", len(parts), \"parts\", parts)\n\t\t\tgoto end\n\t\t}\n\n\t\tif (username != \"\" && parts[0] != username) || (password != \"\" && parts[1] != password) {\n\t\t\tlogger.V(2).Info(\"WARNING: Creds have more than two usernames or passwords\")\n\t\t}\n\n\t\tusername = parts[0]\n\t\tpassword = parts[1]\n\t}\n\nend:\n\tif username == \"\" && password == \"\" {\n\t\treturn \"\", \"\", \"\"\n\t}\n\n\tbasicAuth := base64.StdEncoding.EncodeToString([]byte(username + \":\" + password))\n\tif auth.Auth != \"\" && basicAuth != auth.Auth {\n\t\tlogger.Error(fmt.Errorf(\"base64-encoded auth does not match source\"), \"failed to parse auths JSON\")\n\t}\n\treturn username, password, basicAuth\n}\n\n// This is an ad-hoc implementation and not RFC compliant.\n// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate\nfunc parseAuthenticateHeader(headerValue string) (map[string]string, error) {\n\tauthParams := make(map[string]string)\n\n\tparts := strings.Split(headerValue, \" \")\n\tif len(parts) < 2 {\n\t\treturn nil, fmt.Errorf(\"invalid WWW-Authenticate header format\")\n\t}\n\tauthParams[\"scheme\"] = parts[0]\n\n\tparts = strings.Split(parts[1], \",\")\n\tfor _, part := range parts {\n\t\tkeyVal := strings.SplitN(strings.TrimSpace(part), \"=\", 2)\n\t\tif len(keyVal) == 2 {\n\t\t\tkey := strings.TrimSpace(keyVal[0])\n\t\t\tvalue := strings.Trim(strings.TrimSpace(keyVal[1]), `\"`)\n\t\t\tauthParams[key] = value\n\t\t}\n\t}\n\n\treturn authParams, nil\n}\n"
  },
  {
    "path": "pkg/detectors/docker/docker_auth_config_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage docker\n\nfunc TestDocker_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DOCKER\")\n\tinactiveSecret := testSecrets.MustGetField(\"DOCKER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a docker secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Docker,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Docker,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a docker secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Docker,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a docker secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Docker,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Docker.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Docker.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/docker/docker_auth_config_test.go",
    "content": "package docker\n\nimport (\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"testing\"\n)\n\nfunc TestDocker_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t// Kubernetes public test credentials\n\t\t// https://github.com/kubernetes/autoscaler/blob/f22b40eab867cbc52bdb15dc8768962e21d22837/vertical-pod-autoscaler/e2e/vendor/k8s.io/kubernetes/test/e2e/common/node/runtime.go#L283C1-L290C2\n\t\t{\n\t\t\tname: \"GCP auth\",\n\t\t\tinput: `{\n\t\"auths\": {\n\t\t\"https://gcr.io\": {\n\t\t\t\"auth\": \"X2pzb25fa2V5OnsKICAidHlwZSI6ICJzZXJ2aWNlX2FjY291bnQiLAogICJwcm9qZWN0X2lkIjogImF1dGhlbnRpY2F0ZWQtaW1hZ2UtcHVsbGluZyIsCiAgInByaXZhdGVfa2V5X2lkIjogImI5ZjJhNjY0YWE5YjIwNDg0Y2MxNTg2MDYzZmVmZGExOTIyNGFjM2IiLAogICJwcml2YXRlX2tleSI6ICItLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS1cbk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzdTSG5LVEVFaVlMamZcbkpmQVBHbUozd3JCY2VJNTBKS0xxS21GWE5RL3REWGJRK2g5YVl4aldJTDhEeDBKZTc0bVovS01uV2dYRjVLWlNcbm9BNktuSU85Yi9SY1NlV2VpSXRSekkzL1lYVitPNkNjcmpKSXl4anFWam5mVzJpM3NhMzd0OUE5VEZkbGZycm5cbjR6UkpiOWl4eU1YNGJMdHFGR3ZCMDNOSWl0QTNzVlo1ODhrb1FBZmgzSmhhQmVnTWorWjRSYko0aGVpQlFUMDNcbnZVbzViRWFQZVQ5RE16bHdzZWFQV2dydDZOME9VRGNBRTl4bGNJek11MjUzUG4vSzgySFpydEx4akd2UkhNVXhcbng0ZjhwSnhmQ3h4QlN3Z1NORit3OWpkbXR2b0wwRmE3ZGducFJlODZWRDY2ejNZenJqNHlLRXRqc2hLZHl5VWRcbkl5cVhoN1JSQWdNQkFBRUNnZ0VBT3pzZHdaeENVVlFUeEFka2wvSTVTRFVidi9NazRwaWZxYjJEa2FnbmhFcG9cbjFJajJsNGlWMTByOS9uenJnY2p5VlBBd3pZWk1JeDFBZVF0RDdoUzRHWmFweXZKWUc3NkZpWFpQUm9DVlB6b3VcbmZyOGRDaWFwbDV0enJDOWx2QXNHd29DTTdJWVRjZmNWdDdjRTEyRDNRS3NGNlo3QjJ6ZmdLS251WVBmK0NFNlRcbmNNMHkwaCtYRS9kMERvSERoVy96YU1yWEhqOFRvd2V1eXRrYmJzNGYvOUZqOVBuU2dET1lQd2xhbFZUcitGUWFcbkpSd1ZqVmxYcEZBUW14M0Jyd25rWnQzQ2lXV2lGM2QrSGk5RXRVYnRWclcxYjZnK1JRT0licWFtcis4YlJuZFhcbjZWZ3FCQWtKWjhSVnlkeFVQMGQxMUdqdU9QRHhCbkhCbmM0UW9rSXJFUUtCZ1FEMUNlaWN1ZGhXdGc0K2dTeGJcbnplanh0VjFONDFtZHVjQnpvMmp5b1dHbzNQVDh3ckJPL3lRRTM0cU9WSi9pZCs4SThoWjRvSWh1K0pBMDBzNmdcblRuSXErdi9kL1RFalk4MW5rWmlDa21SUFdiWHhhWXR4UjIxS1BYckxOTlFKS2ttOHRkeVh5UHFsOE1veUdmQ1dcbjJ2aVBKS05iNkhabnY5Q3lqZEo5ZzJMRG5RS0JnUUREcVN2eURtaGViOTIzSW96NGxlZ01SK205Z2xYVWdTS2dcbkVzZlllbVJmbU5XQitDN3ZhSXlVUm1ZNU55TXhmQlZXc3dXRldLYXhjK0krYnFzZmx6elZZdFpwMThNR2pzTURcbmZlZWZBWDZCWk1zVXQ3Qmw3WjlWSjg1bnRFZHFBQ0xwWitaLzN0SVJWdWdDV1pRMWhrbmxHa0dUMDI0SkVFKytcbk55SDFnM2QzUlFLQmdRQ1J2MXdKWkkwbVBsRklva0tGTkh1YTBUcDNLb1JTU1hzTURTVk9NK2xIckcxWHJtRjZcbkMwNGNTKzQ0N0dMUkxHOFVUaEpKbTRxckh0Ti9aK2dZOTYvMm1xYjRIakpORDM3TVhKQnZFYTN5ZUxTOHEvK1JcbjJGOU1LamRRaU5LWnhQcG84VzhOSlREWTVOa1BaZGh4a2pzSHdVNGRTNjZwMVRESUU0MGd0TFpaRFFLQmdGaldcbktyblFpTnEzOS9iNm5QOFJNVGJDUUFKbmR3anhTUU5kQTVmcW1rQTlhRk9HbCtqamsxQ1BWa0tNSWxLSmdEYkpcbk9heDl2OUc2Ui9NSTFIR1hmV3QxWU56VnRocjRIdHNyQTB0U3BsbWhwZ05XRTZWejZuQURqdGZQSnMyZUdqdlhcbmpQUnArdjhjY21MK3dTZzhQTGprM3ZsN2VlNXJsWWxNQndNdUdjUHhBb0dBZWRueGJXMVJMbVZubEFpSEx1L0xcbmxtZkF3RFdtRWlJMFVnK1BMbm9Pdk81dFE1ZDRXMS94RU44bFA0cWtzcGtmZk1Rbk5oNFNZR0VlQlQzMlpxQ1RcbkpSZ2YwWGpveXZ2dXA5eFhqTWtYcnBZL3ljMXpmcVRaQzBNTzkvMVVjMWJSR2RaMmR5M2xSNU5XYXA3T1h5Zk9cblBQcE5Gb1BUWGd2M3FDcW5sTEhyR3pNPVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwKICAiY2xpZW50X2VtYWlsIjogImltYWdlLXB1bGxpbmdAYXV0aGVudGljYXRlZC1pbWFnZS1wdWxsaW5nLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwKICAiY2xpZW50X2lkIjogIjExMzc5NzkxNDUzMDA3MzI3ODcxMiIsCiAgImF1dGhfdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi9hdXRoIiwKICAidG9rZW5fdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsCiAgImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHMiLAogICJjbGllbnRfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9yb2JvdC92MS9tZXRhZGF0YS94NTA5L2ltYWdlLXB1bGxpbmclNDBhdXRoZW50aWNhdGVkLWltYWdlLXB1bGxpbmcuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iCn0=\",\n\t\t\t\"email\": \"image-pulling@authenticated-image-pulling.iam.gserviceaccount.com\"\n\t\t}\n\t}\n}`,\n\t\t\twant: []string{`{\"registry\":\"https://gcr.io\",\"auth\":\"X2pzb25fa2V5OnsKICAidHlwZSI6ICJzZXJ2aWNlX2FjY291bnQiLAogICJwcm9qZWN0X2lkIjogImF1dGhlbnRpY2F0ZWQtaW1hZ2UtcHVsbGluZyIsCiAgInByaXZhdGVfa2V5X2lkIjogImI5ZjJhNjY0YWE5YjIwNDg0Y2MxNTg2MDYzZmVmZGExOTIyNGFjM2IiLAogICJwcml2YXRlX2tleSI6ICItLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS1cbk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzdTSG5LVEVFaVlMamZcbkpmQVBHbUozd3JCY2VJNTBKS0xxS21GWE5RL3REWGJRK2g5YVl4aldJTDhEeDBKZTc0bVovS01uV2dYRjVLWlNcbm9BNktuSU85Yi9SY1NlV2VpSXRSekkzL1lYVitPNkNjcmpKSXl4anFWam5mVzJpM3NhMzd0OUE5VEZkbGZycm5cbjR6UkpiOWl4eU1YNGJMdHFGR3ZCMDNOSWl0QTNzVlo1ODhrb1FBZmgzSmhhQmVnTWorWjRSYko0aGVpQlFUMDNcbnZVbzViRWFQZVQ5RE16bHdzZWFQV2dydDZOME9VRGNBRTl4bGNJek11MjUzUG4vSzgySFpydEx4akd2UkhNVXhcbng0ZjhwSnhmQ3h4QlN3Z1NORit3OWpkbXR2b0wwRmE3ZGducFJlODZWRDY2ejNZenJqNHlLRXRqc2hLZHl5VWRcbkl5cVhoN1JSQWdNQkFBRUNnZ0VBT3pzZHdaeENVVlFUeEFka2wvSTVTRFVidi9NazRwaWZxYjJEa2FnbmhFcG9cbjFJajJsNGlWMTByOS9uenJnY2p5VlBBd3pZWk1JeDFBZVF0RDdoUzRHWmFweXZKWUc3NkZpWFpQUm9DVlB6b3VcbmZyOGRDaWFwbDV0enJDOWx2QXNHd29DTTdJWVRjZmNWdDdjRTEyRDNRS3NGNlo3QjJ6ZmdLS251WVBmK0NFNlRcbmNNMHkwaCtYRS9kMERvSERoVy96YU1yWEhqOFRvd2V1eXRrYmJzNGYvOUZqOVBuU2dET1lQd2xhbFZUcitGUWFcbkpSd1ZqVmxYcEZBUW14M0Jyd25rWnQzQ2lXV2lGM2QrSGk5RXRVYnRWclcxYjZnK1JRT0licWFtcis4YlJuZFhcbjZWZ3FCQWtKWjhSVnlkeFVQMGQxMUdqdU9QRHhCbkhCbmM0UW9rSXJFUUtCZ1FEMUNlaWN1ZGhXdGc0K2dTeGJcbnplanh0VjFONDFtZHVjQnpvMmp5b1dHbzNQVDh3ckJPL3lRRTM0cU9WSi9pZCs4SThoWjRvSWh1K0pBMDBzNmdcblRuSXErdi9kL1RFalk4MW5rWmlDa21SUFdiWHhhWXR4UjIxS1BYckxOTlFKS2ttOHRkeVh5UHFsOE1veUdmQ1dcbjJ2aVBKS05iNkhabnY5Q3lqZEo5ZzJMRG5RS0JnUUREcVN2eURtaGViOTIzSW96NGxlZ01SK205Z2xYVWdTS2dcbkVzZlllbVJmbU5XQitDN3ZhSXlVUm1ZNU55TXhmQlZXc3dXRldLYXhjK0krYnFzZmx6elZZdFpwMThNR2pzTURcbmZlZWZBWDZCWk1zVXQ3Qmw3WjlWSjg1bnRFZHFBQ0xwWitaLzN0SVJWdWdDV1pRMWhrbmxHa0dUMDI0SkVFKytcbk55SDFnM2QzUlFLQmdRQ1J2MXdKWkkwbVBsRklva0tGTkh1YTBUcDNLb1JTU1hzTURTVk9NK2xIckcxWHJtRjZcbkMwNGNTKzQ0N0dMUkxHOFVUaEpKbTRxckh0Ti9aK2dZOTYvMm1xYjRIakpORDM3TVhKQnZFYTN5ZUxTOHEvK1JcbjJGOU1LamRRaU5LWnhQcG84VzhOSlREWTVOa1BaZGh4a2pzSHdVNGRTNjZwMVRESUU0MGd0TFpaRFFLQmdGaldcbktyblFpTnEzOS9iNm5QOFJNVGJDUUFKbmR3anhTUU5kQTVmcW1rQTlhRk9HbCtqamsxQ1BWa0tNSWxLSmdEYkpcbk9heDl2OUc2Ui9NSTFIR1hmV3QxWU56VnRocjRIdHNyQTB0U3BsbWhwZ05XRTZWejZuQURqdGZQSnMyZUdqdlhcbmpQUnArdjhjY21MK3dTZzhQTGprM3ZsN2VlNXJsWWxNQndNdUdjUHhBb0dBZWRueGJXMVJMbVZubEFpSEx1L0xcbmxtZkF3RFdtRWlJMFVnK1BMbm9Pdk81dFE1ZDRXMS94RU44bFA0cWtzcGtmZk1Rbk5oNFNZR0VlQlQzMlpxQ1RcbkpSZ2YwWGpveXZ2dXA5eFhqTWtYcnBZL3ljMXpmcVRaQzBNTzkvMVVjMWJSR2RaMmR5M2xSNU5XYXA3T1h5Zk9cblBQcE5Gb1BUWGd2M3FDcW5sTEhyR3pNPVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwKICAiY2xpZW50X2VtYWlsIjogImltYWdlLXB1bGxpbmdAYXV0aGVudGljYXRlZC1pbWFnZS1wdWxsaW5nLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwKICAiY2xpZW50X2lkIjogIjExMzc5NzkxNDUzMDA3MzI3ODcxMiIsCiAgImF1dGhfdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi9hdXRoIiwKICAidG9rZW5fdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsCiAgImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHMiLAogICJjbGllbnRfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9yb2JvdC92MS9tZXRhZGF0YS94NTA5L2ltYWdlLXB1bGxpbmclNDBhdXRoZW50aWNhdGVkLWltYWdlLXB1bGxpbmcuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iCn0=\"}`},\n\t\t},\n\t\t// Relies on the base64 decoder, which isn't present in this test (yet?)\n\t\t//\t\t{\n\t\t//\t\t\tname: \"kubernetes .dockerconfigjson\",\n\t\t//\t\t\tinput: `apiVersion: v1\n\t\t//data:\n\t\t// .dockerconfigjson: eyJhdXRocyI6eyJodHRwczovL2djci5pbyI6eyJ1c2VybmFtZSI6Il9qc29uX2tleSIsInBhc3N3b3JkIjoie1xuICBcInR5cGVcIjogXCJzZXJ2aWNlX2FjY291bnRcIixcbiAgXCJwcm9qZWN0X2lkXCI6IFwiY29uc3RhbnQtY3ViaXN0LTE3MzEyM1wiLFxuICBcInByaXZhdGVfa2V5X2lkXCI6IFwiYWRiMzY3M2NiOTkzNzkyNjZiY2MxZDU1YmIxZTdiZDFlYzM5NGI1Y1wiLFxuICBcInByaXZhdGVfa2V5XCI6IFwiLS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tXFxuTUlJRXZRSUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NCS2N3Z2dTakFnRUFBb0lCQVFDNm8zN0o4S2kxUWp3RVxcbnhNT3ROUVZaK2xsWUxIdlNXV2tDeXp1a3JwbHdZRU9KRk5VR00yQ3NySHpjM0pDUDhGYWo1RVRHMjlvT1pLVkJcXG5MSjU3eVdKSEpyekhIb2JyOHNsNytpcjRjYUovSzNiS2lybmZWYTZFeXk5azFIa0RMSlZ4T1lsaXFTbkdtRlZ5XFxuQ3lpYXltNTI1V3VqanZIQkRaZUdsYzlqb1RLMG9yQXYvUCthZzhleUUvY05DS0FwTkk4ZTFXYmlhMFNCdWEwblxcblVZbFB1RXRxdzJ3NDhJbkh6akVQY0VmdENzWjBOZGhkY3hTdVNuSVB5NW9ua2JuVXhZWnAzUjF3TmQ3eDdaQk5cXG5ESmFCWEJTMlVkR1M0ditzeWJVQlU1aXFBckRNbVNmWUwxN09TU3ZzdEZSdVEydkJaa0M3TU96RWd2MUlIZjBXXFxubzlOSzBFaHZBZ01CQUFFQ2dnRUFDUm1MbkZaSzJORHFrdU9kRkJ3dnIwYTdoY2NLeW5pOWhxWURaSERNTTduSVxcbmU5aUkxN2ZpNWgyMWdNeVM1OUcwc21KTGV0UDJwUmtCemFtdjdjMGwwNGp2VDFpM3IxZ0pFWU1Oc1V0VHZFRG1cXG42OUorWkRDTjc3K1FYS21DQ2tZKzRHUmVieHhjV0doNC9MUjZrd0Y5Qi9oV1JTL2xBdlZNc1ZmVjRyK3JTZVNjXFxubU1KOTRBUTROM3hyV0VRc3Vpd1ZIZldMdElMTWZGN1JoV3VzdjJiZ1gvRCs0ajdISHRoODVrYlcxSzR0MnFkN1xcbkIwaEJEcVlQTEtjYzJVNkNJR0NRZ1h3THNlYUUxRkptYWpsdnNVK0pXdmY2MmZTNk8wSlVMeVFLMzZkczZkRlJcXG5qaDg1TWJsZVlHMWdpaFpPTXJtcENvWklUazdFT01lU2pQZ0VaWG5pVVFLQmdRRDNtUHJrZmVKVWNXWmpNZndCXFxuYnJJNE1NRWl2R1JJVDR5RzZxZHZpZFIrd1U3djMxbG1Oa1A3S2s4K1hoQWtGMk1pQkRSakFGVm8rNHQ3a3paRFxcbk45Zk9NSlgwakZnUVpLajFuR1gxekZ2ZERqRTh3ZWRTN2ZMV3BOczJlZm5GWUpQRm5SRU16eG81VWpGNTZ4V1pcXG5ZQmI2VHlNaTNRa1lEblA0WTVjTUZCVUpiUUtCZ1FEQStPNDlUc2EyYWdWUnJYbC8xRDNzWHd0UjdKSGhuRURkXFxuNWlZM0FtOVQxV2pVVTE1T2lwTUxOaXpBb3lRWGlKRVlaMmNuRHZnbHdkNEsvMWFLbityc3hzWUdFZjhoWVBIclxcbkJoN3FueW44SzJseTJoakUxY0xpVFg4NEVnd1VMcFJjeGo3bkM0ZWFLOEdJeUdLNnZrR3NoNCs1bnJLVFlkaUtcXG5MeUhSMUc2cnl3S0JnUURnLzJqSGFNbmEySzRsYUUvTWNXNk05MmtiQ3IzS3BGZGNaeksrZmk3Vy9RMmhsNEtqXFxuQ3A4ZVNDVjQxSHV3Z0h3NmRqMncxYVhINEFheHhtWWlFVVlQL2tEVzJRNVIzMWRXMHNnbzVJdDZSeUpoUndmU1xcbmFaOHFoT2NjQ3gzNXlqaWU5SXVBNjFhMlRrWGR0ODZKOFRNUVJnZjA3NDRMQ1Y5RGtpUzUraW5meFFLQmdFMVdcXG5ObHlacXFmR203VWRPZmxSL1RNeThCMTRHd3I1RFVJaEQ2V3lNeDI5QkpNN2lpc2QvRXBjL3RpQlNXQ3BHY1ZYXFxuQTQ4eXY1NmFNTHZsa3pCaFlNeGQ2VlRiZDQxUUJnUXo0c1lTM2Nlek9rS09SNmp6Sm5SOXJJT3pMK1lTdU9EcFxcbmpxSVlDOU5zdjlacXdLNm91emRDNlFYeUpRMU9CSE4wNmkvbTNDZTdBb0dBU01wRStscDlxV2ZWYXlGV2tlWVBcXG5OOFhId2FNUWNkT0ZkbDZFdlF0ZWtQY0xiQ1F6UzRSdEhBT01NTDN5ci9DQUk5SmZkanhWMHdicW1oNlJ3WFAzXFxuKzhkOVJpNjhsMGV3NUhLMDJWRHFhZE8vOTJhaHNrNmYxV1ZOL0dMcFg4Yk9NZEZFdnJOS09zUVk0RW9DV0JTa1xcblF1ZmRBdFZueE1UZG9ydTNxY0N4RG1vPVxcbi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS1cXG5cIixcbiAgXCJjbGllbnRfZW1haWxcIjogXCJrZi1hY2NvdW50QGNvbnN0YW50LWN1YmlzdC0xNzMxMjMuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb21cIixcbiAgXCJjbGllbnRfaWRcIjogXCIxMDkyODcyODAxMzE5ODQ2MTA2MTZcIixcbiAgXCJhdXRoX3VyaVwiOiBcImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi9hdXRoXCIsXG4gIFwidG9rZW5fdXJpXCI6IFwiaHR0cHM6Ly9vYXV0aDIuZ29vZ2xlYXBpcy5jb20vdG9rZW5cIixcbiAgXCJhdXRoX3Byb3ZpZGVyX3g1MDlfY2VydF91cmxcIjogXCJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHNcIixcbiAgXCJjbGllbnRfeDUwOV9jZXJ0X3VybFwiOiBcImh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL3JvYm90L3YxL21ldGFkYXRhL3g1MDkva2YtYWNjb3VudCU0MGNvbnN0YW50LWN1YmlzdC0xNzMxMjMuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb21cIlxufSIsImVtYWlsIjoia2YtYWNjb3VudEBjb25zdGFudC1jdWJpc3QtMTczMTIzLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwiYXV0aCI6IlgycHpiMjVmYTJWNU9uc0tJQ0FpZEhsd1pTSTZJQ0p6WlhKMmFXTmxYMkZqWTI5MWJuUWlMQW9nSUNKd2NtOXFaV04wWDJsa0lqb2dJbU52Ym5OMFlXNTBMV04xWW1semRDMHhOek14TWpNaUxBb2dJQ0p3Y21sMllYUmxYMnRsZVY5cFpDSTZJQ0poWkdJek5qY3pZMkk1T1RNM09USTJObUpqWXpGa05UVmlZakZsTjJKa01XVmpNemswWWpWaklpd0tJQ0FpY0hKcGRtRjBaVjlyWlhraU9pQWlMUzB0TFMxQ1JVZEpUaUJRVWtsV1FWUkZJRXRGV1MwdExTMHRYRzVOU1VsRmRsRkpRa0ZFUVU1Q1oydHhhR3RwUnpsM01FSkJVVVZHUVVGVFEwSkxZM2RuWjFOcVFXZEZRVUZ2U1VKQlVVTTJiek0zU2poTGFURlJhbmRGWEc1NFRVOTBUbEZXV2l0c2JGbE1TSFpUVjFkclEzbDZkV3R5Y0d4M1dVVlBTa1pPVlVkTk1rTnpja2g2WXpOS1ExQTRSbUZxTlVWVVJ6STViMDlhUzFaQ1hHNU1TalUzZVZkS1NFcHlla2hJYjJKeU9ITnNOeXRwY2pSallVb3ZTek5pUzJseWJtWldZVFpGZVhrNWF6RklhMFJNU2xaNFQxbHNhWEZUYmtkdFJsWjVYRzVEZVdsaGVXMDFNalZYZFdwcWRraENSRnBsUjJ4ak9XcHZWRXN3YjNKQmRpOVFLMkZuT0dWNVJTOWpUa05MUVhCT1NUaGxNVmRpYVdFd1UwSjFZVEJ1WEc1VldXeFFkVVYwY1hjeWR6UTRTVzVJZW1wRlVHTkZablJEYzFvd1RtUm9aR040VTNWVGJrbFFlVFZ2Ym10aWJsVjRXVnB3TTFJeGQwNWtOM2czV2tKT1hHNUVTbUZDV0VKVE1sVmtSMU0wZGl0emVXSlZRbFUxYVhGQmNrUk5iVk5tV1V3eE4wOVRVM1p6ZEVaU2RWRXlka0phYTBNM1RVOTZSV2QyTVVsSVpqQlhYRzV2T1U1TE1FVm9ka0ZuVFVKQlFVVkRaMmRGUVVOU2JVeHVSbHBMTWs1RWNXdDFUMlJHUW5kMmNqQmhOMmhqWTB0NWJtazVhSEZaUkZwSVJFMU5OMjVKWEc1bE9XbEpNVGRtYVRWb01qRm5UWGxUTlRsSE1ITnRTa3hsZEZBeWNGSnJRbnBoYlhZM1l6QnNNRFJxZGxReGFUTnlNV2RLUlZsTlRuTlZkRlIyUlVSdFhHNDJPVW9yV2tSRFRqYzNLMUZZUzIxRFEydFpLelJIVW1WaWVIaGpWMGRvTkM5TVVqWnJkMFk1UWk5b1YxSlRMMnhCZGxaTmMxWm1WalJ5SzNKVFpWTmpYRzV0VFVvNU5FRlJORTR6ZUhKWFJWRnpkV2wzVmtobVYweDBTVXhOWmtZM1VtaFhkWE4yTW1KbldDOUVLelJxTjBoSWRHZzROV3RpVnpGTE5IUXljV1EzWEc1Q01HaENSSEZaVUV4TFkyTXlWVFpEU1VkRFVXZFlkMHh6WldGRk1VWktiV0ZxYkhaelZTdEtWM1ptTmpKbVV6WlBNRXBWVEhsUlN6TTJaSE0yWkVaU1hHNXFhRGcxVFdKc1pWbEhNV2RwYUZwUFRYSnRjRU52V2tsVWF6ZEZUMDFsVTJwUVowVmFXRzVwVlZGTFFtZFJSRE50VUhKclptVktWV05YV21wTlpuZENYRzVpY2trMFRVMUZhWFpIVWtsVU5IbEhObkZrZG1sa1VpdDNWVGQyTXpGc2JVNXJVRGRMYXpncldHaEJhMFl5VFdsQ1JGSnFRVVpXYnlzMGREZHJlbHBFWEc1T09XWlBUVXBZTUdwR1oxRmFTMm94YmtkWU1YcEdkbVJFYWtVNGQyVmtVemRtVEZkd1RuTXlaV1p1UmxsS1VFWnVVa1ZOZW5odk5WVnFSalUyZUZkYVhHNVpRbUkyVkhsTmFUTlJhMWxFYmxBMFdUVmpUVVpDVlVwaVVVdENaMUZFUVN0UE5EbFVjMkV5WVdkV1VuSlliQzh4UkROeldIZDBVamRLU0dodVJVUmtYRzQxYVZrelFXMDVWREZYYWxWVk1UVlBhWEJOVEU1cGVrRnZlVkZZYVVwRldWb3lZMjVFZG1kc2QyUTBTeTh4WVV0dUszSnplSE5aUjBWbU9HaFpVRWh5WEc1Q2FEZHhibmx1T0VzeWJIa3lhR3BGTVdOTWFWUllPRFJGWjNkVlRIQlNZM2hxTjI1RE5HVmhTemhIU1hsSFN6WjJhMGR6YURRck5XNXlTMVJaWkdsTFhHNU1lVWhTTVVjMmNubDNTMEpuVVVSbkx6SnFTR0ZOYm1FeVN6UnNZVVV2VFdOWE5rMDVNbXRpUTNJelMzQkdaR05hZWtzclptazNWeTlSTW1oc05FdHFYRzVEY0RobFUwTldOREZJZFhkblNIYzJaR295ZHpGaFdFZzBRV0Y0ZUcxWmFVVlZXVkF2YTBSWE1sRTFVak14WkZjd2MyZHZOVWwwTmxKNVNtaFNkMlpUWEc1aFdqaHhhRTlqWTBONE16VjVhbWxsT1VsMVFUWXhZVEpVYTFoa2REZzJTamhVVFZGU1oyWXdOelEwVEVOV09VUnJhVk0xSzJsdVpuaFJTMEpuUlRGWFhHNU9iSGxhY1hGbVIyMDNWV1JQWm14U0wxUk5lVGhDTVRSSGQzSTFSRlZKYUVRMlYzbE5lREk1UWtwTk4ybHBjMlF2UlhCakwzUnBRbE5YUTNCSFkxWllYRzVCTkRoNWRqVTJZVTFNZG14cmVrSm9XVTE0WkRaV1ZHSmtOREZSUW1kUmVqUnpXVk16WTJWNlQydExUMUkyYW5wS2JsSTVja2xQZWt3cldWTjFUMFJ3WEc1cWNVbFpRemxPYzNZNVduRjNTelp2ZFhwa1F6WlJXSGxLVVRGUFFraE9NRFpwTDIwelEyVTNRVzlIUVZOTmNFVXJiSEE1Y1ZkbVZtRjVSbGRyWlZsUVhHNU9PRmhJZDJGTlVXTmtUMFprYkRaRmRsRjBaV3RRWTB4aVExRjZVelJTZEVoQlQwMU5URE41Y2k5RFFVazVTbVprYW5oV01IZGljVzFvTmxKM1dGQXpYRzRyT0dRNVVtazJPR3d3WlhjMVNFc3dNbFpFY1dGa1R5ODVNbUZvYzJzMlpqRlhWazR2UjB4d1dEaGlUMDFrUmtWMmNrNUxUM05SV1RSRmIwTlhRbE5yWEc1UmRXWmtRWFJXYm5oTlZHUnZjblV6Y1dORGVFUnRiejFjYmkwdExTMHRSVTVFSUZCU1NWWkJWRVVnUzBWWkxTMHRMUzFjYmlJc0NpQWdJbU5zYVdWdWRGOWxiV0ZwYkNJNklDSnJaaTFoWTJOdmRXNTBRR052Ym5OMFlXNTBMV04xWW1semRDMHhOek14TWpNdWFXRnRMbWR6WlhKMmFXTmxZV05qYjNWdWRDNWpiMjBpTEFvZ0lDSmpiR2xsYm5SZmFXUWlPaUFpTVRBNU1qZzNNamd3TVRNeE9UZzBOakV3TmpFMklpd0tJQ0FpWVhWMGFGOTFjbWtpT2lBaWFIUjBjSE02THk5aFkyTnZkVzUwY3k1bmIyOW5iR1V1WTI5dEwyOHZiMkYxZEdneUwyRjFkR2dpTEFvZ0lDSjBiMnRsYmw5MWNta2lPaUFpYUhSMGNITTZMeTl2WVhWMGFESXVaMjl2WjJ4bFlYQnBjeTVqYjIwdmRHOXJaVzRpTEFvZ0lDSmhkWFJvWDNCeWIzWnBaR1Z5WDNnMU1EbGZZMlZ5ZEY5MWNtd2lPaUFpYUhSMGNITTZMeTkzZDNjdVoyOXZaMnhsWVhCcGN5NWpiMjB2YjJGMWRHZ3lMM1l4TDJObGNuUnpJaXdLSUNBaVkyeHBaVzUwWDNnMU1EbGZZMlZ5ZEY5MWNtd2lPaUFpYUhSMGNITTZMeTkzZDNjdVoyOXZaMnhsWVhCcGN5NWpiMjB2Y205aWIzUXZkakV2YldWMFlXUmhkR0V2ZURVd09TOXJaaTFoWTJOdmRXNTBKVFF3WTI5dWMzUmhiblF0WTNWaWFYTjBMVEUzTXpFeU15NXBZVzB1WjNObGNuWnBZMlZoWTJOdmRXNTBMbU52YlNJS2ZRPT0ifX19\n\t\t//kind: Secret\n\t\t//metadata:\n\t\t// name: docker-secret\n\t\t//type: kubernetes.io/dockerconfigjson`,\n\t\t//\t\t\twant: []string{\"3aBcDFE5678901234567890_1a2b3c4d\"},\n\t\t//\t\t},\n\t\t{\n\t\t\tname: \"DOCKER_AUTH_CONFIG escaped\",\n\t\t\tinput: `[[runners]]\n  name = \"docker-test@236\"\n  url = \"http://10.88.26.237:80\"\n  executor = \"docker\"\n  environment = [\"DOCKER_AUTH_CONFIG={\\\"auths\\\":{\\\"docker.contoso.com.tw:8083\\\":{\\\"auth\\\":\\\"c2Zjcy50ZXN0ZXI6c2Zjcw==\\\"}}}\"]\n  [runners.custom_build_dir]\n  [runners.cache]\n    Insecure = false`,\n\t\t\twant: []string{`{\"registry\":\"docker.contoso.com.tw:8083\",\"auth\":\"c2Zjcy50ZXN0ZXI6c2Zjcw==\"}`},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple escapes\",\n\t\t\tinput: `[[runners]]\n  environment = [\"DOCKER_AUTH_CONFIG={\\\\\\\"auths\\\\\\\":{\\\\\\\"docker.contoso.com.tw:8081\\\\\\\":{\\\\\\\"auth\\\\\\\":\\\\\\\"c2Zjcy50ZXN0ZXI6c2Zjcw==\\\\\\\"}}}\"]`,\n\t\t\twant: []string{`{\"registry\":\"docker.contoso.com.tw:8081\",\"auth\":\"c2Zjcy50ZXN0ZXI6c2Zjcw==\"}`},\n\t\t},\n\t\t{\n\t\t\tname: \"DOCKER_AUTH_CONFIG\",\n\t\t\tinput: `variables:\n  DOCKER_DRIVER: overlay2\n  DOCKER_AUTH_CONFIG: '{\"auths\": {\"local-docker.artifactory.university.edu.au\": {\"auth\": \"YmFtYm9vOmpoMkh6UnNRU3pad3liaDc=\"}}}'\n`,\n\t\t\twant: []string{`{\"registry\":\"local-docker.artifactory.university.edu.au\",\"auth\":\"YmFtYm9vOmpoMkh6UnNRU3pad3liaDc=\"}`},\n\t\t},\n\t\t{\n\t\t\tname: \"empty email string\",\n\t\t\tinput: `{\n  \"auths\": {\n    \"quay.io\": {\n      \"auth\": \"dHJ1ZmZsZWhvZzpiZDQyNzQ2Yy1hNzc3LTQ4ZDktYjBhMi04N2I2YzEzMjdkMDA=\",\n      \"email\": \"\"\n    }\n  }\n}`,\n\t\t\twant: []string{`{\"registry\":\"quay.io\",\"auth\":\"dHJ1ZmZsZWhvZzpiZDQyNzQ2Yy1hNzc3LTQ4ZDktYjBhMi04N2I2YzEzMjdkMDA=\"}`},\n\t\t},\n\t\t{\n\t\t\tname:  \"docker.io registry\",\n\t\t\tinput: `{\"auths\":{\"docker.io\":{\"auth\": \"dHJ1ZmZsZWhvZzpiZDQyNzQ2Yy1hNzc3LTQ4ZDktYjBhMi04N2I2YzEzMjdkMDA=\"}}}`,\n\t\t\twant:  []string{`{\"registry\":\"index.docker.io\",\"auth\":\"dHJ1ZmZsZWhvZzpiZDQyNzQ2Yy1hNzc3LTQ4ZDktYjBhMi04N2I2YzEzMjdkMDA=\"}`},\n\t\t},\n\t\t{\n\t\t\tname:  \"registry with slashes\",\n\t\t\tinput: `{\"auths\":{\"https://index.docker.io/v2/\":{\"auth\": \"dHJ1ZmZsZWhvZzpiZDQyNzQ2Yy1hNzc3LTQ4ZDktYjBhMi04N2I2YzEzMjdkMDA=\"}}}`,\n\t\t\twant:  []string{`{\"registry\":\"https://index.docker.io/v2/\",\"auth\":\"dHJ1ZmZsZWhvZzpiZDQyNzQ2Yy1hNzc3LTQ4ZDktYjBhMi04N2I2YzEzMjdkMDA=\"}`},\n\t\t},\n\t\t{\n\t\t\tname:  \"literal newlines\",\n\t\t\tinput: `{\\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`,\n\t\t\twant:  []string{`{\"registry\":\"registry.company.com\",\"auth\":\"Y29uZXhwOkZUQUBDTkNGMG5AenVyZTM=\"}`},\n\t\t},\n\t\t{\n\t\t\tname:  \"literal newlines and tabs\",\n\t\t\tinput: `  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}\"`,\n\t\t\twant:  []string{`{\"registry\":\"https://index.docker.io/v2/\",\"auth\":\"Y29uZXhwOkZUQUBDTkNGMG5AenVyZTM=\"}`},\n\t\t},\n\t\t{\n\t\t\tname: \"content after last }\",\n\t\t\t// This is base64-encoded, however, that doesn't get detected in these tests.\n\t\t\t//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\"}}}`,\n\t\t\tinput: `{\"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\"}}}`,\n\t\t\twant:  []string{`{\"registry\":\"quay.io\",\"auth\":\"MTIzOjEyMw==\"}`},\n\t\t},\n\n\t\t// False-positives\n\t\t{\n\t\t\tname: \"registry.example.com\",\n\t\t\tinput: `1. Modify the runner's config.toml file as follows:\n\n\t\t[[runners]]\n\t\t\tenvironment = [\"DOCKER_AUTH_CONFIG={\\\"auths\\\":{\\\"registry.example.com:5000\\\":{\\\"auth\\\":\\\"bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ=\\\"}}}\"]\n\t\t\t`,\n\t\t},\n\t\t{\n\t\t\tname: \"\",\n\t\t\tinput: `sudo gitlab-runner register -n \\\n   --url https://gitlab.contoso.cn:8443/ \\\n   --registration-token ****** \\\n   --docker-extra-hosts \"gitlab.contoso.cn:10.202.101.22\" \\\n   --tag-list \"golang-test\" \\\n   --executor docker \\\n   --description \"229 contoso golang test\" \\\n   --docker-image \"docker:19.03.1\" \\\n   --docker-privileged \\\n   --env \"DOCKER_AUTH_CONFIG={\\\"auths\\\": {\\\"registry.contoso123.cn:5000\\\": {\\\"auth\\\": \\\"******\\\"},\\\"registry.contoso.com.cn\\\": {\\\"auth\\\": \\\"******\\\"}}}\" \\\n   --custom_build_dir-enabled=true  `,\n\t\t},\n\t\t// TODO: There's currently no solution to detect/ignore environment variables or placeholders.\n\t\t//\t{\n\t\t//\t\tname: \"variables\",\n\t\t//\t\tinput: `analyze_reports:\n\t\t//stage: post\n\t\t//image: registry.gitlab.com/detecttechnologies/software/webapps/t-pulse/web/tpulse-msa/tpulse-msa-cicd:production\n\t\t//variables:\n\t\t//  DOCKER_AUTH_CONFIG: '{\"auths\":{\"registry.gitlab.com\":{\"username\":\"${CI_CD_API_USER}\",\"password\":\"${CI_CD_API_TOKEN}\"}}}'`,\n\t\t//\t},\n\n\t\t{\n\t\t\tname: \"empty registry\",\n\t\t\tinput: `The command outputs the following:\n* A non-bootable configuration ISO ( agentconfig.noarch.iso)\n* 'auth' directory: contains kubeconfig and kubeadmin-password\n\nNote: for disconnected environments, specify a dummy pull-secret in install-config.yaml (e.g. '{\"auths\":{\"\":{\"auth\":\"dXNlcjpwYXNz\"}}}').`,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_ParseAuth(t *testing.T) {\n\ttests := map[dockerAuth]string{\n\t\t// Only auth\n\t\tdockerAuth{\n\t\t\tAuth: \"Ym9iOnMzY3IzdHBAc3N3MHJkIQ==\",\n\t\t}: \"bob:s3cr3tp@ssw0rd!\",\n\t\t// Auth with colon\n\t\tdockerAuth{\n\t\t\tAuth: \"OTM5MDQ5YjQtNTllMS00YzlhLWJlYzgtMjAyZTAxZjc2MWFlOjZCLkpFOmZPT2hvLTI3P244TlYybDZqQS9UdjBMd1hm\",\n\t\t}: \"939049b4-59e1-4c9a-bec8-202e01f761ae:6B.JE:fOOho-27?n8NV2l6jA/Tv0LwXf\",\n\t\t// Only username + password\n\t\tdockerAuth{\n\t\t\tUsername: \"my_username\",\n\t\t\tPassword: \"my_password\",\n\t\t}: \"my_username:my_password\",\n\t\t// Auth and username+password\n\t\tdockerAuth{\n\t\t\tAuth:     \"bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ==\",\n\t\t\tUsername: \"my_username\",\n\t\t\tPassword: \"my_password\",\n\t\t}: \"my_username:my_password\",\n\t\t// Kubernetes public test credentials\n\t\t// https://github.com/kubernetes/autoscaler/blob/f22b40eab867cbc52bdb15dc8768962e21d22837/vertical-pod-autoscaler/e2e/vendor/k8s.io/kubernetes/test/e2e/common/node/runtime.go#L283C1-L290C2\n\t\tdockerAuth{\n\t\t\tAuth: `X2pzb25fa2V5OnsKICAidHlwZSI6ICJzZXJ2aWNlX2FjY291bnQiLAogICJwcm9qZWN0X2lkIjogImF1dGhlbnRpY2F0ZWQtaW1hZ2UtcHVsbGluZyIsCiAgInByaXZhdGVfa2V5X2lkIjogImI5ZjJhNjY0YWE5YjIwNDg0Y2MxNTg2MDYzZmVmZGExOTIyNGFjM2IiLAogICJwcml2YXR\nlX2tleSI6ICItLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS1cbk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzdTSG5LVEVFaVlMamZcbkpmQVBHbUozd3JCY2VJNTBKS0xxS21GWE5RL3REWGJRK2g5YVl4aldJTDhEeDBKZTc0bVovS01uV2dYRjVLWlNcbm9BNktuSU85Yi9SY1NlV2V\npSXRSekkzL1lYVitPNkNjcmpKSXl4anFWam5mVzJpM3NhMzd0OUE5VEZkbGZycm5cbjR6UkpiOWl4eU1YNGJMdHFGR3ZCMDNOSWl0QTNzVlo1ODhrb1FBZmgzSmhhQmVnTWorWjRSYko0aGVpQlFUMDNcbnZVbzViRWFQZVQ5RE16bHdzZWFQV2dydDZOME9VRGNBRTl4bGNJek11MjUzUG4vSzgySFpydEx4akd2UkhNVXhcbng0Zjh\nwSnhmQ3h4QlN3Z1NORit3OWpkbXR2b0wwRmE3ZGducFJlODZWRDY2ejNZenJqNHlLRXRqc2hLZHl5VWRcbkl5cVhoN1JSQWdNQkFBRUNnZ0VBT3pzZHdaeENVVlFUeEFka2wvSTVTRFVidi9NazRwaWZxYjJEa2FnbmhFcG9cbjFJajJsNGlWMTByOS9uenJnY2p5VlBBd3pZWk1JeDFBZVF0RDdoUzRHWmFweXZKWUc3NkZpWFpQUm9\nDVlB6b3VcbmZyOGRDaWFwbDV0enJDOWx2QXNHd29DTTdJWVRjZmNWdDdjRTEyRDNRS3NGNlo3QjJ6ZmdLS251WVBmK0NFNlRcbmNNMHkwaCtYRS9kMERvSERoVy96YU1yWEhqOFRvd2V1eXRrYmJzNGYvOUZqOVBuU2dET1lQd2xhbFZUcitGUWFcbkpSd1ZqVmxYcEZBUW14M0Jyd25rWnQzQ2lXV2lGM2QrSGk5RXRVYnRWclcxYjZ\nnK1JRT0licWFtcis4YlJuZFhcbjZWZ3FCQWtKWjhSVnlkeFVQMGQxMUdqdU9QRHhCbkhCbmM0UW9rSXJFUUtCZ1FEMUNlaWN1ZGhXdGc0K2dTeGJcbnplanh0VjFONDFtZHVjQnpvMmp5b1dHbzNQVDh3ckJPL3lRRTM0cU9WSi9pZCs4SThoWjRvSWh1K0pBMDBzNmdcblRuSXErdi9kL1RFalk4MW5rWmlDa21SUFdiWHhhWXR4UjI\nxS1BYckxOTlFKS2ttOHRkeVh5UHFsOE1veUdmQ1dcbjJ2aVBKS05iNkhabnY5Q3lqZEo5ZzJMRG5RS0JnUUREcVN2eURtaGViOTIzSW96NGxlZ01SK205Z2xYVWdTS2dcbkVzZlllbVJmbU5XQitDN3ZhSXlVUm1ZNU55TXhmQlZXc3dXRldLYXhjK0krYnFzZmx6elZZdFpwMThNR2pzTURcbmZlZWZBWDZCWk1zVXQ3Qmw3WjlWSjg\n1bnRFZHFBQ0xwWitaLzN0SVJWdWdDV1pRMWhrbmxHa0dUMDI0SkVFKytcbk55SDFnM2QzUlFLQmdRQ1J2MXdKWkkwbVBsRklva0tGTkh1YTBUcDNLb1JTU1hzTURTVk9NK2xIckcxWHJtRjZcbkMwNGNTKzQ0N0dMUkxHOFVUaEpKbTRxckh0Ti9aK2dZOTYvMm1xYjRIakpORDM3TVhKQnZFYTN5ZUxTOHEvK1JcbjJGOU1LamRRaU5\nLWnhQcG84VzhOSlREWTVOa1BaZGh4a2pzSHdVNGRTNjZwMVRESUU0MGd0TFpaRFFLQmdGaldcbktyblFpTnEzOS9iNm5QOFJNVGJDUUFKbmR3anhTUU5kQTVmcW1rQTlhRk9HbCtqamsxQ1BWa0tNSWxLSmdEYkpcbk9heDl2OUc2Ui9NSTFIR1hmV3QxWU56VnRocjRIdHNyQTB0U3BsbWhwZ05XRTZWejZuQURqdGZQSnMyZUdqdlh\ncbmpQUnArdjhjY21MK3dTZzhQTGprM3ZsN2VlNXJsWWxNQndNdUdjUHhBb0dBZWRueGJXMVJMbVZubEFpSEx1L0xcbmxtZkF3RFdtRWlJMFVnK1BMbm9Pdk81dFE1ZDRXMS94RU44bFA0cWtzcGtmZk1Rbk5oNFNZR0VlQlQzMlpxQ1RcbkpSZ2YwWGpveXZ2dXA5eFhqTWtYcnBZL3ljMXpmcVRaQzBNTzkvMVVjMWJSR2RaMmR5M2x\nSNU5XYXA3T1h5Zk9cblBQcE5Gb1BUWGd2M3FDcW5sTEhyR3pNPVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwKICAiY2xpZW50X2VtYWlsIjogImltYWdlLXB1bGxpbmdAYXV0aGVudGljYXRlZC1pbWFnZS1wdWxsaW5nLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwKICAiY2xpZW50X2lkIjogIjExMzc5NzkxNDUzMDA\n3MzI3ODcxMiIsCiAgImF1dGhfdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi9hdXRoIiwKICAidG9rZW5fdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsCiAgImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHMiLAogICJjbGllbnRfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9yb2JvdC92MS9tZXRhZGF0YS94NTA5L2ltYWdlLXB1bGxpbmclNDBhdXRoZW50aWNhdGVkLWltYWdlLXB1bGxpbmcuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iCn0=`,\n\t\t}: \"_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}\",\n\n\t\t// Errors\n\t\t// Auth isn't `username:password` format.\n\t\tdockerAuth{\n\t\t\tAuth: \"dGhpc2lzYXN0cmluZ3dpdGhvdXRhbnljb2xvbg==\",\n\t\t}: \"\",\n\t\t// Invalid base64\n\t\tdockerAuth{\n\t\t\tAuth: \"asda42asd214ASDKqwwq==\",\n\t\t}: \"\",\n\t}\n\n\tctx := context.Background()\n\tfor input, expected := range tests {\n\t\tusername, password, encoded := parseBasicAuth(ctx.Logger(), input)\n\n\t\tif expected == \"\" {\n\t\t\tif encoded != \"\" {\n\t\t\t\tt.Errorf(\"expected an error, got: username=%s, password=%s, encoded=%s\", username, password, encoded)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif diff := cmp.Diff(expected, username+\":\"+password); diff != \"\" {\n\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", input, diff)\n\t\t}\n\t}\n}\n\nfunc Test_ParseAuthenticateHeader(t *testing.T) {\n\ttests := map[string]map[string]string{\n\t\t`Bearer realm=\"https://auth.docker.io/token\",service=\"registry.docker.io\"`: {\n\t\t\t\"scheme\":  \"Bearer\",\n\t\t\t\"realm\":   \"https://auth.docker.io/token\",\n\t\t\t\"service\": \"registry.docker.io\",\n\t\t},\n\t\t`Bearer realm=\"https://ghcr.io/token\",service=\"ghcr.io\",scope=\"repository:user/image:pull\"`: {\n\t\t\t\"scheme\":  \"Bearer\",\n\t\t\t\"realm\":   \"https://ghcr.io/token\",\n\t\t\t\"service\": \"ghcr.io\",\n\t\t\t\"scope\":   \"repository:user/image:pull\",\n\t\t},\n\t\t`Bearer realm=\"https://artifactory.example.com:443/artifactory/api/docker/docker-repo/v2/token\",service=\"artifactory.example.com:443\"`: {\n\t\t\t\"scheme\":  \"Bearer\",\n\t\t\t\"realm\":   \"https://artifactory.example.com:443/artifactory/api/docker/docker-repo/v2/token\",\n\t\t\t\"service\": \"artifactory.example.com:443\",\n\t\t},\n\t}\n\n\tfor input, expected := range tests {\n\t\tactual, err := parseAuthenticateHeader(input)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"failed to parse www-authenticate header: %v\", err)\n\t\t}\n\n\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", input, diff)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dockerhub/v1/dockerhub.go",
    "content": "package dockerhub\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/golang-jwt/jwt/v5\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\nfunc (s Scanner) Version() int { return 1 }\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\t// Can use email or username for login.\n\tusernamePat = regexp.MustCompile(detectors.PrefixRegex([]string{\"docker\"}) + `(?im)(?:user|usr|-u|id)\\S{0,40}?[:=\\s]{1,3}[ '\"=]?([a-zA-Z0-9]{4,40})\\b`)\n\temailPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"docker\"}) + common.EmailPattern)\n\n\t// Can use password or personal access token (PAT) for login, but this scanner will only check for PATs.\n\taccessTokenPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"docker\"}\n}\n\n// FromData will find and optionally verify Dockerhub secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// Deduplicate results.\n\ttokens := make(map[string]struct{})\n\tfor _, matches := range accessTokenPat.FindAllStringSubmatch(dataStr, -1) {\n\t\ttokens[matches[1]] = struct{}{}\n\t}\n\tif len(tokens) == 0 {\n\t\treturn\n\t}\n\tusernames := make(map[string]struct{})\n\tfor _, matches := range usernamePat.FindAllStringSubmatch(dataStr, -1) {\n\t\tusernames[matches[1]] = struct{}{}\n\t}\n\tfor _, matches := range emailPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tusernames[matches[1]] = struct{}{}\n\t}\n\n\t// Process results.\n\tfor token := range tokens {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: s.Type(),\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tfor username := range usernames {\n\t\t\ts1.RawV2 = []byte(fmt.Sprintf(\"%s:%s\", username, token))\n\n\t\t\tif verify {\n\t\t\t\tif s.client == nil {\n\t\t\t\t\ts.client = common.SaneHttpClient()\n\t\t\t\t}\n\n\t\t\t\tisVerified, extraData, verificationErr := s.verifyMatch(ctx, username, token)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.ExtraData = extraData\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t\tif s1.Verified {\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\"username\": username,\n\t\t\t\t\t\t\"pat\":      token,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\n\t\t\tif s1.Verified {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\t// PAT matches without usernames cannot be verified but might still be useful.\n\t\tif len(usernames) == 0 {\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn\n}\n\nfunc (s Scanner) verifyMatch(ctx context.Context, username string, password string) (bool, map[string]string, error) {\n\tpayload := strings.NewReader(fmt.Sprintf(`{\"username\": \"%s\", \"password\": \"%s\"}`, username, password))\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://hub.docker.com/v2/users/login\", payload)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\tres, err := s.client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer res.Body.Close()\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\tif res.StatusCode == http.StatusOK {\n\t\tvar tokenRes tokenResponse\n\t\tif err := json.Unmarshal(body, &tokenRes); (err != nil || tokenRes == tokenResponse{}) {\n\t\t\treturn false, nil, err\n\t\t}\n\n\t\tparser := jwt.NewParser()\n\t\ttoken, _, err := parser.ParseUnverified(tokenRes.Token, &hubJwtClaims{})\n\t\tif err != nil {\n\t\t\treturn true, nil, err\n\t\t}\n\n\t\tif claims, ok := token.Claims.(*hubJwtClaims); ok {\n\t\t\textraData := map[string]string{\n\t\t\t\t\"hub_username\": username,\n\t\t\t\t\"hub_email\":    claims.HubClaims.Email,\n\t\t\t\t\"hub_scope\":    claims.Scope,\n\t\t\t}\n\t\t\treturn true, extraData, nil\n\t\t}\n\t\treturn true, nil, nil\n\t} else if res.StatusCode == http.StatusUnauthorized {\n\t\t// Valid credentials can still return a 401 status code if 2FA is enabled\n\t\tvar mfaRes mfaRequiredResponse\n\t\tif err := json.Unmarshal(body, &mfaRes); err != nil || mfaRes.MfaToken == \"\" {\n\t\t\treturn false, nil, nil\n\t\t}\n\n\t\textraData := map[string]string{\n\t\t\t\"hub_username\": username,\n\t\t\t\"2fa_required\": \"true\",\n\t\t}\n\t\treturn true, extraData, nil\n\t} else {\n\t\treturn false, nil, fmt.Errorf(\"unexpected response status %d\", res.StatusCode)\n\t}\n}\n\ntype tokenResponse struct {\n\tToken string `json:\"token\"`\n}\n\ntype userClaims struct {\n\tUsername string `json:\"username\"`\n\tEmail    string `json:\"email\"`\n}\n\ntype hubJwtClaims struct {\n\tScope     string     `json:\"scope\"`\n\tHubClaims userClaims `json:\"https://hub.docker.com\"` // not sure why this is a key, further investigation required.\n\tjwt.RegisteredClaims\n}\n\ntype mfaRequiredResponse struct {\n\tMfaToken string `json:\"login_2fa_token\"`\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Dockerhub\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Docker is a platform used to develop, ship, and run applications. Docker access tokens can be used to authenticate and interact with Docker services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/dockerhub/v1/dockerhub_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage dockerhub\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDockerhub_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tusername := testSecrets.MustGetField(\"DOCKERHUB_USERNAME\")\n\temail := testSecrets.MustGetField(\"DOCKERHUB_EMAIL\")\n\tpat := testSecrets.MustGetField(\"DOCKERHUB_PAT\")\n\tinactivePat := testSecrets.MustGetField(\"DOCKERHUB_INACTIVE_PAT\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"docker login -u %s -p %s\", username, pat)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dockerhub,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified (email)\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"docker login -u %s -p %s\", email, pat)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dockerhub,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"docker login -u %s -p %s\", username, inactivePat)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dockerhub,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Dockerhub.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t\tgot[i].ExtraData = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Dockerhub.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dockerhub/v1/dockerhub_test.go",
    "content": "package dockerhub\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"\"\n\t\t\tin: \"\"\n\t\t\tapi_version: v1\n\t\t\tsecret: \"\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/examples\"\n\t\t\tresponse_code: 200\n\t\tdocker:\n\t\t\tuser: rRwOdIJpY90QrIzOXO95d3hlSzRk5Z9a\n\t\t\tdocker_email: \"docker-test@dockerhub.com\"\n\t\t\tdocker_token: \"9jyxkwvk-rjnp-7eo1-1gtc-ruj6rqmiyapo\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecrets = []string{\n\t\t\"rRwOdIJpY90QrIzOXO95d3hlSzRk5Z9a:9jyxkwvk-rjnp-7eo1-1gtc-ruj6rqmiyapo\",\n\t\t\"docker-test@dockerhub.com:9jyxkwvk-rjnp-7eo1-1gtc-ruj6rqmiyapo\",\n\t}\n)\n\nfunc TestDockerHub_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  secrets,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dockerhub/v2/dockerhub.go",
    "content": "package dockerhub\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/golang-jwt/jwt/v5\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\nfunc (s Scanner) Version() int { return 2 }\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\t// Can use email or username for login.\n\tusernamePat = regexp.MustCompile(`(?im)(?:user|usr|-u|id)\\S{0,40}?[:=\\s]{1,3}[ '\"=]?([a-zA-Z0-9]{4,40})\\b`)\n\temailPat    = regexp.MustCompile(common.EmailPattern)\n\n\t// Can use password or personal/organization access token (PAT/OAT) for login, but this scanner will only check for PATs and OATs.\n\taccessTokenPat = regexp.MustCompile(`\\b(dckr_pat_[a-zA-Z0-9_-]{27}|dckr_oat_[a-zA-Z0-9_-]{32})(?:[^a-zA-Z0-9_-]|\\z)`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"docker\", \"dckr_pat_\", \"dckr_oat_\"}\n}\n\n// FromData will find and optionally verify Dockerhub secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// Deduplicate results.\n\ttokens := make(map[string]struct{})\n\tfor _, matches := range accessTokenPat.FindAllStringSubmatch(dataStr, -1) {\n\t\ttokens[matches[1]] = struct{}{}\n\t}\n\tif len(tokens) == 0 {\n\t\treturn\n\t}\n\tusernames := make(map[string]struct{})\n\tfor _, matches := range usernamePat.FindAllStringSubmatch(dataStr, -1) {\n\t\tusernames[matches[1]] = struct{}{}\n\t}\n\tfor _, matches := range emailPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tusernames[matches[1]] = struct{}{}\n\t}\n\n\t// Process results.\n\tfor token := range tokens {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: s.Type(),\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tfor username := range usernames {\n\t\t\ts1.RawV2 = []byte(fmt.Sprintf(\"%s:%s\", username, token))\n\n\t\t\tif verify {\n\t\t\t\tif s.client == nil {\n\t\t\t\t\ts.client = common.SaneHttpClient()\n\t\t\t\t}\n\n\t\t\t\tisVerified, extraData, verificationErr := s.verifyMatch(ctx, username, token)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.ExtraData = extraData\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t\tif s1.Verified {\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\"username\": username,\n\t\t\t\t\t\t\"pat\":      token,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\n\t\t\tif s1.Verified {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\t// PAT matches without usernames cannot be verified but might still be useful.\n\t\tif len(usernames) == 0 {\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn\n}\n\nfunc (s Scanner) verifyMatch(ctx context.Context, username string, password string) (bool, map[string]string, error) {\n\tpayload := strings.NewReader(fmt.Sprintf(`{\"identifier\": \"%s\", \"secret\": \"%s\"}`, username, password))\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://hub.docker.com/v2/auth/token\", payload)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\tres, err := s.client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer res.Body.Close()\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\tif res.StatusCode == http.StatusOK {\n\t\tvar tokenRes tokenResponse\n\t\tif err := json.Unmarshal(body, &tokenRes); (err != nil || tokenRes == tokenResponse{}) {\n\t\t\treturn false, nil, err\n\t\t}\n\n\t\tparser := jwt.NewParser()\n\t\ttoken, _, err := parser.ParseUnverified(tokenRes.Token, &hubJwtClaims{})\n\t\tif err != nil {\n\t\t\treturn true, nil, err\n\t\t}\n\n\t\tif claims, ok := token.Claims.(*hubJwtClaims); ok {\n\t\t\textraData := map[string]string{\n\t\t\t\t\"hub_username\": username,\n\t\t\t\t\"hub_email\":    claims.HubClaims.Email,\n\t\t\t\t\"hub_scope\":    claims.Scope,\n\t\t\t}\n\t\t\treturn true, extraData, nil\n\t\t}\n\t\treturn true, nil, nil\n\t} else if res.StatusCode == http.StatusUnauthorized {\n\t\t// Valid credentials can still return a 401 status code if 2FA is enabled\n\t\tvar mfaRes mfaRequiredResponse\n\t\tif err := json.Unmarshal(body, &mfaRes); err != nil || mfaRes.MfaToken == \"\" {\n\t\t\treturn false, nil, nil\n\t\t}\n\n\t\textraData := map[string]string{\n\t\t\t\"hub_username\": username,\n\t\t\t\"2fa_required\": \"true\",\n\t\t}\n\t\treturn true, extraData, nil\n\t} else {\n\t\treturn false, nil, fmt.Errorf(\"unexpected response status %d\", res.StatusCode)\n\t}\n}\n\ntype tokenResponse struct {\n\tToken string `json:\"access_token\"`\n}\n\ntype userClaims struct {\n\tUsername string `json:\"username\"`\n\tEmail    string `json:\"email\"`\n}\n\ntype hubJwtClaims struct {\n\tScope     string     `json:\"scope\"`\n\tHubClaims userClaims `json:\"https://hub.docker.com\"` // not sure why this is a key, further investigation required.\n\tjwt.RegisteredClaims\n}\n\ntype mfaRequiredResponse struct {\n\tMfaToken string `json:\"login_2fa_token\"`\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Dockerhub\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/dockerhub/v2/dockerhub_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage dockerhub\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDockerhub_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tusername := testSecrets.MustGetField(\"DOCKERHUB_USERNAME\")\n\temail := testSecrets.MustGetField(\"DOCKERHUB_EMAIL\")\n\tpat := testSecrets.MustGetField(\"DOCKERHUB_PAT\")\n\tinactivePat := testSecrets.MustGetField(\"DOCKERHUB_INACTIVE_PAT\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"docker login -u %s -p %s\", username, pat)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dockerhub,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tAnalysisInfo: map[string]string{\n\t\t\t\t\t\t\"username\": username,\n\t\t\t\t\t\t\"pat\":      pat,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified (email)\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"docker login -u %s -p %s\", email, pat)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dockerhub,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tAnalysisInfo: map[string]string{\n\t\t\t\t\t\t\"username\": strings.Split(email, \"-\")[0],\n\t\t\t\t\t\t\"pat\":      pat,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"docker login -u %s -p %s\", username, inactivePat)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dockerhub,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Dockerhub.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t\tgot[i].ExtraData = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Dockerhub.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dockerhub/v2/dockerhub_test.go",
    "content": "package dockerhub\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"\"\n\t\t\tin: \"\"\n\t\t\tapi_version: v1\n\t\t\tsecret: \"\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/examples\"\n\t\t\tresponse_code: 200\n\t\tdocker:\n\t\t\tuser: rRwOdIJpY90QrIzOXO95d3hlSzRk5Z9a\n\t\t\tdocker_email: \"docker-test@dockerhub.com\"\n\t\t\tdocker_token: \"dckr_pat_dlndn9l2JLhWvbdyP3blEZw_j7d\"\n\t\t\tdocker_org_token: \"dckr_oat_7bA9zRt5-JqX3vP0l_MnY8sK2wE-dF6h\"\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecrets = []string{\n\t\t\"rRwOdIJpY90QrIzOXO95d3hlSzRk5Z9a:dckr_pat_dlndn9l2JLhWvbdyP3blEZw_j7d\",\n\t\t\"docker-test@dockerhub.com:dckr_pat_dlndn9l2JLhWvbdyP3blEZw_j7d\",\n\t\t\"rRwOdIJpY90QrIzOXO95d3hlSzRk5Z9a:dckr_oat_7bA9zRt5-JqX3vP0l_MnY8sK2wE-dF6h\",\n\t\t\"docker-test@dockerhub.com:dckr_oat_7bA9zRt5-JqX3vP0l_MnY8sK2wE-dF6h\",\n\t}\n)\n\nfunc TestDockerHub_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  secrets,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/docparser/docparser.go",
    "content": "package docparser\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"docparser\"}) + `\\b([a-f0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"docparser\"}\n}\n\n// FromData will find and optionally verify Docparser secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Docparser,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\turl := fmt.Sprintf(\"https://api.docparser.com/v1/parsers?api_key=%s\", resMatch)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", url, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Docparser\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/docparser/docparser_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage docparser\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDocparser_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DOCPARSER\")\n\tinactiveSecret := testSecrets.MustGetField(\"DOCPARSER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a docparser secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Docparser,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Docparser,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Docparser.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Docparser.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/docparser/docparser_test.go",
    "content": "package docparser\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tapi_version: v1\n\t\t\tdocparser_secret: \"1761d026b1202108b5f9ecd28d1ecae826b0aee8\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example/api_key=$docparser_secret\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"1761d026b1202108b5f9ecd28d1ecae826b0aee8\"\n)\n\nfunc TestDocParser_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/documo/documo.go",
    "content": "package documo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(ey[a-zA-Z0-9]{34}.ey[a-zA-Z0-9]{154}.[a-zA-Z0-9_-]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"documo\"}\n}\n\n// FromData will find and optionally verify Documo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Documo,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.documo.com/v1/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Documo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"A service for creating and modifying documents. API keys can create read update and delete documents.\"\n}\n"
  },
  {
    "path": "pkg/detectors/documo/documo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage documo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDocumo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DOCUMO\")\n\tinactiveSecret := testSecrets.MustGetField(\"DOCUMO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a documo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Documo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Documo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Documo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Documo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/documo/documo_test.go",
    "content": "package documo\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Documo Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Basic\"\n\t\t\tin: \"Path\"\n\t\t\tapi_version: v1\n\t\t\tsecret: \"eyS9YqgD6TgdQ943G8S3aaiz26m2fTN9rcPbpeyts0jBEFd43hEFfr9pC7voqvLsbEi7Px4TbMToCVrstQRe8r2kltKGWyChYCT1Iruo6p3g3PyqZaZ1gOSbjeXz8zARUHZkXo7XR86kape65HLXj59yCNIlW5bvebJYbIAjjgGAAmXVgzldvNv8Zs08KIS5y62QJSNcnipFQbnxA8z6TUMl0F600MJhqEILWo19GaGjw\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"eyS9YqgD6TgdQ943G8S3aaiz26m2fTN9rcPbpeyts0jBEFd43hEFfr9pC7voqvLsbEi7Px4TbMToCVrstQRe8r2kltKGWyChYCT1Iruo6p3g3PyqZaZ1gOSbjeXz8zARUHZkXo7XR86kape65HLXj59yCNIlW5bvebJYbIAjjgGAAmXVgzldvNv8Zs08KIS5y62QJSNcnipFQbnxA8z6TUMl0F600MJhqEILWo19GaGjw\"\n)\n\nfunc TestDocumo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/docusign/docusign.go",
    "content": "package docusign\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/go-errors/errors\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\ntype Response struct {\n\tAccessToken string `json:\"access_token\"`\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tidPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"integration\", \"id\"}) + common.UUIDPattern)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"secret\"}) + common.UUIDPattern)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"docusign\"}\n}\n\n// FromData will find and optionally verify Docusign secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, idMatch := range idMatches {\n\t\tresIDMatch := strings.TrimSpace(idMatch[1])\n\n\t\tfor _, secretMatch := range secretMatches {\n\t\t\tresSecretMatch := strings.TrimSpace(secretMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Docusign,\n\t\t\t\tRaw:          []byte(resIDMatch),\n\t\t\t\tRedacted:     resIDMatch,\n\t\t\t\tRawV2:        []byte(resIDMatch + resSecretMatch),\n\t\t\t}\n\n\t\t\t// Verify client id and secret pair by using an *undocumented* client_credentials grant type on the oauth2 endpoint.\n\t\t\t// If verifier breaks in the future, confirm that the oauth2 endpoint is still accepting the client_credentials grant type.\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://account-d.docusign.com/oauth/token?grant_type=client_credentials\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tencodedCredentials := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(\"%s:%s\", resIDMatch, resSecretMatch)))\n\n\t\t\t\treq.Header.Add(\"Accept\", \"application/vnd.docusign+json; version=3\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", encodedCredentials))\n\t\t\t\tres, err := client.Do(req)\n\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, errors.WrapPrefix(err, \"Error making request\", 0)\n\t\t\t\t}\n\n\t\t\t\tverifiedBodyResponse, err := common.ResponseContainsSubstring(res.Body, \"ey\")\n\t\t\t\tres.Body.Close()\n\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\n\t\t\t\tif err == nil {\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && verifiedBodyResponse {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Docusign\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Docusign is an electronic signature and digital transaction management service. Docusign credentials can be used to access and manage digital transactions and documents.\"\n}\n"
  },
  {
    "path": "pkg/detectors/docusign/docusign_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage docusign\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDocusign_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tintegrationKey := testSecrets.MustGetField(\"DOCUSIGN_INTEGRATION_KEY_ACTIVE\")\n\tactiveSecret := testSecrets.MustGetField(\"DOCUSIGN_SECRET_ACTIVE\")\n\tinactiveSecret := testSecrets.MustGetField(\"DOCUSIGN_SECRET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a docusign id %s and secret %s within\", integrationKey, activeSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Docusign,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRawV2:        []byte(integrationKey + activeSecret),\n\t\t\t\t\tRedacted:     integrationKey,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Docusign,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(integrationKey + inactiveSecret),\n\t\t\t\t\tRedacted:     integrationKey,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Docusign.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Docusign.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/docusign/docusign_test.go",
    "content": "package docusign\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Basic\"\n\t\t\tin: \"Path\"\n\t\t\tapi_version: v1\n\t\t\tdocusign_id: \"03f36108-730e-9061-ad3f-b77c910b2559\"\n\t\t\tdocusign_secret: \"212904c1-60fc-09b2-d615-1849cd748bf4\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"03f36108-730e-9061-ad3f-b77c910b2559212904c1-60fc-09b2-d615-1849cd748bf4\"\n)\n\nfunc TestDocsign_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/doppler/doppler.go",
    "content": "package doppler\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype response struct {\n\tName      string `json:\"name\"`\n\tType      string `json:\"type\"`\n\tWorkplace struct {\n\t\tName string `json:\"name\"`\n\t} `json:\"workplace\"`\n}\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\t//keyPat = regexp.MustCompile(`\\b(dp\\.pt\\.[a-zA-Z0-9]{43})\\b`)\n\tkeyPat = regexp.MustCompile(`\\b(dp\\.(?:ct|pt|st(?:\\.[a-z0-9\\-_]{2,35})?|sa|scim|audit)\\.[a-zA-Z0-9]{40,44})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\n\t\t\"dp.ct.\",\n\t\t\"dp.pt.\",\n\t\t\"dp.st\",\n\t\t\"dp.sa.\",\n\t\t\"dp.scim.\",\n\t\t\"dp.audit.\",\n\t}\n}\n\n// FromData will find and optionally verify Doppler secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Doppler,\n\t\t\tRaw:          []byte(resMatch),\n\t\t\tExtraData:    map[string]string{},\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.doppler.com/v3/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t\tvar r response\n\t\t\t\t\tif err := json.NewDecoder(res.Body).Decode(&r); err != nil {\n\t\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif r.Type != \"\" {\n\t\t\t\t\t\ts1.ExtraData[\"key type\"] = r.Type\n\t\t\t\t\t}\n\t\t\t\t\tif r.Workplace.Name != \"\" {\n\t\t\t\t\t\ts1.ExtraData[\"workplace\"] = r.Workplace.Name\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Doppler\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/doppler/doppler_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage doppler\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDoppler_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DOPPLER\")\n\tinactiveSecret := testSecrets.MustGetField(\"DOPPLER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a doppler secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Doppler,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"key type\":  \"personal\",\n\t\t\t\t\t\t\"workplace\": \"test\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Doppler,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Doppler.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Doppler.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/doppler/doppler_test.go",
    "content": "package doppler\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Documo Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tin: \"Path\"\n\t\t\tapi_version: v1\n\t\t\tdoppler_secret: \"dp.ct.5KE9aLrlMoprKwgigGZl1zJOOMQDcYPTWoTPujmF5Tm3\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"dp.ct.5KE9aLrlMoprKwgigGZl1zJOOMQDcYPTWoTPujmF5Tm3\"\n)\n\nfunc TestDoppler_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dotdigital/dotdigital.go",
    "content": "package dotdigital\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\temailPat = regexp.MustCompile(`\\b(apiuser-[a-z0-9]{12}@apiconnector.com)\\b`)\n\tpassPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"pw\", \"pass\"}) + `\\b([a-zA-Z0-9\\S]{8,24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"@apiconnector.com\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Dotdigital secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueEmails, uniquePasswords = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, matches := range emailPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueEmails[matches[1]] = struct{}{}\n\t}\n\tfor _, matches := range passPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniquePasswords[matches[1]] = struct{}{}\n\t}\n\n\tfor email := range uniqueEmails {\n\t\tfor password := range uniquePasswords {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Dotdigital,\n\t\t\t\tRaw:          []byte(email),\n\t\t\t\tRawV2:        []byte(email + password),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.getClient()\n\t\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, email, password)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\n\t\t\tif s1.Verified {\n\t\t\t\t// Once the email is verified, we can stop checking other passwords for it.\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, email, pass string) (bool, error) {\n\t// Reference: https://developer.dotdigital.com/reference/get-account-information\n\n\ttimeout := 10 * time.Second\n\tclient.Timeout = timeout\n\turl := \"https://r1-api.dotdigital.com/v2/account-info\"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.SetBasicAuth(email, pass)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Dotdigital\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Dotdigital is an email marketing automation platform. API keys can be used to access and manage email campaigns and related data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/dotdigital/dotdigital_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage dotdigital\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDotdigital_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\temail := testSecrets.MustGetField(\"DOTDIGITAL_EMAIL\")\n\tpassword := testSecrets.MustGetField(\"DOTDIGITAL_PASSWORD\")\n\tinactivePassword := testSecrets.MustGetField(\"DOTDIGITAL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a dotdigital user %s within dotdigital pass %s\", email, password)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dotdigital,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dotdigital,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Dotdigital.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Dotdigital.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dotdigital/dotdigital_test.go",
    "content": "package dotdigital\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\n\t\tapi:\n\t\t\tauth_type: \"Basic\"\n\t\t\tdotdigital_email: \"apiuser-trq6zw9mmdlt@apiconnector.com\"\n\t\t\tdotdigital_password: \"N{w44mqa'2si(zY8\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecrets = []string{\"apiuser-trq6zw9mmdlt@apiconnector.comN{w44mqa'2si(zY8\"}\n)\n\nfunc TestDotdigital_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  secrets,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dovico/dovico.go",
    "content": "package dovico\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"dovico\"}) + `\\b([0-9a-z]{32}\\.[0-9a-z]{1,}\\b)`)\n\tuserPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"dovico\"}) + `\\b([0-9a-z]{32}\\.[0-9a-z]{1,}\\b)`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"dovico\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Dovico secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueKeys := make(map[string]struct{})\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tuniqueUserKeys := make(map[string]struct{})\n\tfor _, matches := range userPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueUserKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\tfor userKey := range uniqueUserKeys {\n\t\t\tif key == userKey {\n\t\t\t\tcontinue // Skip if ID and secret are the same.\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Dovico,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s\", key, userKey)),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.getClient()\n\t\t\t\tisVerified, err := verifyMatch(ctx, client, key, userKey)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(err, key, userKey)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\n\t\t\t// Credentials have 1:1 mapping so we can stop checking other user keys once it is verified\n\t\t\tif s1.Verified {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, key, user string) (bool, error) {\n\t// Reference: https://timesheet.dovico.com/developer/API_doc/#t=API_Overview.html\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.dovico.com/employees/?version=7\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(`WRAP access_token=\"client=%s&user_token=%s\"`, key, user))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Dovico\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Dovico is a time tracking and project management service. Dovico keys can be used to access and manage time tracking and project data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/dovico/dovico_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage dovico\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDovico_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DOVICO_CLIENT\")\n\tuser := testSecrets.MustGetField(\"DOVICO_USER\")\n\tinactiveSecret := testSecrets.MustGetField(\"DOVICO_CLIENT_INACTIVE\")\n\tinactiveUser := testSecrets.MustGetField(\"DOVICO_USER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a dovico secret %s within dovico user %s \", secret, user)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dovico,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dovico,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dovico,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dovico,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Dovico.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Dovico.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dovico/dovico_test.go",
    "content": "package dovico\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Token\"\n\t\t\tin: \"Header\"\n\t\t\tapi_version: v1\n\t\t\tdovico_user: \"ntb4fnhk5iot7hzbfjw08jm661iocdd4.3ws4ol\"\n\t\t\tdovico_token: \"nuhkw7nsrybuvmetium29a6oajxr3xdg.sbpi6e\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecrets = []string{\n\t\t\"nuhkw7nsrybuvmetium29a6oajxr3xdg.sbpi6e:ntb4fnhk5iot7hzbfjw08jm661iocdd4.3ws4ol\",\n\t\t\"ntb4fnhk5iot7hzbfjw08jm661iocdd4.3ws4ol:nuhkw7nsrybuvmetium29a6oajxr3xdg.sbpi6e\",\n\t}\n)\n\nfunc TestDovico_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  secrets,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dronahq/dronahq.go",
    "content": "package dronahq\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"dronahq\"}) + `\\b([a-z0-9]{50})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"dronahq\"}\n}\n\n// FromData will find and optionally verify DronaHQ secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_DronaHQ,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://plugin.api.dronahq.com/users/?tokenkey=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_DronaHQ\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"DronaHQ is a platform for building internal tools and applications. DronaHQ keys can be used to access and manage these tools and applications.\"\n}\n"
  },
  {
    "path": "pkg/detectors/dronahq/dronahq_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage dronahq\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDronaHQ_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DRONAHQ\")\n\tinactiveSecret := testSecrets.MustGetField(\"DRONAHQ_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a dronahq secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DronaHQ,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DronaHQ,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"DronaHQ.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"DronaHQ.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dronahq/dronahq_test.go",
    "content": "package dronahq\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Header\"\n\t\t\tapi_version: v1\n\t\t\tdronahq_secret: \"\"\n\t\t\tbase_url: \"https://api.dronahq.com/$api_version/example?tokenkey=5j5jvhn9hm6qojajn61pe1ccqly424lrd0g41vbh6wwscer3pa\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"5j5jvhn9hm6qojajn61pe1ccqly424lrd0g41vbh6wwscer3pa\"\n)\n\nfunc TestDronahq_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/droneci/droneci.go",
    "content": "package droneci\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"droneci\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"droneci\"}\n}\n\n// FromData will find and optionally verify DroneCI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_DroneCI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://cloud.drone.io/api/user\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_DroneCI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/droneci/droneci_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage droneci\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDroneCI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DRONECI_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"DRONECI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a droneci secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DroneCI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_DroneCI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"DroneCI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"DroneCI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/droneci/droneci_test.go",
    "content": "package droneci\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tin: \"Header\"\n\t\t\tapi_version: v1\n\t\t\tdroneci_secret: \"Kf6ZyWFttCZwO9SqEB94opCHaQ5n00WF\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"Kf6ZyWFttCZwO9SqEB94opCHaQ5n00WF\"\n)\n\nfunc TestDroneCI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dropbox/dropbox.go",
    "content": "package dropbox\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\tkeyPat        = regexp.MustCompile(detectors.PrefixRegex([]string{\"dropbox\"}) + `\\b(sl\\.(u\\.)?[A-Za-z0-9\\-\\_]{130,})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"dropbox\", \"sl.\"}\n}\n\n// FromData will find and optionally verify Dropbox secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matches[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Dropbox,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyDropboxToken(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\tif s1.Verified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\"token\": key}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyDropboxToken(ctx context.Context, client *http.Client, key string) (bool, error) {\n\t// Reference: https://www.dropbox.com/developers/documentation/http/documentation\n\turl := \"https://api.dropboxapi.com/2/users/get_current_account\"\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusBadRequest:\n\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, fmt.Errorf(\"failed to read response body: %w\", err)\n\t\t}\n\t\tbody := string(bodyBytes)\n\n\t\tif strings.Contains(body, \"missing_scope\") ||\n\t\t\tstrings.Contains(body, \"does not have the required scope\") {\n\t\t\treturn true, nil // The token is valid but lacks the required scope\n\t\t}\n\t\tif strings.Contains(body, \"invalid_access_token\") ||\n\t\t\tstrings.Contains(body, \"expired_access_token\") {\n\t\t\treturn false, nil // The token is invalid or expired\n\t\t}\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d, body: %s\", res.StatusCode, body)\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Dropbox\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/dropbox/dropbox_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage dropbox\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDropbox_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DROPBOX\")\n\tsecretInactive := testSecrets.MustGetField(\"DROPBOX_INACTIVE\")\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a dropbox secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dropbox,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a dropbox secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dropbox,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Dropbox.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Dropbox.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dropbox/dropbox_test.go",
    "content": "package dropbox\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tin: \"Header\"\n\t\t\tapi_version: v1\n\t\t\tdropbox_secret: \"sl.4ihqlizKRm9J8tJvdBUecLPfYunjh3Nx73cUBGcRKpTFxRny3cYKdaQdzVF_rBIEO9emJaHyRWeM_tm5pYJFTc1TwYjM2fHlhSdhKkzHJjf5dx86fUlaO_eKY9r4ijZ8eD\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"sl.4ihqlizKRm9J8tJvdBUecLPfYunjh3Nx73cUBGcRKpTFxRny3cYKdaQdzVF_rBIEO9emJaHyRWeM_tm5pYJFTc1TwYjM2fHlhSdhKkzHJjf5dx86fUlaO_eKY9r4ijZ8eD\"\n)\n\nfunc TestDropBox_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/duply/duply.go",
    "content": "package duply\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = 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`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"duply\"}) + `\\b([0-9A-Z]{7}-[0-9A-Z]{7}-[0-9A-Z]{7}-[0-9A-Z]{7})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"duply\"}\n}\n\n// FromData will find and optionally verify Duply secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Duply,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\ttimeout := 10 * time.Second\n\t\t\t\tclient.Timeout = timeout\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://gen.duply.co/v1/usage\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\treq.SetBasicAuth(resIdMatch, resMatch)\n\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Duply\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"An API for generating images. API keys can fetch and create images.\"\n}\n"
  },
  {
    "path": "pkg/detectors/duply/duply_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage duply\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDuply_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DUPLY\")\n\tid := testSecrets.MustGetField(\"DUPLY_USER\")\n\tinactiveSecret := testSecrets.MustGetField(\"DUPLY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a duply secret %s within duply %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Duply,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Duply,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Duply.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Duply.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/duply/duply_test.go",
    "content": "package duply\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Basic\"\n\t\t\tin: \"Header\"\n\t\t\tapi_version: v1\n\t\t\tduply_id: \"JN9YXKN-2OB6UTI-VTN7DX8-FIZZM7P\"\n\t\t\tduply_secret: \"24cc4537-f4ea-b9de-7369-41481c6e914f\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"24cc4537-f4ea-b9de-7369-41481c6e914f\"\n)\n\nfunc TestDuply_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dwolla/dwolla.go",
    "content": "package dwolla\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tidPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"dwolla\"}) + `\\b([a-zA-Z-0-9]{50})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"dwolla\"}) + `\\b([a-zA-Z-0-9]{50})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"dwolla\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Dwolla secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueIDs := make(map[string]struct{})\n\tfor _, matches := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueIDs[matches[1]] = struct{}{}\n\t}\n\n\tuniqueSecrets := make(map[string]struct{})\n\tfor _, matches := range secretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueSecrets[matches[1]] = struct{}{}\n\t}\n\n\tfor id := range uniqueIDs {\n\t\tfor secret := range uniqueSecrets {\n\t\t\tif id == secret {\n\t\t\t\tcontinue // Skip if ID and secret are the same.\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Dwolla,\n\t\t\t\tRaw:          []byte(id),\n\t\t\t\tRawV2:        []byte(id + secret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.getClient()\n\t\t\t\tisVerified, err := verifyMatch(ctx, client, id, secret)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(err, id, secret)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, id, secret string) (bool, error) {\n\tdata := fmt.Sprintf(\"%s:%s\", id, secret)\n\tencoded := b64.StdEncoding.EncodeToString([]byte(data))\n\tpayload := strings.NewReader(\"grant_type=client_credentials\")\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://api-sandbox.dwolla.com/token\", payload)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", encoded))\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Dwolla\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/dwolla/dwolla_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage dwolla\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDwolla_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tid := testSecrets.MustGetField(\"DWOLLA_ID\")\n\tsecret := testSecrets.MustGetField(\"DWOLLA_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"DWOLLA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a dwolla secret %s within dwolla id %s but verified\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dwolla,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dwolla,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dwolla,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dwolla,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Dwolla.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Dwolla.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dwolla/dwolla_test.go",
    "content": "package dwolla\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Basic\"\n\t\t\tin: \"Header\"\n\t\t\tapi_version: v1\n\t\t\tdwolla_id: \"MvkLktYDS7PSE0xRMHIYBKrAjXruEk5P1VrJUUGtgspa3KTi6r\"\n\t\t\tdwolla_secret: \"q3DZbY7iviUpewfCHEpK1I51G8XW63GuLuJyAIEqOFtEB1qlg1\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecrets = []string{\n\t\t\"MvkLktYDS7PSE0xRMHIYBKrAjXruEk5P1VrJUUGtgspa3KTi6rq3DZbY7iviUpewfCHEpK1I51G8XW63GuLuJyAIEqOFtEB1qlg1\",\n\t\t\"q3DZbY7iviUpewfCHEpK1I51G8XW63GuLuJyAIEqOFtEB1qlg1MvkLktYDS7PSE0xRMHIYBKrAjXruEk5P1VrJUUGtgspa3KTi6r\",\n\t}\n)\n\nfunc TestDwollaPattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  secrets,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dynalist/dynalist.go",
    "content": "package dynalist\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"dynalist\"}) + `\\b([a-zA-Z0-9-_]{128})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"dynalist\"}\n}\n\n// FromData will find and optionally verify Dynalist secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Dynalist,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(fmt.Sprintf(`{\"token\": \"%s\"}`, resMatch))\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://dynalist.io/api/v1/file/list\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `\"_code\":\"Ok\"`)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Dynalist\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/dynalist/dynalist_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage dynalist\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDynalist_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DYNALIST\")\n\tinactiveSecret := testSecrets.MustGetField(\"DYNALIST_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a dynalist secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dynalist,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dynalist,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Dynalist.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Dynalist.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dynalist/dynalist_test.go",
    "content": "package dynalist\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Token\"\n\t\t\tin: \"Body\"\n\t\t\tapi_version: v1\n\t\t\tdynalist_secret: \"60l0XYOX_VZrJsbpid4TllHwdek_3NXxgKz_DkO3lrw_B8aHxSov-TOPojCMtBED8q4awfqsMdNcOkCxrmqkbDQW2dJ8lukGTgJZBqfPQKOYujZZBXKZng3SXM-huIRM\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"60l0XYOX_VZrJsbpid4TllHwdek_3NXxgKz_DkO3lrw_B8aHxSov-TOPojCMtBED8q4awfqsMdNcOkCxrmqkbDQW2dJ8lukGTgJZBqfPQKOYujZZBXKZng3SXM-huIRM\"\n)\n\nfunc TestDynalist_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dyspatch/dyspatch.go",
    "content": "package dyspatch\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"dyspatch\"}) + `\\b([A-Z0-9]{52})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"dyspatch\"}\n}\n\n// FromData will find and optionally verify Dyspatch secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Dyspatch,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.dyspatch.io/templates\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.dyspatch.2020.11+json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\t\t\t\tvalidResponse := strings.Contains(body, \"limited_usage\") || strings.Contains(body, \"data\")\n\n\t\t\t\tif validResponse {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Dyspatch\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/dyspatch/dyspatch_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage dyspatch\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDyspatch_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"DYSPATCH_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"DYSPATCH_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a dyspatch secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dyspatch,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Dyspatch,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Dyspatch.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Dyspatch.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/dyspatch/dyspatch_test.go",
    "content": "package dyspatch\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tin: \"Header\"\n\t\t\tapi_version: v1\n\t\t\tdyspatch_secret: \"RLZTXG010RHW7FCSAEX72TPRJS1JU1PU0PVSWFF6HZQOUEVY5MFN\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"RLZTXG010RHW7FCSAEX72TPRJS1JU1PU0PVSWFF6HZQOUEVY5MFN\"\n)\n\nfunc TestDyspatch_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/eagleeyenetworks/eagleeyenetworks.go",
    "content": "package eagleeyenetworks\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"eagleeyenetworks\"}) + `\\b([a-zA-Z0-9]{15})\\b`)\n\temail  = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"eagleeyenetworks\"}\n}\n\n// FromData will find and optionally verify EagleEyeNetworks secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\temailMatches := email.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, emailMatch := range emailMatches {\n\n\t\t\tresEmailPatMatch := strings.TrimSpace(emailMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_EagleEyeNetworks,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tpayload := strings.NewReader(fmt.Sprintf(`{\"username\": \"%s\", \"password\": \"%s\"}`, resEmailPatMatch, resMatch))\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://login.eagleeyenetworks.com/g/aaa/authenticate\", payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_EagleEyeNetworks\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Eagle Eye Networks provides cloud-based video surveillance solutions. The credentials can be used to access and manage surveillance data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/eagleeyenetworks/eagleeyenetworks_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage eagleeyenetworks\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestEagleEyeNetworks_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"EAGLEEYENETWORKS\")\n\temail := testSecrets.MustGetField(\"EAGLEEYENETWORKS_USER\")\n\tinactiveSecret := testSecrets.MustGetField(\"EAGLEEYENETWORKS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a eagleeyenetworks secret %s within eagleeyenetworks %s\", secret, email)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EagleEyeNetworks,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EagleEyeNetworks,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"EagleEyeNetworks.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"EagleEyeNetworks.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/eagleeyenetworks/eagleeyenetworks_test.go",
    "content": "package eagleeyenetworks\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Token\"\n\t\t\tin: \"Body\"\n\t\t\tapi_version: v1\n\t\t\teagleeyenetworks_email: \"test08@eagleeyenetworks.com\"\n\t\t\teagleeyenetworks_secret: \"Y6YWq0NfYgyJCL0\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"Y6YWq0NfYgyJCL0\"\n)\n\nfunc TestEagleEyeNetworks_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/easyinsight/easyinsight.go",
    "content": "package easyinsight\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"easyinsight\", \"easy-insight\", \"key\"}) + `\\b([0-9a-zA-Z]{20})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"easyinsight\", \"easy-insight\", \"id\"}) + `\\b([a-zA-Z0-9]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"easyinsight\", \"easy-insight\"}\n}\n\n// FromData will find and optionally verify EasyInsight secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar keyMatches, idMatches = make(map[string]struct{}), make(map[string]struct{})\n\n\t// get unique key and id matches\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tkeyMatches[matches[1]] = struct{}{}\n\t}\n\n\tfor _, matches := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tidMatches[matches[1]] = struct{}{}\n\t}\n\n\tfor keyMatch := range keyMatches {\n\t\tfor idMatch := range idMatches {\n\t\t\t//as key and id regex are same, the strings captured by both regex will be same.\n\t\t\t//avoid processing when key is same as id. This will allow detector to process only different combinations\n\t\t\tif keyMatch == idMatch {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_EasyInsight,\n\t\t\t\tRaw:          []byte(keyMatch),\n\t\t\t\tRawV2:        []byte(keyMatch + idMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tverified, verificationErr := verifyEasyInsight(ctx, idMatch, keyMatch)\n\t\t\t\ts1.Verified = verified\n\t\t\t\tif verificationErr != nil {\n\t\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t\t// if key id combination is verified, skip other idMatches for that key\n\t\t\tif s1.Verified {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_EasyInsight\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc verifyEasyInsight(ctx context.Context, id, key string) (bool, error) {\n\t// docs: https://www.easy-insight.com/api/users.html\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.easy-insight.com/app/api/users.json\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// add required headers to the request\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Accept\", \"application/json\")\n\t// set basic auth for the request\n\treq.SetBasicAuth(id, key)\n\n\tres, reqErr := client.Do(req)\n\tif reqErr != nil {\n\t\treturn false, reqErr\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\t// id, key verified\n\tcase http.StatusOK:\n\t\treturn true, nil\n\t// id, key unverified\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\t// something invalid\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/easyinsight/easyinsight_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage easyinsight\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestEasyInsight_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"EASYINSIGHT\")\n\tinactiveSecret := testSecrets.MustGetField(\"EASYINSIGHT_INACTIVE\")\n\tid := testSecrets.MustGetField(\"EASYINSIGHT_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a easyinsight secret %s within easyid %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EasyInsight,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EasyInsight,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"EasyInsight.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"EasyInsight.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/easyinsight/easyinsight_test.go",
    "content": "package easyinsight\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern = \"987ahjjdasgUcaaraAdd\"\n\tvalidIDPattern  = \"poiuy76RaEf90ertgh0K\"\n\t// this should result in 4 combinations\n\tcomplexPattern = `easyinsight credentials\n\t\t\t\t\t\tthese credentials are for testing a pattern\n\t\t\t\t\t\tkey: A876AcaraTsaAKcae09a\n\t\t\t\t\t\tid: chECk12345ChecK12345\n\t\t\t\t\t\t-------------------------\n\t\t\t\t\t\tsecond credentials:\n\t\t\t\t\t\tkey: B874CDaraTsaAKVBe08A\n\t\t\t\t\t\tid: CHECK12345ChecK09876`\n\tinvalidPattern = \"poiuy76=a_$90ertgh0K\"\n)\n\nfunc TestEasyInsight_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: fmt.Sprintf(\"easyinsight key = '%s' easy-insight id = '%s\", validKeyPattern, validIDPattern),\n\t\t\twant:  []string{validKeyPattern + validIDPattern, validIDPattern + validKeyPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - complex\",\n\t\t\tinput: fmt.Sprintf(\"easyinsight token = '%s'\", complexPattern),\n\t\t\twant: []string{\n\t\t\t\t\"A876AcaraTsaAKcae09achECk12345ChecK12345\",\n\t\t\t\t\"A876AcaraTsaAKcae09aCHECK12345ChecK09876\",\n\t\t\t\t\"B874CDaraTsaAKVBe08ACHECK12345ChecK09876\",\n\t\t\t\t\"B874CDaraTsaAKVBe08AchECk12345ChecK12345\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"easyinsight key and id keyword is not close to the real token = '%s|%s'\", validKeyPattern, validIDPattern),\n\t\t\twant:  nil,\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"easyinsight = '%s|%s'\", invalidPattern, invalidPattern),\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ecostruxureit/ecostruxureit.go",
    "content": "package ecostruxureit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ecostruxureit\"}) + `\\b(AK1[0-9a-zA-Z\\/]{50,55})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ecostruxureit\"}\n}\n\n// FromData will find and optionally verify EcoStruxureIT secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_EcoStruxureIT,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.ecostruxureit.com/rest/v1/organizations\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_EcoStruxureIT\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ecostruxureit/ecostruxureit_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ecostruxureit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestEcoStruxureIT_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ECOSTRUXUREIT\")\n\tinactiveSecret := testSecrets.MustGetField(\"ECOSTRUXUREIT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ecostruxureit secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EcoStruxureIT,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EcoStruxureIT,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"EcoStruxureIT.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"EcoStruxureIT.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ecostruxureit/ecostruxureit_test.go",
    "content": "package ecostruxureit\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tin: \"Header\"\n\t\t\tapi_version: v1\n\t\t\tecostruxureit_secret: \"AK1CI5QsL1zvRE5KBX/uL9KuiZOJq27GwcJu4fV/xyTJcYCrYP0ykE\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"AK1CI5QsL1zvRE5KBX/uL9KuiZOJq27GwcJu4fV/xyTJcYCrYP0ykE\"\n)\n\nfunc TestEcostruxureit_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/edamam/edamam.go",
    "content": "package edamam\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"edamam\"}) + `\\b([0-9a-z]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"edamam\"}) + `\\b([0-9a-z]{8})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"edamam\"}\n}\n\n// FromData will find and optionally verify Edamam secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, idMatch := range idMatches {\n\t\t\tresId := strings.TrimSpace(idMatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Edamam,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resId),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.edamam.com/auto-complete?app_id=%s&app_key=%s&q=%s\", resId, resMatch, \"\"), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Edamam\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Edamam provides nutrition analysis and diet recommendations. Edamam API keys can be used to access and modify nutrition data and perform diet analysis.\"\n}\n"
  },
  {
    "path": "pkg/detectors/edamam/edamam_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage edamam\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestEdamam_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"EDAMAM\")\n\tid := testSecrets.MustGetField(\"EDAMAM_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"EDAMAM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a edamam secret %s within edamam id %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Edamam,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Edamam,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Edamam.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw v2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Edamam.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/edamam/edamam_test.go",
    "content": "package edamam\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tapi_version: v1\n\t\t\tedamam_id: \"1vsqsubh\"\n\t\t\tedamam_secret: \"e3at3vut4x27aq5wpkjmivjt9kq5cune\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tquery: \"app_id=$edamam_id&app_key=$edamam_secret\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"e3at3vut4x27aq5wpkjmivjt9kq5cune1vsqsubh\"\n)\n\nfunc TestEdamam_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/edenai/edenai.go",
    "content": "package edenai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"edenai\"}) + `\\b([a-zA-Z0-9]{36}.[a-zA-Z0-9]{92}.[a-zA-Z0-9_]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"edenai\"}\n}\n\n// FromData will find and optionally verify EdenAI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_EdenAI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.edenai.run/v1/automl/text/project\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_EdenAI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"EdenAI provides a unified API to access multiple AI engines. EdenAI API keys can be used to access and utilize these AI services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/edenai/edenai_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage edenai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestEdenAI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"EDENAI\")\n\tinactiveSecret := testSecrets.MustGetField(\"EDENAI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a edenai secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EdenAI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EdenAI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"EdenAI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"EdenAI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/edenai/edenai_test.go",
    "content": "package edenai\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tin: \"Header\"\n\t\t\tapi_version: v1\n\t\t\tedenai_secret: \"CQcxzQhT70xdDr8J6zGDpTY4Iv3ro10k1YMG}XLr0DyMXgnxMCqx4m92bgOK5QkBZJJNSoOHk8y6yEuoIu6MBb5I12Jbrjw9TpMWUf8dgxSlFyvFpyUOz5A3gvJu926a4F17oRzQpAfBAjGpL91ZxNtZ5uDy50MNnh1VgadWFnRzR\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tquery: \"\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"CQcxzQhT70xdDr8J6zGDpTY4Iv3ro10k1YMG}XLr0DyMXgnxMCqx4m92bgOK5QkBZJJNSoOHk8y6yEuoIu6MBb5I12Jbrjw9TpMWUf8dgxSlFyvFpyUOz5A3gvJu926a4F17oRzQpAfBAjGpL91ZxNtZ5uDy50MNnh1VgadWFnRzR\"\n)\n\nfunc TestEdenai_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/eightxeight/eightxeight.go",
    "content": "package eightxeight\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"8x8\"}) + `\\b([a-zA-Z0-9]{43})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"8x8\"}) + `\\b([a-zA-Z0-9_]{18,30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"8x8\"}\n}\n\n// FromData will find and optionally verify EightxEight secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_EightxEight,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resIdMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\ttimeout := 10 * time.Second\n\t\t\t\tclient.Timeout = timeout\n\t\t\t\tpayload := strings.NewReader(`{\"source\":\"abcde\",\"destination\":\"+6512345678\",\"text\":\"Hello World!\",\"encoding\":\"AUTO\"}`)\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", fmt.Sprintf(\"https://sms.8x8.com/api/v1/subaccounts/%s/messages\", resIdMatch), payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_EightxEight\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"8x8 is a provider of cloud-based communication services including voice, video, chat, and contact center solutions.\"\n}\n"
  },
  {
    "path": "pkg/detectors/eightxeight/eightxeight_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage eightxeight\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestEightxEight_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"EIGHTXEIGHT\")\n\tid := testSecrets.MustGetField(\"EIGHTXEIGHT_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"EIGHTXEIGHT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a 8x8 secret %s within 8x8 %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EightxEight,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EightxEight,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"EightxEight.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"EightxEight.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/eightxeight/eightxeight_test.go",
    "content": "package eightxeight\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Bearer\"\n\t\t\tin: \"Header\"\n\t\t\tapi_version: v1\n\t\t\t8x8_id: \"ByvWSRLcNhS_bgLBjD4hAhUvkWLz\"\n\t\t\t8x8_secret: \"LiE1BOtWbU7YucNYPnXNG0LIFlkfWcMt8KLBu1MfjeS\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tquery: \"\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"LiE1BOtWbU7YucNYPnXNG0LIFlkfWcMt8KLBu1MfjeSByvWSRLcNhS_bgLBjD4hAhUvkWLz\"\n)\n\nfunc TestEightXEight_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/elasticemail/elasticemail.go",
    "content": "package elasticemail\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"elastic\"}) + `\\b([A-Za-z0-9_-]{96})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"elasticemail\"}\n}\n\n// FromData will find and optionally verify ElasticEmail secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ElasticEmail,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.elasticemail.com/v2/account/profileoverview?apikey=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdata, readErr := io.ReadAll(res.Body)\n\t\t\t\tres.Body.Close()\n\t\t\t\tif readErr == nil {\n\t\t\t\t\tvar ResVar struct {\n\t\t\t\t\t\tSuccess bool `json:\"success\"`\n\t\t\t\t\t}\n\t\t\t\t\tif err := json.Unmarshal(data, &ResVar); err == nil {\n\t\t\t\t\t\tif ResVar.Success {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ElasticEmail\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ElasticEmail is an email marketing service. ElasticEmail API keys can be used to send emails, manage contacts, and access other features of the service.\"\n}\n"
  },
  {
    "path": "pkg/detectors/elasticemail/elasticemail_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage elasticemail\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestElasticEmail_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ELASTICEMAIL_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"ELASTICEMAIL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a elasticemail secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ElasticEmail,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ElasticEmail,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ElasticEmail.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ElasticEmail.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/elasticemail/elasticemail_test.go",
    "content": "package elasticemail\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tapi_version: v1\n\t\t\telasticemail_secret: \"KjzCaS0dOHBFkH6ljFkQp353jV8FH5Fgmo9-t9Bgl2iP1btjXEEaGwOPVnR8LZFSksLpL4kwxUXOFJBGwz6xBbVeJIR8K17p\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tquery: \"apiKey=$elasticemail_secret\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"KjzCaS0dOHBFkH6ljFkQp353jV8FH5Fgmo9-t9Bgl2iP1btjXEEaGwOPVnR8LZFSksLpL4kwxUXOFJBGwz6xBbVeJIR8K17p\"\n)\n\nfunc TestElasticEmail_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/elevenlabs/v1/elevenlabs.go",
    "content": "package elevenlabs\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nfunc (Scanner) Version() int { return 1 }\n\ntype UserRes struct {\n\tSubscription struct {\n\t\tTier string `json:\"tier\"`\n\t} `json:\"subscription\"`\n\tName string `json:\"first_name\"`\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`(?i)(?:elevenlabs|xi-api-key|el|token|key)[^\\.].{0,40}[ =:'\"]+([a-f0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"elevenlabs\", \"xi-api-key\", \"xi_api_key\"}\n}\n\n// FromData will find and optionally verify Elevenlabs secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ElevenLabs,\n\t\t\tRaw:          []byte(match),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"version\":        \"1\",\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/elevenlabs/\",\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, userResponse, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\tif userResponse != nil {\n\t\t\t\ts1.ExtraData[\"Name\"] = userResponse.Name\n\t\t\t\ts1.ExtraData[\"Tier\"] = userResponse.Subscription.Tier\n\t\t\t}\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\n\t\t\tif s1.Verified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\"key\": match,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, *UserRes, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.elevenlabs.io/v1/user\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Set(\"xi-api-key\", token)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\t// If the endpoint returns useful information, we can return it as a map.\n\t\tvar userResponse UserRes\n\t\tif err = json.NewDecoder(res.Body).Decode(&userResponse); err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\t\treturn true, &userResponse, nil\n\tcase http.StatusBadRequest, http.StatusUnauthorized:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ElevenLabs\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Elevenlabs is an AI-driven voice synthesis platform. Elevenlabs API keys can be used to access and manipulate voice synthesis features and services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/elevenlabs/v1/elevenlabs_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage elevenlabs\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n)\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/elevenlabs/v1/elevenlabs_test.go",
    "content": "package elevenlabs\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestElevenlabs_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: \"XI_API_KEY = 'b41b9d78aefb8c7c6cf9ebf01231340b'\",\n\t\t\twant:  []string{\"b41b9d78aefb8c7c6cf9ebf01231340b\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/elevenlabs/v2/elevenlabs.go",
    "content": "package elevenlabs\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nfunc (Scanner) Version() int { return 2 }\n\ntype UserRes struct {\n\tSubscription struct {\n\t\tTier string `json:\"tier\"`\n\t} `json:\"subscription\"`\n\tName string `json:\"first_name\"`\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b((?:sk)_[a-f0-9]{48})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"elevenlabs\", \"xi-api-key\", \"xi_api_key\"}\n}\n\n// FromData will find and optionally verify Elevenlabs secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ElevenLabs,\n\t\t\tRaw:          []byte(match),\n\t\t\tExtraData:    map[string]string{\"version\": \"2\"},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, userResponse, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\tif userResponse != nil {\n\t\t\t\ts1.ExtraData[\"Name\"] = userResponse.Name\n\t\t\t\ts1.ExtraData[\"Tier\"] = userResponse.Subscription.Tier\n\t\t\t}\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\n\t\t\tif s1.Verified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\"key\": match,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, *UserRes, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.elevenlabs.io/v1/user\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Set(\"xi-api-key\", token)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\t// If the endpoint returns useful information, we can return it as a map.\n\t\tvar userResponse UserRes\n\t\tif err = json.NewDecoder(res.Body).Decode(&userResponse); err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\t\treturn true, &userResponse, nil\n\tcase http.StatusBadRequest, http.StatusUnauthorized:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ElevenLabs\n}\n"
  },
  {
    "path": "pkg/detectors/elevenlabs/v2/elevenlabs_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage elevenlabs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestElevenlabs_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ELEVENLABS\")\n\tinactiveSecret := testSecrets.MustGetField(\"ELEVENLABS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a elevenlabs secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ElevenLabs,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"2\",\n\t\t\t\t\t\t\"Name\":    \"Trufflesecurity\",\n\t\t\t\t\t\t\"Tier\":    \"free\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ElevenLabs,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a elevenlabs secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ElevenLabs,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a elevenlabs secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ElevenLabs,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Elevenlabs.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"AnalysisInfo\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Elevenlabs.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/elevenlabs/v2/elevenlabs_test.go",
    "content": "package elevenlabs\n\nimport (\n\t\"context\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"testing\"\n)\n\nfunc TestElevenlabs_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: \"XI_API_KEY = 'sk_c43667f9bedd46fcff858f09f648d984533645e30f0541df'\",\n\t\t\twant:  []string{\"sk_c43667f9bedd46fcff858f09f648d984533645e30f0541df\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/enablex/enablex.go",
    "content": "package enablex\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"enablex\"}) + `\\b([a-zA-Z0-9]{36})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"enablex\"}) + `\\b([a-z0-9]{24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"enablex\"}\n}\n\n// FromData will find and optionally verify Enablex secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\ttokenPatMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tuserPatMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Enablex,\n\t\t\t\tRaw:          []byte(tokenPatMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.enablex.io/voice/v1/call\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.SetBasicAuth(userPatMatch, tokenPatMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Enablex\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/enablex/enablex_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage enablex\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestEnablex_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ENABLEX\")\n\tuser := testSecrets.MustGetField(\"ENABLEX_USER\")\n\tinactiveSecret := testSecrets.MustGetField(\"ENABLEX_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a enablex secret %s within enablex %s\", secret, user)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Enablex,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Enablex,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Enablex.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Enablex.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/enablex/enablex_test.go",
    "content": "package enablex\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"Basic\"\n\t\t\tin: \"Header\"\n\t\t\tapi_version: v1\n\t\t\tenablex_id: \"hkhihhsneir2aablmbk55u8f\"\n\t\t\tenablex_secret: \"iSgJYVk9ZhWwgLTH9hyTv1IjqIKUNeX6B623\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tquery: \"\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"iSgJYVk9ZhWwgLTH9hyTv1IjqIKUNeX6B623\"\n)\n\nfunc TestEnableX_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/endorlabs/endorlabs.go",
    "content": "package endorlabs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyAndSecretPat = regexp.MustCompile(`\\b(endr\\+[a-zA-Z0-9-]{16})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"endr+\"}\n}\n\n// FromData will find and optionally verify Endorlabs secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatches := make(map[string]struct{})\n\tfor _, match := range keyAndSecretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tkeyMatches[match[1]] = struct{}{}\n\t}\n\n\tsecretMatches := make(map[string]struct{})\n\tfor _, match := range keyAndSecretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tsecretMatches[match[1]] = struct{}{}\n\t}\n\n\tfor key := range keyMatches {\n\t\tfor secret := range secretMatches {\n\t\t\tif key == secret { // Minor optimization\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_EndorLabs,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tRawV2:        []byte(key + secret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, key, secret)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.ExtraData = extraData\n\t\t\t\ts1.SetVerificationError(verificationErr, key, secret)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, key, secret string) (bool, map[string]string, error) {\n\tauthData := fmt.Sprintf(`{\"key\":\"%s\", \"secret\":\"%s\"}`, key, secret)\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://api.endorlabs.com/v1/auth/api-key\", strings.NewReader(authData))\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\t// If the endpoint returns useful information, we can return it as a map.\n\t\treturn true, nil, nil\n\tcase http.StatusUnauthorized:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_EndorLabs\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/endorlabs/endorlabs_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage endorlabs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestEndorlabs_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tkey := testSecrets.MustGetField(\"ENDOR_KEY\")\n\tsecret := testSecrets.MustGetField(\"ENDOR_SECRET\")\n\tinactiveKey := testSecrets.MustGetField(\"ENDOR_KEY_INACTIVE\")\n\tinactiveSecret := testSecrets.MustGetField(\"ENDOR_SECRET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a endorlabs key %s and endorlabs secret %s within\", key, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EndorLabs,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EndorLabs,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EndorLabs,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EndorLabs,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a endorlabs key %s and endorlabs secret %s within\", key, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EndorLabs,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EndorLabs,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a endorlabs key %s and endorlabs secret %s within\", key, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EndorLabs,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EndorLabs,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Endorlabs.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tsortOpts := cmpopts.SortSlices(func(a, b []detectors.Result) bool {\n\t\t\t\treturn string(a[0].Raw) < string(b[0].Raw)\n\t\t\t})\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts, sortOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Endorlabs.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/endorlabs/endorlabs_test.go",
    "content": "package endorlabs\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestEndorlabs_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"typical pattern\",\n\t\t\tinput: `\n\t\t\t\tendorlabs_key = 'endr+xTGNjttLb8kVOHZC'\n\t\t\t\tendorlabs_secret = 'endr+gGVYIIrCq1VZTQMW'\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"endr+xTGNjttLb8kVOHZC\" + \"endr+gGVYIIrCq1VZTQMW\",\n\t\t\t\t\"endr+gGVYIIrCq1VZTQMW\" + \"endr+xTGNjttLb8kVOHZC\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/endpoint_customizer.go",
    "content": "package detectors\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\n// EndpointSetter implements a sensible default for the SetEndpoints function\n// of the EndpointCustomizer interface. A detector can embed this struct to\n// gain the functionality.\ntype EndpointSetter struct {\n\tconfiguredEndpoints []string\n\tcloudEndpoint       string\n\tuseCloudEndpoint    bool\n\tuseFoundEndpoints   bool\n}\n\nfunc (e *EndpointSetter) SetConfiguredEndpoints(userConfiguredEndpoints ...string) error {\n\tif len(userConfiguredEndpoints) == 0 {\n\t\treturn fmt.Errorf(\"at least one endpoint required\")\n\t}\n\tdeduped := make([]string, 0, len(userConfiguredEndpoints))\n\tfor _, endpoint := range userConfiguredEndpoints {\n\t\tcommon.AddStringSliceItem(endpoint, &deduped)\n\t}\n\te.configuredEndpoints = deduped\n\treturn nil\n}\n\nfunc (e *EndpointSetter) SetCloudEndpoint(url string) {\n\te.cloudEndpoint = url\n}\n\nfunc (e *EndpointSetter) UseCloudEndpoint(enabled bool) {\n\te.useCloudEndpoint = enabled\n}\n\nfunc (e *EndpointSetter) UseFoundEndpoints(enabled bool) {\n\te.useFoundEndpoints = enabled\n}\n\nfunc (e *EndpointSetter) Endpoints(foundEndpoints ...string) []string {\n\tendpoints := e.configuredEndpoints\n\tif e.useCloudEndpoint && e.cloudEndpoint != \"\" {\n\t\tendpoints = append(endpoints, e.cloudEndpoint)\n\t}\n\tif e.useFoundEndpoints {\n\t\tendpoints = append(endpoints, foundEndpoints...)\n\t}\n\treturn endpoints\n}\n"
  },
  {
    "path": "pkg/detectors/endpoint_customizer_test.go",
    "content": "package detectors\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestEmbeddedEndpointSetter(t *testing.T) {\n\ttype Scanner struct{ EndpointSetter }\n\n\tvar s Scanner\n\n\tt.Run(\"useFoundEndpoints is true\", func(t *testing.T) {\n\t\ts.useFoundEndpoints = true\n\n\t\t// \"baz\" is passed to Endpoints, should appear in the result\n\t\tassert.Equal(t, []string{\"baz\"}, s.Endpoints(\"baz\"))\n\t})\n\n\tt.Run(\"setting configured endpoints\", func(t *testing.T) {\n\t\t// Setting \"foo\" and \"bar\"\n\t\tassert.NoError(t, s.SetConfiguredEndpoints(\"foo\", \"bar\"))\n\n\t\t// Returning error because no endpoints are passed\n\t\tassert.Error(t, s.SetConfiguredEndpoints())\n\t})\n\n\t// \"foo\" and \"bar\" are added as configured endpoint\n\n\tt.Run(\"useFoundEndpoints adds new endpoints\", func(t *testing.T) {\n\t\t// \"baz\" is added because useFoundEndpoints is true\n\t\tassert.Equal(t, []string{\"foo\", \"bar\", \"baz\"}, s.Endpoints(\"baz\"))\n\t})\n\n\tt.Run(\"useCloudEndpoint is true\", func(t *testing.T) {\n\t\ts.useCloudEndpoint = true\n\t\ts.cloudEndpoint = \"test\"\n\n\t\t// \"test\" is added because useCloudEndpoint is true and cloudEndpoint is set\n\t\tassert.Equal(t, []string{\"foo\", \"bar\", \"test\"}, s.Endpoints())\n\t})\n\n\tt.Run(\"disable both foundEndpoints and cloudEndpoint\", func(t *testing.T) {\n\t\t// now disable both useFoundEndpoints and useCloudEndpoint\n\t\ts.useFoundEndpoints = false\n\t\ts.useCloudEndpoint = false\n\n\t\t// \"test\" won't be added\n\t\tassert.Equal(t, []string{\"foo\", \"bar\"}, s.Endpoints(\"test\"))\n\t})\n\n\tt.Run(\"cloudEndpoint not added when useCloudEndpoint is false\", func(t *testing.T) {\n\t\ts.cloudEndpoint = \"new\"\n\n\t\t// \"new\" is not added because useCloudEndpoint is false\n\t\tassert.Equal(t, []string{\"foo\", \"bar\"}, s.Endpoints())\n\t})\n\n}\n"
  },
  {
    "path": "pkg/detectors/enigma/enigma.go",
    "content": "package enigma\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"enigma\"}) + `\\b([a-zA-Z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"enigma\"}\n}\n\n// FromData will find and optionally verify Enigma secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Enigma,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := 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\"}}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.enigma.com/businesses/match\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"x-api-key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Enigma\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/enigma/enigma_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage enigma\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestEnigma_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ENIGMA\")\n\tinactiveSecret := testSecrets.MustGetField(\"ENIGMA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a enigma secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Enigma,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Enigma,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Enigma.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Enigma.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/enigma/enigma_test.go",
    "content": "package enigma\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Header\"\n\t\t\tapi_version: v1\n\t\t\tenigma_secret: \"dkQePsD59DdzfoSuIZ2Po2md3q0ENVnvyIDdxs2E\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tquery: \"\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"dkQePsD59DdzfoSuIZ2Po2md3q0ENVnvyIDdxs2E\"\n)\n\nfunc TestEnigma_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/envoyapikey/envoyapikey.go",
    "content": "package envoyapikey\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"envoy\"}) + `\\b([a-zA-Z0-9]{220})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"envoy\"}\n}\n\n// FromData will find and optionally verify Envoy secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_EnvoyApiKey,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.envoy.com/v1/locations\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.envoy+json; version=3\")\n\t\t\treq.Header.Add(\"X-Api-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbody, _ := io.ReadAll(res.Body)\n\n\t\t\t\t// Invalid API keys can also return status code 200, so check for presence of 'status 401' in response body.\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 || res.StatusCode == 403 {\n\t\t\t\t\tif !strings.Contains(string(body), `\"status\":401`) {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_EnvoyApiKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/envoyapikey/envoyapikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage envoyapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestEnvoyapikey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ENVOYAPIKEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"ENVOYAPIKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a envoyapikey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EnvoyApiKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_EnvoyApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Envoyapikey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Envoyapikey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/envoyapikey/envoyapikey_test.go",
    "content": "package envoyapikey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Header\"\n\t\t\tapi_version: v1\n\t\t\tenvoy_secret: \"53PbWnxV5h7pZGNmw7U6FL79ithvedz1PWSvhFyJDZbqT5ECihUDeQ4MY6O3qTtKMKNFh2Hc5D54pchSKYyTVKi3nqJITLhZi17uCHJVQKrinOrkGL9IUh6QFjDjN3NcK1HKAimUgcNY2B8meGBfQmQ2QnVhKZcK1E8ldT9w4eb9ihgEwnG2lMjG41k5bZEPos3sJDEJWZ39U2J2Yu6OP8h8AVLw\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tquery: \"\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"53PbWnxV5h7pZGNmw7U6FL79ithvedz1PWSvhFyJDZbqT5ECihUDeQ4MY6O3qTtKMKNFh2Hc5D54pchSKYyTVKi3nqJITLhZi17uCHJVQKrinOrkGL9IUh6QFjDjN3NcK1HKAimUgcNY2B8meGBfQmQ2QnVhKZcK1E8ldT9w4eb9ihgEwnG2lMjG41k5bZEPos3sJDEJWZ39U2J2Yu6OP8h8AVLw\"\n)\n\nfunc TestEnvoyAPIKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/eraser/eraser.go",
    "content": "package eraser\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"eraser\"}) + `\\b([0-9a-zA-Z]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"eraser\"}\n}\n\n// FromData will find and optionally verify Eraser secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Eraser,\n\t\t\tRaw:          []byte(match),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/eraser/\",\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\t// https://docs.eraser.io/reference/generate-diagram-from-eraser-dsl\n\tpayload := strings.NewReader(\"{\\\"elements\\\":[{\\\"type\\\":\\\"diagram\\\"}]}\")\n\n\turl := \"https://app.eraser.io/api/render/elements\"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, payload)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header = http.Header{\"Authorization\": []string{\"Bearer \" + token}}\n\treq.Header.Add(\"content-type\", \"application/json\")\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil, nil\n\tcase http.StatusUnauthorized:\n\t\t// 401 API token unauthorized\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\tdefault:\n\t\t// 400 The request is missing the 'text' parameter\n\t\t// 500 Eraser was unable to generate a result\n\t\t// 503 Service temporarily unavailable. This may be the result of too many requests.\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Eraser\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Eraser is a tool used for generating diagrams from DSL. Eraser API tokens can be used to authenticate and interact with the Eraser API.\"\n}\n"
  },
  {
    "path": "pkg/detectors/eraser/eraser_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage eraser\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestEraser_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ERASER\")\n\tinactiveSecret := testSecrets.MustGetField(\"ERASER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a eraser secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Eraser,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Eraser,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a eraser secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Eraser,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a eraser secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Eraser,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Eraser.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Eraser.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/eraser/eraser_test.go",
    "content": "package eraser\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestEraser_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: \"eraser_token = 'KkBmh6TUBIcyFAp20XXa'\",\n\t\t\twant:  []string{\"KkBmh6TUBIcyFAp20XXa\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/etherscan/etherscan.go",
    "content": "package etherscan\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"etherscan\"}) + `\\b([0-9A-Z]{34})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"etherscan\"}\n}\n\n// FromData will find and optionally verify Etherscan secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Etherscan,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.etherscan.io/api?module=account&action=balance&address=0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae&tag=latest&apikey=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\n\t\t\t\tif strings.Contains(body, `\"OK\"`) {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Etherscan\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/etherscan/etherscan_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage etherscan\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestEtherscan_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ETHERSCAN\")\n\tinactiveSecret := testSecrets.MustGetField(\"ETHERSCAN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a etherscan secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Etherscan,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Etherscan,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Etherscan.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Etherscan.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/etherscan/etherscan_test.go",
    "content": "package etherscan\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tapi_version: v1\n\t\t\tetherscan_secret: \"9VROD0TR8VNW4ZEC0U2YK5W9X0B2HO1KAD\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tquery: \"apikey=$etherscan_secret\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"9VROD0TR8VNW4ZEC0U2YK5W9X0B2HO1KAD\"\n)\n\nfunc TestEtherScan_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ethplorer/ethplorer.go",
    "content": "package ethplorer\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ethplorer\"}) + `\\b([a-z0-9A-Z-]{22})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ethplorer\"}\n}\n\n// FromData will find and optionally verify Ethplorer secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Ethplorer,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(\"apiKey=\" + resMatch + \"&addresses=0xb2930b35844a230f00e51431acae96fe543a0347%2C0xb52d3141ee731fac89927476c6a5207b37cd72ff\")\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api-mon.ethplorer.io/createPool\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Ethplorer\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Ethplorer API keys can be used to interact with the Ethplorer service, which provides access to Ethereum blockchain data and analytics.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ethplorer/ethplorer_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ethplorer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestEthplorer_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ETHPLORER\")\n\tinactiveSecret := testSecrets.MustGetField(\"ETHPLORER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ethplorer secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ethplorer,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ethplorer,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Ethplorer.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Ethplorer.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ethplorer/ethplorer_test.go",
    "content": "package ethplorer\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Payload\"\n\t\t\tapi_version: v1\n\t\t\tethplorer_secret: \"QGp6JMwswjqb5FJFGuslKQ\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tquery: \"\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"QGp6JMwswjqb5FJFGuslKQ\"\n)\n\nfunc TestEthplorer_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/eventbrite/eventbrite.go",
    "content": "package eventbrite\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"eventbrite\"}) + `\\b([0-9A-Z]{20})\\b`)\n)\n\nfunc (s *Scanner) getClient() *http.Client {\n\tif s.client == nil {\n\t\treturn defaultClient\n\t}\n\n\treturn s.client\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"eventbrite\"}\n}\n\n// FromData will find and optionally verify Eventbrite secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueTokenMatches := make(map[string]struct{})\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokenMatches[match[1]] = struct{}{}\n\t}\n\n\tfor token := range uniqueTokenMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Eventbrite,\n\t\t\tRaw:          []byte(token),\n\t\t\tExtraData:    map[string]string{},\n\t\t}\n\n\t\tif verify {\n\t\t\textraData, isVerified, verificationErr := verifyEventBrite(ctx, s.getClient(), token)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\ts1.ExtraData = extraData\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Eventbrite\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Eventbrite is an event management and ticketing website. Eventbrite API keys can be used to access and manage event data.\"\n}\n\nfunc verifyEventBrite(ctx context.Context, client *http.Client, token string) (map[string]string, bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.eventbriteapi.com/v3/users/me/?token=\"+token, nil)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tvar response map[string]interface{}\n\t\tif err := json.NewDecoder(resp.Body).Decode(&response); err != nil {\n\t\t\treturn nil, false, err\n\t\t}\n\n\t\tuserName := response[\"name\"].(string)\n\n\t\treturn map[string]string{\"user name\": userName}, true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil, false, nil\n\tdefault:\n\t\treturn nil, false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/eventbrite/eventbrite_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage eventbrite\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestEventbrite_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"EVENTBRITE\")\n\tinactiveSecret := testSecrets.MustGetField(\"EVENTBRITE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a eventbrite secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Eventbrite,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Eventbrite,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a eventbrite secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Eventbrite,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a eventbrite secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Eventbrite,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Eventbrite.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Eventbrite.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/eventbrite/eventbrite_test.go",
    "content": "package eventbrite\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tapi_version: v1\n\t\t\teventbrite_secret: \"1SS1TOXV0S90JCAQ3G8F\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tquery: \"token=$eventbrite_secret\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"1SS1TOXV0S90JCAQ3G8F\"\n)\n\nfunc TestEventBrite_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/everhour/everhour.go",
    "content": "package everhour\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"everhour\"}\n}\n\n// FromData will find and optionally verify Everhour secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Everhour,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.everhour.com/clients\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"X-Api-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Everhour\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Everhour is a time tracking software for teams. Everhour API keys can be used to access and manage project and time tracking data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/everhour/everhour_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage everhour\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestEverhour_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"EVERHOUR\")\n\tinactiveSecret := testSecrets.MustGetField(\"EVERHOUR_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a everhour secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Everhour,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Everhour,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Everhour.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Everhour.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/everhour/everhour_test.go",
    "content": "package everhour\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Header\"\n\t\t\tapi_version: v1\n\t\t\teverhour_secret: \"a289-1dad-dbeeeb-2c0b1f-dc0ed546\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tquery: \"\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"a289-1dad-dbeeeb-2c0b1f-dc0ed546\"\n)\n\nfunc TestEventBrite_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/exchangerateapi/exchangerateapi.go",
    "content": "package exchangerateapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"exchangerate\", \"exchange-rate\"}) + `\\b([a-f0-9]{24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"exchangerate\", \"exchange-rate\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ExchangeRateAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"An API key for determining the exchange rate of currencies\"\n}\n\n// FromData will find and optionally verify ExchangeRateAPI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ExchangeRateAPI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyExchangeRateKey(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyExchangeRateKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://v6.exchangerate-api.com/v6/latest/USD\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// authentication docs: https://www.exchangerate-api.com/docs/authentication\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/exchangerateapi/exchangerateapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage exchangerateapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestExchangeRateAPI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"EXCHANGERATEAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"EXCHANGERATEAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a exchangerateapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ExchangeRateAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ExchangeRateAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ExchangeRateAPI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ExchangeRateAPI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/exchangerateapi/exchangerateapi_test.go",
    "content": "package exchangerateapi\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestExchangeRateAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t# Configuration File: config.yaml\n\t\t\t\tdatabase:\n\t\t\t\t\thost: $DB_HOST\n\t\t\t\t\tport: $DB_PORT\n\t\t\t\t\tusername: $DB_USERNAME\n\t\t\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\t\t\tapi:\n\t\t\t\t\tauth_type: \"Bearer\"\n\t\t\t\t\tin: \"Header\"\n\t\t\t\t\tapi_version: v1\n\t\t\t\t\texchangerate_secret: \"a1039cd66170a7bf214199d4\"\n\t\t\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\t\t\tquery: \"\"\n\t\t\t\t\tresponse_code: 200\n\n\t\t\t\t# Notes:\n\t\t\t\t# - Remember to rotate the secret every 90 days.\n\t\t\t\t# - The above credentials should only be used in a secure environment.\n\t\t\t`,\n\t\t\twant: []string{\"a1039cd66170a7bf214199d4\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/exchangeratesapi/exchangeratesapi.go",
    "content": "package exchangeratesapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"exchangerates\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"exchangerates\"}\n}\n\n// FromData will find and optionally verify ExchangeRatesAPI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ExchangeRatesAPI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.exchangeratesapi.io/v1/latest?access_key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ExchangeRatesAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ExchangeRatesAPI provides exchange rate data for various currencies. The API key can be used to access and retrieve this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/exchangeratesapi/exchangeratesapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage exchangeratesapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestExchangeRatesAPI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"EXCHANGERATESAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"EXCHANGERATESAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a exchangeratesapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ExchangeRatesAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ExchangeRatesAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ExchangeRatesAPI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ExchangeRatesAPI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/exchangeratesapi/exchangeratesapi_test.go",
    "content": "package exchangeratesapi\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tapi_version: v1\n\t\t\texchangerates_secret: \"flo7en8mnclsnz50dme89e9vwr3l9jbb\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tquery: \"accesskey=$exchangerates_secret\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"flo7en8mnclsnz50dme89e9vwr3l9jbb\"\n)\n\nfunc TestExchangeRatesAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/exportsdk/exportsdk.go",
    "content": "package exportsdk\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"exportsdk\"}) + `\\b([0-9a-z]{5,15}_[0-9a-z-]{36})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"exportsdk\"}) + `\\b([0-9a-z-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"exportsdk\"}\n}\n\n// FromData will find and optionally verify ExportSDK secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_ExportSDK,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tpayload := strings.NewReader(`{  \"templateId\": \"` + resIdMatch + `\"}`)\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.exportsdk.com/v1/pdf\", payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\treq.Header.Add(\"X-API-KEY\", resMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ExportSDK\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ExportSDK is a service used for exporting data and generating PDFs. ExportSDK keys can be used to authenticate API requests and generate documents.\"\n}\n"
  },
  {
    "path": "pkg/detectors/exportsdk/exportsdk_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage exportsdk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestExportSDK_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"EXPORTSDK\")\n\tid := testSecrets.MustGetField(\"EXPORTSDK_TEMPLATE\")\n\tinactiveSecret := testSecrets.MustGetField(\"EXPORTSDK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a exportsdk secret %s within exportsdk %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ExportSDK,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ExportSDK,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ExportSDK.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ExportSDK.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/exportsdk/exportsdk_test.go",
    "content": "package exportsdk\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Header\"\n\t\t\tapi_version: v1\n\t\t\texportsdk_id: \"ipwa96igr30chlcfr8xb7gack2xgfd7ov8zk\"\n\t\t\texportsdk_secret: \"q6l59i_dd8w6gfvh--le8xasayvsufpvt4uh1pzmu07\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tquery: \"\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"q6l59i_dd8w6gfvh--le8xasayvsufpvt4uh1pzmu07\"\n)\n\nfunc TestExportSDK_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/extractorapi/extractorapi.go",
    "content": "package extractorapi\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"extractorapi\"}) + `\\b([a-zA-Z-0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"extractorapi\"}\n}\n\n// FromData will find and optionally verify ExtractorAPI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ExtractorAPI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://extractorapi.com/api/v1/extractor?apikey=\"+resMatch+\"&url=example.com\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ExtractorAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ExtractorAPI is a service for extracting data from various sources. ExtractorAPI keys can be used to access and extract data from these sources.\"\n}\n"
  },
  {
    "path": "pkg/detectors/extractorapi/extractorapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage extractorapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestExtractorAPI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"EXTRACTORAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"EXTRACTORAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a extractorapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ExtractorAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ExtractorAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ExtractorAPI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ExtractorAPI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/extractorapi/extractorapi_test.go",
    "content": "package extractorapi\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\t# Configuration File: config.yaml\n\t\tdatabase:\n\t\t\thost: $DB_HOST\n\t\t\tport: $DB_PORT\n\t\t\tusername: $DB_USERNAME\n\t\t\tpassword: $DB_PASS  # IMPORTANT: Do not share this password publicly\n\n\t\tapi:\n\t\t\tauth_type: \"API-Key\"\n\t\t\tin: \"Path\"\n\t\t\tapi_version: v1\n\t\t\textractorapi_secret: \"jSCInysVesUIQ8vn7ZIQg3vKUCB8FgMnXTvJ4CKN\"\n\t\t\tbase_url: \"https://api.example.com/$api_version/example\"\n\t\t\tquery: \"apikey=$extractorapi_secret\"\n\t\t\tresponse_code: 200\n\n\t\t# Notes:\n\t\t# - Remember to rotate the secret every 90 days.\n\t\t# - The above credentials should only be used in a secure environment.\n\t`\n\tsecret = \"jSCInysVesUIQ8vn7ZIQg3vKUCB8FgMnXTvJ4CKN\"\n)\n\nfunc TestExtractorAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/facebookoauth/facebookoauth.go",
    "content": "package facebookoauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tapiIdPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"facebook\"}) + `\\b([0-9]{15,18})\\b`) // not actually sure of the upper bound\n\tapiSecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"facebook\"}) + `\\b([A-Za-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"facebook\"}\n}\n\n// FromData will find and optionally verify FacebookOAuth secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tapiIdMatches := apiIdPat.FindAllStringSubmatch(dataStr, -1)\n\tapiSecretMatches := apiSecretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, apiIdMatch := range apiIdMatches {\n\t\tapiIdRes := strings.TrimSpace(apiIdMatch[1])\n\n\t\tfor _, apiSecretMatch := range apiSecretMatches {\n\t\t\tapiSecretRes := strings.TrimSpace(apiSecretMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_FacebookOAuth,\n\t\t\t\tRedacted:     apiIdRes,\n\t\t\t\tRaw:          []byte(apiSecretRes),\n\t\t\t\tRawV2:        []byte(apiIdRes + apiSecretRes),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\t// thanks https://stackoverflow.com/questions/15621471/validate-a-facebook-app-id-and-app-secret\n\t\t\t\t// https://stackoverflow.com/questions/24401241/how-to-get-a-facebook-access-token-using-appid-and-app-secret-without-any-login\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://graph.facebook.com/me?access_token=%s|%s\", apiIdRes, apiSecretRes), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FacebookOAuth\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Facebook OAuth tokens are used to authenticate users and provide access to Facebook's API services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/facebookoauth/facebookoauth_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage facebookoauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFacebookOAuth_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tappId := testSecrets.MustGetField(\"FACEBOOK_APP_ID\")\n\tappSecret := testSecrets.MustGetField(\"FACEBOOK_APP_SECRET\")\n\tinactiveAppSecret := testSecrets.MustGetField(\"FACEBOOK_APP_SECRET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a facebook appid %s and secret %s within\", appId, appSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FacebookOAuth,\n\t\t\t\t\tRedacted:     appId,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FacebookOAuth,\n\t\t\t\t\tRedacted:     appId,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"FacebookOAuth.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"FacebookOAuth.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/facebookoauth/facebookoauth_test.go",
    "content": "package facebookoauth\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Facebook\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"OAuth\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"facebook_appid\": \"5295912532069628\",\n\t\t\t\"facebook_secret\": \"rw6rTIk14bOEW84MkNbLVqVbrLJugJo7\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"5295912532069628rw6rTIk14bOEW84MkNbLVqVbrLJugJo7\"\n)\n\nfunc TestFacebookOAuth_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/faceplusplus/faceplusplus.go",
    "content": "package faceplusplus\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"faceplusplus\"}) + `\\b([0-9a-zA-Z_-]{32})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"faceplusplus\"}) + `\\b([0-9a-zA-Z_-]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"faceplusplus\"}\n}\n\n// FromData will find and optionally verify Faceplusplus secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, secretMatch := range secretMatches {\n\t\t\tresSecret := strings.TrimSpace(secretMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_FacePlusPlus,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resSecret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, 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)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FacePlusPlus\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/faceplusplus/faceplusplus_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage faceplusplus\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFaceplusplus_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tkey := testSecrets.MustGetField(\"FACEPLUSPLUS_KEY\")\n\tsecret := testSecrets.MustGetField(\"FACEPLUSPLUS_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"FACEPLUSPLUS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a faceplusplus key %s within faceplusplus secret %s\", key, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FacePlusPlus,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FacePlusPlus,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Faceplusplus.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Faceplusplus.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/faceplusplus/faceplusplus_test.go",
    "content": "package faceplusplus\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FacePlusPlus\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"faceplusplus_id\": \"ipScAHzUxOS2CQ3JwTdIDG1ClxZl_iVH\",\n\t\t\t\"faceplusplus_secret\": \"Qomsw0IQtp3iz1jlxAqQJO5afpbeEeAh\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"POST\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecrets = []string{\n\t\t// TODO: Add logic to avoid verification when key and id is same because the regex is same for both\n\t\t\"Qomsw0IQtp3iz1jlxAqQJO5afpbeEeAhQomsw0IQtp3iz1jlxAqQJO5afpbeEeAh\",\n\t\t\"ipScAHzUxOS2CQ3JwTdIDG1ClxZl_iVHQomsw0IQtp3iz1jlxAqQJO5afpbeEeAh\",\n\t\t\"Qomsw0IQtp3iz1jlxAqQJO5afpbeEeAhipScAHzUxOS2CQ3JwTdIDG1ClxZl_iVH\",\n\t\t\"ipScAHzUxOS2CQ3JwTdIDG1ClxZl_iVHipScAHzUxOS2CQ3JwTdIDG1ClxZl_iVH\",\n\t}\n)\n\nfunc TestFacePlusPlus_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  secrets,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/falsepositives.go",
    "content": "package detectors\n\nimport (\n\t_ \"embed\"\n\t\"math\"\n\t\"strings\"\n\t\"unicode\"\n\t\"unicode/utf8\"\n\n\tahocorasick \"github.com/BobuSumisu/aho-corasick\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nvar (\n\tDefaultFalsePositives = map[FalsePositive]struct{}{\n\t\t\"example\": {}, \"xxxxxx\": {}, \"aaaaaa\": {}, \"abcde\": {}, \"00000\": {}, \"sample\": {}, \"*****\": {},\n\t}\n\tUuidFalsePositives map[FalsePositive]struct{}\n)\n\ntype FalsePositive string\n\ntype CustomFalsePositiveChecker interface {\n\t// IsFalsePositive returns two values:\n\t// 1. Whether the result is a false positive.\n\t// 2. If #1 is `true`, the reason why.\n\tIsFalsePositive(result Result) (bool, string)\n}\n\nvar (\n\tfilter *ahocorasick.Trie\n\n\t//go:embed \"fp_badlist.txt\"\n\tbadList []byte\n\t//go:embed \"fp_words.txt\"\n\twordList []byte\n\t//go:embed \"fp_programmingbooks.txt\"\n\tprogrammingBookWords []byte\n\t//go:embed \"fp_uuids.txt\"\n\tuuidList []byte\n)\n\nfunc init() {\n\t// Populate trie.\n\tbuilder := ahocorasick.NewTrieBuilder()\n\n\twordList := bytesToCleanWordList(wordList)\n\tbuilder.AddStrings(wordList)\n\n\tbadList := bytesToCleanWordList(badList)\n\tbuilder.AddStrings(badList)\n\n\tprogrammingBookWords := bytesToCleanWordList(programmingBookWords)\n\tbuilder.AddStrings(programmingBookWords)\n\n\tuuidList := bytesToCleanWordList(uuidList)\n\tbuilder.AddStrings(uuidList)\n\n\tfilter = builder.Build()\n\n\t// Populate custom FalsePositive list\n\tUuidFalsePositives = make(map[FalsePositive]struct{}, len(uuidList))\n\tfor _, uuid := range uuidList {\n\t\tUuidFalsePositives[FalsePositive(uuid)] = struct{}{}\n\t}\n}\n\nfunc GetFalsePositiveCheck(detector Detector) func(Result) (bool, string) {\n\tchecker, ok := detector.(CustomFalsePositiveChecker)\n\tif ok {\n\t\treturn checker.IsFalsePositive\n\t}\n\n\treturn func(res Result) (bool, string) {\n\t\treturn IsKnownFalsePositive(string(res.Raw), DefaultFalsePositives, true)\n\t}\n}\n\n// IsKnownFalsePositive returns whether a finding is (likely) a known false positive, and the reason for the detection.\n//\n// Currently, this includes: english word in key or matches common example patterns.\n// Only the secret key material should be passed into this function\nfunc IsKnownFalsePositive(match string, falsePositives map[FalsePositive]struct{}, wordCheck bool) (bool, string) {\n\tif !utf8.ValidString(match) {\n\t\treturn true, \"invalid utf8\"\n\t}\n\tlower := strings.ToLower(match)\n\n\tif _, exists := falsePositives[FalsePositive(lower)]; exists {\n\t\treturn true, \"matches term: \" + lower\n\t}\n\n\tfor fp := range falsePositives {\n\t\tfps := string(fp)\n\t\tif strings.Contains(lower, fps) {\n\t\t\treturn true, \"contains term: \" + fps\n\t\t}\n\t}\n\n\tif wordCheck {\n\t\tif m := filter.MatchFirstString(lower); m != nil {\n\t\t\treturn true, \"matches wordlist: \" + m.MatchString()\n\t\t}\n\t}\n\n\treturn false, \"\"\n}\n\nfunc HasDigit(key string) bool {\n\tfor _, ch := range key {\n\t\tif unicode.IsDigit(ch) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc bytesToCleanWordList(data []byte) []string {\n\twords := make(map[string]struct{})\n\tfor _, word := range strings.Split(string(data), \"\\n\") {\n\t\tif strings.TrimSpace(word) != \"\" {\n\t\t\twords[strings.TrimSpace(strings.ToLower(word))] = struct{}{}\n\t\t}\n\t}\n\n\twordList := make([]string, 0, len(words))\n\tfor word := range words {\n\t\twordList = append(wordList, word)\n\t}\n\treturn wordList\n}\n\nfunc StringShannonEntropy(input string) float64 {\n\tchars := make(map[rune]float64)\n\tinverseTotal := 1 / float64(len(input)) // precompute the inverse\n\n\tfor _, char := range input {\n\t\tchars[char]++\n\t}\n\n\tentropy := 0.0\n\tfor _, count := range chars {\n\t\tprobability := count * inverseTotal\n\t\tentropy += probability * math.Log2(probability)\n\t}\n\n\treturn -entropy\n}\n\n// FilterResultsWithEntropy filters out determinately unverified results that have a shannon entropy below the given value.\nfunc FilterResultsWithEntropy(ctx context.Context, results []Result, entropy float64, shouldLog bool) []Result {\n\tvar filteredResults []Result\n\tfor _, result := range results {\n\t\tif !result.Verified {\n\t\t\tif result.Raw != nil {\n\t\t\t\tif StringShannonEntropy(string(result.Raw)) >= entropy {\n\t\t\t\t\tfilteredResults = append(filteredResults, result)\n\t\t\t\t} else {\n\t\t\t\t\tif shouldLog {\n\t\t\t\t\t\tctx.Logger().Info(\"Filtered out result with low entropy\", \"result\", result)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfilteredResults = append(filteredResults, result)\n\t\t\t}\n\t\t} else {\n\t\t\tfilteredResults = append(filteredResults, result)\n\t\t}\n\t}\n\treturn filteredResults\n}\n"
  },
  {
    "path": "pkg/detectors/falsepositives_test.go",
    "content": "package detectors\n\nimport (\n\t\"context\"\n\t_ \"embed\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype fakeDetector struct{}\ntype customFalsePositiveChecker struct{ fakeDetector }\n\nfunc (d fakeDetector) FromData(ctx context.Context, verify bool, data []byte) ([]Result, error) {\n\treturn nil, nil\n}\n\nfunc (d fakeDetector) Keywords() []string {\n\treturn nil\n}\n\nfunc (d fakeDetector) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType(0)\n}\n\nfunc (f fakeDetector) Description() string { return \"\" }\n\nfunc (d customFalsePositiveChecker) IsFalsePositive(result Result) (bool, string) {\n\treturn IsKnownFalsePositive(string(result.Raw), map[FalsePositive]struct{}{\"a specific magic string\": {}}, false)\n}\n\n// This test validates that GetFalsePositiveCheck, when invoked on a detector that does not implement\n// CustomFalsePositiveChecker, returns a predicate that behaves as expected.\nfunc TestGetFalsePositiveCheck_DefaultLogic(t *testing.T) {\n\ttestCases := []struct {\n\t\traw             string\n\t\tisFalsePositive bool\n\t}{\n\t\t{\"00000\", true},  // \"default\" false positive list\n\t\t{\"number\", true}, // from wordlist\n\t\t{\"00000000-0000-0000-0000-000000000000\", true}, // from uuid list\n\t\t{\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\", true}, // from uuid list\n\t\t{\"hga8adshla3434g\", false},\n\t\t{\"f795f7db-2dfe-4095-96f3-8f8370c735f9\", false},\n\t}\n\n\tfor _, tt := range testCases {\n\t\tisFalsePositive, _ := GetFalsePositiveCheck(fakeDetector{})(Result{Raw: []byte(tt.raw)})\n\t\tassert.Equal(t, tt.isFalsePositive, isFalsePositive, \"secret %q had unexpected false positive status\", tt.raw)\n\t}\n}\n\n// This test validates that GetFalsePositiveCheck, when invoked on a detector that implements\n// CustomFalsePositiveChecker, returns a predicate that behaves as expected. (Specifically, the predicate should not\n// flag secrets that are present in the standard false positive lists.)\nfunc TestGetFalsePositiveCheck_CustomLogic(t *testing.T) {\n\ttestCases := []struct {\n\t\traw             string\n\t\tisFalsePositive bool\n\t}{\n\t\t{\"a specific magic string\", true}, // the specific value the custom checker is looking for\n\t\t{\"00000\", false},\n\t\t{\"number\", false},\n\t\t{\"00000000-0000-0000-0000-000000000000\", false},\n\t\t{\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\", false},\n\t\t{\"hga8adshla3434g\", false},\n\t\t{\"f795f7db-2dfe-4095-96f3-8f8370c735f9\", false},\n\t}\n\n\tfor _, tt := range testCases {\n\t\tisFalsePositive, _ := GetFalsePositiveCheck(customFalsePositiveChecker{})(Result{Raw: []byte(tt.raw)})\n\t\tassert.Equal(t, tt.isFalsePositive, isFalsePositive, \"secret %q had unexpected false positive status\", tt.raw)\n\t}\n}\n\nfunc TestIsFalsePositive(t *testing.T) {\n\ttype args struct {\n\t\tmatch          string\n\t\tfalsePositives map[FalsePositive]struct{}\n\t\tuseWordlist    bool\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"fp\",\n\t\t\targs: args{\n\t\t\t\tmatch:          \"example\",\n\t\t\t\tfalsePositives: DefaultFalsePositives,\n\t\t\t\tuseWordlist:    false,\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"fp - in wordlist\",\n\t\t\targs: args{\n\t\t\t\tmatch:          \"sdfdsfprivatesfsdfd\",\n\t\t\t\tfalsePositives: DefaultFalsePositives,\n\t\t\t\tuseWordlist:    true,\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"fp - not in wordlist\",\n\t\t\targs: args{\n\t\t\t\tmatch:          \"sdfdsfsfsdfd\",\n\t\t\t\tfalsePositives: DefaultFalsePositives,\n\t\t\t\tuseWordlist:    true,\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not fp\",\n\t\t\targs: args{\n\t\t\t\tmatch:          \"notafp123\",\n\t\t\t\tfalsePositives: DefaultFalsePositives,\n\t\t\t\tuseWordlist:    false,\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"fp - in wordlist exact match\",\n\t\t\targs: args{\n\t\t\t\tmatch:          \"private\",\n\t\t\t\tfalsePositives: DefaultFalsePositives,\n\t\t\t\tuseWordlist:    true,\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got, _ := IsKnownFalsePositive(tt.args.match, tt.args.falsePositives, tt.args.useWordlist); got != tt.want {\n\t\t\t\tt.Errorf(\"IsKnownFalsePositive() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestStringShannonEntropy(t *testing.T) {\n\ttype args struct {\n\t\tinput string\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant float64\n\t}{\n\t\t{\n\t\t\tname: \"entropy 1\",\n\t\t\targs: args{\n\t\t\t\tinput: \"aaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\n\t\t\t},\n\t\t\twant: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"entropy 2\",\n\t\t\targs: args{\n\t\t\t\tinput: \"aaaaaaaaaaaaaaaaaaaaaaaaaaab\",\n\t\t\t},\n\t\t\twant: 0.22,\n\t\t},\n\t\t{\n\t\t\tname: \"entropy 3\",\n\t\t\targs: args{\n\t\t\t\tinput: \"aaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaab\",\n\t\t\t},\n\t\t\twant: 0.22,\n\t\t},\n\t\t{\n\t\t\tname: \"empty\",\n\t\t\targs: args{\n\t\t\t\tinput: \"\",\n\t\t\t},\n\t\t\twant: 0.0,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := StringShannonEntropy(tt.args.input)\n\t\t\tif len(tt.args.input) > 0 && tt.want != 0 {\n\t\t\t\tassert.InEpsilon(t, tt.want, got, 0.1)\n\t\t\t} else {\n\t\t\t\tassert.Equal(t, tt.want, got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkDefaultIsKnownFalsePositive(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\t// Use a string that won't be found in any dictionary for the worst case check.\n\t\tIsKnownFalsePositive(\"aoeuaoeuaoeuaoeuaoeuaoeu\", DefaultFalsePositives, true)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fastforex/fastforex.go",
    "content": "package fastforex\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"fastforex\"}) + `\\b([a-z0-9-]{28})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"fastforex\"}\n}\n\n// FromData will find and optionally verify FastForex secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_FastForex,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.fastforex.io/fetch-all?api_key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FastForex\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"FastForex provides foreign exchange rate data. FastForex API keys can be used to access and retrieve this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/fastforex/fastforex_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage fastforex\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFastForex_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FASTFOREX\")\n\tinactiveSecret := testSecrets.MustGetField(\"FASTFOREX_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a fastforex secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FastForex,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FastForex,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"FastForex.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"FastForex.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fastforex/fastforex_test.go",
    "content": "package fastforex\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FastForex\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"fastforex_secret\": \"jk-qatdz1xcgoz3yssqexstefbtq\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"jk-qatdz1xcgoz3yssqexstefbtq\"\n)\n\nfunc TestFastForex_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fastlypersonaltoken/fastlypersonaltoken.go",
    "content": "package fastlypersonaltoken\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"fastly\"}) + `\\b([A-Za-z0-9_-]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"fastly\"}\n}\n\ntype token struct {\n\tTokenID   string `json:\"id\"`\n\tUserID    string `json:\"user_id\"`\n\tExpiresAt string `json:\"expires_at\"`\n\tScope     string `json:\"scope\"`\n}\n\n// FromData will find and optionally verify FastlyPersonalToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueMatches = make(map[string]struct{})\n\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[matches[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_FastlyPersonalToken,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\textraData, verified, verificationErr := verifyFastlyApiToken(ctx, match)\n\t\t\ts1.Verified = verified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\n\t\t\tif s1.Verified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\"key\": match,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FastlyPersonalToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Fastly is a content delivery network (CDN) and cloud service provider. Fastly personal tokens can be used to authenticate API requests to Fastly services.\"\n}\n\nfunc verifyFastlyApiToken(ctx context.Context, apiToken string) (map[string]string, bool, error) {\n\t// api-docs: https://www.fastly.com/documentation/reference/api/auth-tokens/user/\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.fastly.com/tokens/self\", nil)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\n\t// add api key in the header\n\treq.Header.Add(\"Fastly-Key\", apiToken)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tvar self token\n\t\tif err = json.NewDecoder(resp.Body).Decode(&self); err != nil {\n\t\t\treturn nil, false, err\n\t\t}\n\n\t\t// capture token details in the map\n\t\textraData := map[string]string{\n\t\t\t// token id is the alphanumeric string uniquely identifying a token\n\t\t\t\"token_id\": self.TokenID,\n\t\t\t// user id is the alphanumeric string uniquely identifying the user\n\t\t\t\"user_id\": self.UserID,\n\t\t\t// expires at is time-stamp (UTC) of when the token will expire\n\t\t\t\"token_expires_at\": self.ExpiresAt,\n\t\t\t// token scope is space-delimited list of authorization scope of the token\n\t\t\t\"token_scope\": self.Scope,\n\t\t}\n\n\t\t// if expires at is empty which mean token is set to never expire, add 'Never' as the value\n\t\tif extraData[\"token_expires_at\"] == \"\" {\n\t\t\textraData[\"token_expires_at\"] = \"never\"\n\t\t}\n\n\t\treturn extraData, true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\t// 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.\n\t\treturn nil, false, nil\n\tdefault:\n\t\treturn nil, false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fastlypersonaltoken/fastlypersonaltoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage fastlypersonaltoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFastlyPersonalToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FASTLYPERSONALTOKEN_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"FASTLYPERSONALTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a fastlypersonaltoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FastlyPersonalToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"token_id\":         \"2ICO7ArmhY8OMiiOyNpXfc\",\n\t\t\t\t\t\t\"user_id\":          \"7anDA1ct17E8pkFAE0tJkk\",\n\t\t\t\t\t\t\"token_expires_at\": \"never\",\n\t\t\t\t\t\t\"token_scope\":      \"global:read global\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FastlyPersonalToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData:    nil,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"FastlyPersonalToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"AnalysisInfo\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"FastlyPersonalToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fastlypersonaltoken/fastlypersonaltoken_test.go",
    "content": "package fastlypersonaltoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\t// example picked from: https://github.com/ryan-miller/learn-go-with-tests/blob/181467c9e512f7e68d3f3cbcea89f0050982416c/fastly/users.go#L22\n\tvalidPattern = `\n\t// headers and header values\n\tconst fastlyKeyToken string = \"Fastly-Key\"\n\tconst fastlyKey string = \"TVAWji0p7uDI6OP9DyWvmV-vgoUoXIuf\"\n\tconst contentTypeToken string = \"Content-Type\"\n\tconst appJsonContentType = \"application/json\"`\n\n\tvalidPatternToken = \"TVAWji0p7uDI6OP9DyWvmV-vgoUoXIuf\"\n\n\tinvalidPattern = `\n\t// headers and header values\n\tconst fastlyKeyToken string = \"Fastly-Key\"\n\tconst fastlyKey string = \"$FASTLY_KEY\"\n\tconst contentTypeToken string = \"Content-Type\"\n\tconst appJsonContentType = \"application/json\"`\n)\n\nfunc TestFastlyPersonalToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{validPatternToken},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: invalidPattern,\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/feedier/feedier.go",
    "content": "package feedier\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"feedier\"}) + `\\b([a-z0-9A-Z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"feedier\"}\n}\n\n// FromData will find and optionally verify Feedier secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Feedier,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.feedier.com/v1/carriers\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Feedier\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/feedier/feedier_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage feedier\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFeedier_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FEEDIER_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"FEEDIER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a feedier secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Feedier,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Feedier,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Feedier.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Feedier.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/feedier/feedier_test.go",
    "content": "package feedier\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Feedier\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"feedier_secret\": \"kZ581ej1fDjtvE8iXNcgFJ8V2t0Lfv1d\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"kZ581ej1fDjtvE8iXNcgFJ8V2t0Lfv1d\"\n)\n\nfunc TestFeedier_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fetchrss/fetchrss.go",
    "content": "package fetchrss\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"fetchrss\"}) + `\\b([a-zA-Z0-9.]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"fetchrss\"}\n}\n\n// FromData will find and optionally verify Fetchrss secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor token := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Fetchrss,\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tverified, verificationErr := verifyToken(ctx, client, token)\n\t\t\ts1.Verified = verified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyToken(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://fetchrss.com/api/v1/feed/list?auth=\"+token, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\t// The API seems to always return a 200 status code.\n\t// See: https://fetchrss.com/developers\n\tif res.StatusCode != http.StatusOK {\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n\n\tvar apiRes response\n\tif err := json.NewDecoder(res.Body).Decode(&apiRes); err != nil {\n\t\treturn false, err\n\t}\n\n\tif apiRes.Success {\n\t\t// The key is valid.\n\t\treturn true, nil\n\t} else if apiRes.Error.Code == 401 {\n\t\t// The key is invalid.\n\t\treturn false, nil\n\t} else {\n\t\treturn false, fmt.Errorf(\"unexpected error: [code=%d, message=%s]\", apiRes.Error.Code, apiRes.Error.Message)\n\t}\n}\n\ntype response struct {\n\tSuccess bool `json:\"success\"`\n\tError   struct {\n\t\tMessage string `json:\"message\"`\n\t\tCode    int    `json:\"code\"`\n\t} `json:\"error\"`\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Fetchrss\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"FetchRSS is a service used to convert web content into RSS feeds. FetchRSS API keys can be used to manage and access these feeds.\"\n}\n"
  },
  {
    "path": "pkg/detectors/fetchrss/fetchrss_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage fetchrss\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFetchrss_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FETCHRSS_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"FETCHRSS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a fetchrss secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Fetchrss,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Fetchrss,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Fetchrss.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Fetchrss.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fetchrss/fetchrss_test.go",
    "content": "package fetchrss\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FetchRSS\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"fetchrss_secret\": \"x3lljmW2KHoljMrcFSTN5nWWAvDjwdQA0ed0QmHL\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"x3lljmW2KHoljMrcFSTN5nWWAvDjwdQA0ed0QmHL\"\n)\n\nfunc TestFetchRSS_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fibery/fibery.go",
    "content": "package fibery\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"fibery\"}) + `\\b([0-9a-f]{8}\\.[0-9a-f]{35})\\b`)\n\tdomainPat = regexp.MustCompile(`(?:https?:\\/\\/)?([a-zA-Z0-9-]{1,63})\\.fibery\\.io(?:\\/.*)?`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\".fibery.io\"}\n}\n\n// Description returns a description for the result being detected\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Fibery secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueSecrets := make(map[string]struct{})\n\tuniqueDomains := make(map[string]struct{})\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueSecrets[match[1]] = struct{}{}\n\t}\n\tfor _, match := range domainPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueDomains[match[1]] = struct{}{}\n\t}\n\n\tfor secret := range uniqueSecrets {\n\t\tfor domain := range uniqueDomains {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Fibery,\n\t\t\t\tRaw:          []byte(secret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, verificationErr := verifyMatch(ctx, s.getClient(), secret, domain)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, secret, domain)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, secret, domain string) (bool, error) {\n\ttimeout := 10 * time.Second\n\tclient.Timeout = timeout\n\turl := fmt.Sprintf(\"https://%s.fibery.io/api/commands\", domain)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Token %s\", secret))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Fibery\n}\n"
  },
  {
    "path": "pkg/detectors/fibery/fibery_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage fibery\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFibery_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FIBERY_SECRET\")\n\tdomain := testSecrets.MustGetField(\"FIBERY_DOMAIN\")\n\tinactiveSecret := testSecrets.MustGetField(\"FIBERY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a fibery secret %s within fibery domain %s \", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Fibery,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Fibery,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Fibery.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Fibery.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fibery/fibery_test.go",
    "content": "package fibery\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Fibery\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://detector.fibery.io/example\",\n\t\t\"domain\": \"nonprod\",\n\t\t\"test_secrets\": {\n\t\t\t\"fibery_secret\": \"42b2eda8.3fe6b086bb21be7e3548368626d01aaf2cd\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"42b2eda8.3fe6b086bb21be7e3548368626d01aaf2cd\"\n)\n\nfunc TestFibery_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/figmapersonalaccesstoken/v1/figmapersonalaccesstoken.go",
    "content": "package figmapersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nfunc (Scanner) Version() int { return 1 }\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"figma\"}\n}\n\n// FromData will find and optionally verify FigmaPersonalAccessToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,\n\t\t\tRaw:          []byte(resMatch),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"version\": fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.figma.com/v1/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-Figma-Token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode != 403 {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t\tif s1.Verified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\"token\": resMatch}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FigmaPersonalAccessToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Figma is a web-based design tool. Personal Access Tokens can be used to access and modify design files and other resources.\"\n}\n"
  },
  {
    "path": "pkg/detectors/figmapersonalaccesstoken/v1/figmapersonalaccesstoken_test.go",
    "content": "package figmapersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Figma\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"figma_secret\": \"647501-6p71dd66-3k6s-un9a-0ri0-0ypi87cz3rmx\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"647501-6p71dd66-3k6s-un9a-0ri0-0ypi87cz3rmx\"\n)\n\nfunc TestFigmaPersonalAccessToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/figmapersonalaccesstoken/v1/figmapersonalacesstoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage figmapersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFigmaPersonalAccessToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FIGMAPERSONALACCESSTOKEN_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"FIGMAPERSONALACCESSTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a figmapersonalaccesstoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a figmapersonalaccesstoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a figmapersonalaccesstoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"FigmaPersonalAccessToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"FigmaPersonalAccessToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/figmapersonalaccesstoken/v2/figmapersonalaccesstoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage figmapersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFigmaPersonalAccessToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FIGMAPERSONALACCESSTOKEN_V2_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"FIGMAPERSONALACCESSTOKEN_V2_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a figmapersonalaccesstoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a figmapersonalaccesstoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a figmapersonalaccesstoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"FigmaPersonalAccessToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"FigmaPersonalAccessToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/figmapersonalaccesstoken/v2/figmapersonalaccesstoken_v2.go",
    "content": "package figmapersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nfunc (Scanner) Version() int { return 2 }\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\tkeyPat        = regexp.MustCompile(detectors.PrefixRegex([]string{\"figma\"}) + `\\b(fig[d|((u|o)(r|h)?)]_[a-z0-9A-Z_-]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"figma\"}\n}\n\n// Description returns a description for the result being detected.\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// FromData will find and optionally verify FigmaPersonalAccessToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_FigmaPersonalAccessToken,\n\t\t\tRaw:          []byte(resMatch),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"version\": fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.figma.com/v1/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-Figma-Token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode != 403 {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t\tif s1.Verified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\"token\": resMatch}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FigmaPersonalAccessToken\n}\n"
  },
  {
    "path": "pkg/detectors/figmapersonalaccesstoken/v2/figmapersonalaccesstoken_v2_test.go",
    "content": "package figmapersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Figma\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"figma_secret\": \"figr_EZe7plhYvN92IyiDCjkvTcbNVZsuRVpDcHOwNNP1\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"figr_EZe7plhYvN92IyiDCjkvTcbNVZsuRVpDcHOwNNP1\"\n)\n\nfunc TestFigmaPersonalAccessToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fileio/fileio.go",
    "content": "package fileio\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"fileio\"}) + `\\b([A-Z0-9.-]{39})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"fileio\"}\n}\n\n// FromData will find and optionally verify FileIO secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_FileIO,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://file.io/\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tisJson := json.Valid(bodyBytes)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif isJson {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FileIO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"FileIO is a service for temporary file sharing. The detected key can be used to access and manage shared files.\"\n}\n"
  },
  {
    "path": "pkg/detectors/fileio/fileio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage fileio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFileIO_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FILEIO\")\n\tinactiveSecret := testSecrets.MustGetField(\"FILEIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a fileio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FileIO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FileIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"FileIO.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"FileIO.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fileio/fileio_test.go",
    "content": "package fileio\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Fileio\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"fileio_secret\": \"4N4VTAX5KCE0L6R56HS9778HVC2.KH83JBNN7F3\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"4N4VTAX5KCE0L6R56HS9778HVC2.KH83JBNN7F3\"\n)\n\nfunc TestFileIO_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/finage/finage.go",
    "content": "package finage\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(API_KEY[0-9A-Z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"finage\"}\n}\n\n// FromData will find and optionally verify Finage secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Finage,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.finage.co.uk/symbol-list/crypto?apikey=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Finage\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Finage provides financial data APIs for stocks, forex, and cryptocurrencies. Finage API keys can be used to access and retrieve financial data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/finage/finage_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage finage\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFinage_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FINAGE\")\n\tinactiveSecret := testSecrets.MustGetField(\"FINAGE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a finage secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Finage,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Finage,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Finage.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Finage.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/finage/finage_test.go",
    "content": "package finage\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Finage\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"secret\": \"API_KEYN2B1NFN5CP6CK5BJHY8B15YF535TP681\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"API_KEYN2B1NFN5CP6CK5BJHY8B15YF535TP681\"\n)\n\nfunc TestFinage_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/financialmodelingprep/financialmodelingprep.go",
    "content": "package financialmodelingprep\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"financialmodelingprep\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"financialmodelingprep\"}\n}\n\n// FromData will find and optionally verify FinancialModelingPrep secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_FinancialModelingPrep,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://financialmodelingprep.com/api/v3/financial-statement-symbol-lists?apikey=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\tif err == nil {\n\t\t\t\t\t// valid response should be an array of currencies\n\t\t\t\t\t// error response is in json\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `[ \"`)\n\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FinancialModelingPrep\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"FinancialModelingPrep provides financial data APIs. The API keys can be used to access financial data and related services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/financialmodelingprep/financialmodelingprep_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage financialmodelingprep\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFinancialModelingPrep_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FINANCIALMODELINGPREP\")\n\tinactiveSecret := testSecrets.MustGetField(\"FINANCIALMODELINGPREP_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a financialmodelingprep secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FinancialModelingPrep,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FinancialModelingPrep,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"FinancialModelingPrep.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"FinancialModelingPrep.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/financialmodelingprep/financialmodelingprep_test.go",
    "content": "package financialmodelingprep\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Financial Modeling Prep\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"financialmodelingprep_secret\": \"WXEUwkx44VjTRlunqyncJOCDeszMoC6p\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"WXEUwkx44VjTRlunqyncJOCDeszMoC6p\"\n)\n\nfunc TestFinancialModelingPrep_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/findl/findl.go",
    "content": "package findl\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"findl\"}\n}\n\n// FromData will find and optionally verify Findl secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Findl,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := 5 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.findl.com/v1.0/query?limit=6\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-API-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Findl\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Findl is a service used for searching and querying data. Findl API keys can be used to access and modify this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/findl/findl_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage findl\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFindl_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FINDL\")\n\tinactiveSecret := testSecrets.MustGetField(\"FINDL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a findl secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Findl,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Findl,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Findl.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Findl.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/findl/findl_test.go",
    "content": "package findl\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Findl\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"findl_secret\": \"l06ebuli-0k4m-b5yg-xieh-81s5b9s04ssu\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"l06ebuli-0k4m-b5yg-xieh-81s5b9s04ssu\"\n)\n\nfunc TestFindl_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/finnhub/finnhub.go",
    "content": "package finnhub\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"finnhub\"}) + `\\b([0-9a-z]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"finnhub\"}\n}\n\n// FromData will find and optionally verify Finnhub secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Finnhub,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://finnhub.io/api/v1/calendar/economic?token=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Finnhub\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/finnhub/finnhub_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage finnhub\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFinnhub_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FINNHUB\")\n\tinactiveSecret := testSecrets.MustGetField(\"FINNHUB_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a finnhub secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Finnhub,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Finnhub,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Finnhub.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Finnhub.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/finnhub/finnhub_test.go",
    "content": "package finnhub\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Finnhub\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"finnhub_secret\": \"5rjqnul3u250d36i73lc\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"5rjqnul3u250d36i73lc\"\n)\n\nfunc TestFinnHub_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fixerio/fixerio.go",
    "content": "package fixerio\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"fixer\"}) + `\\b([A-Za-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"fixer\"}\n}\n\n// FromData will find and optionally verify FixerIO secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_FixerIO,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://data.fixer.io/api/latest?access_key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\n\t\t\t\t// if client_id and client_secret is valid -> 403 {\"error\":\"invalid_grant\",\"error_description\":\"Invalid authorization code\"}\n\t\t\t\t// if invalid -> 401 {\"error\":\"access_denied\",\"error_description\":\"Unauthorized\"}\n\t\t\t\t// ingenious!\n\n\t\t\t\tvalidResponse := strings.Contains(body, `\"success\": true`) || strings.Contains(body, `\"info\":\"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption.\"`)\n\t\t\t\tdefer res.Body.Close()\n\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && validResponse {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FixerIO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/fixerio/fixerio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage fixerio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFixerIO_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FIXERIO_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"FIXERIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a fixerio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FixerIO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FixerIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"FixerIO.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"FixerIO.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fixerio/fixerio_test.go",
    "content": "package fixerio\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Fixerio\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"fixerio_secret\": \"adAM8pezol6tzRrFufnOmUSd4UUO2DoZ\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"adAM8pezol6tzRrFufnOmUSd4UUO2DoZ\"\n)\n\nfunc TestFixerio_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/flatio/flatio.go",
    "content": "package flatio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"flat\"}) + `\\b([0-9a-z]{128})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"flat\"}\n}\n\n// FromData will find and optionally verify FlatIO secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_FlatIO,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.flat.io/v2/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FlatIO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"FlatIO is a music notation software. FlatIO keys can be used to access and modify musical scores and related data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/flatio/flatio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage flatio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFlatIO_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FLATIO\")\n\tinactiveSecret := testSecrets.MustGetField(\"FLATIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a flatio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FlatIO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FlatIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"FlatIO.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"FlatIO.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/flatio/flatio_test.go",
    "content": "package flatio\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Flatio\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"flatio_secret\": \"n8dihgssrd0h0vv51l29da4wneg6ypo7qegcem2k3jcs9f6ywisvqu8vdimwp0m7pzo6ohnb01d13trnpun3couzbhvtlkbu2fsy8tliiww9ggis53s7xi9mvejj2idy\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"n8dihgssrd0h0vv51l29da4wneg6ypo7qegcem2k3jcs9f6ywisvqu8vdimwp0m7pzo6ohnb01d13trnpun3couzbhvtlkbu2fsy8tliiww9ggis53s7xi9mvejj2idy\"\n)\n\nfunc TestFlatIO_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fleetbase/fleetbase.go",
    "content": "package fleetbase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(flb_live_[0-9a-zA-Z]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"fleetbase\"}\n}\n\n// FromData will find and optionally verify Fleetbase secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Fleetbase,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.fleetbase.io/v1/contacts/\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Fleetbase\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/fleetbase/fleetbase_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage fleetbase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFleetbase_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FLEETBASE\")\n\tinactiveSecret := testSecrets.MustGetField(\"FLEETBASE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a fleetbase secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Fleetbase,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Fleetbase,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Fleetbase.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Fleetbase.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fleetbase/fleetbase_test.go",
    "content": "package fleetbase\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Fleetbase\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"secret\": \"flb_live_ZtWtb6hVkUMVdUDg2lgK\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"flb_live_ZtWtb6hVkUMVdUDg2lgK\"\n)\n\nfunc TestFleetBase_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/flexport/flexport.go",
    "content": "package flexport\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(shltm_[0-9a-zA-Z-_]{40})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"shltm_\"}\n}\n\n// FromData will find and optionally verify Flexport secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Flexport,\n\t\t\tRaw:          []byte(match),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/flexport/\",\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\t// docs: https://docs.logistics-api.flexport.com/2024-04/tag/Webhooks#operation/GetWebhook\n\turl := \"https://logistics-api.flexport.com/logistics/api/2024-04/webhooks\"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+token)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK, http.StatusForbidden:\n\t\t// If the endpoint returns useful information, we can return it as a map.\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Flexport\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Flexport is a global logistics company that provides shipping, freight forwarding, and supply chain management services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/flexport/flexport_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage flexport\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFlexport_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: \"flexport_token = 'shltm_ZnpDDh4AEj_n2WHHqjYErtv3ZGS0kH1bWVdl7V9D'\",\n\t\t\twant:  []string{\"shltm_ZnpDDh4AEj_n2WHHqjYErtv3ZGS0kH1bWVdl7V9D\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestFlexport_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FLEXPORT\")\n\tinactiveSecret := testSecrets.MustGetField(\"FLEXPORT_INACTIVE\")\n\tsecretNoPermissions := testSecrets.MustGetField(\"FLEXPORT_NO_PERMISSIONS\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified - with permissions\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a flexport secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Flexport,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified - without permissions\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a flexport secret %s within\", secretNoPermissions)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Flexport,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Flexport,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a flexport secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Flexport,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a flexport secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Flexport,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Flexport.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Flexport.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/flickr/flickr.go",
    "content": "package flickr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"flickr\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"flickr\"}\n}\n\n// FromData will find and optionally verify Flickr secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Flickr,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://www.flickr.com/services/rest/?method=flickr.tags.getHotList&api_key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\t\t\t\tif (res.StatusCode >= 200 && res.StatusCode < 300) && strings.Contains(body, \"owner=\") {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Flickr\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/flickr/flickr_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage flickr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFlickr_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FLICKR\")\n\tinactiveSecret := testSecrets.MustGetField(\"FLICKR_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a flickr secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Flickr,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Flickr,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Flickr.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Flickr.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/flickr/flickr_test.go",
    "content": "package flickr\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Flickr\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"flickr_secret\": \"x0b3lyve4dzszjak9afwb1bp3bz9z4z3\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"x0b3lyve4dzszjak9afwb1bp3bz9z4z3\"\n)\n\nfunc TestFlickr_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/flightapi/flightapi.go",
    "content": "package flightapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"flightapi\"}) + `\\b([a-z0-9]{24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"flightapi\"}\n}\n\n// FromData will find and optionally verify FlightApi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_FlightApi,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.flightapi.io/iata/%s/london/airport\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FlightApi\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"FlightApi is a service used for accessing flight-related data. FlightApi keys can be used to query flight information and other related services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/flightapi/flightapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage flightapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFlightApi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FLIGHTAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"FLIGHTAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a flightapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FlightApi,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FlightApi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"FlightApi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"FlightApi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/flightapi/flightapi_test.go",
    "content": "package flightapi\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FlightAPI\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"flightapi_secret\": \"024j4wjk6671d9kvm8a7iouu\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"024j4wjk6671d9kvm8a7iouu\"\n)\n\nfunc TestFlightAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/flightlabs/flightlabs.go",
    "content": "package flightlabs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(`\\b(eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9\\.ey[a-zA-Z0-9_-]+\\.[a-zA-Z0-9_-]{86})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tclient := s.client\n\tif client == nil {\n\t\tclient = defaultClient\n\t}\n\n\treturn client\n}\n\n// FromData will find and optionally verify FlightLabs secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueKeys := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[match[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_FlightLabs,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyMatch(ctx, s.getClient(), key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, key)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, secret string) (bool, error) {\n\t// API Reference: https://www.goflightlabs.com/airports-by-filters\n\n\turl := fmt.Sprintf(\"https://www.goflightlabs.com/airports-by-filter?access_key=%s&iata_code=JFK\", secret)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FlightLabs\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/flightlabs/flightlabs_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage flightlabs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFlightLabs_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FLIGHTLABS\")\n\tinactiveSecret := testSecrets.MustGetField(\"FLIGHTLABS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a flightlabs secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FlightLabs,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FlightLabs,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"FlightLabs.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"FlightLabs.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/flightlabs/flightlabs_test.go",
    "content": "package flightlabs\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FlightLabs\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"flightlabs_secret\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.ey3UspgSVM9j311NMff2N27tGEWSmr8sl1SguOxwzelJSYPOOVp-8BwHsdHqWKWpoVvZAc4kXKJ2kpROZ1RY_0xSj51iWOoi5UvvxOlaIHTzMEEiudOJRQuzYxwtqtl1rZyRlFuxTm0YR5wWPFM0GlWzmCf_yKz.atNcL556uLcZ9D6MTIlQoC9hD1u3EbBqL6nb32cgFowGosYnqkSgbCFPLg6LIhK_PADfDzUY2bTEsk7uEIbGxP\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.ey3UspgSVM9j311NMff2N27tGEWSmr8sl1SguOxwzelJSYPOOVp-8BwHsdHqWKWpoVvZAc4kXKJ2kpROZ1RY_0xSj51iWOoi5UvvxOlaIHTzMEEiudOJRQuzYxwtqtl1rZyRlFuxTm0YR5wWPFM0GlWzmCf_yKz.atNcL556uLcZ9D6MTIlQoC9hD1u3EbBqL6nb32cgFowGosYnqkSgbCFPLg6LIhK_PADfDzUY2bTEsk7uEIbGxP\"\n)\n\nfunc TestFlightLabs_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/flightstats/flightstats.go",
    "content": "package flightstats\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"flightstats\"}) + `\\b([0-9a-z]{8})\\b`)\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"flightstats\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"flightstats\"}\n}\n\n// FromData will find and optionally verify Flightstats secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\t\t\tresId := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Flightstats,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.flightstats.com/flex/aircraft/rest/v1/json/availableFields?appId=%s&appKey=%s\", resId, resMatch), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tbody := string(bodyBytes)\n\t\t\t\t\tvalidResponse := (res.StatusCode >= 200 && res.StatusCode < 300 && strings.Contains(body, \"id\")) || (res.StatusCode == 403 && strings.Contains(body, \"application is not active\"))\n\t\t\t\t\tif validResponse {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Flightstats\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Flightstats provides APIs for accessing flight data and statistics. Flightstats API keys can be used to retrieve and manipulate flight-related information.\"\n}\n"
  },
  {
    "path": "pkg/detectors/flightstats/flightstats_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage flightstats\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFlightstats_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tid := testSecrets.MustGetField(\"FLIGHTSTATS_ID\")\n\tsecret := testSecrets.MustGetField(\"FLIGHTSTATS_KEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"FLIGHTSTATS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a flightstats secret %s within flightstats id %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Flightstats,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Flightstats,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Flightstats.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Flightstats.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/flightstats/flightstats_test.go",
    "content": "package flightstats\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FlightStats\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"flightstats_id\":\"35o5omng\",\n\t\t\t\"flightstats_secret\": \"ksqxv0hkdkli9s71bd7ebfl5cijbab7f\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"ksqxv0hkdkli9s71bd7ebfl5cijbab7f\"\n)\n\nfunc TestFlightStats_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/float/float.go",
    "content": "package float\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"float\"}) + `\\b([a-f0-9]{16}[A-Za-z0-9+/]{42,43}=)`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"float\"}\n}\n\n// FromData will find and optionally verify Float secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Float,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.float.com/v3/people\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\treq.Header.Add(\"User-Agent\", \"TruffleHog3 (example@example.com)\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Float\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/float/float_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage float\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFloat_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FLOAT_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"FLOAT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a float secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Float,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Float,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Float.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Float.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/float/float_test.go",
    "content": "package float\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Float\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"float_secret\": \"50604f993cb9e4dfCsmIjdN5bCx5FnnfaukUdv7S9sm9L5wB2fZSUkZqHn=\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"50604f993cb9e4dfCsmIjdN5bCx5FnnfaukUdv7S9sm9L5wB2fZSUkZqHn=\"\n)\n\nfunc TestFloat_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/flowflu/flowflu.go",
    "content": "package flowflu\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"flowflu\"}) + `\\b([a-zA-Z0-9]{51})\\b`)\n\taccountPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"flowflu\", \"account\"}) + `\\b([a-zA-Z0-9]{4,30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"flowflu\"}\n}\n\n// FromData will find and optionally verify FlowFlu secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\taccountMatches := accountPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, accountMatch := range accountMatches {\n\n\t\t\tresAccount := strings.TrimSpace(accountMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_FlowFlu,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://%s.flowlu.com/api/v1/module/crm/lead/list?api_key=%s\", resAccount, resMatch), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `total_result`)\n\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FlowFlu\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"FlowFlu is a service used for managing customer relationships and projects. FlowFlu API keys can be used to access and manipulate CRM data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/flowflu/flowflu_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage flowflu\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFlowFlu_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\taccount := testSecrets.MustGetField(\"FLOWFLU_ACCOUNT\")\n\tsecret := testSecrets.MustGetField(\"FLOWFLU_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"FLOWFLU_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a flowflu secret %s within flowflu account %s\", secret, account)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FlowFlu,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FlowFlu,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"FlowFlu.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"FlowFlu.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/flowflu/flowflu_test.go",
    "content": "package flowflu\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FlowFlu\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"flowflu_account\": \"tsPX0KAuOZPMy9BMjTwmph\",\n\t\t\t\"flowflu_secret\": \"QdUZ0jRet5Z8nQjMgbLUGHZqShpFHCydCnL7hpTNXnwpUy75SJi\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecrets = []string{\n\t\t\"QdUZ0jRet5Z8nQjMgbLUGHZqShpFHCydCnL7hpTNXnwpUy75SJi\",\n\t\t\"QdUZ0jRet5Z8nQjMgbLUGHZqShpFHCydCnL7hpTNXnwpUy75SJi\",\n\t}\n)\n\nfunc TestFlowFlu_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  secrets,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/flutterwave/flutterwave.go",
    "content": "package flutterwave\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\n\tkeyPat = regexp.MustCompile(`\\b(FLWSECK-[0-9a-z]{32}-X)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"FLWSECK-\"}\n}\n\n// FromData will find and optionally verify Flutterwave secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Flutterwave,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.flutterwave.com/v3/subaccounts\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Flutterwave\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/flutterwave/flutterwave_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage flutterwave\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFlutterwave_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FLUTTERWAVE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"FLUTTERWAVE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a flutterwave secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Flutterwave,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Flutterwave,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Flutterwave.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Flutterwave.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/flutterwave/flutterwave_test.go",
    "content": "package flutterwave\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FlutterWave\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"flutterwave_secret\": \"FLWSECK-aylhdv2oo3wf5tylj8s4d9bqb8adoebx-X\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"FLWSECK-aylhdv2oo3wf5tylj8s4d9bqb8adoebx-X\"\n)\n\nfunc TestFlutterWave_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/flyio/flyio.go",
    "content": "package flyio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(FlyV1 fm\\d+_[A-Za-z0-9+\\/=,_-]{500,700})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"FlyV1\"}\n}\n\n// FromData will find and optionally verify Flyio secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_FlyIO,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, match)\n\n\t\t\ts1.Verified = isVerified\n\t\t\tif verificationErr != nil {\n\t\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\t// Not setting org_slug intentionally, as it's not required for the token to be valid.\n\t// 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.\n\t// 403 is returned if incorrect org_slug is sent.\n\t// 401 is returned if the token is invalid.\n\t// 400 is returned if the token is valid but no org_slug is sent.\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.machines.dev/v1/apps?org_slug=\", http.NoBody)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\treq.Header.Add(\"accept\", \"application/json\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusBadRequest:\n\t\t// Not setting org_slug returns a 400 error, which is expected.\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil\n\tdefault:\n\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\treturn false, err\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FlyIO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Fly.io is a platform for running applications globally. Fly.io tokens can be used to access the Fly.io API and manage applications.\"\n}\n\nfunc (s Scanner) IsFalsePositive(result detectors.Result) (bool, string) {\n\t// ignore AAAAAA for Flyio detector\n\tif strings.Contains(string(result.Raw), \"AAAAAA\") {\n\t\treturn false, \"\"\n\t}\n\n\t// For non-matching patterns, fall back to default false positive logic\n\treturn detectors.IsKnownFalsePositive(string(result.Raw), detectors.DefaultFalsePositives, true)\n}\n"
  },
  {
    "path": "pkg/detectors/flyio/flyio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage flyio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFlyio_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FLYIO\")\n\tinactiveSecret := testSecrets.MustGetField(\"FLYIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a flyio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FlyIO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FlyIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a flyio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FlyIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a flyio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FlyIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"unexpected HTTP response status 404\"))\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Flyio.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"AnalysisInfo\")\n\t\t\tignoreUnexported := cmpopts.IgnoreUnexported(detectors.Result{})\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts, ignoreUnexported); diff != \"\" {\n\t\t\t\tt.Errorf(\"Flyio.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/flyio/flyio_test.go",
    "content": "package flyio\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFlyio_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: \"flyio_token = 'FlyV1 fm2_AD1shwGbLSpZSPEXM1vhcbPZowurCDkXySOOJj0w4G2abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'\",\n\t\t\twant:  []string{\"FlyV1 fm2_AD1shwGbLSpZSPEXM1vhcbPZowurCDkXySOOJj0w4G2abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern - too short\",\n\t\t\tinput: \"flyio_token = 'FlyV1 fm2_short'\",\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern - wrong prefix\",\n\t\t\tinput: \"flyio_token = 'FlyV2 fm2_AD1shwGbLSpZSPEXM1vhcbPZowurCDkXySOOJj0w4G2abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'\",\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(test.want) > 0 && len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 && len(test.want) > 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else if len(results) > 0 && len(test.want) == 0 {\n\t\t\t\t\tt.Errorf(\"expected no results, but received %d\", len(results))\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestFlyio_IsFalsePositive(t *testing.T) {\n\ts := Scanner{}\n\n\ttests := []struct {\n\t\tname     string\n\t\ttoken    string\n\t\texpected bool\n\t\treason   string\n\t}{\n\t\t{\n\t\t\tname:     \"token with AAAAAA - should not be flagged as false positive\",\n\t\t\ttoken:    \"FlyV1 fm2_abcdAAAAAA1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\",\n\t\t\texpected: false,\n\t\t\treason:   \"\",\n\t\t},\n\t\t{\n\t\t\tname:     \"token with example pattern - should be false positive\",\n\t\t\ttoken:    \"FlyV1 fm2_1234example567890zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321\",\n\t\t\texpected: true,\n\t\t\treason:   \"contains term: example\",\n\t\t},\n\t\t{\n\t\t\tname:     \"token with sample pattern - should be false positive\",\n\t\t\ttoken:    \"FlyV1 fm2_1234sample567890zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321\",\n\t\t\texpected: true,\n\t\t\treason:   \"contains term: sample\",\n\t\t},\n\t\t{\n\t\t\tname:     \"token with xxxxxx pattern - should be false positive\",\n\t\t\ttoken:    \"FlyV1 fm2_1234xxxxxx567890zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA0987654321\",\n\t\t\texpected: true,\n\t\t\treason:   \"contains term: xxxxxx\",\n\t\t},\n\t\t{\n\t\t\tname:     \"valid token without AAAAAA - should not be false positive\",\n\t\t\ttoken:    \"FlyV1 fm2_1234567890zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321zyxwvutsrqponmlkjihgfZYXWVUTSRQPONMLKJIHGF0987654321\",\n\t\t\texpected: false,\n\t\t\treason:   \"\",\n\t\t},\n\t\t{\n\t\t\tname:     \"regular string without pattern - should not be false positive\",\n\t\t\ttoken:    \"XYZABC123789def456\",\n\t\t\texpected: false,\n\t\t\treason:   \"\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_FlyIO,\n\t\t\t\tRaw:          []byte(tt.token),\n\t\t\t}\n\n\t\t\tisFP, reason := s.IsFalsePositive(result)\n\n\t\t\tif isFP != tt.expected {\n\t\t\t\tt.Errorf(\"IsFalsePositive() got = %v, want %v (reason: %s)\", isFP, tt.expected, reason)\n\t\t\t}\n\n\t\t\tif tt.expected && reason != tt.reason {\n\t\t\t\tt.Errorf(\"IsFalsePositive() reason got = %v, want %v\", reason, tt.reason)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fmfw/fmfw.go",
    "content": "package fmfw\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"fmfw\"}) + `\\b([a-zA-Z0-9_-]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"fmfw\"}) + `\\b([a-zA-Z0-9-]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"fmfw\"}\n}\n\n// FromData will find and optionally verify Fmfw secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\ttokenPatMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tuserPatMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Fmfw,\n\t\t\t\tRaw:          []byte(tokenPatMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\ttimeout := 10 * time.Second\n\t\t\t\tclient.Timeout = timeout\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.fmfw.io/api/3/spot/balance\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.SetBasicAuth(userPatMatch, tokenPatMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Fmfw\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"FMFW is a cryptocurrency exchange platform. FMFW API keys can be used to access and manage account data and perform trading operations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/fmfw/fmfw_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage fmfw\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFmfw_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FMFW\")\n\tuser := testSecrets.MustGetField(\"FMFW_USER\")\n\tinactiveSecret := testSecrets.MustGetField(\"FMFW_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a fmfw secret %s within fmfw %s\", secret, user)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Fmfw,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Fmfw,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Fmfw.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Fmfw.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fmfw/fmfw_test.go",
    "content": "package fmfw\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FMFW\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"fmfw_key\": \"cno3jaTTtgBeo3b_y82FPdrw4Yxfspvd\",\n\t\t\t\"fmfw_id\": \"nsrD8XVjeXc4Z-uGw6CgTBRXHmTjbizL\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecrets = []string{\n\t\t\"cno3jaTTtgBeo3b_y82FPdrw4Yxfspvd\",\n\t\t\"nsrD8XVjeXc4Z-uGw6CgTBRXHmTjbizL\",\n\t}\n)\n\nfunc TestFmFw_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  secrets,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/formbucket/formbucket.go",
    "content": "package formbucket\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"formbucket\"}) + `\\b([0-9A-Za-z]{1,}.[0-9A-Za-z]{1,}\\.[0-9A-Z-a-z\\-_]{1,})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"formbucket\"}\n}\n\n// FromData will find and optionally verify FormBucket secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_FormBucket,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.formbucket.com/v1/profile\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbody, errBody := io.ReadAll(res.Body)\n\t\t\t\tif errBody != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbodyString := string(body)\n\t\t\t\tvalidResponse := strings.Contains(bodyString, `created_on`)\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif errBody == nil {\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && validResponse {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"FormBucket is a service used to collect and manage form submissions. The detected credential can be used to access and modify form data.\"\n}\n\ntype Response struct {\n\tAnonymous bool `json:\"anonymous\"`\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FormBucket\n}\n"
  },
  {
    "path": "pkg/detectors/formbucket/formbucket_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage formbucket\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFormBucket_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FORMBUCKET\")\n\tinactiveSecret := testSecrets.MustGetField(\"FORMBUCKET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a formbucket secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FormBucket,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FormBucket,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"FormBucket.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"FormBucket.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/formbucket/formbucket_test.go",
    "content": "package formbucket\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FormBucket\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"formbucket_secret\": \"qE4P6YytmrnbI4o7xmr3Ct9umMOgM7CKqlUTvdMsXICpUEEow2ZDQi0CyZ7AYir4BkqsxvKdV33095olnQO6gkHgoZsSHPG41oqLrrM3g.l1Vt_Jv9iuT7w4si\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"qE4P6YytmrnbI4o7xmr3Ct9umMOgM7CKqlUTvdMsXICpUEEow2ZDQi0CyZ7AYir4BkqsxvKdV33095olnQO6gkHgoZsSHPG41oqLrrM3g.l1Vt_Jv9iuT7w4si\"\n)\n\nfunc TestFormBucket_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/formcraft/formcraft.go",
    "content": "package formcraft\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"formcraft\"}) + `\\b([0-9a-z]{16})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"formcraft\"}\n}\n\n// FromData will find and optionally verify Formcraft secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Formcraft,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tdata := fmt.Sprintf(\"%s:\", resMatch)\n\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://formcrafts.com/api/v1/\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Formcraft\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Formcraft is a form building and data collection service. Formcraft keys can be used to access and manage forms and collected data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/formcraft/formcraft_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage formcraft\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFormcraft_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FORMCRAFT\")\n\tinactiveSecret := testSecrets.MustGetField(\"FORMCRAFT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a formcraft secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Formcraft,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Formcraft,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Formcraft.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Formcraft.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/formcraft/formcraft_test.go",
    "content": "package formcraft\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FormCraft\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"formcraft_secret\": \"zgej8qae3ehc0mjo\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"zgej8qae3ehc0mjo\"\n)\n\nfunc TestFormCraft_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/formio/formio.go",
    "content": "package formio\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"formio\"}) + `\\b(eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\\.[0-9A-Za-z]{220,310}\\.[0-9A-Z-a-z\\-_]{43}[ \\r\\n]{1})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"formio\"}\n}\n\n// FromData will find and optionally verify FormIO secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_FormIO,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://formio.form.io/current\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"x-jwt-token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FormIO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"FormIO is a platform for building form-based applications. FormIO JWT tokens can be used to authenticate and interact with FormIO services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/formio/formio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage formio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFormIO_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FORMIO\")\n\tinactiveSecret := testSecrets.MustGetField(\"FORMIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a formio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FormIO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FormIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"FormIO.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"FormIO.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/formio/formio_test.go",
    "content": "package formio\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FormIO\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"formio_secret\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.3IJk8Ys67c6tWlZi346ptymjjgkzwSyE5G2RbPS3kNyxuD4DFUj1vJFqlzZUTwUTHzhTEiUCPG3xtBFPfEBCGBtKDdh4SB3QhWHZAvEx3v61Mv1bsg3dhiKeGEJBluxNr8FRWHNmCaWq7KQpqK6YDX7ItacPKYKzOWXw16Swwj8lnKORhut3TjIsNa0dSoTCGeVZQey0RD0GuWuuXIz5Bu6xQoVnexXGKmbm3wu4VMxsXaquKvW6xXo.lQWeje6Ck-SNJR1LEwHqOFjVfad7-SXyV2nivyHnpxt \"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.3IJk8Ys67c6tWlZi346ptymjjgkzwSyE5G2RbPS3kNyxuD4DFUj1vJFqlzZUTwUTHzhTEiUCPG3xtBFPfEBCGBtKDdh4SB3QhWHZAvEx3v61Mv1bsg3dhiKeGEJBluxNr8FRWHNmCaWq7KQpqK6YDX7ItacPKYKzOWXw16Swwj8lnKORhut3TjIsNa0dSoTCGeVZQey0RD0GuWuuXIz5Bu6xQoVnexXGKmbm3wu4VMxsXaquKvW6xXo.lQWeje6Ck-SNJR1LEwHqOFjVfad7-SXyV2nivyHnpxt\"\n)\n\nfunc TestFormIO_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/formsite/formsite.go",
    "content": "package formsite\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"formsite\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n\tserverPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"formsite\"}) + `\\b(fs[0-9]{1,4})\\b`)\n\tuserPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"formsite\"}) + `\\b([a-zA-Z0-9]{6})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"formsite\"}\n}\n\n// FromData will find and optionally verify Formsite secrets in a given set of bytes..\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tserverMatches := serverPat.FindAllStringSubmatch(dataStr, -1)\n\tuserMatches := userPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, serverMatch := range serverMatches {\n\t\t\tresServerMatch := strings.TrimSpace(serverMatch[1])\n\t\t\tfor _, userMatch := range userMatches {\n\t\t\t\tresUserMatch := strings.TrimSpace(userMatch[1])\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Formsite,\n\t\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://%s.formsite.com/api/v2/%s/forms\", resServerMatch, resUserMatch), nil)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\t\t\tres, err := client.Do(req)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Formsite\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Formsite is an online form builder service. Formsite API keys can be used to access and manage forms and data submissions.\"\n}\n"
  },
  {
    "path": "pkg/detectors/formsite/formsite_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage formsite\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFormsite_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FORMSITE\")\n\tinactiveSecret := testSecrets.MustGetField(\"FORMSITE_INACTIVE\")\n\tserver := testSecrets.MustGetField(\"FORMSITE_SERVER\")\n\tuser := testSecrets.MustGetField(\"FORMSITE_USER\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a formsite secret %s within formsite server %s formsite user %s\", secret, server, user)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Formsite,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Formsite,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Formsite,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Formsite,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Formsite.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Formsite.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/formsite/formsite_test.go",
    "content": "package formsite\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Formsite\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"formsite_server\": \"fs02\",\n\t\t\t\"formsite_user\": \"ITest2\",\n\t\t\t\"formsite_secret\": \"8PKXsB1ohFUGnw0j8y3g9pRUvDj0I1Ha\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"8PKXsB1ohFUGnw0j8y3g9pRUvDj0I1Ha\"\n)\n\nfunc TestFormsite_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/foursquare/foursquare.go",
    "content": "package foursquare\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat      = regexp.MustCompile(detectors.PrefixRegex([]string{\"foursquare\"}) + `\\b([0-9A-Z]{48})\\b`)\n\tsecretMatch = regexp.MustCompile(detectors.PrefixRegex([]string{\"foursquare\"}) + `\\b([0-9A-Z]{48})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"foursquare\"}\n}\n\n// FromData will find and optionally verify Foursquare secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretMatch.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, secretMatch := range secretMatches {\n\t\t\tresSecret := strings.TrimSpace(secretMatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_FourSquare,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resSecret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, 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)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FourSquare\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/foursquare/foursquare_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage foursquare\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFoursquare_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tid := testSecrets.MustGetField(\"FOURSQUARE\")\n\tsecret := testSecrets.MustGetField(\"FOURSQUARE_SECRET\")\n\tinactiveId := testSecrets.MustGetField(\"FOURSQUARE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a foursquare secret %s within foursquare id %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FourSquare,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FourSquare,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Foursquare.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Foursquare.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/foursquare/foursquare_test.go",
    "content": "package foursquare\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FormSquare\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"foursquare_key\": \"NUAL8SFC7DGMAA0V79J57TSMOVZTH5HI5B7IM1BCF4L3IQXG\",\n\t\t\t\"foursquare_secret\": \"CII2183RFI60TZGXHK25A06VWZYE19IBMGJQZAIG3PPJFZ6M\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecrets = []string{\n\t\t\"CII2183RFI60TZGXHK25A06VWZYE19IBMGJQZAIG3PPJFZ6MCII2183RFI60TZGXHK25A06VWZYE19IBMGJQZAIG3PPJFZ6M\",\n\t\t\"NUAL8SFC7DGMAA0V79J57TSMOVZTH5HI5B7IM1BCF4L3IQXGCII2183RFI60TZGXHK25A06VWZYE19IBMGJQZAIG3PPJFZ6M\",\n\t\t\"CII2183RFI60TZGXHK25A06VWZYE19IBMGJQZAIG3PPJFZ6MNUAL8SFC7DGMAA0V79J57TSMOVZTH5HI5B7IM1BCF4L3IQXG\",\n\t\t\"NUAL8SFC7DGMAA0V79J57TSMOVZTH5HI5B7IM1BCF4L3IQXGNUAL8SFC7DGMAA0V79J57TSMOVZTH5HI5B7IM1BCF4L3IQXG\",\n\t}\n)\n\nfunc TestFourSquare_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  secrets,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fp_badlist.txt",
    "content": "value\nfrom\narray\nuint\nboolean\nconfig\nparse\nfunc\nparam\ncancel\nexport\nsubstr\nname\nutils\ntoken\ndata\nencode\nelse\nauth\ndefine\nspace\nident\nblock\ntype\nindex\ncase\nsafe\ndecrypt\nevent\nmessage\nargs\nhead\ncookie\nbuffer\nreturn\nthrow\nderive\nbits\nbytes\nnode\ncode\nconst\nlogger\nhash\nsource\ntuple\nthis\nstyle\nversion\nbatch\nenable\ndisable\nremove\nport\nmodifi\nkind\nsort\nformat\ntext\nnumer\npunct\nlitera\ncontext\nlayer\ndelta\ninit\nfinal\ncolor\ncancel\ncalc\nappend\nslice\nforce\nescape\nexit\nframe\nchar\nalign\nimplem\nkeyword\ntrace\ntruncate\ngroup\nhref\nscale\nmodel\nvisual\nmodel\nnever\nwin32\ngoto\nsmall\nlarge\nlexer\nreplace\nvariab\nclose\ndefer\nstart\n;var\nstorage\nblob\ncred\nmath\n.xml\nconflict\nhack\npackage\ncontract\nschema\nvec<\ned25519\nprefix\nsuffix\ncompress\nhmac\nsha256\nrequest\nbase\nrest\nsession\nspec\ndate\ntime\ncache\nbuild\ncheck\ninstall\nasset\nvendor\nx509\nusage\nerrno\ncrypto\nsha384\nsha512\nsha1\necdsa\nalgo\ncert\nprogress\nmarshal\nstrconv\nprimary\nunseal\npkcs\nrecurs\nstruct\nentry\nvault:v\nlease\nshare\nmouse\npress\npublic\ncloud\namq.gem\nresp\nring\nerror\nrevoke\nencrypt\nbinary\n2018-\n2019-\n2020-\n2021-\n2022-\nbyte\nroot\nreadon\ntest\n2048\nmatch\nprivate\nkey_\naes256\naes128\nstate\nalloc\nproto\nterm\nserver\nstep\nlimit\nbackend\nlen(\nincrem\nbucket\nobject\nlast\nfirst\nstart\nstop\nseal\ntransit\noffset\npointer\narr[\ncluster\nvalue\nread\nsign\nleave\nlock\npart\ncentral\nlocal\nhttp:\nhttps:\ndelete\ninsert\nappend\ntable\nmutat\ncolon\nbound\n{key\nvalid\nproc\nenum\nquery\nopen\nmodule\nprogram\nimport\npinned\npubexp\nkeygen\nshim\nexpr\nbuf[\nkeyid\n.key\nkeys\nqueue\nsha-1\nsha-256\nsha-512\nsha-384\nuser\ninfo\n[idx\ngray\nblack\nwhite\nyellow\norange\npurple\n=val\nkey=\npolicy\nfield\njson\npiece\ndepth\nlabel\ndaemon\ncron\nuuid\nk8s.\nrole\napplication\nexplic\nrandom\nDES3\n3DES\namq.gen\nxml:\"\ntag:\n.Get\n.Put\n.Delete\nextend\nsplit\noption\nfontsize\n&quot;\nkeyboard\ncustom\nitem\nemulate\niphone\ndevelop\nmaster\nslave\nsecondary\nexample\n"
  },
  {
    "path": "pkg/detectors/fp_programmingbooks.txt",
    "content": "--+--+--\n--><!]]>\n$${balance\n$curry(...args\n${email\n$.getjson(url\n$ident\n$('<img\n$('#myform\n${name\n${p1.name\n${p2.name\n$('#submit\n${ts.map(kv\n${uname\n$value\n$x:expr\n+3=err\t\t\t\t\t\t\na][appendix_a\nabbreviated\nabcabcabc\nabiding\nabilities\nableton\nabnormally\nabomination?\n></a><br\nabsence\nabs(input\nabsolute\nabsorb\nabstract\nabsurd\nabundance\nacademic\naccented\naccept\naccess\naccident\nacclaimed\naccompanies\naccording\naccount\naccumulate\naccurate\nachieve\nacknowledge\na.clone\na.concat(b\nacquaintance\nacquiesce\nacrimony\nacronym\nacross\nacting\naction\nactivated\nactors\nactual\nadd10to\nadd42to\nadd(add(x\naddall\naddassign\nadd(flocka\naddgrade\nadding\naddison-wesley\naddition\nadd<meters>\nadd(multiply(x\naddnextgrade\nadd-one\nadd_one\naddpage\naddress\nadd<rhs=self>\nadd(self\nadd</span>\naddstudents\nadd/target\naddten\nadd_text\nadd-two\nadequate\nadhere\nadjust\nadministrative\nadmirable\nadmonition\nadvanced\nadvent\nadvertising\nadvice\nadvisable\naesthetic\naffect\naforementioned\nafraid\na.get('team\naggregates\naimlessly\na.iter\najaxcall\najax(url,cb\nalarms!\nalbeit\nalbert\nalgebra\nalgorithm\nall.empty\nall(false\nallocate\nall-or-nothing\nallthechildren\nall(true\nallupper\nalmost\nalphabetic\nalready\nalt=\"a\nalthough\naltogether\n_always_\nalways\namalgamations\na'].map($\namateur\namazement\namazing\nambiguity\nambition\namd-style\namenable\namending\namidst\namoduleineed\namount\nampersand\nanalog\nanalysis\nanarchy\nanatomy\n@anaufalm\nancestor\nanchor\nancients\nand/or\nandrew\nandthisonetoo\nangels\nanimal\naniston\nannihilate\nannotate\nannouncement\nannoyed\n<anonymous>\nanonymous\nanother\nanswer\nanti-class\nanxious\nany.empty\nany(false\nanyfunctor\nanyhow\nanymore\nanyone\nanything\nany(true\nanyway\nanywhere\na.of(f\napiendpoints\napi.flickr.com\nap`ing\napostrophe\nap(other\napparent\napp('cats\nappeal\nappend\nappetizer\napple”\napplicability\nappreciate\napproach\napp</title>\narabic\narbitrarily\narcane\narchitectural\narc<mutex<t>>\narc<t>\narea(&self\naren't\naren’t\nargs.length\nargstr\narguably\narguing\nargument\narithmetic\narmstrong\naround\narrangement\narrcopy\narr.entries\narrived\narr.length\nart4thesould\narthur\narticle\nartifact\nartist\nas_bytes\nashamed\nasking\naskquestion\nasm.js\nas_mut_ptr\na`</span>\naspect\nas_ref\nassemble\nassert!\nassign\nassist\nassociate\n‘associated\nassortment\nassume\nassure\nasterisk\nast.ident\nastound\nast)—to\natomic\nattach\nattempt\nattend\nattitude\nattractions\nattribute\naudience\naudited\naugments\naugust\naustin\nauthenticate\nauthor\nauto-complete\nautocompletion\nautomate\nautosave\navailable\na_value\naverage\nawesome!\nawhile\nawkward\nazure<br>\nb${str\nbabylonians\nbaby_name\nbacked\nbackground\nbacking\nback_of_house\nbackported\nbackspace\nback-tick\nbacktrace\nbackward\nbadidea\nbahasa\nbalance\nballoons!\nbanana\nbandwagoning\nb)).ap(f\nbar...it\nbarren\nbarrier\nbartenders\nbaseless\nbasename\nbathwater\nbattle\nbearing\nbeatnik\nbeautiful\nbecame\nbecause\nbecome\nbeen!—should\nbefore\n#beginners\nbehalf\nbehave\nbehind\nbehold\nbelabor\nbelieve\nbelong\nbending\nbeneath\nbeneficial\nbenkort\nbesides\nbetrays\nbetter\nbetween\nbeware\nbeyond\nbibliography\nbigger\nbigint\nbig-integer\nbikeshedding\nbillion\nbinaries\nbinding\nbind(this\nbioinformatics\nbitand\nbitten\nbitwise\nbitxor\nbizarrely\nbjarne\nblaring\nbleeding\nblogcontroller\nblog({}).fork\nblogpage\nblog’s\nbloody\nbludgeon\nblurp_blurp\nboasting\nbodies\n</body>\n<body>\nbody)?\nboiler\nboldly\nbolster\nbonafide\n--book\nbookkeeping\nbook</li>\nbook's\nboolean\nboring\nborrow\nbothering\nboth_float\nbottle\nbottom\nbounce\nbox<button>\n&box<dyn\nbox<dyn\nbox<list>\nbox<self>\nbox</span>\nbox`</span>\nbox<t>\nbr#\"...\"#\nbr##\"...\"##\nbracket\nbranch\n<br><br>\n<!--break-->\nbreath\nbrendan\nbrevity\nbridge\nbrilliant\nbrittle\nbroke!\nbrought\nbrowser\nbs.concat(b\nbtn,btnname\nbtnname\nbtreemap\nbubble\nbucket\nbuckle\nbuddhist\nbuffer\nbugger\n![build\nbullseye\nbundle\nburden\nburied\nburritos\nbusiness\nbusted\nbut...!?\nbutterfingered\nbutthisis\n</button>\n<button\nbutton\nbyte-code\ncaboodle\ncaching\n^cagain!\ncalc(\"+\ncalc.eq\ncalc[fn](key\ncalculate\ncallable\ncallback\ncalled\ncall_from_c\ncalling\ncallmoduleone\ncall—nothing\ncall-site\ncall</span>\ncamelcase\ncampaign\ncamping\ncancelevt(evt\ncandidate\ncandle\ncan_hold\ncannot\ncanonical\ncapabilities\ncapacity\ncapital\nc][app-c]<!--\ncapsule\ncaptain\ncapture\ncareer\ncareful\ncar.horsepower\ncaring\ncar.make\ncarriage\ncars.length\ncascading\ncase-by-case\ncase_sensitive\ncase</span>\ncasing\ncasting\ncasual\ncatalog\ncatastrophic\ncategorical\ncatfirstchar\ncaught\ncausing\ncaution\ncb(evt\ncb.name\nc.dollar_value\nceases\ncelestial\ncell<t>\ncelsius\ncensored\ncenter\ncentral\ncenturies\nceremony\ncertain\ncfg-if\ncfg(test\nch01-es.md\nch01.md\nch10-lifetimes\nch11-anatomy\nch13-iterators\nch3-iter\nch9-result\nchallenge\nchampion\nchance\nchange\nchannel\nchaperone\nchapter\ncharacter”\ncharge\ncharms\nchasing\nch*c*l*t\nchewing\nchicago\nchills\nchinese\nchoice\nchokes\nchoose\nchristmas\n_chrome:_\nchrome\nchuckle\ncircle\ncircuiting\ncirque\ncitizen\nclarifications\nclause\nclient\nclimbing\nclippy\nclockwork\ncloning\nclosing\nclosure\nclowns\nclumsy\ncluster\nclutter\ncmd.exe\ncmdlet\ncmp_display\ncode.”\ncodebase\n<code>expr\ncode—plus\ncode's\ncode</span>\ncode][vscode\ncodewise\ncoding\ncodomain\ncoefficient\ncoerce\nco-exist\ncognitive\ncoherence\ncohesion\ncoincidence\ncoin::dime\ncoined\ncoin-sorting\ncollaborating\ncollect\ncollide\ncolloquially\nco-located\ncolumns\ncombination\ncomedic\ncomfortable\ncoming\ncommences\ncommit\ncommon\ncommunicate\ncomonads\ncompact\ncompel\ncompilation\ncomplain\ncomponent\n_compose_\ncomprehensive\ncompulsion\nconcat\nconceivable\nconcise\nconclude\nconcrete\nconcurrency\ncondemns\ncondition\nconduct\nconference\nconfidence\nconflict\nconform\nconfuse\ncongrats!\nconjecture\nconjoin\nconjunction\nconnect\nconnotation\nconscience\nconsecutively\nconsider\nconsole\nconsult\ncontact\ncontemplate\ncontiguous\ncontortionist\ncontract\nconvenience\nconvince\nconvoluted\ncoolmethod\ncooperate\ncoordinate\ncopied\ncoproduct\ncopying\ncopyright\ncorner\ncorrect\ncorruption\ncostello\ncostume\ncouple\ncourse\ncourtesy\ncrackers\ncradles\ncrazier\ncreate\ncredit\ncredos\ncriminal\ncrisis\ncriteria\ncritical\ncropping\ncrucial\ncrudely\ncruise\ncrutch\ncryptic\ncss/html\ncssprops\ncstring\nctrl-c\nculture\ncuriosity\ncurmonth\ncurrent\ncurried\ncustom\ncutting\nc.value\ncyrillic\ndamage\ndancing\ndangerous\ndangle\ndasherize\ndata`!\ndatabase\ndata/behavior\ndata.future\ndata-kind\ndata’s\ndata</span>\ndatastruct\ndatestr\ndaunting\ndayend\ndaystart\nday-to-day\ndazzled\ndbconnection\ndb.create\ndb.destroy\ndb.save\ndb.update\ndeadline\ndead-tree\ndeal!?\ndealing\ndeallocate\ndebatable\ndecades\ndecapitated\ndecember\ndecent\ndeceptively\ndecide\ndecimal\ndecipher\ndecision\ndeclaration\ndecoder\ndeconstruct\ndecorator\ndecouples\ndecrease\ndedicated\ndeducing\ndeemed\nde-emphasizes\ndeep-copy\ndeepen\ndeeply\ndefault\ndefeat\ndefend\ndefine\ndeflate\ndegree\ndelegate\ndelete\ndeliberate\ndelightful\ndelimit\n%`-delimited\n-delimited\ndeliver\ndemand\ndemonstrate\ndenial\ndenominator\ndenote\ndepartment\ndepend\ndepicts\ndeploy\ndeprecated\nderailed\n_dereference\nderivable\n#[derive\ndescendant\ndescribe\ndeserving\ndesign\ndesirable\ndespair\ndespite\ndestination\ndestroy\ndetail\ndetect\ndetermination\ndetour\ndetriment\ndevanagari\ndevelop\ndeviate\ndevice\ndevil's\ndevops\ndevote\ndiacritics\ndiagnose\ndiagram\ndialect\ndiamond\ndickinson\ndictate\ndictionary\ndidn't\ndidn’t\ndiffer\ndifficult\ndiff(x,y\ndigest\ndigging\ndigits\ndijkstra\ndimension\ndiminish\ndining\ndirect\n__dirname\ndisable\ndisagree\ndisallow\ndisambiguate\ndisappointing\ndisassemble\ndiscard\ndiscipline\ndiscomfort\ndiscuss\ndishwashers\ndisjoint\ndisjunction\ndislike\ndismay\ndismiss\ndispatch\ndisplay\ndisposal\ndisprove\ndisqualifies\ndisregard\ndisservice\ndistance\ndistinct\ndistraction\ndisturb\ndivassign\ndivergence\ndivide\ndiving\ndiv.innerhtml\ndivisible\ndoccargo\ndoc-comments\ndoccratesio\ndoc-tests\n<!doctype\ndocument\ndoesn’t\ndo!—even\ndog::baby_name\ndog-eared\ndogmatic\ndog`</span>\ndolistythings_\ndollar\ndomain\ndominates\ndone—all\ndonning\ndo-nothing\ndoomed\ndooptionone\ndo-si-do\ndo_something\ndosomething\ndothisinstead\ndot-notation\ndo_twice\ndouble\ndownload\ndownside\ndownward\ndramatic\ndrasner<br>\ndrastically\ndrawing\ndraw(&self\n@drboolean\ndreams\ndreary\ndrop](drop.md\ndrop(&mut\ndropped\n\\d/.test(key\ndubbed\ndubious\ndumping\nduplicate\nduration\nduring\nduties\ndynamic\neach</span>\nearbuds\nearlier\nearnest\nearning\neasier\neasily\necmascript\ne.constructor\necosystem\nedited\nediting\neditor\nedsger\neducate\neffect\nefficiency\neffort\ne-help-wanted\ne.isleft\neither\nelaborate\nelapsed\nelectric\nelegance\nelement\nelevated\n@eliagentili\nelided\neligible\neliminate\nelision\nelse's\nelse’s\nelse</span>\nelse`</span>\nelsewhere\nel.src\nel.text\nelusive\nembarking\nembellish\nembody\nembrace\nemerge\nemitted\nemphasis\nemploy\nempower\nemptied\nemulate\nenable\nenamoured\nencapsulate\nenclose\nencode\nencompasses\nencounter\nendeavor\nendexercise\nending\nendless\nendo.empty\nendofunctors\nendo(identity\nendomorphisms\nendpoint\nenduring\nenforce\nengine\nenglish\nenhance\nenlightened\nenormous\nenough\n@enshahar\nensure\nentanglement\n<enter>\nenthusiastic\nentire\nentity\nentomology\nentries\nenumerate\nenum’s\nenum</span>\nenv::args\nenvelope\nenvironment\nenvision\neprintln!\nequate\nequivalence\neratosthenes\nergonomic\nerlang\nerrata\nerroneous\nes20xx\nes5-shim\nes6-shim\nescape\nescher\nes.next\nesoteric\nespañol\nespecially\nessence\nes-shim\nestablished\nestimate\netc.)?\netc.—so\nevaluate\neval(\"var\nevasion\nevening\nevicts\nevidence\nevolution\nevolve\nevt.target\nexamine\nexample\nexcavated\nexceeded\nexcellent\nexcept\nexcess\nexchange\nexcised\nexcited\nexclaim\nexclude\nexcuse\nexecutable\nexercise\\_\\\nexhaust\nexhibit\nexited\nexiting\nexorcism\n@expalmer\nexpand\nexpect\nexpedient\nexpend\nexperience\n--explain\nexplain\nexplicit\nexplode\nexponential\nexport\nexpose\n”exposing\nexpr[a\nexpr</code>\nexpress\ne**xpression\nexpr(expr\nexpr...expr\nextend\nextern\nextremely\neyebrows\nfabric\nfacilitate\nfactually\nfahrenheit\nfailed\nfailing\nfailure\nfairly\nfairytale\nf-algebras\nfallback\nfallen\nfallible\nfalters\nfamiliar\nfamous\nfanciful\nfantastic\nfaq](faq.md\nfaq.md](faq.md\nf(...args\nfarther\nfashion\nfaster\nfavicon.ico\nfavourite\nf(e.$value\nfearless\nfeature-\nfebruary\nfeedback\nfeeling\nfeigned\nfellow\nferocious\nferris\nffi](ffi.md\nfibonacci\nfickle\nfictional\nfiddling\nfiguratively\n</figure>\n<figure>\nfile-based\nfile::create\n!filename\nfilename\nfilepath\nfile/program\nfile's\nfile(s\nfile’s\nfile</span>\nfilippov\nfilled\nfilling\nfilter\nfinagling\n…finally\nfinancially\nfindfactors\nfinding\nfindnamebyid\nfindparam\nfinduserbyid\nfine-grained\nfinest\nfinicky\nfinish\nfinite\n_firefox:_\nfirefox\nfirehose\nfiring\nfirmly\nfits,”\nfive-stage\nfive_times\nfixing\nflatmap\nflatten\nflavor\nflawed\nfledged\nflexibility\nflicker\nflimsy\nflip(concat\nflipped\nflowing\nfluent\nfly(&self\nf(m.$value\nf.map(fn\nfn(acc\nfn(a).map(b\nf.name\nfn.call(null\nfn.length\nfnonce\nfn(this.$value\nf.of(x).map(f\nfold(any.empty\nfolder\nfolding\nfolktale\nfollow\nfontcolor\nfontsize\nfoo<'a\nfoobar\nfoo_derive\nfoolish\nfoo()`'s\nfoothills\nfootprint\nfor<...>\nforagainstlet\nforbidden\nforcing\nforeach\nforefathers\n_foreign\nforeign\nforesee\nforever\nforeword\nforget\nforgiven\nforgot\nfor..in\nforked\nfork/join\nfor`-loop\nformal\nformer\nformulas\nformvalues\nfortunately\nforum][users\nforward\nfour's\nfour’s\nfourth\nfowler\n^fowlerioc\nfp-style\nfractional\nframed\nfrançais\nfreeing\nfreely\nfreeman\nfreeze\nfreight\nfrequently\nfriend\nfrighten\nfrisby\nfrompredicate\nfrowned\nfrustrating\nf.sequence(of\nfs.write\nf.tostring\nf.traverse(of\nfulfill\nfulltime\n!function\n+function\n~function\nfunction\nfundamental\nfunding\nfuriously\nfurther\nfutile\nfuture\nf(x)).ap(v\ngained\ngaining\ngallant\ngaloshes\ngame’s\ngarbage\ngatekeeper\ngateway\ngather\ng(e.$value\ngeneral\ngen_range\ngentili\ngentle\ngeolocation\ngetage\ngetattribute\ngetchildren\ngetconfig\ngetcurrent\ngetend(end\ngetfile\ngetfromcache\ngetgrade\ngetinfo\ngetitem\ngetjson\ngetlabels\ngetname\ngetrandom\ngetserverstuff\ngetslot(reel\ngetsomedata\ngetstreetname\ngetter\ngetting\ngettwenty\ngetusermedia\ngetval\ngg@allin.com\nghastly\ngitignore\ngiving\nglance\nglider\nglobal\ng**lobally\nglorious\ngoggles\ngolden\ngoodbye\ngoodies\ngoodness\ngoogle\ngorilla\ngotcha\ngotten\ngovern\ngrabanski\ngrabbed\ngraceful\ngraciously\ngrammar\ngrapple\ngreeting\ngregor\ngrenade\ngrinding\nground\ngrowable\ngrowing\nguarantee\nguérir\n&guess\nguidance\nguiding\nguinea\ngustavo\n@guumaster\ngymnastics\nhacked\nhadn't\nhadn’t\nhad</span>\nhairier\nhalf-dozen\nhallmarks\nhalting\nhalves\nhammer\nhamster\nhand-coded\nhandcuffed\nhanded\nhandful\nhandle\nhanging\nhappen\nhappily\nharbor\nhardcode\nhard-coded\nharder\nhardly\nhardware\nharmless\nharmony\nharness\nhasher\nhashing\nhashmap<_\nhashset<i32>\nhaskell\nhasletterr\nhasn't\nhasn’t\nhassle\nhaven't\nhave</span>\nhaving\nhaystack\nhazard\nhazmat\n</head>\n<head>\nheader\nheading\nhead)).join\nheadline\nhealth\nhearts\nheavens\nheavier\nheight\nheinously\nheld/assigned\n![hello\n&hello[0..1]`?\n--help\nhelped\nhelpful\nhelping\nhelp-wanted\nherded\nherein\nhere's\nhere’s\nhesitate\nhexadecimal\nhidden\nhidethecache\nhiding\nhierarchy\nhigher\nhigh-intensity\nhighlight\nhighway\nhi(\"jonas\nhimself\nhi(name\nhindley-milner\nhinted\nhistoric\nholding”\nholmes\nholt<br>\nhomegrown\nhomepage\nhomework\nhomogeneous\nhomomorphism\nhonest\nhonored\nhooray!\nhopeful\nhopelessly\nhoping\nhorror\n@horse_ebooks\nhorse_ebooks\nhosted\nhosting\nhowever\nhowmany\nhow/why\n</html>\nhtmldivelement\nhtml-formatted\nhttp.get\nhttpget\nhttppost\n=https\nhttp-version\nhuddled\nhumble\nhumorously\nhundreds\nhurling\nhurries\nhusbandry\nhydration\nhyperoptimize\nhypotheses\nhyunsok\ni32`</span>\nid`-bearing\nideology\nidiosyncrasies\nid=\"js-main\nidletime\nid,name\nidtoio\nidtomaybe\nid.touppercase\nid_variable\nidx,btn\nidx,val\nif..else\nif/else\nif](if.md\nif`-statement\nignorance\n#[ignore\n--ignored\niife's\nill-advised\nill-designed\nillegal\nillustrate\nimagine\nimmediate\nimmutability\nimpact\nimpart\nimperative\n&(impl\nimpl<...>\nimplement\nimplications\nimport\nimpose\nimpractical\nimprecise\nimprove\nimpure\ninaccessible\ninadequate\ninappropriate\nin-browser\nincantation\nincapable\ninception\nincidentally\ninclination\n#include\ninclude\nincoming\nincorporate\nincrease\nincubator\nindecisive\nindeed\nindent\nindependent\nin-depth\nindicate\nindirect\nindispensable\nindividual\nindonesia\nin—draft\nindustry\ninefficient\ninequality\ninertia\ninevitable\ninexpensive\ninfamous\ninfinite\n-infinity\ninfluence\ninfo(\"grade\ninform\ninfrastructure\ninhabitants\ninherent\ninitial\ninject\ninjustice\ninline\ninnocence\ninnumerable\nin-place\nin-progress\ninsanity\ninsatiable\ninsensitive\ninsert\ninside\ninsight\ninsist\ninspect\ninspiration\ninstead\nin_stock\ninsult\nintact\ninto_iter\ninvigorated\nio([<a>\nio(compose(fn\nio([email\nio(['http\nioio.join\nio(left('one\nio(maybe([x\nio('pizza\nio::result\nioresult\niostdin\nio('tetris\niototask\nio('welcome\niowindow\nipv4addr\nipv6addr\nirrefutable\nirrelevant\nirst-fay.”\nis_err\nis_hello<t\nisjust\nislastinstock\nisleft\nisnothing\nispaid\n!isprime(v\nisright\nissuing\nit_adds_two\nit!—but\nitem’s\nitem</span>\nitem`</span>\nitem.summarize\niteree\niter_mut\nit</span>\nit—the\nit_works\n@ivanzusko\njailed\njanuary\njapanese\njargon\njennifer\n--jeremy\njigsaw\njohnson\njoining\njscript\njs-friendly\njshomework\njs-like\njs-looking\njson.parse\njudging\njugular\njumping\njumpkick\njungle\njustified\njust-in-time\njvm-driven\nkeenly\nkeep-alive\nkeepgoing\nkeephighest\nkeeping\nkeyboard\nkeypress\nkeys-only\nkey</span>\nkeys].reduce\nkeystrokes\nkey-value\nkey/value\nkilometers\nkindergarten\nkind-of\nkitchen\nklabnik\nkleisli\nknowing\nknowledge\nkorean\n@ktorz\nkv.join\nlaborious\nlacked\nlambda\nlanded\nlandmine\nlang=\"en\">\nlang_items\nlarvae\nlastcar\nlastly\nlatest\nlatter\nlaughably\nlawlessness\nlaying\nlayout?\nlaziness\nleading\nleaf.parent\nleaking\nleather\nleaving\nlecture\nleft.of\nleft`s\nlegacy\nleisure\nlending\nlen(values\nlessons\nlet`/`const\nlet`-created\nlet](if-let.md\nlet`</span>\nletting\nliability\nliberal\nlib.rs\nlife-cycle\nlifecycle\nlifted\nli.innertext\nlikely\nlikewise\nlincoln\nlinear\nlingering\nlinghao\nlink.</small>\n--list\nlist—even\nlist's\nlist</span>\nlist.traverse\nlist([x\nlitmus\nlittered\nlivelong\nloading\nlocked\nlocking\nlockresult\nlogfilename\nlogged\nlogging\nlondon\nlonely\nlonging\n_**look**_\nlook-ahead\nlooked\nlooking\nloopback\nlooping\nloop's\nloop</span>\nlosing\nlossy”\nloudlastupper\nlou')))(x\nlovely\nlowest\nlow-intensity\nlow-level\nlow-risk\n&lt;anonymous>\nluckily\nlumped\nlurking\nluster\nluxury\nlyrics\n#[macro_export\nmadness\nmailing\n\\main.exe\nmainly\nmain`</span>\nmakecounter\nmaking\nmaksim\n@maksimf\nmalformed\nmal-intent\nmanifest\nmanila\nmanner\nmanually\nmap(append\nmap(|(&c\nmap(elements\nmap(httpget\nmap({id\nmap/join\nmap(log\nmap`(note\nmap(nt\nmap(of\nmappable\nmap-reluctant\nmap(runquery\nmap</span>\nmap(validate\nmarathon\nmarkdown\nmarking\nmarkup\nmarried\nmartin\nmary's\nmasochistic\nmasquerading\nmassively\n_match\nmathclass\nmathhomework\nmathisfun.com\n^mathjsisprime\nmatrimony\nmatsakis\nmatthias\nmature\nmax.empty\nmaximum\nmax(-infinity\nmax_points\nm.call\nmcdonald\nm.chain(fn\nmdbook\nmeanwhile\nmeasly\nme](ch07.md\nmedium\nmeetups\nme.first\nme[\"first\nmerely\nmerging\nmerits\nmerrily\nmessenger\n#![meta\n#[meta\nmetadata\nmichael\nmid-chapter\nmiddle\nmid-scope\nmid-term\nmilitant\nmin.empty\nminigrep\nmin(infinity\nmini-workshops\nm.insert(k\nminted\nmisbehave\nmisguided\nmisinformed\nmisleading\nmisnomers\nm.isnothing\nmisunderstood\nm.join\nmma.join\nm.map(f).join\nmmo.join\nmockmessenger\nmodern\nmode's\nmoment\nmonarchy\nmondays\nmondrian\nmonikers\nmonolithic\nmonomorphized\nmore/most\nmoreover\nmorgan\nmorning\nmotion\nmotta's\nmountain\nmouthful\nmoving\nmozart\nmpsc::channel\nmukhtar\nmulassign\nmulburry\nmyaddress\nmybirthday\n&mybox<string>\nmy_crate\nmyname\nmyprogram\nmyriad\nmyself\nmystical\nmysubmitbtn\nnaively\nnameable\nnamely\nname/purpose?\nname.split\nname—to\nnaming\nnarrative\nnaufal\nnearest\nnearly\nneatly\nneeded\nneeding\nneedn't\nnefarious\nneither\nnested\nnesting\nnetlify\nnetscape\nneutral\nnewblogpost\nnewborn\nnewest\nnewfound\nnewjob\nnewsarticle\nnew(size\nnew`</span>\nnext_arg\nnext-best\nnextmonth\nnext(&mut\nnextstudent\nnicely\nnickel\nnitty-gritty\nnode_modules\nnode's\nno-listing-\n#[no_mangle\nnomicon\nnondeterminism\nnonequality\nnonetheless\nnon-exclusive\nnonexistent\nnon-generic\nnongeneric\nnoninclusive\nnon-js\nnon-letters\nnon-*lying\nnon-module\nnon-number\nnon-quarter\nnon-recursive\nnonsense\nnon-users\nnon-web\nnonzero\nnorthern\nnostarch\nnot-a-number\nnot-code\nnot-declared\nnotebook\nnotfound\nnoting\nnot-null\nnotone\nnotorious\nnotthree\nnottwo\nnovember\nnovowels\nnowhere\nns/iter\nnsprust\nnudges\nnull’s\nnum1`'s\nnum]-[listing\nnums.push(i\nobfuscate\nobj.import\noblivious\nobnoxiously\nobsolete\noccasionally\noccupying\noctober\noddity\noff-guard\nof(fgx\noffline\noff-roading?\noffset\nof/inside\nof(new\noftheteacher\nof(this\nok(age\nok(num\nok\\r\\n\\r\\n\noldest\noldschool\noliveira\nolivia\nominous\nomissions\nonce</span>\none-argument\none_borrow\none_hundred\none-liner\none-off\none_result\none-to-several\nonholiday\nonline\nonlyhere\nonlyprimes(v\nonscreen\nonsubmit\nonward\noo-style\n--open\nopen-ended\nopening\nopt-in\nopting\nopt-level\norange\nordinary\no'reilly\nor_insert\norphan\northogonal\noubliette\nourselves\noutdated\nout-of-bounds\noutright\noverall\novercome\noverhead\noverkill\noversight\noverview\n_owner_\noxygen\np1.get('team\np2.get('team\npacked\npage.print\npage(text\npaid-for\npainless\npaired\npair<t>\npalatable\npalmer\npanoply\npantaloons\nparagraph\npardon\nparity\npart(s\npassing\npassword\npat</code>\n%path%\npath::<...>\npath-related\npatriarchy\npausing\npaying\npayment\npayoff\npaywall\npbcopy\npeculiar\npedagogical\npenalty\npenultimate\npercolate\nper-function\nperhaps\nperils\nper-project\npertaining\npessimism\np.get('hp\nphones\nphrase\npicked\npicking\npickle\npigeon\npinballed\npincers\npinpoint\npioneering\npiping\npistols\npitfalls\npixels\nplacing\nplatter\nplausible\nplayground\nplaying\nplotting\nplugging\nplug-in\nplugins\nplumbing\n&point\npoisoned\npolished\npollock\nponder\npool.execute\npooling\npoorly\npopped\npopping\npopular\nport.”\nportion\nportrayed\nportuguês\npossess\npost.content\nposter\npost::new\npost’s\npost`</span>\npoured\nprecarious\npreconceived\npredominately\npre-esm\npreface\npre-load\nprelude\nprematurely\npremier\nprerequisite\npre-stage\npresumptuous\nprevalent\nprévenir\np**rint\nprivate\n”processing\nproclaim\n#[proc_macro\nproc-macro\nproc_macro\nprofound\nprohibition\n~/projects\nproliferates\nprollyfill\npronounced\npros/cons\n--proto\nprudent\np.set('hp\npseudo-psychic\npubdate\npub.print\npudding\npulled\npulling\npunctuate\npuppies\npurehttpcall\npurescript\npurify\npurity\npurple\npursuit\npushing\npush`</span>\npush_str\npush-ups\nputting\npuzzle\npyramid\npython\nqlp_shift\nquacks\nquantum\nquarantine\nquietly\nquildreen\nquitting\nquoting\nr#\"...\"#\nr##\"...\"##\nrabbit\nrallying\nrandcrate\nrand::rng\nrapidly\nrarely\nraspberry\nrather\nrational\nrc::downgrade\nrc<dyn\nrc<list>\nrc<mutex<i32>>\nrc<node>\nrc<str>\nrc::weak_count\nreaddir\nreadme\nreal'ish\nreally\nreal-world\nrearrange\nre-calculate\nrecall\nrecipe\n_recoverable_\nrect2?\nrect3?\n&rectangle\nrectify\nre)declaration\nredefine\nredundant\nreel.spin\nre-entry\nreevaluate\nrefcell<t>\n_refers_\nrefmut<t>\nrefrain\nref<t>\nrefusing\nregeneration\nr**egular\nreimplement\nreinforce\nreintroduced\nreiterate\nre-labeled\nrelaying\nre-learn\n--release\n‘release\nrelegates\nrel=\"license\nrelying\nremarkable\nremassign\nremedy\nremiss\nreopen\nre-optimized\nre-ordering\nrepository\nre-provide\nrepublish\nreputation\nrescue\nresearch\nresemblances\nreshape!\nresizes\nresort\nresp.allsales\nrestaurant\nresuming\nretention\nre.test(str\nretirement\nret.push(i\nretreat\nretweet\nreusable\nre-using\nreusing\nreverse\nre-visit\nrevoke\nrework\nre-write\nrhetorical\nrhinestone\nrhs=self\nrichard\nricher\nridiculous\nriding\nrinse/repeat\nripgrep\nr#match(needle\nroadmap\nrobots\nrock-solid\nrolled\nrooted\nroot’s\nrotate\nrtfming\nrubber\nruling\nrumors\nrumpled\nrun-away\nrundown\nrunnable\nrunner\nrunquery\nrun(&self\nrun</span>\nrun-time\nrushing\nrust_backtrace\nrustier\nrustonomicon\nrust's\nrust</span>\nrx`</span>\n===`'s\n&s[0..len\ns1`</span>\n&s[3..len\nsacrificing\nsaddens\nsafari\nsafeadd\nsafehead\nsafeprop\nsafest\nsafety\nsales.”\nsamsung\nsanity\ns.as_ref\nsavecomment\nsaying\nsay(\"kyle\nsay(myname\nsaysomething\nscaffolding\nscared!\nscattered\nscenes\nscheming\nschrödinger's\ns.clear\nscoffs\nscratch\nscreaming\nscrewdriver\nscribbling\n</script>\nscroll\nsealed\nseat_at_table\nseated\nsecurity\nseeded\nseeing\nseemingly\nsegmentation\nseimith\nselfish\nsemi-group\nsemi_token\nsending\nsenior\nsent_messages\nseptember\nsession\nsethtml\nset</span>\nsetstyle\nset_value\nshamed\nsharing\nsharper\nshelter\nshenanigans\nsherlock\nshields\nshipped\nshlassign\nshoe’s\nshoe_size\nshopping\nshot—you\n#[should_panic\nshowed\nshowing\n--show-output\nshow(post\nshowwelcome\nshrassign\nshrink\nshuffle\nshutdown\nshutting\nsiblings\nsidebar\nside-effect\nsidekick\nsidewalk\nsigned\nsign_in_count\nsignup\nsilently\nsilver\nsimpson\nsingularity\n^siphash\nsiphash\nsite's\nsitting\nsit-ups\nsix-week\nsizable\nsize.”\n?sized>(t\nsize</span>\nskeleton\nskepticism\nskewed\nskimmed\nslanted\nslashes\nslicing\nsliding\nsliver\nslogan\nsloppy\nslotmachine\nslower\nslowly\nsmells\nsmooshgate\nsmooth\nsmorgasbord\n--snip--\nsnuffs\nso-called\nsocial\nsoftware\nsoisthisone\nsolely\nsolving\nsomebody!\nsomeday\nsomeerror\nsomehow\nsomename\nsomeproject\nsome`</span>\nsome<u8>\nsome_u8_value\nsooner\nsophisticated\nsort—if\nsorting\n</span>\nspanish\nsparse\nspdx)][spdx\nspears\n^specapb\nspec-compliant\nspewing\nsphere\nspidermonkey\nspikes\nspinreel(reel\nspiral\nspirit\nsplice\nspoiler\nsprinkle\nsql(input\nsql!(select\nsquashes\nsquint\nsrc=..>\nsrc/lib.rs\nsrc/main.rs\nstability\n‘stable\nstaircase\nstakeholders\nstamped\nstarch\n&'static\n_statically\nstayed\nstaying\nstderr\nstdlib\nstdout\nsteering\nstep-by-step\nstepping\nstitched\ns.tolowercase\nstorage\nstoring\ns.touppercase\nstrange\nstrawman\nstrengths\nstressful\n&[string\n&string\n_string\nstrlength\nstrokes\nstroustrup\nstr.replace(re\n&str`</span>\nstr.split(sep\nstrtolist\n_struct_\nstudio\nsturdy\nsubassign\nsub-class\nsubmodules\nsuboptimal\nsub-projects\nsubsumed\nsubteams\nsubtle\nsub-type\nsuccinctly\nsuddenly\nsuffered\nsuitable\nsum.empty\nsum::<i64>\nsummer\nsum(values\nsupplied\nsuppress\nsurely\nsurface\nsurvive\nsusceptible\nswallowed\nswarms\nswaths\nsweeping\nsymptoms\nsyn-docs\nsyn::parse\nsynthesize\n</table>\n<table>\ntacked\ntail-call\ntailored\ntakeaway\ntaking\ntalked\ntalking\ntandem\ntangential\ntangible\ntangle\ntapioca\ntattered\ntaught\ntc39's\ntcplistener\ntcpstream\n<td>1</td>\n<td>2</td>\n<td>3</td>\n<td>4</td>\n<td>6</td>\nteam’s\ntech's\ntedious\nteenagery\nteensy\ntelekinetic\ntelephone\ntelling\ntendency\nten-line\ntennis\n-ternary\nterrain\n#[test\ntestable\ntested\ntest_foo\ntesting\ntest(s\ntest’s\ntest</span>\n--test-threads\ntext-based\ntextfield\n==`—that\nthat)!\nthat's\nthat’s\nthemselves\nthem</span>\nthen(function\nthe-slice-type\nthe-tuple-type\nthe...well\nthimblerigger\n<th>input</th>\nthompson\ntic-tac-toe\ntightly\ntilt-a-whirl\ntime-consuming\ntimeline\ntiming\nt]>::index\ntiniest\ntip-toe\ntiresome\n<title>flickr\ntlborm\ntodo](ch1.md\ntofm('rainy\ntolerance\nto_lowercase\ntoolbox\ntool reformats\ntopairs\ntop-down\ntop-level\ntornado\ntorrented\nto</span>\ntossed\ntoss-up\nto_string\ntostring\nto-the-letter\ntougher\ntouppercase\ntourist\nto_vec\ntraffic\ntranlation\ntrapeze\ntrapped\ntreasure\ntriangle\ntriple-equals\ntripped\ntrolleys\ntrousers\ntrue</span>\ntruncate\ntry..catch\ntrying\ntry_recv\nt.set('hp\nt`.</span>\nt`</span>\nttt.join\ntucked\ntuesday\ntunnels\ntupperware\nturbofish\nturned\nturning\ntutorial\ntwelve\ntwo_borrow\ntwo-horse\ntwo-phase\ntx.send\ntype,”\ntypeerror\ntype=module>\ntype-naming\ntypeof\ntype’s\ntype</span>\ntyping\nu+10ffff\nu.ap(v.ap(w\nubiquitous\nu+d7ff\nu+fffd\nultimately\numbrella!\numd—see\nunable\nunaffected\nunambiguous\nunapproved\nunassigned\nunbinding\nunboundedly\nunboxed\nuncaught\nunchangeable\nunclear\nuncover\nuncreativity\nuncurried\nundeclared\nundeniable\nundesirable\n--undo\nundone\nundoubtedly\nunequivocally\nunescape\nunfavorable\nungraceful\nunguessable\nunhealthy\nunhelpful\nunicode\nunifies\nunimpeded\nuninitialized\nunited\nunit-like\nunleashed\nunless\nunlimited\nunnatural\nunobservable\nunoptimized\nunordered\nunpack\nunpolished\nunpopulated\nunported\nunpredictably\nunprincipled\nunpublished\nunquestionably\nunrecoverable\nunreferenced\nunrelated\nunruly\nunshadowing\nunsigned\n‘unsized\nunsized\nunstable\nunsuitable\nunsure\nuntouchable\nuntyped\nunusable\nunused\nunusual\nunwanted\nunwavering\nunwieldy\nunwittingly\nupfront\nuproar\nupstream\nusable\nusecalc\nused—they're\nuseless\nuser<'a>\nuser_id\nusername\nuser-provided\nuser's\nuser’s\nuse</span>\nuse`</span>\nuses</span>\nuse-strict\n<usize\nv1.iter\nv1_iter\nv4(ipv4addr\nv6(ipv6addr\nvacuum\nvagaries\nvaluable\n__value\nvampire\nvanished\nvanity\nvar`'s\nvarying\nvastly\nvec-api\nvec<i32>\nvec::new\nvec<rc<node>>\nvec<string>\nvec<t>\nvec<u8>\nvec<worker>\nvendor\nverbose\nverminous\nversus\nvertical\nvetted\nviable\nviewbox\nviewed\nviewing\nvillain\nvlissides\nvoilà!\nvolume\nvolunteers\nvscode\nv`</span>\nv.touppercase\nvulgar\nwadler's\nwafflehouse\nwaitamoment\nwaited\nwaitforasecond\nwaiting\nwait(ms\nwait—the\nwaking\nwalked\nwalking\nwalnut\nwanderings\nwanted\nwanting\nwarned\nwasm-targeted\nwasn't\nwasn’t\nwasting\nwaving\nway</span>\nways</span>\nweak_count\nweaker\nwebassembly\nweb-invested\nwebjava\nwebpack\nwebrtc\nwedding?\nweekends\nwell</span>\nwhat!?\nwhatever\nwhat's\nwhat’s\nwhatsoever\nwhattosay\nwhenever\nwhether\nwhilst\nwhisper\nwhoever\nwholly\nwho(name\nwidely\nwidespread\nwidget\nwiggle\nwildly\nwilling\nwin.innerwidth\nwinner\nwiring\nwisdom\nwishing\nwith_capacity\nwithin\nwith—you\nwizard\nwording\nword/phrase\nworking\nworkloads\nwrangling\nwrestle\nwringer\nwritten\nx.$value\nxanadu\nx.childnodes\nx.concat(y\nx.fork(reject\nx(ht)ml-style\nx.inspect\nx.isnothing\nx.length\nx.map(fn\nx.match(/r/g\nx.reverse\nx.sequence(of\nxs.foreach(fn\nxs.length\nxs.map(f\nx_squared\nxs.sort((a\nxs[xs.length\nx.touppercase\nyanked\nyanking\ny.deref\nyear++\nyearly\nyellow\nyepitsaniife\nyet</span>\nyikes!\nyoneda\nyou'll\nyouraddress\nyou're\nyou\\'re\nyourname\nyou've\nyou’ve\ny`</span>\ny_squared\nyyyy-mm-dd\nzeitgeist\nzero</span>\nzip(&buffer[i\nziplock\nzoltar\nрусский\n"
  },
  {
    "path": "pkg/detectors/fp_uuids.txt",
    "content": "00000000-0000-0000-0000-000000000000\r\n11111111-1111-1111-1111-111111111111\r\n22222222-2222-2222-2222-222222222222\r\n33333333-3333-3333-3333-333333333333\r\n44444444-4444-4444-4444-444444444444\r\n55555555-5555-5555-5555-555555555555\r\n66666666-6666-6666-6666-666666666666\r\n77777777-7777-7777-7777-777777777777\r\n88888888-8888-8888-8888-888888888888\r\n99999999-9999-9999-9999-999999999999\r\n12345678-1234-1234-1234-123456789abc\r\n23456789-2345-2345-2345-23456789abcd\r\n34567890-3456-3456-3456-34567890bcde\r\n45678901-4567-4567-4567-45678901cdef\r\n56789012-5678-5678-5678-56789012def0\r\naaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa\r\nbbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb\r\ncccccccc-cccc-cccc-cccc-cccccccccccc\r\ndddddddd-dddd-dddd-dddd-dddddddddddd\r\neeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee\r\nffffffff-ffff-ffff-ffff-ffffffffffff\r\ndeadbeef-dead-beef-dead-beefdeadbeef\r\ncafebabe-cafe-babe-cafe-babecafebabe\r\nbadc0ffee-badc-0ffe-badc-0ffeebadc0f\r\ndeadface-dead-face-dead-facedeadface\r\nfeedface-feed-face-feed-facefeedface\r\na1b2c3d4-a1b2-c3d4-a1b2-c3d4a1b2c3d4\r\n98765432-9876-5432-9876-543298765432\r\nabcdefab-cdef-abcd-efab-cdefabcdefab\r\na0a0a0a0-a0a0-a0a0-a0a0-a0a0a0a0a0a0\r\nb0b0b0b0-b0b0-b0b0-b0b0-b0b0b0b0b0b0\r\nc0c0c0c0-c0c0-c0c0-c0c0-c0c0c0c0c0c0\r\nd0d0d0d0-d0d0-d0d0-d0d0-d0d0d0d0d0d0\r\ne0e0e0e0-e0e0-e0e0-e0e0-e0e0e0e0e0e0\r\nf0f0f0f0-f0f0-f0f0-f0f0-f0f0f0f0f0f0\r\nxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\r\n-xxxx-xxxx-xxxx-xxxxxxxxxxxx\r\n"
  },
  {
    "path": "pkg/detectors/fp_words.txt",
    "content": "number\npeople\nlittle\nthrough\nsentence\ndiffer\nbefore\nfollow\nchange\npicture\nanimal\nmother\nfather\nshould\ncountry\nanswer\nschool\nbetween\nthought\ntogether\nchildren\nexample\nalways\nletter\nsecond\nscience\nfriend\nmountain\nenough\nthough\nfamily\ndirect\nmeasure\nproduct\nnumeral\nquestion\nhappen\ncomplete\nproblem\nbetter\ntrue .\nduring\nhundred\nremember\nground\ninterest\nlisten\ntravel\nmorning\nsimple\nseveral\ntoward\nagainst\npattern\ncenter\nperson\nappear\ngovern\nnotice\ncertain\nmachine\nfigure\ncorrect\nbeauty\ncontain\ndevelop\nminute\nstrong\nspecial\nbehind\nproduce\nstreet\nmultiply\nnothing\ncourse\nobject\ndecide\nsurface\nisland\nsystem\nrecord\ncommon\npossible\nwonder\nthousand\nequate\nbrought\ndistant\nlanguage\npresent\nengine\nposition\nmaterial\nsettle\nweight\ngeneral\nmatter\ncircle\ninclude\ndivide\nsyllable\nperhaps\nsudden\nsquare\nreason\nlength\nrepresent\nsubject\nregion\nenergy\nprobable\nbrother\nbelieve\nfraction\nforest\nwindow\nsummer\nexercise\nwinter\nwritten\ninstrument\nbright\nweather\nmillion\nfinish\nflower\nclothe\nstrange\nvillage\nwhether\nparagraph\ndescribe\neither\nresult\ncentury\nconsider\nphrase\nsilent\ntemperature\nfinger\nindustry\nexcite\nnatural\nmiddle\nmoment\nspring\nobserve\nstraight\nconsonant\nnation\ndictionary\nmethod\nsection\nsurprise\ndesign\nexperiment\nbottom\nsingle\ntwenty\ncrease\nmelody\noffice\nreceive\nsymbol\ntrouble\nexcept\nsuggest\ngarden\nchoose\ncollect\ncontrol\ndecimal\ngentle\ncaptain\npractice\nseparate\ndifficult\ndoctor\nplease\nprotect\nlocate\ncharacter\ninsect\ncaught\nperiod\nindicate\nhistory\neffect\nelectric\nexpect\nmodern\nelement\nstudent\ncorner\nsupply\nimagine\nprovide\ncapital\ndanger\nsoldier\nprocess\noperate\nnecessary\ncreate\nneighbor\nrather\ncompare\nstring\ndepend\nfamous\ndollar\nstream\ntriangle\nplanet\ncolony\nsearch\nyellow\ndesert\ncurrent\ncontinue\nsuccess\ncompany\nsubtract\nparticular\nopposite\nshoulder\nspread\narrange\ninvent\ncotton\ndetermine\nchance\ngather\nstretch\nproperty\ncolumn\nmolecule\nselect\nrepeat\nrequire\nprepare\nplural\ncontinent\noxygen\npretty\nseason\nsolution\nmagnet\nsilver\nbranch\nsuffix\nespecially\nafraid\nsister\ndiscuss\nforward\nsimilar\nexperience\nbought\nevening\ncondition\nvalley\ndouble\narrive\nmaster\nparent\ndivision\nsubstance\nconnect\noriginal\nstation\ncharge\nproper\nsegment\ninstant\nmarket\ndegree\npopulate\nsupport\nspeech\nnature\nmotion\nliquid\nquotient"
  },
  {
    "path": "pkg/detectors/frameio/frameio.go",
    "content": "package frameio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(fio-u-[0-9a-zA-Z_-]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"fio-u-\"}\n}\n\n// FromData will find and optionally verify Frameio secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_FrameIO,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.frame.io/v2/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FrameIO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Frame.io is a video review and collaboration platform. Frame.io API keys can be used to access and manage video projects and assets.\"\n}\n"
  },
  {
    "path": "pkg/detectors/frameio/frameio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage frameio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFrameio_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FRAMEIO_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"FRAMEIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a frameio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FrameIO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a frameio secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FrameIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Frameio.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Frameio.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/frameio/frameio_test.go",
    "content": "package frameio\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FrameIO\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"secret\": \"fio-u-K7eiW0xMd1Jik7454mOy2FkHJP0FX2BYWtK-siLXJrLBgT74SPzfxPJ_A2ryVmms\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"fio-u-K7eiW0xMd1Jik7454mOy2FkHJP0FX2BYWtK-siLXJrLBgT74SPzfxPJ_A2ryVmms\"\n)\n\nfunc TestFrameIO_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/freshbooks/freshbooks.go",
    "content": "package freshbooks\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"freshbooks\"}) + `\\b([0-9a-z]{64})\\b`)\n\t// TODO: this domain pattern is too restrictive\n\turiPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"freshbooks\"}) + `\\b(https://www.[0-9A-Za-z_-]{1,}.com)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"freshbooks\"}\n}\n\n// FromData will find and optionally verify Freshbooks secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\turiMatches := uriPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, uriMatch := range uriMatches {\n\t\t\tresURI := strings.TrimSpace(uriMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Freshbooks,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(`https://auth.freshbooks.com/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code`, resMatch, resURI), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tbody := string(bodyBytes)\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && strings.Contains(body, \"Log In to FreshBooks\") {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Freshbooks\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"FreshBooks is an accounting software package developed and marketed by 2ndSite Inc. FreshBooks API keys can be used to access and modify accounting data and perform other operations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/freshbooks/freshbooks_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage freshbooks\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFreshbooks_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FRESHBOOKS\")\n\turi := testSecrets.MustGetField(\"REDIRECT_URI\")\n\tinactiveSecret := testSecrets.MustGetField(\"FRESHBOOKS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a freshbooks secret %s within freshbooks %s\", secret, uri)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Freshbooks,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a freshbooks secret %s within freshbooks %s but not valid\", inactiveSecret, uri)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Freshbooks,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Freshbooks.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Freshbooks.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/freshbooks/freshbooks_test.go",
    "content": "package freshbooks\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FreshBooks\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"freshbooks_secret\": \"1mf2l4mk58ht4yixjwxmwnvqroiwbj8xtoo3v7c1oq1axli7yx6hvs44emvt1hc0\",\n\t\t\t\"freshbooks_uri\": \"https://www.nonprod-freshbooks.com\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"1mf2l4mk58ht4yixjwxmwnvqroiwbj8xtoo3v7c1oq1axli7yx6hvs44emvt1hc0\"\n)\n\nfunc TestFreshBooks_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/freshdesk/freshdesk.go",
    "content": "package freshdesk\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\turlPat = regexp.MustCompile(`\\b([0-9a-z-]{1,}\\.freshdesk\\.com)\\b`)\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"freshdesk\"}) + `\\b([0-9A-Za-z]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"freshdesk\"}\n}\n\n// FromData will find and optionally verify Freshdesk secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\turlMatches := urlPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, urlMatch := range urlMatches {\n\t\t\tresURL := strings.TrimSpace(urlMatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Freshdesk,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tdata := fmt.Sprintf(\"%s:X\", resMatch)\n\t\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://%s/api/v2/tickets\", resURL), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Freshdesk\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Freshdesk is a customer support software. Freshdesk API keys can be used to access and manage support tickets, contacts, and other customer support data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/freshdesk/freshdesk_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage freshdesk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFreshdesk_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FRESHDESK\")\n\turl := testSecrets.MustGetField(\"FRESHDESK_URL\")\n\tinactiveSecret := testSecrets.MustGetField(\"FRESHDESK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a freshdesk secret %s within %s\", secret, url)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Freshdesk,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a freshdesk secret %s within %s but not valid\", inactiveSecret, url)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Freshdesk,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Freshdesk.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Freshdesk.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/freshdesk/freshdesk_test.go",
    "content": "package freshdesk\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FreshDesk\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"freshdesk_secret\": \"b1guF4w149TTJjj6ofMe\",\n\t\t\t\"freshdesk_uri\": \"api-nonprod.freshdesk.com\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"b1guF4w149TTJjj6ofMe\"\n)\n\nfunc TestFreshDesk_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/front/front.go",
    "content": "package front\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"front\"}) + `\\b([0-9a-zA-Z]{36}.[0-9a-zA-Z\\.\\-\\_]{188,244})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"front\"}\n}\n\n// FromData will find and optionally verify Front secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Front,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api2.frontapp.com/accounts\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Front\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Front is a customer communication hub that allows teams to manage email, social media, SMS, and other channels in one place. Front API keys can be used to access and manage this communication data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/front/front_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage front\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFront_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FRONT\")\n\tinactiveSecret := testSecrets.MustGetField(\"FRONT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a front secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Front,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a front secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Front,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Front.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Front.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/front/front_test.go",
    "content": "package front\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Front\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"front_secret\": \"z9483lnBn5dKi1Aho6o5uTvdFlHlBV2OLsnyilD4emza-My7rHL5.gHIRbQ4mi3_nPknCuZ9SEzwEavOm.Dwm2fHke-Qkh_IhOLBcFe.LKahBD_Mv.gB5BGKNjgJIhS68MxYEEJtTdgqrt4srztG8C08Jz90Otgvq8MCaapezsRbANbUqm-YEZlB40Nl_.05j--fTwe0Ksbxbpti2KIGOeZsTyLGMTuRDawVfe9TH6WkjP\",\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"z9483lnBn5dKi1Aho6o5uTvdFlHlBV2OLsnyilD4emza-My7rHL5.gHIRbQ4mi3_nPknCuZ9SEzwEavOm.Dwm2fHke-Qkh_IhOLBcFe.LKahBD_Mv.gB5BGKNjgJIhS68MxYEEJtTdgqrt4srztG8C08Jz90Otgvq8MCaapezsRbANbUqm-YEZlB40Nl_.05j--fTwe0Ksbxbpti2KIGOeZsTyLGMTuRDawVfe9TH6WkjP\"\n)\n\nfunc TestFront_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ftp/ftp.go",
    "content": "package ftp\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"net/textproto\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/jlaffaye/ftp\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nconst (\n\t// https://datatracker.ietf.org/doc/html/rfc959\n\tftpNotLoggedIn = 530\n\n\tdefaultVerificationTimeout = 5 * time.Second\n)\n\ntype Scanner struct {\n\t// Verification timeout. Defaults to 5 seconds if unset.\n\tverificationTimeout time.Duration\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)\n\nvar (\n\tkeyPat = regexp.MustCompile(`\\bftp://[\\S]{3,50}:([\\S]{3,50})@[-.%\\w\\/:]+\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ftp://\"}\n}\n\n// FromData will find and optionally verify URI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\turlMatch := match[0]\n\t\tpassword := match[1]\n\n\t\t// Skip findings where the password only has \"*\" characters, this is a redacted password\n\t\tif strings.Trim(password, \"*\") == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tparsedURL, err := url.Parse(urlMatch)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := parsedURL.User.Password(); !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif parsedURL.User.Username() == \"anonymous\" {\n\t\t\tcontinue\n\t\t}\n\n\t\trawURL, _ := url.Parse(urlMatch)\n\t\trawURL.Path = \"\"\n\t\tredact := strings.TrimSpace(strings.Replace(rawURL.String(), password, \"********\", -1))\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_FTP,\n\t\t\tRaw:          []byte(rawURL.String()),\n\t\t\tRedacted:     redact,\n\t\t}\n\n\t\tif verify {\n\t\t\tvar timeout time.Duration\n\t\t\tif s.verificationTimeout > 0 {\n\t\t\t\t// Use to configure a simulate timeout for interation tests\n\t\t\t\ttimeout = s.verificationTimeout\n\t\t\t} else if dl, ok := ctx.Deadline(); ok {\n\t\t\t\t// Here we assign the remaining time before our context expires\n\t\t\t\t// This help us cater the timelimit set through --detector-timeout flag\n\t\t\t\ttimeout = time.Until(dl)\n\t\t\t} else {\n\t\t\t\ttimeout = defaultVerificationTimeout\n\t\t\t}\n\t\t\tverificationErr := verifyFTP(timeout, parsedURL)\n\t\t\ts1.Verified = verificationErr == nil\n\t\t\tif !isErrDeterminate(verificationErr) {\n\t\t\t\ts1.SetVerificationError(verificationErr, password)\n\t\t\t}\n\t\t}\n\n\t\tif !s1.Verified {\n\t\t\t// Skip unverified findings where the password starts with a `$` - it's almost certainly a variable.\n\t\t\tif strings.HasPrefix(password, \"$\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nvar ftpFalsePositives = map[detectors.FalsePositive]struct{}{\n\tdetectors.FalsePositive(\"@ftp.freebsd.org\"): {},\n}\n\nfunc (s Scanner) IsFalsePositive(result detectors.Result) (bool, string) {\n\treturn detectors.IsKnownFalsePositive(string(result.Raw), ftpFalsePositives, false)\n}\n\nfunc isErrDeterminate(e error) bool {\n\tftpErr := &textproto.Error{}\n\treturn errors.As(e, &ftpErr) && ftpErr.Code == ftpNotLoggedIn\n}\n\nfunc verifyFTP(timeout time.Duration, u *url.URL) error {\n\thost := u.Host\n\tif !strings.Contains(host, \":\") {\n\t\thost = host + \":21\"\n\t}\n\n\t// Use a custom dial function that sets a deadline on the connection so that\n\t// the FTP banner read and login are also bounded by the timeout. Without this,\n\t// a server that accepts TCP but never sends a banner will block indefinitely.\n\tc, err := ftp.Dial(host, ftp.DialWithDialFunc(func(network, address string) (net.Conn, error) {\n\t\tconn, err := net.DialTimeout(network, address, timeout)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {\n\t\t\tconn.Close()\n\t\t\treturn nil, err\n\t\t}\n\t\treturn conn, nil\n\t}))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\t_ = c.Quit()\n\t}()\n\tpassword, _ := u.User.Password()\n\treturn c.Login(u.User.Username(), password)\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FTP\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"FTP is a protocol for reading and writing files. An FTP password can be used to read and sometimes write files.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ftp/ftp_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ftp\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFTP_FromChunk(t *testing.T) {\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"bad scheme\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"file://user:pass@foo.com:123/wh/at/ever\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"verified FTP\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\t// https://dlptest.com/ftp-test/\n\t\t\t\tdata:   []byte(\"ftp://dlpuser:rNrKYTX9g7z3RgJRmxWuGHbeu@ftp.dlptest.com\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FTP,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     \"ftp://dlpuser:********@ftp.dlptest.com\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"unverified FTP\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\t// https://dlptest.com/ftp-test/\n\t\t\t\tdata:   []byte(\"ftp://dlpuser:invalid@ftp.dlptest.com\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FTP,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"ftp://dlpuser:********@ftp.dlptest.com\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"bad host\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\t// https://dlptest.com/ftp-test/\n\t\t\t\tdata:   []byte(\"ftp://dlpuser:rNrKYTX9g7z3RgJRmxWuGHbeu@ftp.dlptest.com.badhost\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FTP,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"ftp://dlpuser:********@ftp.dlptest.com.badhost\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"timeout\",\n\t\t\ts:    Scanner{verificationTimeout: 1 * time.Microsecond},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\t// https://dlptest.com/ftp-test/\n\t\t\t\tdata:   []byte(\"ftp://dlpuser:rNrKYTX9g7z3RgJRmxWuGHbeu@ftp.dlptest.com\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FTP,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"ftp://dlpuser:********@ftp.dlptest.com\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"blocked FP\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"ftp://abc:123@ftp.freebsd.org/pub/FreeBSD/doc/tr/articles/explaining-bsd/explaining-bsd_tr.pdf\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FTP,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"ftp://abc:********@ftp.freebsd.org\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"URI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// if os.Getenv(\"FORCE_PASS_DIFF\") == \"true\" {\n\t\t\t// \treturn\n\t\t\t// }\n\t\t\tfor i := range got {\n\t\t\t\tgot[i].Raw = nil\n\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\topts := cmpopts.IgnoreFields(detectors.Result{}, \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, opts); diff != \"\" {\n\t\t\t\tt.Errorf(\"FTP.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ftp/ftp_test.go",
    "content": "package ftp\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Ftp\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"secret\": \"ftp://test:nonprod@ftp-enterprise\",\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"ftp://test:nonprod@ftp-enterprise\"\n)\n\nfunc TestFtp_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fulcrum/fulcrum.go",
    "content": "package fulcrum\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"fulcrum\"}) + `\\b([a-z0-9]{80})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"fulcrum\"}\n}\n\n// FromData will find and optionally verify fulcrum secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Fulcrum,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.fulcrumapp.com/api/v2/forms.json\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"X-ApiToken\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Fulcrum\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Fulcrum is a data collection platform used to gather and analyze geospatial data. Fulcrum API tokens can be used to access and manage this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/fulcrum/fulcrum_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage fulcrum\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFulcrum_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FULLCRUM\")\n\tinactiveSecret := testSecrets.MustGetField(\"FULLCRUM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a fulcrum secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Fulcrum,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a fulcrum secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Fulcrum,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"fulcrum.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"fulcrum.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fulcrum/fulcrum_test.go",
    "content": "package fulcrum\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Fulcrum\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"fulcrum_secret\": \"beuowbktxiosarw2nmme5j4qstotfq05sg9mhopulguyulnx91mjwcgcvsi1t9qjzrif13r7eahfc83p\",\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"beuowbktxiosarw2nmme5j4qstotfq05sg9mhopulguyulnx91mjwcgcvsi1t9qjzrif13r7eahfc83p\"\n)\n\nfunc TestFulcrum_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fullstory/v1/fullstory.go",
    "content": "package fullstory\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nfunc (s Scanner) Version() int { return 1 }\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"fullstory\"}) + `\\b([a-zA-Z-0-9/+]{88})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"fullstory\"}\n}\n\n// FromData will find and optionally verify Fullstory secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Fullstory,\n\t\t\tRaw:          []byte(resMatch),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"version\": fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.fullstory.com/operations/v1\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Fullstory\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Fullstory is a digital experience analytics platform that captures and analyzes customer interactions on websites and mobile apps. Fullstory API keys can be used to access and analyze this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/fullstory/v1/fullstory_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage fullstory\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFullstory_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FULLSTORY\")\n\tinactiveSecret := testSecrets.MustGetField(\"FULLSTORY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a fullstory secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Fullstory,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a fullstory secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Fullstory,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Fullstory.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Fullstory.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fullstory/v1/fullstory_test.go",
    "content": "package fullstory\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FullStory\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"fullstory_secret\": \"vKxrRC8ukovVUMaFoSSQVN+iadrjz28Jlxjbt3s//m5xz--jepr-1wrJcj8JmzjQhmEizTeJWgiP4YMYH73bHpFm\",\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"vKxrRC8ukovVUMaFoSSQVN+iadrjz28Jlxjbt3s//m5xz--jepr-1wrJcj8JmzjQhmEizTeJWgiP4YMYH73bHpFm\"\n)\n\nfunc TestFullStory_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fullstory/v2/fullstory_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage fullstory\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFullstory_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FULLSTORY_V2\")\n\tinactiveSecret := testSecrets.MustGetField(\"FULLSTORY_V2_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a fullstory secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Fullstory,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a fullstory secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Fullstory,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Fullstory.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Fullstory.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fullstory/v2/fullstory_v2.go",
    "content": "package fullstory\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nfunc (Scanner) Version() int { return 2 }\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(na1\\.[A-Za-z0-9\\+\\/]{100})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"fullstory\"}\n}\n\n// FromData will find and optionally verify Fullstory secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Fullstory,\n\t\t\tRaw:          []byte(resMatch),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"version\": fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.fullstory.com/v2/users\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Fullstory\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Fullstory is a digital experience analytics platform. Fullstory keys can be used to access user session data and analytics.\"\n}\n"
  },
  {
    "path": "pkg/detectors/fullstory/v2/fullstory_v2_test.go",
    "content": "package fullstory\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FullStory\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"fullstory_secret\": \"na1.0dDp/nOUE6tV/q5YxDtJQmWKBibPLqhX9H0nJcW9rReyBHIFuwnMwbLZcAijj1yYRP8AWgZBbUOOh4QP26sJMiqOYOcEZULACm8U\",\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"na1.0dDp/nOUE6tV/q5YxDtJQmWKBibPLqhX9H0nJcW9rReyBHIFuwnMwbLZcAijj1yYRP8AWgZBbUOOh4QP26sJMiqOYOcEZULACm8U\"\n)\n\nfunc TestFullStory_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fxmarket/fxmarket.go",
    "content": "package fxmarket\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"fxmarket\"}) + `\\b([0-9Aa-zA-Z-_=]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"fxmarket\"}\n}\n\n// FromData will find and optionally verify Fxmarket secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_FXMarket,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://fxmarketapi.com/apilive?api_key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_FXMarket\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Fxmarket is a financial market for trading currencies. Fxmarket API keys can be used to access and modify currency trading data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/fxmarket/fxmarket_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage fxmarket\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestFxmarket_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"FXMARKET\")\n\tinactiveSecret := testSecrets.MustGetField(\"FXMARKET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a fxmarket secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FXMarket,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a fxmarket secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_FXMarket,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Fxmarket.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Fxmarket.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/fxmarket/fxmarket_test.go",
    "content": "package fxmarket\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"FxMarket\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"fxmarket_secret\": \"DyB2QCacJ_D8bk1W7PlG\",\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"DyB2QCacJ_D8bk1W7PlG\"\n)\n\nfunc TestFxMarket_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gcp/gcp.go",
    "content": "package gcp\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/json\"\n\t\"encoding/pem\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"golang.org/x/oauth2/google\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ interface {\n\tdetectors.Detector\n\tdetectors.CustomFalsePositiveChecker\n\tdetectors.MaxSecretSizeProvider\n\tdetectors.StartOffsetProvider\n} = (*Scanner)(nil)\n\nvar (\n\tkeyPat = regexp.MustCompile(`\\{[^{]+auth_provider_x509_cert_url[^}]+\\}`)\n)\n\ntype gcpKey struct {\n\tType                    string `json:\"type\"`\n\tProjectID               string `json:\"project_id\"`\n\tPrivateKeyID            string `json:\"private_key_id\"`\n\tPrivateKey              string `json:\"private_key\"`\n\tClientEmail             string `json:\"client_email\"`\n\tClientID                string `json:\"client_id\"`\n\tAuthURI                 string `json:\"auth_uri\"`\n\tTokenURI                string `json:\"token_uri\"`\n\tAuthProviderX509CertURL string `json:\"auth_provider_x509_cert_url\"`\n\tClientX509CertURL       string `json:\"client_x509_cert_url\"`\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"provider_x509\"}\n}\n\nconst maxGCPKeySize = 2048\n\n// MaxSecretSize returns the maximum size of a secret that this detector can find.\nfunc (Scanner) MaxSecretSize() int64 { return maxGCPKeySize }\n\nconst startOffset = 4096\n\n// StartOffset returns the start offset for the secret this detector finds.\nfunc (Scanner) StartOffset() int64 { return startOffset }\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GCP\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GCP (Google Cloud Platform) is a suite of cloud computing services that runs on the same infrastructure that Google uses internally for its end-user products. GCP keys can be used to access and manage these services.\"\n}\n\n// FromData will find and optionally verify GCP secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllString(dataStr, -1) {\n\t\tuniqueMatches[match] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\tkey := cleanInput(match)\n\n\t\tcreds := gcpKey{}\n\t\tif err := json.NewDecoder(strings.NewReader(key)).Decode(&creds); err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// for Slack mangling (mailto scheme and hyperlinks)\n\t\tif strings.Contains(creds.ClientEmail, `<mailto:`) {\n\t\t\tcreds.ClientEmail = strings.Split(strings.Split(creds.ClientEmail, `<mailto:`)[1], `|`)[0]\n\t\t}\n\t\tcreds.AuthProviderX509CertURL = trimCarets(creds.AuthProviderX509CertURL)\n\t\tcreds.AuthURI = trimCarets(creds.AuthURI)\n\t\tcreds.ClientX509CertURL = trimCarets(creds.ClientX509CertURL)\n\t\tcreds.TokenURI = trimCarets(creds.TokenURI)\n\n\t\t// Not sure why this might happen, but we've observed this with a verified cred\n\t\traw := []byte(creds.ClientEmail)\n\t\tif len(raw) == 0 {\n\t\t\traw = []byte(key)\n\t\t}\n\t\t// This is an unprivileged service account used in Kubernetes' tests. It is intentionally public.\n\t\t// https://github.com/kubernetes/kubernetes/blob/10a06602223eab17e02e197d1da591727c756d32/test/e2e_node/runtime_conformance_test.go#L50\n\t\tif bytes.Equal(raw, []byte(\"image-pulling@authenticated-image-pulling.iam.gserviceaccount.com\")) {\n\t\t\tcontinue\n\t\t}\n\n\t\tcredBytes, _ := json.Marshal(creds)\n\n\t\tresult := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_GCP,\n\t\t\tRaw:          raw,\n\t\t\tRawV2:        credBytes,\n\t\t\tRedacted:     creds.ClientEmail,\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gcp/\",\n\t\t\t\t\"project\":        creds.ProjectID,\n\t\t\t},\n\t\t\tAnalysisInfo: map[string]string{\n\t\t\t\t\"key\": string(credBytes),\n\t\t\t},\n\t\t}\n\n\t\t// Populate private_key_id by matching the private key to certificates from the x509 endpoint.\n\t\t// Only do this when verification is enabled to avoid network calls during fast scans/tests.\n\t\t// Falls back to the value present in the found data when fetching fails or is disabled.\n\t\tvar privateKeyID string\n\t\tif verify && creds.PrivateKey != \"\" {\n\t\t\tcertsURL := strings.TrimSpace(creds.ClientX509CertURL)\n\t\t\tif certsURL == \"\" && creds.ClientEmail != \"\" {\n\t\t\t\tcertsURL = \"https://www.googleapis.com/robot/v1/metadata/x509/\" + url.PathEscape(creds.ClientEmail)\n\t\t\t}\n\t\t\tif certsURL != \"\" {\n\t\t\t\tif matchedKID, err := findMatchingCertificateKID(ctx, certsURL, creds.PrivateKey); err == nil && matchedKID != \"\" {\n\t\t\t\t\tprivateKeyID = matchedKID\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif privateKeyID == \"\" {\n\t\t\tprivateKeyID = creds.PrivateKeyID\n\t\t}\n\t\tif result.ExtraData == nil {\n\t\t\tresult.ExtraData = map[string]string{}\n\t\t}\n\t\tif privateKeyID != \"\" {\n\t\t\tresult.ExtraData[\"private_key_id\"] = privateKeyID\n\t\t}\n\n\t\tif creds.Type != \"\" {\n\t\t\tresult.AnalysisInfo[\"type\"] = creds.Type\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyMatch(ctx, credBytes)\n\t\t\tresult.Verified = isVerified\n\t\t\tresult.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, result)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, credBytes []byte) (bool, error) {\n\tcredentials, err := google.CredentialsFromJSON(ctx, credBytes, \"https://www.googleapis.com/auth/cloud-platform\")\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif _, err = credentials.TokenSource.Token(); err != nil {\n\t\tif strings.Contains(err.Error(), \"invalid_grant\") {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n\n// findMatchingCertificateKID fetches certificates from the x509 endpoint and finds the one\n// that matches the public key derived from the given private key.\nfunc findMatchingCertificateKID(ctx context.Context, certsURL, privateKeyPEM string) (string, error) {\n\t// Extract public key from private key\n\tprivateKey, err := parsePrivateKey(privateKeyPEM)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tpublicKey, ok := privateKey.(*rsa.PrivateKey)\n\tif !ok {\n\t\treturn \"\", nil // Only RSA keys supported for now\n\t}\n\n\t// Fetch certificates from endpoint\n\tkidToCert, err := fetchServiceAccountCerts(ctx, certsURL)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// Compare public keys to find matching certificate\n\tfor kid, certPEM := range kidToCert {\n\t\tcert, err := parseCertificate(certPEM)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tcertPublicKey, ok := cert.PublicKey.(*rsa.PublicKey)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Compare RSA public keys\n\t\tif publicKey.PublicKey.N.Cmp(certPublicKey.N) == 0 && publicKey.PublicKey.E == certPublicKey.E {\n\t\t\treturn kid, nil\n\t\t}\n\t}\n\n\treturn \"\", nil // No matching certificate found\n}\n\n// fetchServiceAccountCerts fetches the service account x509 certificates JSON.\n// Returns a map of kid -> PEM certificate string.\nfunc fetchServiceAccountCerts(ctx context.Context, certsURL string) (map[string]string, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, certsURL, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\tresp, err := detectors.DetectorHttpClientWithNoLocalAddresses.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\treturn nil, nil\n\t}\n\n\tvar kidToCert map[string]string\n\tif err := json.NewDecoder(resp.Body).Decode(&kidToCert); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn kidToCert, nil\n}\n\n// parsePrivateKey parses a PEM-encoded private key\nfunc parsePrivateKey(privateKeyPEM string) (any, error) {\n\tblock, _ := pem.Decode([]byte(privateKeyPEM))\n\tif block == nil {\n\t\treturn nil, nil\n\t}\n\n\tkey, err := x509.ParsePKCS8PrivateKey(block.Bytes)\n\tif err != nil {\n\t\t// Try PKCS1 if PKCS8 fails\n\t\tkey, err = x509.ParsePKCS1PrivateKey(block.Bytes)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn key, nil\n}\n\n// parseCertificate parses a PEM-encoded certificate\nfunc parseCertificate(certPEM string) (*x509.Certificate, error) {\n\tblock, _ := pem.Decode([]byte(certPEM))\n\tif block == nil {\n\t\treturn nil, nil\n\t}\n\n\treturn x509.ParseCertificate(block.Bytes)\n}\n\nfunc (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {\n\treturn false, \"\"\n}\n\n// region Helper methods\nfunc cleanInput(input string) string {\n\tinput = strings.ReplaceAll(input, `,\\\\n`, `\\n`)\n\tinput = strings.ReplaceAll(input, `\\\"\\\\n`, `\\n`)\n\tinput = strings.ReplaceAll(input, `\\\\\"`, `\"`)\n\n\t// If the JSON is encoded, it needs to be unquoted for `json.Unmarshal` to succeed.\n\t// https://github.com/trufflesecurity/trufflehog/issues/2864\n\tif strings.Contains(input, `\\\"auth_provider_x509_cert_url\\\"`) {\n\t\tunquoted, err := strconv.Unquote(`\"` + input + `\"`)\n\t\tif err == nil {\n\t\t\treturn unquoted\n\t\t}\n\t}\n\n\treturn input\n}\n\nfunc trimCarets(s string) string {\n\ts = strings.TrimPrefix(s, \"<\")\n\ts = strings.TrimSuffix(s, \">\")\n\treturn s\n}\n\n//endregion\n"
  },
  {
    "path": "pkg/detectors/gcp/gcp_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage gcp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGCP_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GCP_SECRET\")\n\tsecretInactive := testSecrets.MustGetField(\"GCP_INACTIVE\")\n\tsecretDisabled := testSecrets.MustGetField(\"GCP_DISABLED\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gcp secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GCP,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     \"detector-tester@thog-sandbox.iam.gserviceaccount.com\",\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gcp/\",\n\t\t\t\t\t\t\"project\":        \"thog-sandbox\",\n\t\t\t\t\t\t\"private_key_id\": \"a7c42dc3272c5462d1c1b5f7aadfe7ff1eecc87b\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, not verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gcp secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GCP,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"detector-tester@thog-sandbox.iam.gserviceaccount.com\",\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gcp/\",\n\t\t\t\t\t\t\"project\":        \"thog-sandbox\",\n\t\t\t\t\t\t\"private_key_id\": \"a7c42dc3272c5462d1c1b5f7aadfe7ff1eecc87b\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, disabled\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gcp secret %s within\", secretDisabled)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GCP,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"detector-test@trufflehog-testing.iam.gserviceaccount.com\",\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gcp/\",\n\t\t\t\t\t\t\"project\":        \"trufflehog-testing\",\n\t\t\t\t\t\t\"private_key_id\": \"95cf38cc5e63007aa066e8a710fc64c3554d77f4\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GCP.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"AnalysisInfo\")\n\t\t\tignoreUnexported := cmpopts.IgnoreUnexported(detectors.Result{})\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts, ignoreUnexported); diff != \"\" {\n\t\t\t\tt.Errorf(\"GCP.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestGCP_KeyIDPopulation tests that the private_key_id is properly populated\n// in ExtraData, either from the x509 endpoint (when available) or falling back\n// to the embedded value in the JSON.\nfunc TestGCP_KeyIDPopulation(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GCP_SECRET\")\n\n\ts := Scanner{}\n\tresults, err := s.FromData(ctx, true, []byte(secret))\n\tif err != nil {\n\t\tt.Fatalf(\"FromData() error = %v\", err)\n\t}\n\n\tif len(results) != 1 {\n\t\tt.Fatalf(\"Expected 1 result, got %d\", len(results))\n\t}\n\n\tresult := results[0]\n\n\t// Verify that private_key_id is populated in ExtraData\n\tprivateKeyID, exists := result.ExtraData[\"private_key_id\"]\n\tif !exists {\n\t\tt.Error(\"private_key_id not found in ExtraData\")\n\t}\n\n\t// Since the test service account is disabled (detector-test@trufflehog-testing),\n\t// the x509 endpoint returns 404, so we expect fallback to the embedded private_key_id from the JSON\n\tif privateKeyID == \"\" {\n\t\tt.Error(\"private_key_id should not be empty\")\n\t}\n\n\t// Verify it's a reasonable key ID format (hex string)\n\tif len(privateKeyID) < 20 { // typical GCP key IDs are 40 char hex\n\t\tt.Errorf(\"private_key_id '%s' seems too short for a typical GCP key ID\", privateKeyID)\n\t}\n\n\tt.Logf(\"private_key_id populated as: %s (fallback from embedded JSON due to disabled service account)\", privateKeyID)\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gcp/gcp_test.go",
    "content": "package gcp\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestGCP_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t\tskip  bool\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GCP\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"gcp_secret\": {\n\t\t\t\t\"type\": \"service_account\",\n\t\t\t\t\"project_id\": \"my-test-project\",\n\t\t\t\t\"private_key_id\": \"abc12345def67890ghi\",\n\t\t\t\t\"private_key\": \"-----BEGIN PRIVATE KEY-----\\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASC...\\n-----END PRIVATE KEY-----\\n\",\n\t\t\t\t\"client_email\": \"my-test-project@my-gcp-project.iam.gserviceaccount.com\",\n\t\t\t\t\"client_id\": \"123456789012345678901\",\n\t\t\t\t\"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\",\n\t\t\t\t\"token_uri\": \"https://oauth2.googleapis.com/token\",\n\t\t\t\t\"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\",\n\t\t\t\t\"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/my-service-account%40my-gcp-project.iam.gserviceaccount.com\"\n\t\t\t}\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`,\n\t\t\twant: []string{`{\"type\":\"service_account\",\"project_id\":\"my-test-project\",\"private_key_id\":\"abc12345def67890ghi\",\"private_key\":\"-----BEGIN PRIVATE KEY-----\\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASC...\\n-----END PRIVATE KEY-----\\n\",\"client_email\":\"my-test-project@my-gcp-project.iam.gserviceaccount.com\",\"client_id\":\"123456789012345678901\",\"auth_uri\":\"https://accounts.google.com/o/oauth2/auth\",\"token_uri\":\"https://oauth2.googleapis.com/token\",\"auth_provider_x509_cert_url\":\"https://www.googleapis.com/oauth2/v1/certs\",\"client_x509_cert_url\":\"https://www.googleapis.com/robot/v1/metadata/x509/my-service-account%40my-gcp-project.iam.gserviceaccount.com\"}`},\n\t\t},\n\t\t{\n\t\t\tname: \"normal file\",\n\t\t\tinput: `{\n  \"type\": \"service_account\",\n  \"project_id\": \"api-5153635936162123384-123456\",\n  \"private_key_id\": \"2b387b72ec1b082aa7e52189d9c43f58fb19fb48\",\n  \"private_key\": \"-----BEGIN PRIVATE KEY-----\\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDE/XlaMP419pkUEXAMPLE=\\n-----END PRIVATE KEY-----\\n\",\n  \"client_email\": \"unit-test-value@api-5153635936162123384-123456.iam.gserviceaccount.com\",\n  \"client_id\": \"109763165530299657612\",\n  \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\",\n  \"token_uri\": \"https://oauth2.googleapis.com/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/unit-test-value%40api-5153635936162123384-123456.iam.gserviceaccount.com\"\n}`,\n\t\t\twant: []string{\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"api-5153635936162123384-123456\\\",\\\"private_key_id\\\":\\\"2b387b72ec1b082aa7e52189d9c43f58fb19fb48\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDE/XlaMP419pkUEXAMPLE=\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"unit-test-value@api-5153635936162123384-123456.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"109763165530299657612\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/unit-test-value%40api-5153635936162123384-123456.iam.gserviceaccount.com\\\"}\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"escaped JSON\",\n\t\t\tinput: `{\"credentials\":\"{\\n  \\\"type\\\": \\\"service_account\\\",\\n  \\\"project_id\\\": \\\"unit-test\\\",\\n  \\\"private_key_id\\\": \\\"10f922eb17fba903dc59f7baf753976233520012\\\",\\n  \\\"private_key\\\": \\\"-----BEGIN PRIVATE KEY-----\\\\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCgyAZHbtJu1MRf\\\\ng9+Wg==\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\n  \\\"client_email\\\": \\\"fake-value@unit-test.iam.gserviceaccount.com\\\",\\n  \\\"client_id\\\": \\\"123456476766156356779\\\",\\n  \\\"auth_uri\\\": \\\"https://accounts.google.com/o/oauth2/auth\\\",\\n  \\\"token_uri\\\": \\\"https://oauth2.googleapis.com/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/fake-value%40unit-test.iam.gserviceaccount.com\\\"\\n}\\n\"}`,\n\t\t\twant:  []string{\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"unit-test\\\",\\\"private_key_id\\\":\\\"10f922eb17fba903dc59f7baf753976233520012\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCgyAZHbtJu1MRf\\\\ng9+Wg==\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake-value@unit-test.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"123456476766156356779\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake-value%40unit-test.iam.gserviceaccount.com\\\"}\"},\n\t\t},\n\t\t{\n\t\t\tname: \"no private_key_id (1)\",\n\t\t\tinput: `      {\n  \t\t\tresolve: 'gatsby-source-google-spreadsheet',\n  \t\t\toptions: {\n  \t\t\t\t/// TODO ///\n          // Find a better way to include credentials without literally displaying in the config.\n          credentials: {\n            client_email: \"gatsby-project@gatsby-project.iam.gserviceaccount.com\",\n            private_key: \"-----BEGIN PRIVATE KEY-----\\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCuT6NTCf5cILhY\\nQrqiah7ukgW1yzOR2brT2V1jiux77q22ljeB2XcnVivg97MWLKpPbghvV+zd7QLi\\n4WQZL0f7x5W0yTs8CU6cyVNn/dWewEyWQbKifxWoJZiOhxw7cLIoZkY0Vtz9WQy3\\nt0rvwMrkRpvA8dSVtgfOr2uSIAxA5vqsvl9VkT+WFy3bU1oKdUcUWGqhB2kg9wJh\\nXLFdfGewt1xezEiFom2WTHx1lltQ5NURJALzo9GfelMfeBR2BPlRdrDp+vpPT1V4\\nv90JO7V7yaJm5LeyOX1eKmcrQRw/gKnA9XYifdKl01sTIM4p3Rl6mJZe1iPkWVNj\\nWGhW+zzZAgMBAAECggEABO85tSXVBsghV9RJwsPEzOWi1j7inpgejxU57NG3uJLs\\n3vyZJSqiEiHBG7z/W5sXliiMAhTn3m9xc7lEMjdQfxrrDMNekyhDSrJzU0APDk+s\\ny1sgQrPcydYIn85I2RDrZjPg/GXSGzTsZH4Cl89qHvS1v4xJA5Tz2yDBp5ETL+oe\\nK3Xba1+TtvsCh14xAnx81yTFIGvjVukh3iXa8PjRdeANrw/WV/0eK6rA5wga20Ik\\nsiV8C5t0rXMtF/ZMTagtQa/PL7G890T8CnbulMeYlZ1vhXTlNTMqrL9gl6Scmyuk\\n0LWEWeElLHm9C+jDkdPjp+GAF7S6T7QO3r7k4tFHJQKBgQDqjmgtu0nqUqdpl5Un\\nr6oUqLCYdMqCl2QjXmBEWdzXqtwo0YeBYGJHI+fc1JhI/XXAZX3AlIvaxzTuEgK6\\n3T5pix51jXafFP3mR/WSWj9oFaH3MVp4V28fdnTkZjugPnEDl2vIKYMaiKQdEV0u\\nqQLVHi1PT98STp1fnICew3h3/QKBgQC+Pz4qiJUTkuLP1n77K3/GzqVVnQFtPYMr\\n1Ob04tDMeAnVL/yW38eP7tVHufeRMGt3A936vl+TF+c2doQLH3zsUFKF368TXsDi\\nhSNsxYWeMhi/VejQkpPG7MXZj30nT3AI6YAmcwd5wwWkhGUSDeF6p/H9p89t+f/m\\njpyBAB5JDQKBgFjWcxLPGuHLSGkv5mhPmkWU1r4HjiQEHwNeXWvF9WUh65zyLzaL\\nQO3c5Za4Vq1egljKl+R23rmQNWXt0GbiIR9sd67iU4lRNBEiNBqoX9eWSfAMG031\\nH7t07DUNm4vH2poXodUAFA3arv3rc7WWgeIiOdsOT1jpuaVa60Q2mMwpAoGBAI9d\\ng1B0KrtcZpWvE3PdvOWplghlT8ztnOqr/vut7SEimHhSOCvOKUn69jieGMUN0v4W\\nKPKrAcUML03ok+r56J8AjJ+cCAg10G8jW6W9V8r1/5Y4fECpJvxzM0mXCv5Tq57b\\nr5nJ92k3oQnwR2YKlc9jvkWjbvp2efRZpfDEkQ4FAoGBAJLgC5YaWiFjn2EGtlGa\\nKxunQ+zU/TtReRNCA9fjTdlxzH7uqHPrTTZ58TAf8VfI1T01rqY2LPXYsgxfU6ut\\nbJrxof0hp00dDKldxFOl5QbvoXhF7T3Bq5RK9uFpxSOpaXvKIMjmYmACgeP1XNNq\\n4ThWdWJOMDXLaJN2Mj8n3kih\\n-----END PRIVATE KEY-----\\n\"\n          },\n          // Spreadsheet is here: https://docs.google.com/spreadsheets/d/...\n`,\n\t\t\tskip: true,\n\t\t},\n\t\t{\n\t\t\tname: \"no private_key_id (2)\",\n\t\t\tinput: `\nmodule.exports = {\n    credentials: {\n        google: {\n            client_email: process.env.GOOGLE_CLIENT_EMAIL || 'cloudsploit@your-project-name.iam.gserviceaccount.com',\n            private_key: process.env.GOOGLE_PRIVATE_KEY || '-----BEGIN PRIVATE KEY-----\\nYOUR-PRIVATE-KEY-GOES-HERE\\n-----END PRIVATE KEY-----\\n'\n        },`,\n\t\t\tskip: true,\n\t\t},\n\t\t{\n\t\t\tname: \".env\",\n\t\t\tinput: `GOOGLE_SERVICE_ACCOUNT_EMAIL=jedicconnect@f4k3acc.iam.gserviceaccount.com\nGOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY=\"-----BEGIN PRIVATE KEY-----\\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCkVEmh423RApn2\\nv262VYWKhkyf11erwQPIdQP2f+zSXDxZJwQ2vAY/ecamlYz2TnDoXTM7LghLGXXd\\n1ZdcMaAX3dgQdxt36B/gRLOO8eyQwEQjvseSm3NFQjyrX9x94STmrEOWP7xR8DuZ\\nhq3hFXT9knD8CW+TwG7rQn0e6mdUqz2tujptLFwo+GUZZ4vbw1Y2pU9QBiVDtA8R\\nIVdpDFujLS3nxJEhKUY7WYSWr1cwJwqBNMfIq0GW+Sk8/E25qHztZVsMsvyUtIIF\\nH21dGMoyc4CSDWqdgSQvEeyTi9zrlVWA4/gHyb246UlkvEdOBVKH1jiZy76atsLS\\nNCtIk6A3AgMBAAECggEAHiluXm19EZj1o4mdi5AE89kUpV4ENH038Yow0QTH9hCB\\n7ycvKdC3IN18LcVTWz4okS3SInGfihFBRhdXMc/V/6tzZgpGm2qalzJE9t7Gugbg\\nOuNghDNOJA81TYtJ0D0L5d8GhMRsD2oVtmc28RJcJ9LCNDCTLz5p3XqUhTSnBK4h\\n2ad5BHgXgCinVXZ+KgCnrm6obijcrlvTGacGXT42UsRA/AJE9Lh2t+zNQE0v6T+B\\niI/DEHcq0gNU8yKQSiLsL7P1nc3sLZs7DKsLP0NH1Wd4wgupM9PnPMPAHAwye1tA\\nz/hdaUbU94lPW/40rw26DwwX1rfHFavRy/JN2VH52QKBgQDl9v4XxGjgmcted01H\\nXPELsfJPod9PCr6M7swUVCRzP2m1CB9efOJBd3yzFI/beQxNjhVH3tUEioHdMgzJ\\nrJHB44NyYLaVDFhIcTDlyTKlEYpd7kT5xMGqbcuLRSr0/EH9Ut69+buMqvcXCo9t\\nlpgfvx5PizZ6FWCzUeR/qQ5DAwKBgQC27v9UT6dpl47DYUKls1laTsN+wiaFXiuY\\n39MPGwXW4AmfY2VqrVuGaGc1+ls9Am75/BkEQr9rDmuy/p+tqeOrILGMskuSzwzO\\n5NJ9DbXwgU+rBkRGYP4QdY3eYlsQQJXjrYmtXi03J6NZiVLozMN5Clk6yJLjZue0\\nNPMpjl8YXQKBgGfiqmrGObKtB2hHcMu6OtJTsukycRTd/7Le9aaBVG4TyYcUgkdH\\nF1cHyXeE5G/7QQmQFCEBky2X/I6WW5yHrtjuFKWI9zJh/0fKipJjz9MuF1nTl6lV\\nrz90liz2NC+z/YOY+jLMLGOhoDnydVTGYTaGOgUpGJUSLzsS1ayuDFlNAoGALJRz\\nrrU2pBnmFaEHH+BkHwjgxWxE/O1lDH1HLwAz3Rh939TWKzgR/OBGfrYDNAv6xXr1\\nEb++bDV4c4dvnF+xdsuh2Rq+JgnFIkpLLWSA+RpaMuB1FP2gDJzJNO+dJ4nFvVVW\\nHuS7ehxzx2cayFbWMBIgip3EhhxWzOi2dWMXs5UCgYA6wgiuw7w/Yw+/E6VThyPz\\nzAspslQJzT4CzrW1WYwyZPCKAkdxIUd31pVtfj3M8duIpFkT5QidaBY54a3A8aU6\\nY3kigasNRpSEMK7/ldMkX9pgPL6LGC1mmb7oTxIJjjAN6xcKVznF756LoK9A7+m6\\nNrT7PzoKmZkC7BpmKV4CdQ==\\n-----END PRIVATE KEY-----\\n\"\n`,\n\t\t\tskip: true,\n\t\t},\n\t\t// TODO: Create an example of these.\n\t\t// {\n\t\t//\tname:  \"Slack mangled email\",\n\t\t//\tinput: ``,\n\t\t//\twant:  []string{\"\"},\n\t\t// },\n\t\t// {\n\t\t//\tname:  \"Empty client email\",\n\t\t//\tinput: ``,\n\t\t//\twant:  []string{\"\"},\n\t\t// },\n\t\t// {\n\t\t//\tname:  \"Carets\",\n\t\t//\tinput: ``,\n\t\t//\twant:  []string{\"\"},\n\t\t// },\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tif test.skip {\n\t\t\t\tt.Skip()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gcpapplicationdefaultcredentials/gcpapplicationdefaultcredentials.go",
    "content": "package gcpapplicationdefaultcredentials\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"golang.org/x/oauth2\"\n\t\"golang.org/x/oauth2/google\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.MaxSecretSizeProvider = (*Scanner)(nil)\nvar _ detectors.StartOffsetProvider = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(`\\{[^{]+client_secret[^}]+\\}`)\n)\n\ntype gcpApplicationDefaultCredentials struct {\n\tClientID     string `json:\"client_id\"`\n\tClientSecret string `json:\"client_secret\"`\n\tRefreshToken string `json:\"refresh_token\"`\n\tType         string `json:\"type\"`\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\".apps.googleusercontent.com\"}\n}\n\nconst maxGCPADCKeySize = 1024\n\n// MaxSecretSize returns the maximum size of a secret that this detector can find.\nfunc (Scanner) MaxSecretSize() int64 { return maxGCPADCKeySize }\n\nconst startOffset = maxGCPADCKeySize\n\n// StartOffset returns the start offset for the secret this detector finds.\nfunc (Scanner) StartOffset() int64 { return startOffset }\n\n// FromData will find and optionally verify Gcpapplicationdefaultcredentials secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllString(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tkey := match\n\n\t\t// Detect keys by unmarshalling the data.\n\t\tcreds := gcpApplicationDefaultCredentials{}\n\t\terr := json.Unmarshal([]byte(key), &creds)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Trim prefix (\".apps.googleusercontent.com\") because it will be labeled as false positive\n\t\tdetectedClientID, _, _ := strings.Cut(creds.ClientID, \".\")\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_GCPApplicationDefaultCredentials,\n\t\t\tRaw:          []byte(detectedClientID),\n\t\t\tRawV2:        []byte(detectedClientID + creds.RefreshToken),\n\t\t}\n\n\t\tif len(creds.RefreshToken) > 3 {\n\t\t\ts1.Redacted = creds.RefreshToken[:3] + \"...\" + creds.RefreshToken[min(len(creds.RefreshToken)-1, 47):]\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\t// Use marshalled credential to verify if the found key is active\n\t\t\tcredBytes, _ := json.Marshal(creds)\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, string(credBytes))\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\t// First load the credential from the found key\n\tcredentials, err := google.CredentialsFromJSON(ctx, []byte(token), \"https://www.googleapis.com/auth/cloud-platform\")\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\t// Credential not loaded. Not sure this can happened but it should be labeled unverified.\n\tif credentials == nil {\n\t\treturn false, nil, nil\n\t}\n\n\t// Get token from the credentials\n\tgcpToken, err := credentials.TokenSource.Token()\n\n\tif err != nil {\n\t\t// Return verification error if the error is temporary\n\t\t// See https://pkg.go.dev/golang.org/x/oauth2/google#AuthenticationError.Temporary for details\n\t\tvar temporaryError *(google.AuthenticationError)\n\t\tif errors.As(err, &temporaryError) {\n\t\t\tif err.(*google.AuthenticationError).Temporary() {\n\t\t\t\treturn false, nil, err\n\t\t\t}\n\t\t}\n\t\treturn false, nil, nil\n\t}\n\n\t// Return verification error if the retrieved token is invalid\n\tif !gcpToken.Valid() {\n\t\treturn false, nil, err\n\t}\n\n\t// Build request to call an IAM endpoint\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://iam.googleapis.com/v1/roles\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\t// If we are not using a faketransport, leave it as is because the test wants to modify the response. Otherwise, set the retrieved token to the client.\n\tif _, ok := client.Transport.(common.FakeTransport); !ok {\n\t\tclient.Transport = &oauth2.Transport{\n\t\t\tSource: credentials.TokenSource,\n\t\t}\n\t}\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t// If the endpoint returns useful information, we can return it as a map.\n\t\treturn true, nil, nil\n\t} else if res.StatusCode == 401 {\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\t} else {\n\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\treturn false, nil, err\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GCPApplicationDefaultCredentials\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GCP Application Default Credentials are used to authenticate and authorize API requests to Google Cloud services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/gcpapplicationdefaultcredentials/gcpapplicationdefaultcredentials_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage gcpapplicationdefaultcredentials\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGcpapplicationdefaultcredentials_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GCPAPPLICATIONDEFAULTCREDENTIALS\")\n\tinactiveSecret := testSecrets.MustGetField(\"GCPAPPLICATIONDEFAULTCREDENTIALS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gcpapplicationdefaultcredentials secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GCPApplicationDefaultCredentials,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gcpapplicationdefaultcredentials secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GCPApplicationDefaultCredentials,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gcpapplicationdefaultcredentials secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GCPApplicationDefaultCredentials,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gcpapplicationdefaultcredentials secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GCPApplicationDefaultCredentials,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Gcpapplicationdefaultcredentials.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Gcpapplicationdefaultcredentials.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gcpapplicationdefaultcredentials/gcpapplicationdefaultcredentials_test.go",
    "content": "package gcpapplicationdefaultcredentials\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestGcpapplicationdefaultcredentials_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"typical pattern\",\n\t\t\tinput: `{\n\t\t\t\t\t\"client_id\": \"191375729402-oiuj2498ry3497gjveoierj8294jfj41.apps.googleusercontent.com\",\n\t\t\t\t\t\"client_secret\": \"z-OIFJWEOIJGWER91834325R\",\n\t\t\t\t\t\"refresh_token\": \"1//0_joijgor3i4ut98579862709342j3kjJOIE02834jijfewoifjowiejfhghyzznfoiwejfwnvuhewiufnwinciwu_-o2i3jjfcc\",\n\t\t\t\t\t\"type\": \"authorized_user\"\n\t\t\t\t}`,\n\t\t\twant: []string{\n\t\t\t\t\"191375729402-oiuj2498ry3497gjveoierj8294jfj411//0_joijgor3i4ut98579862709342j3kjJOIE02834jijfewoifjowiejfhghyzznfoiwejfwnvuhewiufnwinciwu_-o2i3jjfcc\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tdetectorMatches := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(detectorMatches) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/geckoboard/geckoboard.go",
    "content": "package geckoboard\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"geckoboard\"}) + `\\b([a-zA-Z0-9]{44})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"geckoboard\"}\n}\n\n// FromData will find and optionally verify Geckoboard secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Geckoboard,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.geckoboard.com/\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Geckoboard\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Geckoboard is a data visualization tool that allows users to create real-time dashboards. Geckoboard API keys can be used to access and modify these dashboards.\"\n}\n"
  },
  {
    "path": "pkg/detectors/geckoboard/geckoboard_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage geckoboard\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGeckoboard_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GECKOBOARD\")\n\tinactiveSecret := testSecrets.MustGetField(\"GECKOBOARD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a geckoboard secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Geckoboard,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a geckoboard secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Geckoboard,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Geckoboard.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Geckoboard.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/geckoboard/geckoboard_test.go",
    "content": "package geckoboard\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GeckoBoard\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"geckoboard_secret\": \"pi7BjvBbRvB3DkmnfEBQazWLiL55IY2jPlim7z1bqmO3\",\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"pi7BjvBbRvB3DkmnfEBQazWLiL55IY2jPlim7z1bqmO3\"\n)\n\nfunc TestGeckoBoard_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gemini/gemini.go",
    "content": "package gemini\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha512\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\nconst (\n\tbaseURL       = \"https://api.gemini.com\"\n\taccountDetail = \"/v1/account\"\n\taccount       = \"primary\"\n)\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(`\\b((?:master-|account-)[0-9A-Za-z]{20})\\b`)\n\tsecretPat = regexp.MustCompile(`[A-Za-z0-9]{27,28}`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"master-\", \"account-\"}\n}\n\n// FromData will find and optionally verify Gemini secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tidMatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range idMatches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, secretMatch := range secretMatches {\n\t\t\tresSecretMatch := strings.TrimSpace(secretMatch[0])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Gemini,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resSecretMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := constructRequest(ctx, resSecretMatch, resMatch)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc constructRequest(ctx context.Context, secret, keyID string) (*http.Request, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"POST\", baseURL+accountDetail, &bytes.Buffer{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tparams := map[string]any{\n\t\t\"request\": accountDetail,\n\t\t\"nonce\":   time.Now().UnixNano(),\n\t}\n\n\tacct := strings.Split(keyID, \"-\")\n\t// Not entirely sure how to handle master account keys where one of the accounts is named \"primary\".\n\tif len(acct) > 1 && acct[0] == \"master\" {\n\t\tparams[\"account\"] = account\n\t}\n\n\treqStr, err := json.Marshal(&params)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpayload := base64.StdEncoding.EncodeToString(reqStr)\n\tsignature := constructSignature(payload, secret)\n\n\treq.Header = http.Header{\n\t\t\"Content-Type\":       {\"text/plain\"},\n\t\t\"Content-Length\":     {\"0\"},\n\t\t\"X-GEMINI-APIKEY\":    {keyID},\n\t\t\"X-GEMINI-PAYLOAD\":   {payload},\n\t\t\"X-GEMINI-SIGNATURE\": {signature},\n\t\t\"Cache-Control\":      {\"no-cache\"},\n\t}\n\treturn req, err\n}\n\nfunc constructSignature(payload string, resSecretMatch string) string {\n\th := hmac.New(sha512.New384, []byte(resSecretMatch))\n\th.Write([]byte(payload))\n\tsignature := hex.EncodeToString(h.Sum(nil))\n\treturn signature\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Gemini\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Gemini is a cryptocurrency exchange platform. API keys can be used to access and manage account details and perform transactions.\"\n}\n"
  },
  {
    "path": "pkg/detectors/gemini/gemini_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage gemini\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGemini_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecretMaster := testSecrets.MustGetField(\"GEMINI\")\n\tkeyMaster := testSecrets.MustGetField(\"GEMINI_KEY\")\n\tsecretAccount := testSecrets.MustGetField(\"GEMINI_ACCOUNT\")\n\tkeyAccount := testSecrets.MustGetField(\"GEMINI_KEY_ACCOUNT\")\n\tinactiveSecret := testSecrets.MustGetField(\"GEMINI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified; master\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gemini %s and secretMaster %s within\", keyMaster, secretMaster)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gemini,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified; account\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gemini %s and secretAccount %s within\", keyAccount, secretAccount)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gemini,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gemini secretMaster %s and secretMaster %s within but not valid\", keyMaster, inactiveSecret)), // the secretMaster would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gemini,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secretMaster within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Gemini.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secretMaster present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"RawV2\")\n\t\t\tif diff := cmp.Diff(tt.want, got, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Gemini.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gemini/gemini_test.go",
    "content": "package gemini\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Gemini\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"gemini_key\": \"master-SBR89HWzNzbSfsj9ADI8\",\n\t\t\t\"gemini_secret\": \"via91gY9tLJKVz5At3DQjs8Slz1\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"master-SBR89HWzNzbSfsj9ADI8via91gY9tLJKVz5At3DQjs8Slz1\"\n)\n\nfunc TestGemini_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/generic/generic.go",
    "content": "package generic\n\n// cat scanner/pkg/secrets/generic/top-1000.txt | awk 'length($0)>5' > scanner/pkg/secrets/generic/words.txt\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc New() Scanner {\n\texcludePatterns := []string{\n\t\t`[0-9A-Fa-f]{8}(?:-[0-9A-Fa-f]{4}){3}-[0-9A-Fa-f]{12}`,                                    // UUID\n\t\t`[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}`, // UUIDv4\n\t\t`[A-Z]{2,6}\\-[0-9]{2,6}`, // issue tracker\n\t\t`#[a-fA-F0-9]{6}\\b`,      // hex color code\n\t\t`\\b[A-Fa-f0-9]{64}\\b`,    // hex encoded hash\n\t\t`https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)`, // http\n\t\t`\\b([/]{0,1}([\\w]+[/])+[\\w\\.]*)\\b`,                 // filepath\n\t\t`([0-9A-F]{2}[:-]){5}([0-9A-F]{2})`,                // MAC addr\n\t\t`\\d{4}[-/]{1}([0]\\d|1[0-2])[-/]{1}([0-2]\\d|3[01])`, // date\n\t\t`[v|\\-]\\d\\.\\d`, // version\n\t\t`\\d\\.\\d\\.\\d-`,  // version\n\t\t`[\\d]{1,3}\\.[\\d]{1,3}\\.[\\d]{1,3}\\.[\\d]{1,3}`,      // IPs and OIDs\n\t\t`[A-Fa-f0-9x]{2}:[A-Fa-f0-9x]{2}:[A-Fa-f0-9x]{2}`, // hex encoding\n\t\t`[\\w]+\\([\\w, ]+\\)`, // function\n\t}\n\n\tvar excludeMatchers []*regexp.Regexp\n\tfor _, pat := range excludePatterns {\n\t\texcludeMatchers = append(excludeMatchers, regexp.MustCompile(pat))\n\t}\n\n\treturn Scanner{\n\t\texcludeMatchers: excludeMatchers,\n\t}\n}\n\ntype Scanner struct {\n\texcludeMatchers []*regexp.Regexp\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar keywords = []string{\"pass\", \"token\", \"cred\", \"secret\", \"key\"}\n\nvar (\n\t// \\x21-\\x7e == ASCII 33 (0x21) and 126 (0x7e)\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex(keywords) + `(\\b[\\x21-\\x7e]{16,64}\\b)`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn keywords\n}\n\n// FromData will find and optionally verify Generic secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\n\t\ttoken := match[1]\n\n\t\t// Least expensive-> most expensive filters.\n\t\t// Substrings, then patterns.\n\n\t\t// toss any that match regexes\n\t\tif hasReMatch(s.excludeMatchers, token) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// clean up containment chars\n\t\ttoken = strings.Trim(token, fmt.Sprintf(`%s\" '.,)(][}{`, \"`\"))\n\n\t\t// toss any that b64 decode\n\t\t// TODO: run them through again?\n\t\t_, err := base64.StdEncoding.DecodeString(token)\n\t\tif err == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\ts := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Generic,\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tresults = append(results, s)\n\t}\n\n\treturn\n}\n\nfunc hasReMatch(matchers []*regexp.Regexp, token string) bool {\n\tfor _, m := range matchers {\n\t\tif m.MatchString(token) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Generic\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Generic secret key that may provide access to sensitive data or systems.\"\n}\n"
  },
  {
    "path": "pkg/detectors/generic/generic_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage generic\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n)\n\nfunc TestGeneric_FromChunk(t *testing.T) {\n\tctx := context.Background()\n\ts := New()\n\n\tfound := []string{\n\t\t\"export CONFIG_SERVICE_PASSWORD=34t98hofi2309pr230\",\n\t\t\"CONFIG_SERVICE_PASSWORD: 34t98hofi2309pr230\",\n\t\t\"the secret is 34t98hofi2309pr230\", // keyword within distance of cred-looking token\n\t}\n\tnotFound := []string{\n\t\t\"export MAGIC_VAR=piggymetrics201925\",              // key does not have keyword\n\t\t\"export CONFIG_SERVICE_PASSWORD=testcredentials\",   // has test, cred\n\t\t\"export CONFIG_SERVICE_PASSWORD=password123\",       // has pass\n\t\t\"export CONFIG_SERVICE_PASSWORD=too-plain\",         // no digit\n\t\t\"export CONFIG_SERVICE_PASSWORD=abcdefg123\",        // known FP\n\t\t\"SECRET: mountain422\",                              // excluded by word list\n\t\t\"secret_guid=3fc0b7f7-da09-4ae7-a9c8-d69824b1819b\", // excluded by matcher\n\t\t\"secret_issue_key: BLAH-23490\",                     // excluded by matcher\n\t}\n\n\tfor _, data := range found {\n\t\tgot, _ := s.FromData(ctx, false, []byte(data))\n\t\tif len(got) == 0 {\n\t\t\tt.Errorf(\"Generic.FromData() expected secret for data: %s\", data)\n\t\t\treturn\n\t\t}\n\t}\n\n\tfor _, data := range notFound {\n\t\tgot, _ := s.FromData(ctx, false, []byte(data))\n\t\tif len(got) != 0 {\n\t\t\tt.Errorf(\"Generic.FromData() expected no secret for data: %s\", data)\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := New()\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/generic/generic_test.go",
    "content": "package generic\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\", // should not be detected as this is a UUID\n\t\t\"name\": \"Generic\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"secret\": \"3kJnB7gH9pLqX1ZsV4tUvWxY2aBcD5fGiJkLmN9OpQrStUvWxY1z3bC5eFgH7\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false,\n\t\t\"hexkey\": \"#ADCc49\"\n\t}]`\n\tsecret = \"3kJnB7gH9pLqX1ZsV4tUvWxY2aBcD5fGiJkLmN9OpQrStUvWxY1z3bC5eFgH7\"\n)\n\nfunc TestGeneric_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gengo/gengo.go",
    "content": "package gengo\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha1\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\t// Removed bounds since there are some cases where the start and end of the token is a special character\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"gengo\"}) + `([ ]{0,1}[0-9a-zA-Z\\[\\]\\-\\(\\)\\{\\}|_^@$=~]{64}[ \\r\\n]{1})`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"gengo\"}) + `([ ]{0,1}[0-9a-zA-Z\\[\\]\\-\\(\\)\\{\\}|_^@$=~]{64}[ \\r\\n]{1})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"gengo\"}\n}\n\n// FromData will find and optionally verify Gengo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, secretMatch := range secretMatches {\n\t\t\tresSecretMatch := strings.TrimSpace(secretMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Gengo,\n\t\t\t\tRaw:          []byte(resSecretMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resSecretMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\n\t\t\t\ttimestamp := strconv.FormatInt(time.Now().Unix(), 10)\n\t\t\t\tsignature := getGengoSignature(timestamp, resSecretMatch)\n\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.gengo.com/v2/account/me?ts=%s&api_key=%s&api_sig=%s\", timestamp, resMatch, signature), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tbody, errBody := io.ReadAll(res.Body)\n\n\t\t\t\t\tif errBody == nil {\n\t\t\t\t\t\tvar response Response\n\t\t\t\t\t\tif err := json.Unmarshal(body, &response); err != nil {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && response.OpStat == \"ok\" {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\ntype Response struct {\n\tOpStat string `json:\"opstat\"`\n}\n\nfunc getGengoSignature(timeStamp string, secret string) string {\n\n\tmac := hmac.New(sha1.New, []byte(secret))\n\tmac.Write([]byte(timeStamp))\n\tmacsum := mac.Sum(nil)\n\treturn hex.EncodeToString(macsum)\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Gengo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Gengo is a platform for professional human translation services. Gengo API keys can be used to access and manage translation projects and resources.\"\n}\n"
  },
  {
    "path": "pkg/detectors/gengo/gengo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage gengo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGengo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tkey := testSecrets.MustGetField(\"GENGO_KEY\")\n\tinactiveKey := testSecrets.MustGetField(\"GENGO_KEY_INACTIVE\")\n\tsecret := testSecrets.MustGetField(\"GENGO\")\n\tinactiveSecret := testSecrets.MustGetField(\"GENGO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gengo key %s with gengo secret %s within\", key, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gengo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gengo key %s with gengo secret %s within but not valid\", inactiveKey, inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gengo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Gengo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Gengo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gengo/gengo_test.go",
    "content": "package gengo\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Gengo\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"gengo_key\": \"1aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890abcdefgHIJKLMNOpqRSTUVWXY=~\",\n\t\t\t\"gengo_secret\": \"2bCdEfGGHiJkLmNoPqRsTuVwXyZ1234567890abcdefgH^JKLMNOpq@STUVWXYZ$ \"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"2bCdEfGGHiJkLmNoPqRsTuVwXyZ1234567890abcdefgH^JKLMNOpq@STUVWXYZ$2bCdEfGGHiJkLmNoPqRsTuVwXyZ1234567890abcdefgH^JKLMNOpq@STUVWXYZ$\"\n)\n\nfunc TestGengo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/geoapify/geoapify.go",
    "content": "package geoapify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"geoapify\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"geoapify\"}\n}\n\n// FromData will find and optionally verify Geoapify secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Geoapify,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.geoapify.com/v1/geocode/search?text=California&apiKey=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Geoapify\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Geoapify provides APIs for geolocation services, including geocoding, routing, and places. Geoapify API keys can be used to access these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/geoapify/geoapify_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage geoapify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGeoapify_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GEOAPIFY\")\n\tinactiveSecret := testSecrets.MustGetField(\"GEOAPIFY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a geoapify secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Geoapify,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a geoapify secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Geoapify,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Geoapify.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Geoapify.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/geoapify/geoapify_test.go",
    "content": "package geoapify\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Geoapify\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"geoapify_secret\": \"ac6yw6nhgqz7pjgk23llmig3fttl3ssk\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"ac6yw6nhgqz7pjgk23llmig3fttl3ssk\"\n)\n\nfunc TestGeoapify_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/geocode/geocode.go",
    "content": "package geocode\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"geocode\"}) + `\\b([a-z0-9]{28})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"geocode\"}\n}\n\n// FromData will find and optionally verify Geocode secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Geocode,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://geocode.xyz/51.4647,0.0079?json=1&auth=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `\"remaining_credits\" :`)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Geocode\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Geocode API keys can be used to access geolocation services, converting addresses into geographic coordinates and vice versa.\"\n}\n"
  },
  {
    "path": "pkg/detectors/geocode/geocode_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage geocode\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGeocode_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GEOCODE\")\n\tinactiveSecret := testSecrets.MustGetField(\"GEOCODE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a geocode secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Geocode,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a geocode secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Geocode,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Geocode.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Geocode.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/geocode/geocode_test.go",
    "content": "package geocode\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GeoCode\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"geocode_secret\": \"f0xlcqbhbzr773i6ngyl98u373wq\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"f0xlcqbhbzr773i6ngyl98u373wq\"\n)\n\nfunc TestGeoCode_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/geocodify/geocodify.go",
    "content": "package geocodify\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"geocodify\"}) + `\\b([0-9a-z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"geocodify\"}\n}\n\n// FromData will find and optionally verify Geocodify secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Geocodify,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.geocodify.com/v2/geocode?api_key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Geocodify\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Geocodify is a service that provides geocoding and reverse geocoding capabilities. Geocodify API keys can be used to access these geocoding services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/geocodify/geocodify_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage geocodify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGeocodify_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GEOCODIFY\")\n\tinactiveSecret := testSecrets.MustGetField(\"GEOCODIFY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a geocodify secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Geocodify,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a geocodify secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Geocodify,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Geocodify.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Geocodify.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/geocodify/geocodify_test.go",
    "content": "package geocodify\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GeoCodify\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"geocodify_secret\": \"xgfu00jv0d9oz8svj9uvmuzqveuesdkfafthbxsj\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"xgfu00jv0d9oz8svj9uvmuzqveuesdkfafthbxsj\"\n)\n\nfunc TestGeoCodify_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/geocodio/geocodio.go",
    "content": "package geocodio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"geocod\"}) + `\\b([a-z0-9]{39})\\b`)\n\tsearchPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"geocod\"}) + `\\b([a-zA-Z0-9\\S]{7,30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"geocod\"}\n}\n\n// FromData will find and optionally verify Geocodio secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsearchMatches := searchPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, searchMatch := range searchMatches {\n\t\t\tresSearchMatch := strings.TrimSpace(searchMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Geocodio,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resSearchMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.geocod.io/v1.6/geocode?q=%s&api_key=%s\", resSearchMatch, resMatch), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Geocodio\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Geocodio is a service that provides geocoding and reverse geocoding. Geocodio API keys can be used to convert addresses into geographic coordinates and vice versa.\"\n}\n"
  },
  {
    "path": "pkg/detectors/geocodio/geocodio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage geocodio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGeocodio_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GEOCODIO\")\n\tsearch := testSecrets.MustGetField(\"GEOCODIO_SEARCH\")\n\tinactiveSecret := testSecrets.MustGetField(\"GEOCODIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a geocodio secret %s within geocodio %s\", secret, search)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Geocodio,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a geocodio secret %s within geocodio %s but not valid\", inactiveSecret, search)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Geocodio,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Geocodio.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw v2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Geocodio.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/geocodio/geocodio_test.go",
    "content": "package geocodio\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GeoCodio\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"geocodio_key\": \"ar71u7xpxt0rlecmb5iree5mjroggsf6l2kzf92\",\n\t\t\t\"geocodio_secret\": \"ftn+YYXO\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecrets = []string{\n\t\t\"ar71u7xpxt0rlecmb5iree5mjroggsf6l2kzf92ftn+YYXO\",\n\t\t\"ar71u7xpxt0rlecmb5iree5mjroggsf6l2kzf92Detector\", // TODO: secret pattern is too broad - simplify\n\t}\n)\n\nfunc TestGeoCodio_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  secrets,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/geoipifi/geoipifi.go",
    "content": "package geoipifi\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ipifi\"}) + `\\b([a-z0-9A-Z_]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ipifi\"}\n}\n\n// FromData will find and optionally verify GeoIpifi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_GeoIpifi,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://geo.ipify.org/api/v2/country?apiKey=\"+resMatch+\"&ipAddress=8.8.8.8\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GeoIpifi\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GeoIpifi is a service that provides geolocation information. GeoIpifi API keys can be used to retrieve geolocation data based on IP addresses.\"\n}\n"
  },
  {
    "path": "pkg/detectors/geoipifi/geoipifi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage geoipifi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGeoIpifi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GEOIPIFI\")\n\tinactiveSecret := testSecrets.MustGetField(\"GEOIPIFI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a geoipifi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GeoIpifi,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a geoipifi secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GeoIpifi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GeoIpifi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"GeoIpifi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/geoipifi/geoipifi_test.go",
    "content": "package geoipifi\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Geoipifi\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"geoipifi_secret\": \"jd65TO4gFe3ZHb0_6e7lllifgYXLsJLV\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"jd65TO4gFe3ZHb0_6e7lllifgYXLsJLV\"\n)\n\nfunc TestGeoipifi_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/getemail/getemail.go",
    "content": "package getemail\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClientTimeOut(5 * time.Second)\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"getemail\"}) + `\\b([a-zA-Z0-9-]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"getemail\"}\n}\n\n// FromData will find and optionally verify GetEmail secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_GetEmail,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.getemail.io/v1/find-mail?firstname=Larry&lastname=Page&domain=google.com&api_key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\terrCode := strings.Contains(bodyString, `\"code\":\"USER_NOT_EXIST\"`)\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\tif errCode {\n\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GetEmail\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GetEmail is a service used to find email addresses based on names and domains. GetEmail API keys can be used to access and retrieve email addresses.\"\n}\n"
  },
  {
    "path": "pkg/detectors/getemail/getemail_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage getemail\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGetEmail_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GETEMAIL\")\n\tinactiveSecret := testSecrets.MustGetField(\"GETEMAIL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a getemail secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GetEmail,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a getemail secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GetEmail,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GetEmail.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"GetEmail.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/getemail/getemail_test.go",
    "content": "package getemail\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Getemail\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"getemail_secret\": \"oOJbzObT5MI0tBNEbTV8\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"oOJbzObT5MI0tBNEbTV8\"\n)\n\nfunc TestGetemail_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/getemails/getemails.go",
    "content": "package getemails\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"getemails\"}) + `\\b([a-z0-9-]{26})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"getemails\"}) + `\\b([a-z0-9-]{18})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"getemails\"}\n}\n\n// FromData will find and optionally verify GetEmails secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_GetEmails,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.getemails.com/api/v1/contacts\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"api-key\", resMatch)\n\t\t\t\treq.Header.Add(\"api-id\", resIdMatch)\n\n\t\t\t\tres, err := client.Do(req)\n\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GetEmails\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GetEmails is a service used to find email addresses of website visitors. The API keys can be used to access and manage contact data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/getemails/getemails_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage getemails\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGetEmails_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GETEMAILS\")\n\tid := testSecrets.MustGetField(\"GETEMAILS_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"GETEMAILS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a getemails secret %s within getemails %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GetEmails,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a getemails secret %s within getemails %s but not valid\", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GetEmails,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GetEmails.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"GetEmails.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/getemails/getemails_test.go",
    "content": "package getemails\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Getemails\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"getemails_key\": \"fhy0w66kd5ed2fjadegmb5ok50\",\n\t\t\t\"getemails_secret\": \"4-mc01babzbuqkjq8c\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"fhy0w66kd5ed2fjadegmb5ok50\"\n)\n\nfunc TestGetemails_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/getgeoapi/getgeoapi.go",
    "content": "package getgeoapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"getgeoapi\"}) + `\\b([0-9a-z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"getgeoapi\"}\n}\n\n// FromData will find and optionally verify Getgeoapi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_GetGeoAPI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.getgeoapi.com/v2/currency/list?api_key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GetGeoAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GetGeoAPI provides geolocation and currency conversion services. The API keys can be used to access these services and retrieve data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/getgeoapi/getgeoapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage getgeoapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGetgeoapi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GETGEOAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"GETGEOAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a getgeoapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GetGeoAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a getgeoapi secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GetGeoAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Getgeoapi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Getgeoapi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/getgeoapi/getgeoapi_test.go",
    "content": "package getgeoapi\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GetGeoAPI\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"getgeoapi_secret\": \"yxpckx30ido57mz9uboa3k8nf0kfv8cz858kxavh\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"yxpckx30ido57mz9uboa3k8nf0kfv8cz858kxavh\"\n)\n\nfunc TestGetGeoAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/getgist/getgist.go",
    "content": "package getgist\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"getgist\"}) + `\\b([a-z0-9A-Z+=]{68})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"getgist\"}\n}\n\n// FromData will find and optionally verify Getgist secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Getgist,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.getgist.com/contacts/\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Getgist\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Getgist is a service used for managing contacts and communications. Getgist API keys can be used to access and manage contact data and communication settings.\"\n}\n"
  },
  {
    "path": "pkg/detectors/getgist/getgist_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage getgist\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGetgist_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GETGIST_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"GETGIST_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a getgist secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Getgist,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a getgist secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Getgist,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Getgist.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Getgist.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/getgist/getgist_test.go",
    "content": "package getgist\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GetGist\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"getgist_secret\": \"N8Br7K+jbDiuLtKWw9Jd1Gr3OvC+vNSJYkGEmVYWduJ6tDmSaX0r3iWmUZT6cfWC=31u\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"N8Br7K+jbDiuLtKWw9Jd1Gr3OvC+vNSJYkGEmVYWduJ6tDmSaX0r3iWmUZT6cfWC=31u\"\n)\n\nfunc TestGetGist_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/getresponse/getresponse.go",
    "content": "package getresponse\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"getresponse\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"getresponse\"}\n}\n\n// FromData will find and optionally verify Getresponse secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Getresponse,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.getresponse.com/v3/accounts\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-Auth-Token\", fmt.Sprintf(\"api-key %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Getresponse\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GetResponse is an email marketing service. GetResponse API keys can be used to access and manage email marketing campaigns, contacts, and other related data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/getresponse/getresponse_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage getresponse\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGetresponse_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GETRESPONSE\")\n\tinactiveSecret := testSecrets.MustGetField(\"GETRESPONSE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a getresponse secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Getresponse,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a getresponse secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Getresponse,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Getresponse.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Getresponse.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/getresponse/getresponse_test.go",
    "content": "package getresponse\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GetResponse\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"getresponse_secret\": \"d5ru70ibb2c63a1z7vh5l6b9ou8rot29\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"d5ru70ibb2c63a1z7vh5l6b9ou8rot29\"\n)\n\nfunc TestGetResponse_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/getsandbox/getsandbox.go",
    "content": "package getsandbox\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"getsandbox\"}) + `\\b([a-z0-9-]{40})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"getsandbox\"}) + `\\b([a-z0-9-]{15,30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"getsandbox\"}\n}\n\n// FromData will find and optionally verify GetSandbox secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_GetSandbox,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://getsandbox.com/api/1/sandboxes/%s\", resIdMatch), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"API-Key\", resMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GetSandbox\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GetSandbox is a service for creating and managing sandboxes. GetSandbox API keys can be used to access and control these sandboxes.\"\n}\n"
  },
  {
    "path": "pkg/detectors/getsandbox/getsandbox_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage getsandbox\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGetSandbox_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GETSANDBOX\")\n\tid := testSecrets.MustGetField(\"GETSANDBOX_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"GETSANDBOX_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a getsandbox secret %s within getsandbox %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GetSandbox,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a getsandbox secret %s within getsandbox %s but not valid\", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GetSandbox,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GetSandbox.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"GetSandbox.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/getsandbox/getsandbox_test.go",
    "content": "package getsandbox\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GetSandbox\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"getsandbox_secret\": \"jovt715yi8nrre8-6ftll7p9g8fik8jxjmbghpfm\",\n\t\t\t\"getsandbox_key\": \"fx4x62tc7y05nz8t1h\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecrets = []string{\n\t\t\"jovt715yi8nrre8-6ftll7p9g8fik8jxjmbghpfm\",\n\t\t\"jovt715yi8nrre8-6ftll7p9g8fik8jxjmbghpfm\",\n\t}\n)\n\nfunc TestGetSandbox_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  secrets,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/github/v1/github_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage github\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGitHub_FromChunk(t *testing.T) {\n\tt.Skip(\"old tokens no longer valid, and no way to generate new ones\")\n\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GITHUB_OLD\")\n\tsecretInactive := testSecrets.MustGetField(\"GITHUB_OLD_INACTIVE\")\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a github secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Github,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/github/\",\n\t\t\t\t\t\t\"version\":        \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a github secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Github,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/github/\",\n\t\t\t\t\t\t\"version\":        \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found url\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"https://raw.github.com/k/d890e8640f20fba3215ba7be8e0ff145aeb8c17c/include/base64.js\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found ref\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"https://github.com/lz4/lz4 @ dccf8826f1d76efcbdc655e63cc04cdbd1123619\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GitHub.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"GitHub.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/github/v1/github_old.go",
    "content": "package github\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{ detectors.EndpointSetter }\n\n// Ensure the Scanner satisfies the interfaces at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\nvar _ detectors.EndpointCustomizer = (*Scanner)(nil)\nvar _ detectors.CloudProvider = (*Scanner)(nil)\n\nfunc (Scanner) Version() int          { return 1 }\nfunc (Scanner) CloudEndpoint() string { return \"https://api.github.com\" }\n\nvar (\n\t// Oauth token\n\t// https://developer.github.com/v3/#oauth2-token-sent-in-a-header\n\tkeyPat = regexp.MustCompile(\n\t\tdetectors.PrefixRegex([]string{\n\t\t\t\"github_token\", \"github_secret\", \"github_key\", \"github_api\", \"github_pat\",\n\t\t\t\"githubtoken\", \"githubsecret\", \"githubkey\", \"githubapi\", \"githubpat\",\n\t\t\t\"gh_token\", \"gh_secret\", \"gh_key\", \"gh_api\", \"gh_pat\",\n\t\t\t\"ghtoken\", \"ghsecret\", \"ghkey\", \"ghapi\", \"ghpat\",\n\t\t}) + `\\b([0-9a-f]{40})\\b`,\n\t)\n)\n\n// TODO: Add secret context?? Information about access, ownership etc\ntype UserRes struct {\n\tLogin     string `json:\"login\"`\n\tType      string `json:\"type\"`\n\tSiteAdmin bool   `json:\"site_admin\"`\n\tName      string `json:\"name\"`\n\tCompany   string `json:\"company\"`\n\tUserURL   string `json:\"html_url\"`\n\tEmail     string `json:\"email\"`\n\tLocation  string `json:\"location\"`\n\t// Included in GitHub Enterprise Server.\n\tLdapDN string `json:\"ldap_dn\"`\n}\n\ntype HeaderInfo struct {\n\tScopes string `json:\"X-OAuth-Scopes\"`\n\tExpiry string `json:\"github-authentication-token-expiration\"`\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"github\", \"gh\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Github\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GitHub is a web-based platform used for version control and collaborative software development. GitHub tokens can be used to access and modify repositories and other resources.\"\n}\n\nvar ghFalsePositives = map[detectors.FalsePositive]struct{}{\n\tdetectors.FalsePositive(\"github commit\"): {},\n}\n\nvar ghKnownNonSensitivePrefixes = []string{\n\t\"avatars.githubusercontent.com\",            // GitHub avatar URLs\n\t\"actions/\",                                 // GitHub Actions paths\n\t\"raw.githubusercontent.com/\",               // Raw file URLs from GitHub\n\t\"api.github.com/repos/\",                    // GitHub API repository endpoints\n\t\"gist.github.com/\",                         // GitHub Gist URLs\n\t\"sha256:\",                                  // SHA256 hash prefix\n\t\"github.com/\",                              // General GitHub repo URLs\n\t\"pipelines.actions.githubusercontent.com/\", // GitHub Actions infrastructure\n\t\"ghcr.io/\",                                 // GitHub Container Registry\n}\n\n// FromData will find and optionally verify GitHub secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\t// First match is entire regex, second is the first group.\n\t\tmatchPrefix := match[0]\n\t\ttoken := match[1]\n\n\t\t// It may seem strange to filter out findings prior to verifying them. However, this credential looks like a\n\t\t// normal sha256 hash, which is an incredibly common string to see. So the filter here prevents an excessive\n\t\t// number of requests to be sent for findings which will almost certainly not be verified. It must occur before\n\t\t// verification, because otherwise the number of verification requests can be quite excessive.\n\t\tif isKnownNonSensitiveCommonPrefix(matchPrefix) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Note that this false positive check happens **before** verification! I don't know why it's written this way\n\t\t// but that's why this logic wasn't moved into a CustomFalsePositiveChecker implementation.\n\t\tif isFp, _ := detectors.IsKnownFalsePositive(token, ghFalsePositives, false); isFp {\n\t\t\tcontinue\n\t\t}\n\n\t\tif detectors.StringShannonEntropy(token) < 3.5 {\n\t\t\tcontinue\n\t\t}\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Github,\n\t\t\tRaw:          []byte(token),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/github/\",\n\t\t\t\t\"version\":        fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t},\n\t\t\tAnalysisInfo: map[string]string{\"key\": token},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := common.SaneHttpClient()\n\n\t\t\tisVerified, userResponse, headers, err := s.VerifyGithub(ctx, client, token)\n\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(err, token)\n\n\t\t\tif userResponse != nil {\n\t\t\t\tSetUserResponse(userResponse, &s1)\n\t\t\t}\n\t\t\tif headers != nil {\n\t\t\t\tSetHeaderInfo(headers, &s1)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) VerifyGithub(ctx context.Context, client *http.Client, token string) (bool, *UserRes, *HeaderInfo, error) {\n\t// https://developer.github.com/v3/users/#get-the-authenticated-user\n\tvar requestErr error\n\tfor _, url := range s.Endpoints() {\n\t\trequestErr = nil\n\n\t\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"%s/user\", url), nil)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\treq.Header.Set(\"Content-Type\", \"application/json; charset=utf-8\")\n\t\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"token %s\", token))\n\t\tres, err := client.Do(req)\n\t\tif err != nil {\n\t\t\trequestErr = err\n\t\t\tcontinue\n\t\t}\n\n\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\tvar userResponse UserRes\n\t\t\terr = json.NewDecoder(res.Body).Decode(&userResponse)\n\t\t\tres.Body.Close()\n\t\t\tif err == nil {\n\t\t\t\t// GitHub does not seem to consistently return this header.\n\t\t\t\tscopes := res.Header.Get(\"X-OAuth-Scopes\")\n\t\t\t\texpiry := res.Header.Get(\"github-authentication-token-expiration\")\n\t\t\t\treturn true, &userResponse, &HeaderInfo{Scopes: scopes, Expiry: expiry}, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn false, nil, nil, requestErr\n}\n\nfunc SetUserResponse(userResponse *UserRes, s1 *detectors.Result) {\n\ts1.ExtraData[\"username\"] = userResponse.Login\n\ts1.ExtraData[\"url\"] = userResponse.UserURL\n\ts1.ExtraData[\"account_type\"] = userResponse.Type\n\n\tif userResponse.SiteAdmin {\n\t\ts1.ExtraData[\"site_admin\"] = \"true\"\n\t}\n\tif userResponse.Name != \"\" {\n\t\ts1.ExtraData[\"name\"] = userResponse.Name\n\t}\n\tif userResponse.Company != \"\" {\n\t\ts1.ExtraData[\"company\"] = userResponse.Company\n\t}\n\tif userResponse.LdapDN != \"\" {\n\t\ts1.ExtraData[\"ldap_dn\"] = userResponse.LdapDN\n\t}\n\n\t// email & location if user has made them public\n\tif userResponse.Email != \"\" {\n\t\ts1.ExtraData[\"email\"] = userResponse.Email\n\t}\n\tif userResponse.Location != \"\" {\n\t\ts1.ExtraData[\"location\"] = userResponse.Location\n\t}\n}\n\nfunc SetHeaderInfo(headers *HeaderInfo, s1 *detectors.Result) {\n\tif headers.Scopes != \"\" {\n\t\ts1.ExtraData[\"scopes\"] = headers.Scopes\n\t}\n\tif headers.Expiry != \"\" {\n\t\ts1.ExtraData[\"expiry\"] = headers.Expiry\n\t}\n}\n\n// isKnownNonSensitiveCommonPrefix checks if the given prefix is a known, non-sensitive value.\n// The GitHub v1 detector uses a broad regex that can capture many false positives.\n// This function helps filter out matches that begin with common, safe prefixes.\n// Example: avatars.githubusercontent.com/u/56769451?u=088102b6160822bc68c25a2a5df170080d0b16a2\nfunc isKnownNonSensitiveCommonPrefix(matchPrefix string) bool {\n\tfor _, prefix := range ghKnownNonSensitivePrefixes {\n\t\tif strings.Contains(matchPrefix, prefix) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "pkg/detectors/github/v1/github_old_test.go",
    "content": "package github\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestGithub_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `[{\n\t\t\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\t\t\"name\": \"Github\",\n\t\t\t\t\"type\": \"Detector\",\n\t\t\t\t\"api\": true,\n\t\t\t\t\"authentication_type\": \"\",\n\t\t\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\t\t\"test_secrets\": {\n\t\t\t\t\t\"github_secret\": \"abc123def4567890abcdef1234567890abcdef12\"\n\t\t\t\t},\n\t\t\t\t\"expected_response\": \"200\",\n\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\"deprecated\": false\n\t\t\t}]`,\n\t\t\twant: []string{\"abc123def4567890abcdef1234567890abcdef12\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_isKnownNonSensitiveCommonPrefix(t *testing.T) {\n\ttype args struct {\n\t\tmatchPrefix string\n\t}\n\ttests := []struct {\n\t\tname          string\n\t\targs          args\n\t\tisKnownPrefix bool\n\t}{\n\t\t{\n\t\t\tname:          \"repo url\",\n\t\t\targs:          args{matchPrefix: \"github.com/repos/symfony/monolog-bridge/zipball/9d14621e59f22c2b6d030d92d37ffe5ae1e60452\"},\n\t\t\tisKnownPrefix: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"sha256 hash\",\n\t\t\targs:          args{matchPrefix: \"Digest: sha256:f9a92af4d46ca171bffa5c00509414a19d9887c9ed4fe98d1f43757b52600e39\"},\n\t\t\tisKnownPrefix: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"real looking token\",\n\t\t\targs:          args{matchPrefix: \"github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e\"},\n\t\t\tisKnownPrefix: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"github url\",\n\t\t\targs:          args{matchPrefix: \"github.com/wrandelshofer/FastDoubleParser/blob/39e123b15b71f29a38a087d16a0bc620fc879aa6/bigint-LICENSE\"},\n\t\t\tisKnownPrefix: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := isKnownNonSensitiveCommonPrefix(tt.args.matchPrefix); got != tt.isKnownPrefix {\n\t\t\t\tt.Errorf(\"isKnownNonSensitiveCommonPrefix() = %v, want %v\", got, tt.isKnownPrefix)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/github/v2/github.go",
    "content": "package github\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\tv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/github/v1\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tv1.Scanner\n}\n\n// Ensure the Scanner satisfies the interfaces at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\nvar _ detectors.EndpointCustomizer = (*Scanner)(nil)\nvar _ detectors.CloudProvider = (*Scanner)(nil)\n\nfunc (s Scanner) Version() int {\n\treturn 2\n}\nfunc (Scanner) CloudEndpoint() string { return \"https://api.github.com\" }\n\nvar (\n\t// Oauth token\n\t// https://developer.github.com/v3/#oauth2-token-sent-in-a-header\n\t// Token type list:\n\t// https://github.blog/2021-04-05-behind-githubs-new-authentication-token-formats/\n\t// https://github.blog/changelog/2022-10-18-introducing-fine-grained-personal-access-tokens/\n\tkeyPat = regexp.MustCompile(`\\b((?:ghp|gho|ghu|ghs|ghr|github_pat)_[a-zA-Z0-9_]{36,255})\\b`)\n\n\t// TODO: Oauth2 client_id and client_secret\n\t// https://developer.github.com/v3/#oauth2-keysecret\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ghp_\", \"gho_\", \"ghu_\", \"ghs_\", \"ghr_\", \"github_pat_\"}\n}\n\n// FromData will find and optionally verify GitHub secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\t// First match is entire regex, second is the first group.\n\n\t\ttoken := match[1]\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Github,\n\t\t\tRaw:          []byte(token),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/github/\",\n\t\t\t\t\"version\":        fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t},\n\t\t\tAnalysisInfo: map[string]string{\"key\": token},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := common.SaneHttpClient()\n\n\t\t\tisVerified, userResponse, headers, err := s.VerifyGithub(ctx, client, token)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(err, token)\n\n\t\t\tif userResponse != nil {\n\t\t\t\tv1.SetUserResponse(userResponse, &s1)\n\t\t\t}\n\t\t\tif headers != nil {\n\t\t\t\tv1.SetHeaderInfo(headers, &s1)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Github\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GitHub is a platform for version control and collaboration. Personal access tokens (PATs) can be used to access and modify repositories and other resources.\"\n}\n"
  },
  {
    "path": "pkg/detectors/github/v2/github_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage github\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGitHub_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tunverifiedGhp := testSecrets.MustGetField(\"GITHUB_UNVERIFIED_GHP\")\n\tunverifiedGhpLong := testSecrets.MustGetField(\"GITHUB_UNVERIFIED_GHP_LONG\")\n\tunverifiedGho := testSecrets.MustGetField(\"GITHUB_UNVERIFIED_GHO\")\n\tunverifiedGhu := testSecrets.MustGetField(\"GITHUB_UNVERIFIED_GHU\")\n\tunverifiedGhs := testSecrets.MustGetField(\"GITHUB_UNVERIFIED_GHS\")\n\tunverifiedGhr := testSecrets.MustGetField(\"GITHUB_UNVERIFIED_GHR\")\n\tverifiedGhp := testSecrets.MustGetField(\"GITHUB_VERIFIED_GHP\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified ghp\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a github secret %s within\", verifiedGhp)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Github,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"account_type\": \"User\",\n\t\t\t\t\t\t// \"company\":        \"\", // not present in test verifiedGhp\n\t\t\t\t\t\t// \"name\":           \"\", // not present in test verifiedGhp\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/github/\",\n\t\t\t\t\t\t\"scopes\":         \"notifications\",\n\t\t\t\t\t\t// \"site_admin\":     \"false\", // not present in test verifiedGhp\n\t\t\t\t\t\t\"url\":      \"https://github.com/truffle-sandbox\",\n\t\t\t\t\t\t\"username\": \"truffle-sandbox\",\n\t\t\t\t\t\t\"version\":  \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified ghp\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a github secret %s within\", unverifiedGhp)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Github,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/github/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified gho\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a github secret %s within\", unverifiedGho)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Github,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/github/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified ghu\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a github secret %s within\", unverifiedGhu)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Github,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/github/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified ghs\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a github secret %s within\", unverifiedGhs)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Github,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/github/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified ghr\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a github secret %s within\", unverifiedGhr)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Github,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/github/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified ghp future length 255\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a github secret %s within\", unverifiedGhpLong)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Github,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/github/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"https://raw.github.com/k/d890e8640f20fba3215ba7be8e0ff145aeb8c17c/include/base64.js\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\ts.UseCloudEndpoint(true)\n\t\t\ts.SetCloudEndpoint(s.CloudEndpoint())\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GitHub.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"GitHub.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/github/v2/github_test.go",
    "content": "package github\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Github\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"github_secret\": \"ghs_RWGUZ6kS8_Ut7PbtR72k2miJwwYtxkpe8mOpT8feAWYZcwz43PxBVGCNATnycaQV9VUlPJe1uST5Xen7d3uZ5lilVlEVvT9AbxnhURdT3OzPtCvXydIrvE4LrDO\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"ghs_RWGUZ6kS8_Ut7PbtR72k2miJwwYtxkpe8mOpT8feAWYZcwz43PxBVGCNATnycaQV9VUlPJe1uST5Xen7d3uZ5lilVlEVvT9AbxnhURdT3OzPtCvXydIrvE4LrDO\"\n)\n\nfunc TestGithub_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/github_oauth2/github_oauth2.go",
    "content": "package github_oauth2\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"golang.org/x/oauth2/clientcredentials\"\n\t\"golang.org/x/oauth2/github\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interfaces at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\t// Oauth2 client ID and secret\n\toauth2ClientIDPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"github\"}) + `\\b([a-zA-Z0-9]{20})\\b`)\n\toauth2ClientSecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"github\"}) + `\\b([a-f0-9]{40})\\b`)\n)\n\nconst (\n\tgithubBadVerificationCodeError = \"bad_verification_code\"\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"github\"}\n}\n\n// FromData will find and optionally verify GitHub secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// Oauth2 client ID and secret\n\toauth2ClientIDMatches := oauth2ClientIDPat.FindAllStringSubmatch(dataStr, -1)\n\toauth2ClientSecretMatches := oauth2ClientSecretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, idMatch := range oauth2ClientIDMatches {\n\t\tfor _, secretMatch := range oauth2ClientSecretMatches {\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_GitHubOauth2,\n\t\t\t\tRaw:          []byte(idMatch[1]),\n\t\t\t\tRawV2:        []byte(idMatch[1] + secretMatch[1]),\n\t\t\t}\n\t\t\ts1.ExtraData = map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/github/\",\n\t\t\t}\n\n\t\t\tconfig := &clientcredentials.Config{\n\t\t\t\tClientID:     idMatch[1],\n\t\t\t\tClientSecret: secretMatch[1],\n\t\t\t\tTokenURL:     github.Endpoint.TokenURL,\n\t\t\t}\n\t\t\tif verify {\n\t\t\t\t_, err := config.Token(ctx)\n\t\t\t\t// if client id and client secret is correct, it will return bad verification code error as we do not pass any verification code\n\t\t\t\t// docs: https://docs.github.com/en/apps/oauth-apps/maintaining-oauth-apps/troubleshooting-oauth-app-access-token-request-errors#bad-verification-code\n\t\t\t\tif err != nil && strings.Contains(err.Error(), githubBadVerificationCodeError) {\n\t\t\t\t\t// mark result as verified only in case of bad verification code error, for any other error the result will be unverified\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GitHubOauth2\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GitHub OAuth2 credentials are used to authenticate and authorize applications to access GitHub's API on behalf of a user or organization. These credentials include a client ID and client secret, which can be used to obtain access tokens for accessing GitHub resources.\"\n}\n"
  },
  {
    "path": "pkg/detectors/github_oauth2/github_oauth2_test.go",
    "content": "package github_oauth2\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestGithubOAuth2_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern - with keyword github\",\n\t\t\tinput: \"github = '9c14koUc3f04PrzlpCcU', 'caa3224b5ddb83924e6a487ee3f6543bae428309'\",\n\t\t\twant: []string{\n\t\t\t\t\"9c14koUc3f04PrzlpCcUcaa3224b5ddb83924e6a487ee3f6543bae428309\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"typical pattern - invalid client ID\",\n\t\t\tinput: \"github = '10c14koUc3f04PrzlpCcU', 'caa3224b5ddb83924e6a487ee3f6543bae428309'\",\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/githubapp/githubapp.go",
    "content": "package githubapp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/golang-jwt/jwt/v5\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tappPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"github\"}) + `\\b([0-9]{6})\\b`)\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"github\"}) + `(-----BEGIN RSA PRIVATE KEY-----\\s[A-Za-z0-9+\\/\\s]*\\s-----END RSA PRIVATE KEY-----)`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"github\"}\n}\n\n// FromData will find and optionally verify GitHubApp secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tappMatches := appPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, appMatch := range appMatches {\n\t\t\tappResMatch := strings.TrimSpace(appMatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_GitHubApp,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\t\t\ts1.ExtraData = map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/github/\",\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tsignKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(resMatch))\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\t// issued at time\n\t\t\t\tiat := time.Now().Add(-60 * time.Second)\n\t\t\t\texp := time.Now().Add(9 * 60 * time.Second)\n\n\t\t\t\tiss := appResMatch\n\t\t\t\ttoken := jwt.New(jwt.SigningMethodRS256)\n\t\t\t\tclaims := token.Claims.(jwt.MapClaims)\n\t\t\t\tclaims[\"iat\"] = iat.Unix()\n\t\t\t\tclaims[\"exp\"] = exp.Unix()\n\t\t\t\tclaims[\"iss\"] = iss\n\t\t\t\ttokenString, err := token.SignedString(signKey)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\t// end get token\n\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.github.com/app\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Accept\", \"application/vnd.github.v3+json\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", tokenString))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GitHubApp\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GitHub Apps allow you to automate and improve your workflow. GitHub App keys can be used to authenticate and interact with the GitHub API on behalf of the app.\"\n}\n"
  },
  {
    "path": "pkg/detectors/githubapp/githubapp_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage githubapp\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGitHubApp_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GITHUBAPP_PRIVATEKEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"GITHUBAPP_PRIVATEKEY_INACTIVE\")\n\tappKey := testSecrets.MustGetField(\"GITHUBAPP_APPKEY\")\n\n\tsDec, _ := b64.StdEncoding.DecodeString(secret)\n\tinactiveSDec, _ := b64.StdEncoding.DecodeString(inactiveSecret)\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a githubapp secret %s within githubapp %s\", sDec, appKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GitHubApp,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:  context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(\"You can find a githubapp secret %s within githubapp %s but not valid\", inactiveSDec, appKey)), // the secret would satisfy the regex but not pass validation\n\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GitHubApp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GitHubApp.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].ExtraData = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"GitHubApp.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/githubapp/githubapp_test.go",
    "content": "package githubapp\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GithubApp\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"githubapp_key\": \"456731\",\n\t\t\t\"githubapp_secret\": \"-----BEGIN RSA PRIVATE KEY----- Rg8Tc1jrjetAy G+zTXJG+iTiGMZLmwHSO49LMIAnMHZYUJcXpxFw Y+ -----END RSA PRIVATE KEY-----\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"-----BEGIN RSA PRIVATE KEY----- Rg8Tc1jrjetAy G+zTXJG+iTiGMZLmwHSO49LMIAnMHZYUJcXpxFw Y+ -----END RSA PRIVATE KEY-----\"\n)\n\nfunc TestGithubApp_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gitlab/v1/gitlab.go",
    "content": "package gitlab\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"maps\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.EndpointSetter\n}\n\n// Ensure the Scanner satisfies the interfaces at compile time.\nvar (\n\t_ detectors.Detector           = (*Scanner)(nil)\n\t_ detectors.EndpointCustomizer = (*Scanner)(nil)\n\t_ detectors.Versioner          = (*Scanner)(nil)\n\t_ detectors.CloudProvider      = (*Scanner)(nil)\n)\n\nfunc (Scanner) Version() int          { return 1 }\nfunc (Scanner) CloudEndpoint() string { return \"https://gitlab.com\" }\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\tkeyPat        = regexp.MustCompile(detectors.PrefixRegex([]string{\"gitlab\"}) + `\\b([a-zA-Z0-9][a-zA-Z0-9\\-=_]{19,21})\\b`)\n\n\tBlockedUserMessage = \"403 Forbidden - Your account has been blocked\"\n)\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"gitlab\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Gitlab\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GitLab is a web-based DevOps lifecycle tool that provides a Git repository manager providing wiki, issue-tracking, and CI/CD pipeline features. GitLab API tokens can be used to access and modify repository data and other resources.\"\n}\n\n// FromData will find and optionally verify Gitlab secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\t// ignore v2 detectors which have a prefix of `glpat-`\n\t\tif strings.Contains(match[0], \"glpat-\") {\n\t\t\tcontinue\n\t\t}\n\n\t\t// to avoid false positives\n\t\tif detectors.StringShannonEntropy(resMatch) < 3.6 {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, endpoint := range s.Endpoints() {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + endpoint),\n\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\"version\":        fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, extraData, verificationErr := VerifyGitlab(ctx, s.getClient(), endpoint, resMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\tmaps.Copy(s1.ExtraData, extraData)\n\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\n\t\t\t\t// for verified keys set the analysis info\n\t\t\t\tif s1.Verified {\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\"key\":  resMatch,\n\t\t\t\t\t\t\"host\": endpoint,\n\t\t\t\t\t}\n\n\t\t\t\t\t// if secret is verified with one endpoint, break the loop to continue to next secret\n\t\t\t\t\tresults = append(results, s1)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc VerifyGitlab(ctx context.Context, client *http.Client, baseEndpoint, resMatch string) (bool, map[string]string, error) {\n\t// there are 4 read 'scopes' for a gitlab token: api, read_user, read_repo, and read_registry\n\t// they all grant access to different parts of the API. I couldn't find an endpoint that every\n\t// one of these scopes has access to, so we just check an example endpoint for each scope. If any\n\t// of them contain data, we know we have a valid key, but if they all fail, we don't\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, baseEndpoint+\"/api/v4/user\", http.NoBody)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\tdefer res.Body.Close()\n\n\tbodyBytes, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\t// 200 means good key and has `read_user` scope\n\t// 403 means good key but not the right scope\n\t// 401 is bad key\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn json.Valid(bodyBytes), nil, nil\n\tcase http.StatusForbidden:\n\t\t// check if the user account is blocked or not\n\t\tstringBody := string(bodyBytes)\n\t\tif strings.Contains(stringBody, BlockedUserMessage) {\n\t\t\treturn true, map[string]string{\n\t\t\t\t\"blocked\": \"True\",\n\t\t\t}, nil\n\t\t}\n\n\t\t// Good key but not the right scope\n\t\treturn true, nil, nil\n\tcase http.StatusUnauthorized:\n\t\t// Nothing to do; zero values are the ones we want\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gitlab/v1/gitlab_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage gitlab\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGitlab_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GITLAB\")\n\tsecretInactive := testSecrets.MustGetField(\"GITLAB_INACTIVE\")\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\t\"version\":        \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found only secret phrase\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"gitlab %s\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\t\"version\":        \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\t\"version\":        \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified but for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\t\"version\":        \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found and valid but unexpected api response\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\t\"version\":        \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, good key but wrong scope\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(403, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\t\"version\":        \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.s.SetCloudEndpoint(\"https://gitlab.com\")\n\t\t\ttt.s.UseCloudEndpoint(true)\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\" wantVerificationError = %v, verification error = %v,\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\topts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, opts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// This test ensures gitlab v1 detector does not work on gitlab v2 secrets\nfunc TestGitlab_FromChunk_WithV2Secrets(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GITLABV2\")\n\tsecretInactive := testSecrets.MustGetField(\"GITLABV2_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"verified secret, not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"unverified secret, not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\" wantVerificationError = %v, verification error = %v,\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\topts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, opts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// This test ensures gitlab v1 detector does not work on gitlab v3 secrets\nfunc TestGitlab_FromChunk_WithV3Secrets(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GITLABV3\")\n\tsecretInactive := testSecrets.MustGetField(\"GITLABV3_INACTIVE\")\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"verified v3 secret, not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"verified v3 secret, not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"gitlab %s\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"unverified v3 secret, not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.s.SetCloudEndpoint(\"https://gitlab.com\")\n\t\t\ttt.s.UseCloudEndpoint(true)\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\" wantVerificationError = %v, verification error = %v,\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\topts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, opts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkV2FromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gitlab/v1/gitlab_v1_test.go",
    "content": "package gitlab\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestGitLab_Pattern(t *testing.T) {\n\td := Scanner{}\n\td.SetCloudEndpoint(\"https://gitlab.com\")\n\td.UseCloudEndpoint(true)\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `[{\n\t\t\t\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\t\t\t\"name\": \"Gitlab\",\n\t\t\t\t\t\"type\": \"Detector\",\n\t\t\t\t\t\"api\": true,\n\t\t\t\t\t\"authentication_type\": \"\",\n\t\t\t\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\t\t\t\"test_secrets\": {\n\t\t\t\t\t\t\"gitlab_secret\": \"oXCt4JT2wf1_WlZl2OVG\"\n\t\t\t\t\t},\n\t\t\t\t\t\"docs\":\"https://docs.gitlab.com/test/api/example.json#get-drone-test-example-settings\", // this matches the pattern but fail in entropy check\n\t\t\t\t\t\"expected_response\": \"200\",\n\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\"deprecated\": false\n\t\t\t\t}]`,\n\t\t\twant: []string{\"oXCt4JT2wf1_WlZl2OVGhttps://gitlab.com\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern (with = before secret)\",\n\t\t\tinput: \"GITLAB_TOKEN=ABc123456789dEFghIJK\",\n\t\t\twant:  []string{\"ABc123456789dEFghIJKhttps://gitlab.com\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gitlab/v2/gitlab_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage gitlab\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\n// This test ensures gitlab v2 detector does not work on gitlab v1 secrets\nfunc TestGitlabV2_FromChunk_WithV1Secrets(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GITLAB\")\n\tsecretInactive := testSecrets.MustGetField(\"GITLAB_INACTIVE\")\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"verified v1 secret, not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"verified v1 secret, not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"gitlab %s\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"unverified v1 secret, not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.s.SetCloudEndpoint(\"https://gitlab.com\")\n\t\t\ttt.s.UseCloudEndpoint(true)\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\" wantVerificationError = %v, verification error = %v,\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\topts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, opts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// This test ensures gitlab v2 detector does not work on gitlab v3 secrets\nfunc TestGitlabV2_FromChunk_WithV3Secrets(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GITLABV3\")\n\tsecretInactive := testSecrets.MustGetField(\"GITLABV3_INACTIVE\")\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"verified v3 secret, not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"verified v3 secret, not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"gitlab %s\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"unverified v3 secret, not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.s.SetCloudEndpoint(\"https://gitlab.com\")\n\t\t\ttt.s.UseCloudEndpoint(true)\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\" wantVerificationError = %v, verification error = %v,\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\topts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, opts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGitlabV2_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GITLABV2\")\n\tsecretInactive := testSecrets.MustGetField(\"GITLABV2_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified but for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found and valid but unexpected api response\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, good key but wrong scope\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(403, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.s.UseCloudEndpoint(true)\n\t\t\ttt.s.SetCloudEndpoint(tt.s.CloudEndpoint())\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\" wantVerificationError = %v, verification error = %v,\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\topts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, opts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkV2FromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gitlab/v2/gitlab_v2.go",
    "content": "package gitlab\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"maps\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\tv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gitlab/v1\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.EndpointSetter\n}\n\n// Ensure the Scanner satisfies the interfaces at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.EndpointCustomizer = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nfunc (Scanner) Version() int          { return 2 }\nfunc (Scanner) CloudEndpoint() string { return \"https://gitlab.com\" }\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\tkeyPat        = regexp.MustCompile(`\\b(glpat-[a-zA-Z0-9\\-=_]{20,22})\\b`)\n)\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string { return []string{\"glpat-\"} }\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Gitlab\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GitLab is a web-based DevOps lifecycle tool that provides a Git repository manager providing wiki, issue-tracking, and CI/CD pipeline features. GitLab Personal Access Tokens (PATs) can be used to authenticate and access GitLab resources.\"\n}\n\n// FromData will find and optionally verify Gitlab secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, endpoint := range s.Endpoints() {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + endpoint),\n\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\"version\":        fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tif verify {\n\n\t\t\t\tisVerified, extraData, verificationErr := v1.VerifyGitlab(ctx, s.getClient(), endpoint, resMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\tmaps.Copy(s1.ExtraData, extraData)\n\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\n\t\t\t\t// for verified keys set the analysis info\n\t\t\t\tif s1.Verified {\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\"key\":  resMatch,\n\t\t\t\t\t\t\"host\": endpoint,\n\t\t\t\t\t}\n\n\t\t\t\t\t// if secret is verified with one endpoint, break the loop to continue to next secret\n\t\t\t\t\tresults = append(results, s1)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n"
  },
  {
    "path": "pkg/detectors/gitlab/v2/gitlab_v2_test.go",
    "content": "package gitlab\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestGitLab_Pattern(t *testing.T) {\n\td := Scanner{}\n\td.SetCloudEndpoint(\"https://gitlab.com\")\n\td.UseCloudEndpoint(true)\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `[{\n\t\t\t\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\t\t\t\"name\": \"Gitlab\",\n\t\t\t\t\t\"type\": \"Detector\",\n\t\t\t\t\t\"api\": true,\n\t\t\t\t\t\"authentication_type\": \"\",\n\t\t\t\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\t\t\t\"test_secrets\": {\n\t\t\t\t\t\t\"gitlab_secret\": \"glpat-W6fYSu70dPEo5w_SwbHWgQ\"\n\t\t\t\t\t},\n\t\t\t\t\t\"expected_response\": \"200\",\n\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\"deprecated\": false\n\t\t\t\t}]`,\n\t\t\twant: []string{\"glpat-W6fYSu70dPEo5w_SwbHWgQhttps://gitlab.com\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gitlab/v3/gitlab_v3.go",
    "content": "package gitlab\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"maps\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\tv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gitlab/v1\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.EndpointSetter\n}\n\n// Ensure the Scanner satisfies the interfaces at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.EndpointCustomizer = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nfunc (Scanner) Version() int          { return 3 }\nfunc (Scanner) CloudEndpoint() string { return \"https://gitlab.com\" }\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// pattern taken from gitlab's PR for the format change: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169322\n\tkeyPat = regexp.MustCompile(`\\b(glpat-[a-zA-Z0-9\\-=_]{27,300}.[0-9a-z]{2}.[a-z0-9]{9})\\b`)\n)\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string { return []string{\"glpat-\"} }\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Gitlab\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GitLab is a web-based DevOps lifecycle tool that provides a Git repository manager providing wiki, issue-tracking, and CI/CD pipeline features. GitLab Personal Access Tokens (PATs) can be used to authenticate and access GitLab resources.\"\n}\n\n// FromData will find and optionally verify Gitlab secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, endpoint := range s.Endpoints() {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + endpoint),\n\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\"version\":        fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tif verify {\n\n\t\t\t\tisVerified, extraData, verificationErr := v1.VerifyGitlab(ctx, s.getClient(), endpoint, resMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\tmaps.Copy(s1.ExtraData, extraData)\n\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\n\t\t\t\t// for verified keys set the analysis info\n\t\t\t\tif s1.Verified {\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\"key\":  resMatch,\n\t\t\t\t\t\t\"host\": endpoint,\n\t\t\t\t\t}\n\n\t\t\t\t\t// if secret is verified with one endpoint, break the loop to continue to next secret\n\t\t\t\t\tresults = append(results, s1)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n"
  },
  {
    "path": "pkg/detectors/gitlab/v3/gitlab_v3_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage gitlab\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGitlabV3_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GITLABV3\")\n\tsecretInactive := testSecrets.MustGetField(\"GITLABV3_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\t\"version\":        \"3\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\t\"version\":        \"3\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified but for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\t\"version\":        \"3\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found and valid but unexpected api response\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\t\"version\":        \"3\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, good key but wrong scope\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(403, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitlab,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/gitlab/\",\n\t\t\t\t\t\t\"version\":        \"3\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.s.UseCloudEndpoint(true)\n\t\t\ttt.s.SetCloudEndpoint(tt.s.CloudEndpoint())\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\" wantVerificationError = %v, verification error = %v,\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\topts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, opts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// This test ensures gitlab v3 detector does not work on gitlab v1 secrets\nfunc TestGitlabV3_FromChunk_WithV1Secrets(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GITLAB\")\n\tsecretInactive := testSecrets.MustGetField(\"GITLAB_INACTIVE\")\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"verified v1 secret, not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"verified v1 secret, not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"gitlab %s\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"unverified v1 secret, not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.s.SetCloudEndpoint(\"https://gitlab.com\")\n\t\t\ttt.s.UseCloudEndpoint(true)\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\" wantVerificationError = %v, verification error = %v,\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\topts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, opts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// This test ensures gitlab v3 detector does not work on gitlab v2 secrets\nfunc TestGitlabV3_FromChunk_WithV2Secrets(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GITLABV2\")\n\tsecretInactive := testSecrets.MustGetField(\"GITLABV2_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"verified secret, not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"unverified secret, not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitlab secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\" wantVerificationError = %v, verification error = %v,\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\topts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, opts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gitlab/v3/gitlab_v3_test.go",
    "content": "package gitlab\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestGitLab_Pattern(t *testing.T) {\n\td := Scanner{}\n\td.SetCloudEndpoint(\"https://gitlab.com\")\n\td.UseCloudEndpoint(true)\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `[{\n\t\t\t\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\t\t\t\"name\": \"Gitlab\",\n\t\t\t\t\t\"type\": \"Detector\",\n\t\t\t\t\t\"api\": true,\n\t\t\t\t\t\"authentication_type\": \"\",\n\t\t\t\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\t\t\t\"test_secrets\": {\n\t\t\t\t\t\t\"gitlab_secret\": \"glpat-3fZ1p5y4XWcCvMGVlfakeW86MQp1Oml3Ymg0Cw.01.1203afake\"\n\t\t\t\t\t},\n\t\t\t\t\t\"expected_response\": \"200\",\n\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\"deprecated\": false\n\t\t\t\t}]`,\n\t\t\twant: []string{\"glpat-3fZ1p5y4XWcCvMGVlfakeW86MQp1Oml3Ymg0Cw.01.1203afakehttps://gitlab.com\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gitter/gitter.go",
    "content": "package gitter\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"gitter\"}) + `\\b([a-z0-9-]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"gitter\"}\n}\n\n// FromData will find and optionally verify Gitter secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Gitter,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.gitter.im/v1/user/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Gitter\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Gitter is a chat and networking platform. Gitter API keys can be used to access and interact with Gitter services and data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/gitter/gitter_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage gitter\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGitter_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GITTER\")\n\tinactiveSecret := testSecrets.MustGetField(\"GITTER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitter secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitter,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gitter secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gitter,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Gitter.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Gitter.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gitter/gitter_test.go",
    "content": "package gitter\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Gitter\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"gitter_secret\": \"tksznjt2cw6zwrrpbrryjah13g0wteb0j5ljlj7j\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"tksznjt2cw6zwrrpbrryjah13g0wteb0j5ljlj7j\"\n)\n\nfunc TestGitter_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/glassnode/glassnode.go",
    "content": "package glassnode\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"glassnode\"}) + `\\b([0-9A-Za-z]{27})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"glassnode\"}\n}\n\n// FromData will find and optionally verify Glassnode secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Glassnode,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.glassnode.com/v1/metrics/indicators/sopr?a=btc&api_key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Glassnode\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Glassnode is an on-chain market intelligence platform providing data and insights on blockchain and cryptocurrency markets. Glassnode API keys can be used to access various blockchain metrics and data points.\"\n}\n"
  },
  {
    "path": "pkg/detectors/glassnode/glassnode_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage glassnode\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGlassnode_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GLASSNODE\")\n\tinactiveSecret := testSecrets.MustGetField(\"GLASSNODE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a glassnode secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Glassnode,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a glassnode secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Glassnode,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Glassnode.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Glassnode.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/glassnode/glassnode_test.go",
    "content": "package glassnode\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Glassnode\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"glassnode_secret\": \"g7ms7BebQFCDMgIMcBBupn25iUd\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"g7ms7BebQFCDMgIMcBBupn25iUd\"\n)\n\nfunc TestGlassNode_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gocanvas/gocanvas.go",
    "content": "package gocanvas\n\nimport (\n\t\"context\"\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"gocanvas\"}) + `\\b([0-9A-Za-z/+]{43}=[ \\r\\n]{1})`)\n\temailPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"gocanvas\"}) + common.EmailPattern)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"gocanvas\"}\n}\n\n// FromData will find and optionally verify GoCanvas secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tuniqueEmailMatches := make(map[string]struct{})\n\tfor _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor emailMatch := range uniqueEmailMatches {\n\t\tfor _, match := range matches {\n\t\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_GoCanvas,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tpayload := url.Values{}\n\t\t\t\tpayload.Add(\"username\", emailMatch)\n\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.gocanvas.com/apiv2/forms.xml\", strings.NewReader(payload.Encode()))\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tbody, errBody := io.ReadAll(res.Body)\n\n\t\t\t\t\tif errBody == nil {\n\t\t\t\t\t\tresponse := Response{}\n\t\t\t\t\t\tif err := xml.Unmarshal(body, &response); err != nil {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && response.Error == nil {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\ntype Response struct {\n\tError []struct {\n\t\tErrorCode int `xml:\"ErrorCode\"`\n\t} `xml:\"Error\"`\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GoCanvas\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GoCanvas is a platform for automating business processes using mobile forms. GoCanvas API keys can be used to access and modify data within the GoCanvas platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/gocanvas/gocanvas_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage gocanvas\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGoCanvas_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tusername := testSecrets.MustGetField(\"SCANNERS_EMAIL\")\n\tsecret := testSecrets.MustGetField(\"GOCANVAS\")\n\tinactiveSecret := testSecrets.MustGetField(\"GOCANVAS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gocanvas username %s with gocanvas secret %s within\", username, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GoCanvas,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gocanvas username %s with gocanvas secret %s within but not valid\", username, inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GoCanvas,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GoCanvas.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"GoCanvas.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gocanvas/gocanvas_test.go",
    "content": "package gocanvas\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"Abc123+/Xyz456mnopQRStuvw89YZ12345678ABad6C= / gocanvasemail = testuser1005@example.com\"\n\tinvalidPattern = \"abcD123efg456HIJklmn789OPQ_rstUVWxYZ-012/testing@go\"\n)\n\nfunc TestGoCanvas_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: fmt.Sprintf(\"gocanvas: %s\", validPattern),\n\t\t\twant:  []string{\"Abc123+/Xyz456mnopQRStuvw89YZ12345678ABad6C=\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"gocanvas keyword is not close to the real key and id = %s\", validPattern),\n\t\t\twant:  nil,\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"gocanvas: %s\", invalidPattern),\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 && test.want != nil {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"expected %d results, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gocardless/gocardless.go",
    "content": "package gocardless\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\t// Removed bounds at the end of the regex since there are some cases that the token ends with an underscore (_) or a dash (-)\n\tkeyPat = regexp.MustCompile(`\\b(live_[0-9A-Za-z\\_\\-]{40}[ \"'\\r\\n]{1})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"gocardless\"}\n}\n\n// FromData will find and optionally verify GoCardless secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_GoCardless,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.gocardless.com/customers/\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\treq.Header.Add(\"GoCardless-Version\", \"2015-07-06\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GoCardless\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GoCardless is an online payments service that makes collecting payments by direct debit easy for everyone. GoCardless API keys can be used to access and manage payment transactions.\"\n}\n"
  },
  {
    "path": "pkg/detectors/gocardless/gocardless_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage gocardless\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGoCardless_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GOCARDLESS\")\n\tinactiveSecret := testSecrets.MustGetField(\"GOCARDLESS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gocardless secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GoCardless,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gocardless secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GoCardless,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GoCardless.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"GoCardless.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gocardless/gocardless_test.go",
    "content": "package gocardless\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GoCardless\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"gocardless_secret\": \"live__cH1Sorzhk0st1wlWeIdJpHY32hkgTsvk3SxojnY\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = `live__cH1Sorzhk0st1wlWeIdJpHY32hkgTsvk3SxojnY\"`\n)\n\nfunc TestGoCardless_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/godaddy/v1/godaddy.go",
    "content": "package godaddy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nvar (\n\t// ensure the scanner satisfies the interface at compile time.\n\t_ detectors.Detector  = (*Scanner)(nil)\n\t_ detectors.Versioner = (*Scanner)(nil)\n\n\tdefaultClient = common.SaneHttpClient()\n\n\t// the key for the GoDaddy OTE environment is a 37-character alphanumeric string that may include underscores.\n\tkeyPattern = regexp.MustCompile(detectors.PrefixRegex([]string{\"godaddy\"}) + common.BuildRegex(\"a-zA-Z0-9\", \"_\", 37))\n\t// the secret for the GoDaddy OTE environment is a 22-character alphanumeric string.\n\tsecretPattern = regexp.MustCompile(detectors.PrefixRegex([]string{\"godaddy\"}) + common.BuildRegex(\"a-zA-Z0-9\", \"\", 22))\n\n\t// ote environment\n\tote = \"api.ote-godaddy.com\"\n)\n\nfunc (s *Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\nfunc (s *Scanner) Version() int { return 1 }\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"godaddy\"}\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GoDaddy offers website building, hosting and security tools and services to construct, expand and protect the online presence.\" +\n\t\t\"GoDaddy provides applications and access to relevant third-party products and platforms to connect their customers\"\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GoDaddy\n}\n\n// FromData will find and optionally verify GoDaddy API Key and secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\t// convert the data to string\n\tdataStr := string(data)\n\n\t// find all the matching keys and secret in data and make a unique maps of both keys and secret.\n\tuniqueKeys, uniqueSecrets := make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, foundKey := range keyPattern.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[foundKey[1]] = struct{}{}\n\t}\n\n\tfor _, foundSecret := range secretPattern.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueSecrets[foundSecret[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\tfor secret := range uniqueSecrets {\n\t\t\tresult := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_GoDaddy,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tExtraData:    make(map[string]string),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, verificationErr := VerifyGoDaddySecret(ctx, s.getClient(), ote, MakeAuthHeaderValue(key, secret))\n\n\t\t\t\tresult.Verified = isVerified\n\t\t\t\tresult.SetVerificationError(verificationErr, secret)\n\n\t\t\t\t// add the env name in extradata to let user know which env this secret belong to.\n\t\t\t\tresult.ExtraData[\"Environment\"] = \"OTE\"\n\t\t\t}\n\n\t\t\tresults = append(results, result)\n\t\t}\n\t}\n\n\treturn results, nil\n\n}\n\n// VerifyGoDaddySecret make a call to godaddy api with given secret to check if secret is valid or not.\nfunc VerifyGoDaddySecret(ctx context.Context, client *http.Client, environment, secret string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://%s/v1/domains/available?domain=example.com\", environment), http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// set the required auth header\n\treq.Header.Set(\"Authorization\", secret)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tcase http.StatusForbidden:\n\t\t// as per documentation in case of 403 the token is actually verified but it does not have access.\n\t\treturn true, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\n// MakeAuthHeaderValue return a value made from key and secret that can be used as authorization header value for godaddy API's.\nfunc MakeAuthHeaderValue(key, secret string) string {\n\treturn fmt.Sprintf(\"sso-key %s:%s\", key, secret)\n}\n"
  },
  {
    "path": "pkg/detectors/godaddy/v1/godaddy_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage godaddy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGoDaddy_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GODADDY_OTE\")\n\tinactiveSecret := testSecrets.MustGetField(\"GODADDY_OTE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a godaddy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GoDaddy,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a godaddy secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GoDaddy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GoDaddy.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].ExtraData = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"GoDaddy.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/godaddy/v1/godaddy_test.go",
    "content": "package godaddy\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GoDaddy\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"godaddyKey\": \"2TM44WqB21o4zH_3xM44WkB21i4zHHhXSoHjO\",\n\t\t\t\"godaddySecret\": \"3xM44WkB21i4zHHhXSoHjO\",\n\t\t\t\"not_godaddySecret\": \"2TM44WqB21o4zH$3xM44WkB21i4zHHhXSoHjO\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"2TM44WqB21o4zH_3xM44WkB21i4zHHhXSoHjO\"\n)\n\nfunc TestGoDaddy_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/godaddy/v2/godaddy.go",
    "content": "package godaddy\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\tv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/godaddy/v1\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nvar (\n\t// ensure the scanner satisfies the interface at compile time.\n\t_ detectors.Detector  = (*Scanner)(nil)\n\t_ detectors.Versioner = (*Scanner)(nil)\n\n\tdefaultClient = common.SaneHttpClient()\n\n\t// the key for the GoDaddy Prod environment is a 35-character alphanumeric string that may include underscores.\n\tkeyPattern = regexp.MustCompile(detectors.PrefixRegex([]string{\"godaddy\"}) + common.BuildRegex(\"a-zA-Z0-9\", \"_\", 35))\n\t// the secret for the GoDaddy Prod environment is a 22-character alphanumeric string.\n\tsecretPattern = regexp.MustCompile(detectors.PrefixRegex([]string{\"godaddy\"}) + common.BuildRegex(\"a-zA-Z0-9\", \"\", 22))\n\n\t// prod environment\n\tprod = \"api.godaddy.com\"\n)\n\nfunc (s *Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\nfunc (s *Scanner) Version() int { return 2 }\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"godaddy\"}\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GoDaddy offers website building, hosting and security tools and services to construct, expand and protect the online presence.\" +\n\t\t\"GoDaddy provides applications and access to relevant third-party products and platforms to connect their customers\"\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GoDaddy\n}\n\n// FromData will find and optionally verify GoDaddy API Key and secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\t// convert the data to string\n\tdataStr := string(data)\n\n\t// find all the matching keys and secret in data and make a unique maps of both keys and secret.\n\tuniqueKeys, uniqueSecrets := make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, foundKey := range keyPattern.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[foundKey[1]] = struct{}{}\n\t}\n\n\tfor _, foundSecret := range secretPattern.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueSecrets[foundSecret[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\tfor secret := range uniqueSecrets {\n\t\t\tresult := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_GoDaddy,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tExtraData:    make(map[string]string),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, verificationErr := v1.VerifyGoDaddySecret(ctx, s.getClient(), prod, v1.MakeAuthHeaderValue(key, secret))\n\n\t\t\t\tresult.Verified = isVerified\n\t\t\t\tresult.SetVerificationError(verificationErr, secret)\n\n\t\t\t\t// add the env name in extradata to let user know which env this secret belong to.\n\t\t\t\tresult.ExtraData[\"Environment\"] = \"Prod\"\n\t\t\t}\n\n\t\t\tresults = append(results, result)\n\t\t}\n\t}\n\n\treturn results, nil\n\n}\n"
  },
  {
    "path": "pkg/detectors/godaddy/v2/godaddy_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage godaddy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGoDaddy_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GODADDY_PROD\")\n\tinactiveSecret := testSecrets.MustGetField(\"GODADDY_PROD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a godaddy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GoDaddy,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a godaddy secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GoDaddy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GoDaddy.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"GoDaddy.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/godaddy/v2/godaddy_test.go",
    "content": "package godaddy\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GoDaddy\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"godaddyKey\": \"2TM44WqB21o4zH_3xM44WkB21i4zHHhXSoH\",\n\t\t\t\"godaddySecret\": \"3xM44WkB21i4zHHhXSoHjO\",\n\t\t\t\"not_godaddySecret\": \"2TM44WqB21o4zH@3xM44WkB21i4zHHhXSoH\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"2TM44WqB21o4zH_3xM44WkB21i4zHHhXSoH\"\n)\n\nfunc TestGoDaddy_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/goodday/goodday.go",
    "content": "package goodday\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"goodday\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"goodday\"}\n}\n\n// FromData will find and optionally verify GoodDay secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_GoodDay,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.goodday.work/2.0/users\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"gd-api-token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GoodDay\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GoodDay is a project management tool. GoodDay API tokens can be used to access and manage project data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/goodday/goodday_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage goodday\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGoodDay_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GOODDAY\")\n\tinactiveSecret := testSecrets.MustGetField(\"GOODDAY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a goodday secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GoodDay,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a goodday secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GoodDay,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GoodDay.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"GoodDay.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/goodday/goodday_test.go",
    "content": "package goodday\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GoodDay\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"goodday_secret\": \"473jyn60qcqsbiesfgx8vpu1io0faj6s\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"473jyn60qcqsbiesfgx8vpu1io0faj6s\"\n)\n\nfunc TestGoodDay_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/googlegemini/googlegemini.go",
    "content": "package googlegemini\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interfaces at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// A trailing word boundary is not added because the key can end\n\t// with a hyphen, and having a trailing \\b will not match such a key\n\tkeyPat = regexp.MustCompile(`\\b(AIzaSy[A-Za-z0-9_-]{33})`)\n)\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\nfunc (s Scanner) Keywords() []string { return []string{\"aizasy\"} }\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GoogleGeminiAPIKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Google Gemini API provides access to Google's latest generative AI models for building applications that understand and generate text, images, audio, and code with high performance and low latency.\"\n}\n\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: s.Type(),\n\t\t\tRaw:          []byte(resMatch),\n\t\t\tRedacted:     resMatch[:8] + \"...\",\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, extraData, verificationErr := s.verify(ctx, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) verify(ctx context.Context, key string) (bool, map[string]string, error) {\n\treq, err := http.NewRequestWithContext(\n\t\tctx, http.MethodGet, \"https://generativelanguage.googleapis.com/v1/models\", http.NoBody)\n\tif err != nil {\n\t\treturn false, nil, fmt.Errorf(\"error constructing request: %w\", err)\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"X-goog-api-key\", key)\n\n\tclient := s.getClient()\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, fmt.Errorf(\"error making request: %w\", err)\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\t// Key is valid and has access to gemini\n\t\treturn true, map[string]string{\"active_google_key\": \"true\"}, nil\n\tcase http.StatusForbidden:\n\t\t// Key is valid but does not have access to gemini\n\t\treturn false, map[string]string{\"active_google_key\": \"true\"}, nil\n\tcase http.StatusBadRequest:\n\t\t// Key is invalid (expired, revoked)\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/googlegemini/googlegemini_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage googlegemini\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGoogleGemini_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tkeyGemini := testSecrets.MustGetField(\"GOOGLE_GEMINI_API_KEY\")\n\tkeyNonGemini := testSecrets.MustGetField(\"GOOGLE_CLOUD_API_KEY\")\n\tkeyInactive := testSecrets.MustGetField(\"GOOGLE_GEMINI_API_KEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a google gemini key %s within\", keyGemini)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GoogleGeminiAPIKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"active_google_key\": \"true\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified but valid google cloud api key\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a google api key %s within\", keyNonGemini)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GoogleGeminiAPIKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"active_google_key\": \"true\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a google api key %s within\", keyInactive)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GoogleGeminiAPIKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GoogleGeminiAPIKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tif len(got[i].Redacted) == 0 {\n\t\t\t\t\tt.Fatalf(\"no redacted secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Redacted = \"\"\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"GoogleGeminiAPIKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/googlegemini/googlegemini_test.go",
    "content": "package googlegemini\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"AIzaSyDW1PXXav-TxriHUIrj1djZfHKIuInr7Aw\"\n\tinvalidPattern = \"AIzaSyDW1PXXav-TxriHUIrj1djZfHKIuInr7A\"\n)\n\nfunc TestGoogleGemini_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: fmt.Sprintf(\"gemini api key = '%s'\", validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"gemini api key = '%s'\", invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/googleoauth2/googleoauth2_access_token.go",
    "content": "package googleoauth2\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// There is conflicting information about the expected length of access tokens.\n\t// 10 seems like a reasonable minimum that will weed out placeholders.\n\t//\n\t// https://cloud.google.com/docs/authentication/token-types#access\n\t// https://github.com/GoogleChrome/developer.chrome.com/blob/51dd7dd5d510ed85d86f5a91cb8fde50b62351c7/site/en/docs/webstore/using_webstore_api/index.md?plain=1#L95\n\tkeyPat = regexp.MustCompile(`\\b(ya29\\.(?i:[a-z0-9_-]{10,}))(?:[^a-z0-9_-]|\\z)`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ya29.\"}\n}\n\n// FromData will find and optionally verify Googleoauth2 secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\ttokens := make(map[string]struct{})\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\ttokens[matches[1]] = struct{}{}\n\t}\n\n\tfor token := range tokens {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: s.Type(),\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tif verify {\n\t\t\tif s.client == nil {\n\t\t\t\ts.client = defaultClient\n\t\t\t}\n\n\t\t\tverified, extraData, vErr := s.verify(ctx, token)\n\t\t\ts1.Verified = verified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(vErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\treturn\n}\n\nfunc (s Scanner) verify(ctx context.Context, token string) (bool, map[string]string, error) {\n\t// Based on https://stackoverflow.com/a/66957524\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=\"+token, nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\tres, err := s.client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_ = res.Body.Close()\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t}()\n\n\tif res.StatusCode == http.StatusOK {\n\t\tvar token tokenInfo\n\t\tif err := json.NewDecoder(res.Body).Decode(&token); err != nil {\n\t\t\treturn false, nil, fmt.Errorf(\"failed to decode response: %w\", err)\n\t\t}\n\n\t\textraData := map[string]string{\n\t\t\t\"google_email\":   token.Email,\n\t\t\t\"email_verified\": token.EmailVerified,\n\t\t\t\"scope\":          token.Scope,\n\t\t\t\"access_type\":    token.AccessType,\n\t\t}\n\n\t\texp, err := strconv.ParseInt(token.Expiry, 10, 64)\n\t\tif err == nil {\n\t\t\textraData[\"expires_at\"] = time.Unix(exp, 0).String()\n\t\t}\n\t\treturn true, extraData, nil\n\t} else if res.StatusCode == http.StatusBadRequest {\n\t\tvar errInfo errorInfo\n\t\tif err := json.NewDecoder(res.Body).Decode(&errInfo); err != nil {\n\t\t\treturn false, nil, fmt.Errorf(\"failed to decode response: %w\", err)\n\t\t}\n\n\t\tif errInfo.Error == \"Invalid Value\" {\n\t\t\t// Definitively false.\n\t\t\treturn false, nil, nil\n\t\t} else {\n\t\t\treturn false, nil, fmt.Errorf(\"unexpected error description '%s' for %s\", errInfo.Error, req.URL)\n\t\t}\n\t} else {\n\t\treturn false, nil, fmt.Errorf(\"unexpected response %d for %s\", res.StatusCode, req.URL)\n\t}\n}\n\ntype tokenInfo struct {\n\tExpiry        string `json:\"exp\"`\n\tScope         string `json:\"scope\"`\n\tEmail         string `json:\"email\"`\n\tEmailVerified string `json:\"email_verified\"`\n\tAccessType    string `json:\"access_type\"`\n}\n\n// {\"error_description\": \"Invalid Value\"}\ntype errorInfo struct {\n\tError string `json:\"error_description\"`\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GoogleOauth2\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Google OAuth 2.0 tokens are used for authenticating and authorizing access to Google APIs and services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/googleoauth2/googleoauth2_access_token_test.go",
    "content": "package googleoauth2\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GoodDay\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"google_secret\": \"ya29.z0o2793q5q5uf7h6vgcd7q14rw2o1ljoeejvuvm0jqxmki2os_ri8wfsjpV\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"ya29.z0o2793q5q5uf7h6vgcd7q14rw2o1ljoeejvuvm0jqxmki2os_ri8wfsjpV\"\n)\n\nfunc TestGoogleOAuth2_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/googleoauth2/googleoauth2_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage googleoauth2\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGoogleoauth2_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GOOGLEOAUTH2\")\n\tinactiveSecret := testSecrets.MustGetField(\"GOOGLEOAUTH2_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a googleoauth2 secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GoogleOauth2,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a googleoauth2 secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GoogleOauth2,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a googleoauth2 secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GoogleOauth2,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a googleoauth2 secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GoogleOauth2,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Googleoauth2.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Googleoauth2.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/grafana/grafana.go",
    "content": "package grafana\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(glc_eyJ[A-Za-z0-9+\\/=]{60,160})`)\n)\n\nfunc (s Scanner) getClient() *http.Client {\n\tclient := s.client\n\tif client == nil {\n\t\tclient = defaultClient\n\t}\n\n\treturn client\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"glc_eyJ\"}\n}\n\n// FromData will find and optionally verify Grafana secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Grafana,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyGrafanaKey(ctx, s.getClient(), resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Grafana\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Grafana is an open-source platform for monitoring and observability. Grafana API keys can be used to access and manage Grafana resources.\"\n}\n\nfunc verifyGrafanaKey(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://grafana.com/api/v1/tokens?region=us\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\tbodyBytes, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\t// token is valid but has restricted permissions\n\t\treturn strings.Contains(string(bodyBytes), \"Unauthorized\"), nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/grafana/grafana_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage grafana\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGrafana_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GRAFANA\")\n\tinactiveSecret := testSecrets.MustGetField(\"GRAFANA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a grafana secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Grafana,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a grafana secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Grafana,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a grafana secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Grafana,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a grafana secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Grafana,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Grafana.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Grafana.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/grafana/grafana_test.go",
    "content": "package grafana\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Grafana\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"grafana_secret\": \"glc_eyJF057+C0x9J+QwzC5JXb5uQ/WSzn98X/iIrZXKaA3Hh+lum0XBRcu56qMlW7ZaxXrNt33XoI3CXz7IRPci=\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"glc_eyJF057+C0x9J+QwzC5JXb5uQ/WSzn98X/iIrZXKaA3Hh+lum0XBRcu56qMlW7ZaxXrNt33XoI3CXz7IRPci=\"\n)\n\nfunc TestGrafana_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/grafanaserviceaccount/grafanaserviceaccount.go",
    "content": "package grafanaserviceaccount\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(`\\b(glsa_[0-9a-zA-Z_]{41})\\b`)\n\tdomainPat = regexp.MustCompile(`\\b([a-zA-Z0-9-]+\\.grafana\\.net)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"glsa_\"}\n}\n\n// FromData will find and optionally verify Grafanaserviceaccount secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tdomainMatches := domainPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range keyMatches {\n\t\tkey := strings.TrimSpace(match[1])\n\n\t\tfor _, domainMatch := range domainMatches {\n\t\t\tdomainRes := strings.TrimSpace(domainMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_GrafanaServiceAccount,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s\", domainRes, key)),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://\"+domainRes+\"/api/access-control/user/permissions\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\t\ts1.SetVerificationError(err, key)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ts1.SetVerificationError(err, key)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GrafanaServiceAccount\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Grafana service accounts are used to authenticate and interact with Grafana's API. These credentials can be used to access and modify Grafana resources and settings.\"\n}\n"
  },
  {
    "path": "pkg/detectors/grafanaserviceaccount/grafanaserviceaccount_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage grafanaserviceaccount\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGrafanaServiceAccount_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GRAFANASERVICEACCOUNT\")\n\tdomain := testSecrets.MustGetField(\"GRAFANA_DOMAIN\")\n\tinactiveSecret := testSecrets.MustGetField(\"GRAFANASERVICEACCOUNT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a grafanaserviceaccount secret %s within %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GrafanaServiceAccount,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a grafanaserviceaccount secret %s within %s but not valid\", inactiveSecret, domain)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GrafanaServiceAccount,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a grafanaserviceaccount secret %s within %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GrafanaServiceAccount,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a grafanaserviceaccount secret %s within domain %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GrafanaServiceAccount,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GrafanaServiceAccount.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"GrafanaServiceAccount.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/grafanaserviceaccount/grafanaserviceaccount_test.go",
    "content": "package grafanaserviceaccount\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GrafanaServiceAccount\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"grafana_secret\": \"glsa_HU0WvVk_sl4PunHK8JtC7U6fywRm3FJuEFJwct3qi\",\n\t\t\t\"domain: \"1VV04Zn7iJ0B8w2nBWqG5rB-dVUL3ELSE0zMqnMHjWp-AecPbpdwSde.grafana.net\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"1VV04Zn7iJ0B8w2nBWqG5rB-dVUL3ELSE0zMqnMHjWp-AecPbpdwSde.grafana.net:glsa_HU0WvVk_sl4PunHK8JtC7U6fywRm3FJuEFJwct3qi\"\n)\n\nfunc TestGrafanaServiceAccount_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/graphcms/graphcms.go",
    "content": "package graphcms\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(ey[a-zA-Z0-9]{73}.ey[a-zA-Z0-9]{365}.[a-zA-Z0-9_-]{683})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"graph\"}) + `\\b([a-z0-9]{25})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"graphcms\"}\n}\n\n// FromData will find and optionally verify GraphCMS secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_GraphCMS,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tpayload := strings.NewReader(`{users {id name}}`)\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api-ap-northeast-1.graphcms.com/v2/\"+resIdMatch+\"/master\", payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/graphql\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GraphCMS\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GraphCMS, now known as Hygraph, is a CSM software. The GraphCSM API token can enable someone to interact with CMS content.\"\n}\n"
  },
  {
    "path": "pkg/detectors/graphcms/graphcms_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage graphcms\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGraphCMS_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GRAPHCMS_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"GRAPHCMS_INACTIVE\")\n\tid := testSecrets.MustGetField(\"GRAPHCMS_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a graphcms secret %s within graphcms %s but verified\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GraphCMS,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a graphcms secret Bearer %s within graphcms %s but not valid\", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GraphCMS,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GraphCMS.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"GraphCMS.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/graphcms/graphcms_test.go",
    "content": "package graphcms\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GraphCMS\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"secret\": \"eyQLdHMLjpK5snSPYf6ZCiXXxQRlie2wMITSkTjuzywCiga696mNUt2k2nEL4mv70CKQD9STxcN@eyr4kx6eWKut5zTR6Ei9o94jSCNWjkjQgYoD1pihdbTrr0sHys5uSHOUtiICAtcgsXjewxjyHvro9JYMClVEiGMQoxRC8d1NKfChOjrSO2unumWMsSMSgoA1KQLlHXd0efLuN94KiA3tjLN2Om8SsvLrk29LTPhaQYMvyx02x4IPjLlcHLqSt7cSVUqOe0uGxyIGyzsT7wx9PT56zbieLhRmO697zwyuiN4LpCccP7PuJB9qjz9AofCvgP8TJNsUZdwqcLFiyYTmZQ66Tn9Vpa1IJIdp2oq6izYegl49PDQtuP60A5O7xS7wV5QnFrkqmQkj7WeDUAtRECfTSgfFuXYLPwfYD7cYkfBRC7I1sdnH5tV1R4YEizugtQR5FhVeXHJJkfa-eNjLX9rUsnUNEJTvHOkiyjPvJkUfYzbJMUEVAjIhzny9V04DfnCh7l1mrVM0s_dpUP4fEmAe5fJjDHOMpvtZar0AByzBRpac9Rih0eWpbrMv7sNXh3d9pRPf-AtzCyKqzQ25_FJ6J6wN2evxXnqV4KhSmRTkaaNra4jsF3Sh8cMVYN-jAV6UBeKdSSLcFpjhlnVD6y59PnxFxbL7lj4UxVql3GpqnuKdd3MjN9OOQW2oqI8fd7_I8-vNwowWIuh4K5J0MbBIHCCgvqvdfPEHv4tBKFaj71zcEiDwOQJNxtL-kU_xTpcij9Rq5gnSRufQo1D932wSEe4NrHjZJhJu5qjtR1VC1dujLotyZvYhlyFJ22Lr2Tj5btj-VNjZeCJuv3QcQwR7mSI23O1e1_ESHnYkq4EMd17EIVWucgGZ1jZxGURTAU2bNJMDYUuramusFKAPtaL9i2uVDMQNukiWQI3fIrkOFguGnsCksOSWx80pu2C7CdvhH2SpF0kVnggTcz5W2AR4HPKu4645wBAY_IoirLUcCeKCjWTRJBH2kanqUCweHHU1qRSHncvYdkm0TRGkjpewoZs6JNpxc0WzClIatcVKOAbak3SKLULu28y5b-eIY_x2vqgYmjVZKsjqiQpJblkrRpJsnj3w0-B\",\n\t\t\t\"graphcms_id\": \"trevo1rp5egljk1vwk83enlti\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"eyQLdHMLjpK5snSPYf6ZCiXXxQRlie2wMITSkTjuzywCiga696mNUt2k2nEL4mv70CKQD9STxcN@eyr4kx6eWKut5zTR6Ei9o94jSCNWjkjQgYoD1pihdbTrr0sHys5uSHOUtiICAtcgsXjewxjyHvro9JYMClVEiGMQoxRC8d1NKfChOjrSO2unumWMsSMSgoA1KQLlHXd0efLuN94KiA3tjLN2Om8SsvLrk29LTPhaQYMvyx02x4IPjLlcHLqSt7cSVUqOe0uGxyIGyzsT7wx9PT56zbieLhRmO697zwyuiN4LpCccP7PuJB9qjz9AofCvgP8TJNsUZdwqcLFiyYTmZQ66Tn9Vpa1IJIdp2oq6izYegl49PDQtuP60A5O7xS7wV5QnFrkqmQkj7WeDUAtRECfTSgfFuXYLPwfYD7cYkfBRC7I1sdnH5tV1R4YEizugtQR5FhVeXHJJkfa-eNjLX9rUsnUNEJTvHOkiyjPvJkUfYzbJMUEVAjIhzny9V04DfnCh7l1mrVM0s_dpUP4fEmAe5fJjDHOMpvtZar0AByzBRpac9Rih0eWpbrMv7sNXh3d9pRPf-AtzCyKqzQ25_FJ6J6wN2evxXnqV4KhSmRTkaaNra4jsF3Sh8cMVYN-jAV6UBeKdSSLcFpjhlnVD6y59PnxFxbL7lj4UxVql3GpqnuKdd3MjN9OOQW2oqI8fd7_I8-vNwowWIuh4K5J0MbBIHCCgvqvdfPEHv4tBKFaj71zcEiDwOQJNxtL-kU_xTpcij9Rq5gnSRufQo1D932wSEe4NrHjZJhJu5qjtR1VC1dujLotyZvYhlyFJ22Lr2Tj5btj-VNjZeCJuv3QcQwR7mSI23O1e1_ESHnYkq4EMd17EIVWucgGZ1jZxGURTAU2bNJMDYUuramusFKAPtaL9i2uVDMQNukiWQI3fIrkOFguGnsCksOSWx80pu2C7CdvhH2SpF0kVnggTcz5W2AR4HPKu4645wBAY_IoirLUcCeKCjWTRJBH2kanqUCweHHU1qRSHncvYdkm0TRGkjpewoZs6JNpxc0WzClIatcVKOAbak3SKLULu28y5b-eIY_x2vqgYmjVZKsjqiQpJblkrRpJsnj3w0-B\"\n)\n\nfunc TestGraphCMS_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/graphhopper/graphhopper.go",
    "content": "package graphhopper\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"graphhopper\"}) + `\\b([a-z0-9-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"graphhopper\"}\n}\n\n// FromData will find and optionally verify Graphhopper secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Graphhopper,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://graphhopper.com/api/1/geocode?q=Philippines&key=%s&type=json\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Graphhopper\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Graphhopper provides a routing engine for route planning and navigation. The API keys can be used to access and utilize the Graphhopper routing services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/graphhopper/graphhopper_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage graphhopper\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGraphhopper_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GRAPHHOPPER\")\n\tinactiveSecret := testSecrets.MustGetField(\"GRAPHHOPPER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a graphhopper secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Graphhopper,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a graphhopper secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Graphhopper,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Graphhopper.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Graphhopper.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/graphhopper/graphhopper_test.go",
    "content": "package graphhopper\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GraphHopper\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"graphhopper_secret\": \"9g-iiavt5bqwuhsa0bjysb2pmmfo45tl5nd1\",\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"9g-iiavt5bqwuhsa0bjysb2pmmfo45tl5nd1\"\n)\n\nfunc TestGraphHopper_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/groovehq/groovehq.go",
    "content": "package groovehq\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"groove\"}) + `\\b([a-z0-9A-Z]{64})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"groovehq\"}\n}\n\n// FromData will find and optionally verify Groovehq secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Groovehq,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.groovehq.com/v1/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Groovehq\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GrooveHQ is a simple help desk software. GrooveHQ API keys can be used to access and manage support tickets and customer interactions.\"\n}\n"
  },
  {
    "path": "pkg/detectors/groovehq/groovehq_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage groovehq\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGroovehq_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GROOVEHQ_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"GROOVEHQ_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a groovehq secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Groovehq,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a groovehq secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Groovehq,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Groovehq.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Groovehq.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/groovehq/groovehq_test.go",
    "content": "package groovehq\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Groovehq\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"groove_secret\": \"EvzL6XcOKE8B6xgNmjKTuMEY7ArutucifFovqGBvIGzJkujaSXNP9LNZc76u1aHw\",\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"EvzL6XcOKE8B6xgNmjKTuMEY7ArutucifFovqGBvIGzJkujaSXNP9LNZc76u1aHw\"\n)\n\nfunc TestGrooveHQ_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/groq/groq.go",
    "content": "package groq\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\tkeyPat        = regexp.MustCompile(`\\b(gsk_[a-zA-Z0-9]{52})\\b`)\n)\n\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"gsk_\"}\n}\n\n// FromData will find and optionally verify Groq secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Groq,\n\t\t\tRaw:          []byte(match),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/groq/\",\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\n\t\t\tif isVerified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\"key\": match,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\t// https://console.groq.com/docs/models\n\t// This endpoint will return a JSON list of all active models.\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.groq.com/openai/v1/models\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\treq.Header.Add(\"Authorization\", \"Bearer \"+token)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\t// If the endpoint returns useful information, we can return it as a map.\n\t\treturn true, nil, nil\n\tcase http.StatusUnauthorized:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Groq\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Groq is a company that creates data processing hardware and software. Groq API keys can be used to access their services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/groq/groq_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage groq\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGroq_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GROQ\")\n\tinactiveSecret := testSecrets.MustGetField(\"GROQ_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a groq secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Groq,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a groq secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Groq,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a groq secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Groq,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a groq secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Groq,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Groq.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Groq.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/groq/groq_test.go",
    "content": "package groq\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestGroq_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: \"groq_token = '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'\",\n\t\t\twant:  []string{\"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\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tdetectorMatches := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(detectorMatches) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gtmetrix/gtmetrix.go",
    "content": "package gtmetrix\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"gtmetrix\"}) + `\\b([0-9a-f]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"gtmetrix\"}\n}\n\n// FromData will find and optionally verify GTMetrix secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_GTMetrix,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://gtmetrix.com/api/2.0/status\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.SetBasicAuth(resMatch, \"\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_GTMetrix\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"GTMetrix provides website performance analytics and monitoring. GTMetrix API keys can be used to access and manage these analytics.\"\n}\n"
  },
  {
    "path": "pkg/detectors/gtmetrix/gtmetrix_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage gtmetrix\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGTMetrix_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GTMETRIX\")\n\tinactiveSecret := testSecrets.MustGetField(\"GTMETRIX_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gtmetrix secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GTMetrix,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gtmetrix secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_GTMetrix,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GTMetrix.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"GTMetrix.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gtmetrix/gtmetrix_test.go",
    "content": "package gtmetrix\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GtMetrix\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"gtmetrix_secret\": \"79f713655fe19fc451b4ae9e8e8e2fb9\",\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"79f713655fe19fc451b4ae9e8e8e2fb9\"\n)\n\nfunc TestGtMetrix_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/guardianapi/guardianapi.go",
    "content": "package guardianapi\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"guardian\"}) + `\\b([0-9Aa-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"guardianapi\"}\n}\n\n// FromData will find and optionally verify Guardianapi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Guardianapi,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://content.guardianapis.com/search?api-key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Guardianapi\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Guardian API keys are used to access content and data from The Guardian's API. These keys can be used to retrieve articles, search content, and interact with The Guardian's content platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/guardianapi/guardianapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage guardianapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGuardianapi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GUARDIANAPI_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"GUARDIANAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a guardianapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Guardianapi,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a guardianapi secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Guardianapi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Guardianapi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Guardianapi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/guardianapi/guardianapi_test.go",
    "content": "package guardianapi\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GuardianAPI\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"guardian_secret\": \"rxawb8sz-irp5-8h6v-bjpz-hi7opod7tiha\",\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"rxawb8sz-irp5-8h6v-bjpz-hi7opod7tiha\"\n)\n\nfunc TestGuardianAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gumroad/gumroad.go",
    "content": "package gumroad\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"gumroad\"}) + `\\b([a-z0-9A-Z-]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"gumroad\"}\n}\n\n// FromData will find and optionally verify Gumroad secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Gumroad,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\t\tpayload := strings.NewReader(\"access_token=\" + resMatch)\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.gumroad.com/v2/products\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Gumroad\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Gumroad is an online platform that facilitates the sale of products by creators. Gumroad API keys can be used to access and manage these products.\"\n}\n"
  },
  {
    "path": "pkg/detectors/gumroad/gumroad_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage gumroad\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGumroad_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GUMROAD\")\n\tinactiveSecret := testSecrets.MustGetField(\"GUMROAD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gumroad secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gumroad,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gumroad secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gumroad,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Gumroad.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Gumroad.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gumroad/gumroad_test.go",
    "content": "package gumroad\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"GumRoad\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"gumroad_secret\": \"vLw0TtejX72pb01VM-KrPlLYLABt3lXnIOR32qvxKB9\",\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"vLw0TtejX72pb01VM-KrPlLYLABt3lXnIOR32qvxKB9\"\n)\n\nfunc TestGumRoad_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/guru/guru.go",
    "content": "package guru\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tunamePat = regexp.MustCompile(detectors.PrefixRegex([]string{\"guru\"}) + `\\b([a-zA-Z0-9]{3,20}@[a-zA-Z0-9]{2,12}.[a-zA-Z0-9]{2,5})\\b`)\n\tkeyPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"guru\"}) + `\\b([a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"guru\"}\n}\n\n// FromData will find and optionally verify Guru secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tunameMatches := unamePat.FindAllStringSubmatch(dataStr, -1)\n\tkeyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range unameMatches {\n\n\t\tunameMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, secret := range keyMatches {\n\n\t\t\tkeyMatch := strings.TrimSpace(secret[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Guru,\n\t\t\t\tRaw:          []byte(unameMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tdata := fmt.Sprintf(\"%s:%s\", unameMatch, keyMatch)\n\t\t\t\tencoded := b64.StdEncoding.EncodeToString([]byte(data))\n\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.getguru.com/api/v1/teams/teamId/stats\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", encoded))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Guru\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Guru is a knowledge management solution. Guru credentials can be used to access and manage knowledge within an organization.\"\n}\n"
  },
  {
    "path": "pkg/detectors/guru/guru_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage guru\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGuru_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tusername := testSecrets.MustGetField(\"GURU\")\n\tsecret := testSecrets.MustGetField(\"GURU_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"GURU_SECRET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a guru secret %s within guru username %s but verified\", secret, username)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Guru,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a guru secret %s within guru username %s but not valid\", inactiveSecret, username)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Guru,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Guru.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Guru.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/guru/guru_test.go",
    "content": "package guru\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Guru\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"guru_user\": \"434EWv@Z7KcKlP6M5FO0\",\n\t\t\t\"guru_secret\": \"v7zypoou-t6ld-uq7a-qsmm-j3y1mh0w5cf1\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"434EWv@Z7KcKlP6M5FO0\"\n)\n\nfunc TestGuru_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gyazo/gyazo.go",
    "content": "package gyazo\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"gyazo\"}) + `\\b([0-9A-Za-z-]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"gyazo\"}\n}\n\n// FromData will find and optionally verify Gyazo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Gyazo,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.gyazo.com/api/images?access_token=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Gyazo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Gyazo is an image sharing service. Gyazo access tokens can be used to access and manage images stored in a Gyazo account.\"\n}\n"
  },
  {
    "path": "pkg/detectors/gyazo/gyazo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage gyazo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestGyazo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"GYAZO\")\n\tinactiveSecret := testSecrets.MustGetField(\"GYAZO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gyazo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gyazo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a gyazo secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Gyazo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Gyazo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Gyazo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/gyazo/gyazo_test.go",
    "content": "package gyazo\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Gyazo\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"gyazo_secret\": \"bWDRu8itsUDI1zfOdfE6RnrDJPVeq0Ox1bGTKoi0IjP\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"bWDRu8itsUDI1zfOdfE6RnrDJPVeq0Ox1bGTKoi0IjP\"\n)\n\nfunc TestGyazo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/happyscribe/happyscribe.go",
    "content": "package happyscribe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"happyscribe\"}) + `\\b([0-9a-zA-Z]{24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"happyscribe\"}\n}\n\n// FromData will find and optionally verify Happyscribe secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Happyscribe,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.happyscribe.com/api/v1/transcriptions\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Happyscribe\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Happyscribe is a transcription service that converts audio to text. Happyscribe API keys can be used to access and manage these transcription services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/happyscribe/happyscribe_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage happyscribe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHappyscribe_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HAPPYSCRIBE\")\n\tinactiveSecret := testSecrets.MustGetField(\"HAPPYSCRIBE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a happyscribe secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Happyscribe,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a happyscribe secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Happyscribe,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Happyscribe.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Happyscribe.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/happyscribe/happyscribe_test.go",
    "content": "package happyscribe\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"HappyScribe\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"happyscribe_secret\": \"6vEt7Yfgm8nkZ5AM4CvBHPbD\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"6vEt7Yfgm8nkZ5AM4CvBHPbD\"\n)\n\nfunc TestHappyScribe_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/harness/harness.go",
    "content": "package harness\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\n// Response struct for decoding API responses.\ntype response struct {\n\tData struct {\n\t\tLastLogin int `json:\"lastLogin\"`\n\t} `json:\"data\"`\n}\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"harness\"}) + `\\b(pat\\.[A-Za-z0-9]{22}\\.[0-9a-f]{24}\\.[A-Za-z0-9]{20})\\b`)\n)\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"harness\"}\n}\n\n// FromData will find and optionally verify Harness secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Harness,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.getClient()\n\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\n\t\t\tif isVerified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\"key\": match,\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\n/*\nIn the document, all of the APIs are required to provide API Key Token as header and\naccountIdentifier as query parameter. Although, the below API returns successful response\nwithout providing accountIdentifier as query parameter.\nWe may need to update this if Harness decides to enforce this in the future.\nAPI Reference: https://apidocs.harness.io/tag/User/#operation/getCurrentUserInfo\n*/\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://app.harness.io/ng/api/user/currentUser\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Set(\"x-api-key\", token)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\textraData := make(map[string]string)\n\t\tvar response response\n\t\tif err := json.NewDecoder(res.Body).Decode(&response); err == nil {\n\t\t\textraData[\"last_login\"] = strconv.Itoa(response.Data.LastLogin)\n\t\t\treturn true, extraData, nil\n\t\t}\n\t\treturn true, nil, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Harness\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Harness.io is an AI-driven CI/CD platform that automates software delivery, streamlining code building, testing, and deployment with intelligent optimization and multi-cloud support.\"\n}\n"
  },
  {
    "path": "pkg/detectors/harness/harness_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage harness\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHarness_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tapiKeyToken := testSecrets.MustGetField(\"HARNESS_TOKEN\")\n\tinactiveApiKeyToken := testSecrets.MustGetField(\"HARNESS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a harness api key token %s within\", apiKeyToken)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Harness,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a harness harness api key token %s within but not valid\", inactiveApiKeyToken)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Harness,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the api key token within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a harness api key token %s within\", apiKeyToken)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Harness,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a harness api key token %s within\", apiKeyToken)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Harness,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Harness.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"ExtraData\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Harness.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/harness/harness_test.go",
    "content": "package harness\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey               = \"pat.4oXWHvYFRNOGLVpFTZGGTA.68077fc826afe36865614d58.2fFEmr57WO3zPmev3jze\"\n\tvalidKeyWithoutKeyword = `API Key Token: pat.4oXWHvYFRNOGLVpFTZGGTA.68077fc826afe36865614d58.2fFEmr57WO3zPmev3jze\n\turl |https://api.harness.io/`\n\tinvalidKey = \"pat.4oXWHvYFRNOGLVpFTZGGTA.6807c5bed9599c324f6368ce.usCT2fzvADwSoXzXc\"\n\tkeyword    = \"harness\"\n)\n\nfunc TestHarness_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validKey),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - no keyword\",\n\t\t\tinput: fmt.Sprintf(\"token = '%s'\", validKeyWithoutKeyword),\n\t\t\twant:  nil,\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, invalidKey),\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/harvest/harvest.go",
    "content": "package harvest\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"harvest\"}) + `\\b([a-z0-9A-Z._]{97})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"harvest\"}) + `\\b([0-9]{4,9})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"harvest\"}\n}\n\n// FromData will find and optionally verify Harvest secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Harvest,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.harvestapp.com/v2/users/me?access_token=\"+resMatch+\"&account_id=\"+resIdMatch, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Harvest\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Harvest is a time tracking and invoicing software. Harvest API keys can be used to access and manage time tracking data and invoices.\"\n}\n"
  },
  {
    "path": "pkg/detectors/harvest/harvest_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage harvest\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHarvest_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HARVEST_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"HARVEST_INACTIVE\")\n\tid := testSecrets.MustGetField(\"HARVEST_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a harvest secret %s within harvestid %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Harvest,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a harvest secret %s within harvestid %s but not valid\", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Harvest,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Harvest.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Harvest.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/harvest/harvest_test.go",
    "content": "package harvest\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Harvest\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"harvest_id\": \"504290\",\n\t\t\t\"harvest_secret\": \"zzMNPeWUbYTyfK1Vltc4Mue_KXL5OyMc28P28L46DdFspGC0j0_Pim090czbJ5bnio64GoK7hEi8am8V_XL8Ai2z_90EVll9c\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"zzMNPeWUbYTyfK1Vltc4Mue_KXL5OyMc28P28L46DdFspGC0j0_Pim090czbJ5bnio64GoK7hEi8am8V_XL8Ai2z_90EVll9c\"\n)\n\nfunc TestHarvest_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hashicorpvaultauth/hashicorpvaultauth.go",
    "content": "package hashicorpvaultauth\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\troleIdPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"role\"}) + `\\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\b`)\n\n\tsecretIdPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"secret\"}) + `\\b([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`)\n\n\t// Vault URL pattern - HashiCorp Cloud or any HTTPS/HTTP Vault endpoint\n\tvaultUrlPat = regexp.MustCompile(`(https?:\\/\\/[^\\s\\/]*\\.hashicorp\\.cloud(?::\\d+)?)(?:\\/[^\\s]*)?`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"hashicorp\"}\n}\n\n// FromData will find and optionally verify HashiCorp Vault AppRole secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueRoleIds = make(map[string]struct{})\n\tfor _, match := range roleIdPat.FindAllStringSubmatch(dataStr, -1) {\n\t\troleId := strings.TrimSpace(match[1])\n\t\tuniqueRoleIds[roleId] = struct{}{}\n\t}\n\n\tvar uniqueSecretIds = make(map[string]struct{})\n\tfor _, match := range secretIdPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tsecretId := strings.TrimSpace(match[1])\n\t\tuniqueSecretIds[secretId] = struct{}{}\n\t}\n\n\tvar uniqueVaultUrls = make(map[string]struct{})\n\tfor _, match := range vaultUrlPat.FindAllString(dataStr, -1) {\n\t\turl := strings.TrimSpace(match)\n\t\tuniqueVaultUrls[url] = struct{}{}\n\t}\n\n\t// If no names or secrets found, return empty results\n\tif len(uniqueRoleIds) == 0 || len(uniqueSecretIds) == 0 || len(uniqueVaultUrls) == 0 {\n\t\treturn results, nil\n\t}\n\n\t// create combination results that can be verified\n\tfor roleId := range uniqueRoleIds {\n\t\tfor secretId := range uniqueSecretIds {\n\t\t\tfor vaultUrl := range uniqueVaultUrls {\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HashiCorpVaultAuth,\n\t\t\t\t\tRaw:          []byte(secretId),\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s\", roleId, secretId)),\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"URL\": vaultUrl,\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tclient := s.client\n\t\t\t\t\tif client == nil {\n\t\t\t\t\t\tclient = defaultClient\n\t\t\t\t\t}\n\n\t\t\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, roleId, secretId, vaultUrl)\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\ts1.SetVerificationError(verificationErr, roleId, secretId, vaultUrl)\n\t\t\t\t}\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, roleId, secretId, vaultUrl string) (bool, error) {\n\tpayload := map[string]string{\n\t\t\"role_id\":   roleId,\n\t\t\"secret_id\": secretId,\n\t}\n\n\tjsonPayload, err := json.Marshal(payload)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, vaultUrl+\"/v1/auth/approle/login\", bytes.NewBuffer(jsonPayload))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"X-Vault-Namespace\", \"admin\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusBadRequest:\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tif strings.Contains(string(body), \"invalid role or secret ID\") {\n\t\t\treturn false, nil\n\t\t} else {\n\t\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", resp.StatusCode)\n\t\t}\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", resp.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_HashiCorpVaultAuth\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"HashiCorp Vault AppRole authentication method uses role_id and secret_id for machine-to-machine authentication. These credentials can be used to authenticate with Vault and obtain tokens for accessing secrets.\"\n}\n"
  },
  {
    "path": "pkg/detectors/hashicorpvaultauth/hashicorpvaultauth_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage hashicorpvaultauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHashiCorpVaultAuth_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\troleId := testSecrets.MustGetField(\"HASHICORPVAULTAUTH_ROLE_ID\")\n\tsecretId := testSecrets.MustGetField(\"HASHICORPVAULTAUTH_SECRET_ID\")\n\tinactiveRoleId := testSecrets.MustGetField(\"HASHICORPVAULTAUTH_ROLE_ID_INACTIVE\")\n\tinactiveSecretId := testSecrets.MustGetField(\"HASHICORPVAULTAUTH_SECRET_ID_INACTIVE\")\n\tvaultUrl := testSecrets.MustGetField(\"HASHICORPVAULTAUTH_URL\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, unverified - complete set with invalid credentials\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"hashicorp config:\\nrole_id: %s\\nsecret_id: %s\\nvault_url: %s\", inactiveRoleId, inactiveSecretId, vaultUrl)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType:          detectorspb.DetectorType_HashiCorpVaultAuth,\n\t\t\t\t\tVerified:              false,\n\t\t\t\t\tVerificationFromCache: false,\n\t\t\t\t\tRaw:                   []byte(inactiveSecretId),\n\t\t\t\t\tRawV2:                 []byte(fmt.Sprintf(\"%s:%s\", inactiveRoleId, inactiveSecretId)),\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"URL\": vaultUrl,\n\t\t\t\t\t},\n\t\t\t\t\tStructuredData: nil,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified - complete set with valid credentials\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"hashicorp config:\\nrole_id: %s\\nsecret_id: %s\\nvault_url: %s\", roleId, secretId, vaultUrl)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType:          detectorspb.DetectorType_HashiCorpVaultAuth,\n\t\t\t\t\tVerified:              true,\n\t\t\t\t\tVerificationFromCache: false,\n\t\t\t\t\tRaw:                   []byte(secretId),\n\t\t\t\t\tRawV2:                 []byte(fmt.Sprintf(\"%s:%s\", roleId, secretId)),\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"URL\": vaultUrl,\n\t\t\t\t\t},\n\t\t\t\t\tStructuredData: nil,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, incomplete set - credentials without vault url\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"vault config:\\nrole_id: %s\\nsecret_id: %s\", roleId, secretId)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, incomplete set - only role_id\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"vault role_id: %s\", roleId)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, incomplete set - only secret_id\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"vault secret_id: %s\", secretId)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found - no vault context\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"HashiCorpVaultAuth.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Fix: Ignore ALL unexported fields using cmpopts.IgnoreUnexported\n\t\t\tignoreOpts := cmpopts.IgnoreUnexported(detectors.Result{})\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"HashiCorpVaultAuth.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hashicorpvaultauth/hashicorpvaultauth_test.go",
    "content": "package hashicorpvaultauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidRoleId     = \"12345678-1234-1234-1234-123456789abc\" // lowercase hex UUID\n\tvalidSecretId   = \"87654321-4321-4321-4321-CBA987654321\" // mixed case hex UUID\n\tvalidVaultUrl   = \"https://my-org.hashicorp.cloud\"\n\tinvalidRoleId   = \"12345678-1234-1234-1234-123456789abg\" // invalid character 'g'\n\tinvalidSecretId = \"87654321-4321-4321-4321-CBA98765432G\" // invalid character 'G'\n\tkeyword         = \"vault\"\n)\n\nfunc TestHashiCorpVaultAppRoleAuth_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - complete set (role_id + secret_id + vault_url)\",\n\t\t\tinput: fmt.Sprintf(\"%s hashicorp:\\n role_id = '%s'\\nsecret_id = '%s'\\nvault_url = '%s'\", keyword, validRoleId, validSecretId, validVaultUrl),\n\t\t\twant:  []string{fmt.Sprintf(\"%s:%s\", validRoleId, validSecretId)},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - only role_id (incomplete set)\",\n\t\t\tinput: fmt.Sprintf(\"%s role_id = '%s'\", keyword, validRoleId),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - only secret_id (incomplete set)\",\n\t\t\tinput: fmt.Sprintf(\"%s secret_id = '%s'\", keyword, validSecretId),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - role_id + secret_id but no vault_url (incomplete set)\",\n\t\t\tinput: fmt.Sprintf(\"%s config:\\nrole_id = '%s'\\nsecret_id = '%s'\", keyword, validRoleId, validSecretId),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicates in complete set\",\n\t\t\tinput: fmt.Sprintf(\"%s role_id = '%s' | '%s'\\nsecret_id = '%s'\\nvault_url = '%s'\", keyword, validRoleId, validRoleId, validSecretId, validVaultUrl),\n\t\t\twant:  []string{fmt.Sprintf(\"%s:%s\", validRoleId, validSecretId)},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - multiple credentials with vault_url\",\n\t\t\tinput: fmt.Sprintf(\"%s config:\\nrole_id1 = '%s'\\nrole_id2 = '%s'\\nsecret_id1 = '%s'\\nsecret_id2 = '%s'\\nvault_url = '%s'\",\n\t\t\t\tkeyword, validRoleId,\n\t\t\t\t\"abcdef12-3456-7890-abcd-ef1234567890\",\n\t\t\t\tvalidSecretId,\n\t\t\t\t\"FEDCBA09-8765-4321-FEDC-BA0987654321\",\n\t\t\t\tvalidVaultUrl),\n\t\t\twant: []string{\n\t\t\t\tfmt.Sprintf(\"%s:%s\", validRoleId, validSecretId),\n\t\t\t\tfmt.Sprintf(\"%s:%s\", validRoleId, \"FEDCBA09-8765-4321-FEDC-BA0987654321\"),\n\t\t\t\tfmt.Sprintf(\"%s:%s\", \"abcdef12-3456-7890-abcd-ef1234567890\", validSecretId),\n\t\t\t\tfmt.Sprintf(\"%s:%s\", \"abcdef12-3456-7890-abcd-ef1234567890\", \"FEDCBA09-8765-4321-FEDC-BA0987654321\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validRoleId),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern - role_id with invalid character\",\n\t\t\tinput: fmt.Sprintf(\"%s role_id = '%s'\\nvault_url = '%s'\", keyword, invalidRoleId, validVaultUrl),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern - secret_id with invalid character\",\n\t\t\tinput: fmt.Sprintf(\"%s secret_id = '%s'\\nvault_url = '%s'\", keyword, invalidSecretId, validVaultUrl),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern - role_id too short\",\n\t\t\tinput: fmt.Sprintf(\"%s role_id = '%s'\\nvault_url = '%s'\", keyword, \"12345678-1234-1234-1234-123456789ab\", validVaultUrl),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern - secret_id too long\",\n\t\t\tinput: fmt.Sprintf(\"%s secret_id = '%s'\\nvault_url = '%s'\", keyword, \"87654321-4321-4321-4321-CBA9876543211\", validVaultUrl),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern - role_id with uppercase (should be lowercase only)\",\n\t\t\tinput: fmt.Sprintf(\"%s role_id = '%s'\\nvault_url = '%s'\", keyword, \"12345678-1234-1234-1234-123456789ABC\", validVaultUrl),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern - missing hyphens in UUIDs\",\n\t\t\tinput: fmt.Sprintf(\"%s role_id = '%s'\\nsecret_id = '%s'\\nvault_url = '%s'\", keyword, \"123456781234123412341234567890ab\", \"87654321432143214321CBA987654321\", validVaultUrl),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - alternative service keyword\",\n\t\t\tinput: fmt.Sprintf(\"hashicorp role_id = '%s'\\nsecret_id = '%s'\\nvault_url = '%s'\", validRoleId, validSecretId, validVaultUrl),\n\t\t\twant:  []string{fmt.Sprintf(\"%s:%s\", validRoleId, validSecretId)},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - vault_url without credentials\",\n\t\t\tinput: fmt.Sprintf(\"%s vault_url = '%s'\", keyword, validVaultUrl),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern - non-hashicorp vault url\",\n\t\t\tinput: fmt.Sprintf(\"%s role_id = '%s'\\nsecret_id = '%s'\\nvault_url = 'https://my-vault.company.com'\", keyword, validRoleId, validSecretId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(test.want) > 0 && len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hasura/hasura.go",
    "content": "package hasura\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// domainPat finds Hasura cloud domains.\n\tdomainPat = regexp.MustCompile(`\\b([a-zA-Z0-9-]+\\.hasura\\.app)\\b`)\n\t// keyPat finds potential Hasura admin secrets, often prefixed with \"hasura\".\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"hasura\"}) + `\\b([a-zA-Z0-9]{64})\\b`)\n)\n\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"hasura\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Hasura secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeyMatches, uniqueDomainMatches = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeyMatches[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range domainPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueDomainMatches[match[1]] = struct{}{}\n\t}\n\n\t// Logic: For each key, try to verify against each found domain.\n\t// Stop and record a verified finding on the first successful match.\n\tfor key := range uniqueKeyMatches {\n\t\tfor domain := range uniqueDomainMatches {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Hasura,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tRawV2:        fmt.Appendf([]byte(\"\"), \"%s:%s\", domain, key),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, extraData, verificationErr := s.verifyHasura(ctx, s.getClient(), domain, key)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.ExtraData = extraData\n\t\t\t\ts1.SetVerificationError(verificationErr, key)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t\t// If we successfully verified this key with a domain, we don't need to check it against other domains.\n\t\t\tif s1.Verified {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Hasura\n}\n\nfunc (s Scanner) Description() string {\n\treturn `Hasura is an open-source engine that instantly generates GraphQL and REST APIs over PostgreSQL (and other databases). \n\tIt allows you to query, mutate, and subscribe to data in real time. Admin secrets (or admin keys) are used to securely access\n\tand manage Hasura projects, giving full control over data, metadata, and schema.`\n}\n\n// verifyHasura attempts to validate a Hasura key against a given domain.\nfunc (s Scanner) verifyHasura(ctx context.Context, client *http.Client, domain, key string) (bool, map[string]string, error) {\n\tquery := `{\"query\":\"query { __schema { types { name } } }\"}`\n\turl := fmt.Sprintf(\"https://%s/v1/graphql\", domain)\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(query))\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"x-hasura-admin-secret\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\textraData := map[string]string{\"domain\": domain}\n\n\t// Since the API returns 200 OK for both valid and invalid keys, we MUST parse the body.\n\tbodyBytes, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn false, extraData, err\n\t}\n\n\t// Handle non-200 status codes\n\tif resp.StatusCode != http.StatusOK {\n\t\t// Special case: Project not reachable is a definitive \"not verified\", not an error\n\t\tif resp.StatusCode == http.StatusInternalServerError && isHasuraProjectUnavailable(bodyBytes) {\n\t\t\treturn false, extraData, nil\n\t\t}\n\t\treturn false, extraData, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n\n\tvar response struct {\n\t\tData   any   `json:\"data\"`\n\t\tErrors []any `json:\"errors\"`\n\t}\n\n\tif err := json.Unmarshal(bodyBytes, &response); err != nil {\n\t\treturn false, extraData, fmt.Errorf(\"failed to unmarshal json response: %w\", err)\n\t}\n\n\t// Key is verified if we have data and no errors\n\tif response.Data != nil && len(response.Errors) == 0 {\n\t\treturn true, extraData, nil\n\t}\n\n\t// Key is not verified if we have errors or no data\n\treturn false, extraData, nil\n}\n\nfunc isHasuraProjectUnavailable(bodyBytes []byte) bool {\n\treturn bytes.Contains(bodyBytes, []byte(\"Project not reachable\")) || bytes.Contains(bodyBytes, []byte(\"Unable to load this project\"))\n}\n"
  },
  {
    "path": "pkg/detectors/hasura/hasura_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage hasura\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHasura_FromData(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tsecret := testSecrets.MustGetField(\"HASURA\")\n\tinactiveSecret := testSecrets.MustGetField(\"HASURA_INACTIVE\")\n\tdomain := testSecrets.MustGetField(\"HASURA_DOMAIN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hasura secret %s within hasura domain %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Hasura,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRaw:          []byte(secret),\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s\", domain, secret)),\n\t\t\t\t\tExtraData:    map[string]string{\"domain\": domain},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hasura secret %s within hasura domain %s but not valid\", inactiveSecret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Hasura,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(inactiveSecret),\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s\", domain, inactiveSecret)),\n\t\t\t\t\tExtraData:    map[string]string{\"domain\": domain},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hasura secret %s within %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Hasura,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(secret),\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s\", domain, secret)),\n\t\t\t\t\tExtraData:    nil,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unexpected api response\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hasura secret %s within %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Hasura,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(secret),\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s\", domain, secret)),\n\t\t\t\t\tExtraData:    map[string]string{\"domain\": domain},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationErr = %v, got verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(tt.want, got, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Hasura.FromData() %s - diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hasura/hasura_test.go",
    "content": "package hasura\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n)\n\nfunc TestHasura_Pattern(t *testing.T) {\n\td := Scanner{}\n\n\ttests := []struct {\n\t\tname          string\n\t\tinput         string\n\t\texpectedPairs []string\n\t}{\n\t\t{\n\t\t\tname:          \"simple case: one domain, one key\",\n\t\t\tinput:         \"The hasura admin secret is 05tUFwoJfK2dui0CWKxzqJHmNzQrsX40Kwd7g2OEqLl1RZeU6pRvyOSnD4nghgH4 for the domain project-one-12345.hasura.app\",\n\t\t\texpectedPairs: []string{\"project-one-12345.hasura.app:05tUFwoJfK2dui0CWKxzqJHmNzQrsX40Kwd7g2OEqLl1RZeU6pRvyOSnD4nghgH4\"},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple keys, single domain\",\n\t\t\tinput: `\n\t\t\t\tThe domain is project-one-12345.hasura.app\n\t\t\t\thasura key1: 05tUFwoJfK2dui0CWKxzqJHmNzQrsX40Kwd7g2OEqLl1RZeU6pRvyOSnD4nghgH4\n\t\t\t\thasura key2: aBcDeFgHiJkLmNoPqRsTuVwXyZ123456aBcDeFgHiJkLmNoPqRsTuVwXyZ123456\n\t\t\t`,\n\t\t\texpectedPairs: []string{\n\t\t\t\t\"project-one-12345.hasura.app:05tUFwoJfK2dui0CWKxzqJHmNzQrsX40Kwd7g2OEqLl1RZeU6pRvyOSnD4nghgH4\",\n\t\t\t\t\"project-one-12345.hasura.app:aBcDeFgHiJkLmNoPqRsTuVwXyZ123456aBcDeFgHiJkLmNoPqRsTuVwXyZ123456\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"single key, multiple domains\",\n\t\t\tinput: `\n\t\t\t\tThe hasura key is 05tUFwoJfK2dui0CWKxzqJHmNzQrsX40Kwd7g2OEqLl1RZeU6pRvyOSnD4nghgH4\n\t\t\t\tDomain 1: project-one-12345.hasura.app\n\t\t\t\tDomain 2: project-two-67890.hasura.app\n\t\t\t`,\n\t\t\texpectedPairs: []string{\n\t\t\t\t\"project-one-12345.hasura.app:05tUFwoJfK2dui0CWKxzqJHmNzQrsX40Kwd7g2OEqLl1RZeU6pRvyOSnD4nghgH4\",\n\t\t\t\t\"project-two-67890.hasura.app:05tUFwoJfK2dui0CWKxzqJHmNzQrsX40Kwd7g2OEqLl1RZeU6pRvyOSnD4nghgH4\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"many-to-many: multiple keys and domains\",\n\t\t\tinput: `\n\t\t\t\tHere is a hasura key: 05tUFwoJfK2dui0CWKxzqJHmNzQrsX40Kwd7g2OEqLl1RZeU6pRvyOSnD4nghgH4\n\t\t\t\tAnd another hasura key: aBcDeFgHiJkLmNoPqRsTuVwXyZ123456aBcDeFgHiJkLmNoPqRsTuVwXyZ123456\n\t\t\t\tAnd a hasura domain: project-one-12345.hasura.app\n\t\t\t\tAnd another domain: project-two-67890.hasura.app\n\t\t\t`,\n\t\t\texpectedPairs: []string{\n\t\t\t\t\"project-one-12345.hasura.app:05tUFwoJfK2dui0CWKxzqJHmNzQrsX40Kwd7g2OEqLl1RZeU6pRvyOSnD4nghgH4\",\n\t\t\t\t\"project-one-12345.hasura.app:aBcDeFgHiJkLmNoPqRsTuVwXyZ123456aBcDeFgHiJkLmNoPqRsTuVwXyZ123456\",\n\t\t\t\t\"project-two-67890.hasura.app:05tUFwoJfK2dui0CWKxzqJHmNzQrsX40Kwd7g2OEqLl1RZeU6pRvyOSnD4nghgH4\",\n\t\t\t\t\"project-two-67890.hasura.app:aBcDeFgHiJkLmNoPqRsTuVwXyZ123456aBcDeFgHiJkLmNoPqRsTuVwXyZ123456\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:          \"negative case: only a key, no domain\",\n\t\t\tinput:         \"A hasura secret without a domain: 05tUFwoJfK2dui0CWKxzqJHmNzQrsX40Kwd7g2OEqLl1RZeU6pRvyOSnD4nghgH4\",\n\t\t\texpectedPairs: []string{},\n\t\t},\n\t\t{\n\t\t\tname:          \"negative case: only a domain, no key\",\n\t\t\tinput:         \"A hasura domain without a secret: project-one-12345.hasura.app\",\n\t\t\texpectedPairs: []string{},\n\t\t},\n\t\t{\n\t\t\tname:          \"negative case: invalid key with valid domain\",\n\t\t\tinput:         \"An invalid hasura key not-a-valid-key-12345 with a valid domain project-one-12345.hasura.app\",\n\t\t\texpectedPairs: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed valid and invalid keys with one domain\",\n\t\t\tinput: `\n\t\t\t\tThe domain is project-one-12345.hasura.app\n\t\t\t\tValid hasura key: 05tUFwoJfK2dui0CWKxzqJHmNzQrsX40Kwd7g2OEqLl1RZeU6pRvyOSnD4nghgH4\n\t\t\t\tInvalid hasura key: not-a-valid-key-12345\n\t\t\t`,\n\t\t\texpectedPairs: []string{\"project-one-12345.hasura.app:05tUFwoJfK2dui0CWKxzqJHmNzQrsX40Kwd7g2OEqLl1RZeU6pRvyOSnD4nghgH4\"},\n\t\t},\n\t\t{\n\t\t\tname:          \"negative case: invalid domain with valid key\",\n\t\t\tinput:         \"A hasura key 05tUFwoJfK2dui0CWKxzqJHmNzQrsX40Kwd7g2OEqLl1RZeU6pRvyOSnD4nghgH4 with an invalid domain example.com\",\n\t\t\texpectedPairs: []string{},\n\t\t},\n\t\t{\n\t\t\tname:          \"negative case: key without 'hasura' keyword nearby\",\n\t\t\tinput:         \"A random 64-char string 05tUFwoJfK2dui0CWKxzqJHmNzQrsX40Kwd7g2OEqLl1RZeU6pRvyOSnD4nghgH4 with a valid domain project-one-12345.hasura.app\",\n\t\t\texpectedPairs: []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t// Run the detector with verification turned off for pattern testing.\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"FromData() returned an unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\t// Check if the number of results matches what we expect.\n\t\t\tif len(results) != len(test.expectedPairs) {\n\t\t\t\tt.Errorf(\"expected %d results, but got %d\", len(test.expectedPairs), len(results))\n\t\t\t\t// Log results for easier debugging\n\t\t\t\tfor i, r := range results {\n\t\t\t\t\tt.Logf(\"Got result %d: %s\", i, string(r.RawV2))\n\t\t\t\t}\n\t\t\t\tt.FailNow()\n\t\t\t}\n\n\t\t\t// For a more robust comparison, load the results into maps to ignore order.\n\t\t\tactualPairs := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\t// The RawV2 field should contain the \"domain:key\" pair.\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactualPairs[string(r.RawV2)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpectedPairsMap := make(map[string]struct{}, len(test.expectedPairs))\n\t\t\tfor _, v := range test.expectedPairs {\n\t\t\t\texpectedPairsMap[v] = struct{}{}\n\t\t\t}\n\n\t\t\t// Use cmp.Diff to find any mismatches between the expected and actual pairs.\n\t\t\tif diff := cmp.Diff(expectedPairsMap, actualPairs); diff != \"\" {\n\t\t\t\tt.Errorf(\"FromData() results mismatch (-want +got):\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hellosign/hellosign.go",
    "content": "package hellosign\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"hellosign\"}) + `\\b([a-zA-Z-0-9/+]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"hellosign\"}\n}\n\n// FromData will find and optionally verify HelloSign secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_HelloSign,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.hellosign.com/v3/account\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.SetBasicAuth(resMatch, \"\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_HelloSign\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"HelloSign is an eSignature provider that allows users to sign documents online. HelloSign API keys can be used to interact with the HelloSign API for managing and sending documents.\"\n}\n"
  },
  {
    "path": "pkg/detectors/hellosign/hellosign_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage hellosign\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHelloSign_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HELLOSIGN\")\n\tinactiveSecret := testSecrets.MustGetField(\"HELLOSIGN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hellosign secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HelloSign,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hellosign secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HelloSign,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"HelloSign.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"HelloSign.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hellosign/hellosign_test.go",
    "content": "package hellosign\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"HelloSign\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"hellosign_secret\": \"GKo-dMdrgu6KKy0eE8pwoDEQ2P9zQmuQQrPLNXg6FL5qeB-MVpBhesz6y6bZqu5t\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"GKo-dMdrgu6KKy0eE8pwoDEQ2P9zQmuQQrPLNXg6FL5qeB-MVpBhesz6y6bZqu5t\"\n)\n\nfunc TestHelloSign_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/helpcrunch/helpcrunch.go",
    "content": "package helpcrunch\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"helpcrunch\"}) + `\\b([a-zA-Z-0-9+/=]{328})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"helpcrunch\"}\n}\n\n// FromData will find and optionally verify HelpCrunch secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_HelpCrunch,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.helpcrunch.com/v1/departments\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_HelpCrunch\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"HelpCrunch is a customer communication platform offering live chat, email automation, and more. HelpCrunch API keys can be used to access and manage customer interactions and data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/helpcrunch/helpcrunch_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage helpcrunch\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHelpCrunch_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HELPCRUNCH_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"HELPCRUNCH_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a helpcrunch secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HelpCrunch,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a helpcrunch secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HelpCrunch,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"HelpCrunch.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"HelpCrunch.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/helpcrunch/helpcrunch_test.go",
    "content": "package helpcrunch\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"HelpCrunch\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"helpcrunch_secret\": \"TDV4P0BqcSgI8ZX-GK0o5GbC0-N12GG1thviz6TLRjQxGQSQha+WO78Qz9lMcNvVQ1doHalcElenPD4QoVnZgfEMk2=OOL5-bit2wxH+ykI97mG3jAMhxd5yBm+xMdE8FCdFXQfDPblQ3CjKJBDCfQQNxE+6LkQqS7CoiX2RnlJV8a0ztpe54hHgfirH8oyz=YOvBu9p+FPAj3zv9Ph4W/rV63yPoJsE0l9SLbcCF8uQz/ot1epzk5aqXb-UtZ7WEKApQJO+gEptNV=ylZKceF2KN7irbtmsmKeW0Mf12quDqqj+Yd4zMP3C1wEodnOm9RSofIEX\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"TDV4P0BqcSgI8ZX-GK0o5GbC0-N12GG1thviz6TLRjQxGQSQha+WO78Qz9lMcNvVQ1doHalcElenPD4QoVnZgfEMk2=OOL5-bit2wxH+ykI97mG3jAMhxd5yBm+xMdE8FCdFXQfDPblQ3CjKJBDCfQQNxE+6LkQqS7CoiX2RnlJV8a0ztpe54hHgfirH8oyz=YOvBu9p+FPAj3zv9Ph4W/rV63yPoJsE0l9SLbcCF8uQz/ot1epzk5aqXb-UtZ7WEKApQJO+gEptNV=ylZKceF2KN7irbtmsmKeW0Mf12quDqqj+Yd4zMP3C1wEodnOm9RSofIEX\"\n)\n\nfunc TestHelpCrunch_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/helpscout/helpscout.go",
    "content": "package helpscout\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"helpscout\"}) + `\\b([a-z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"helpscout\"}\n}\n\n// FromData will find and optionally verify Helpscout secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Helpscout,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://docsapi.helpscout.net/v1/collections\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.SetBasicAuth(resMatch, \"X\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Helpscout\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Helpscout is a customer service software that provides a help desk for managing customer communications. Helpscout API keys can be used to access and manage customer interactions and data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/helpscout/helpscout_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage helpscout\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHelpscout_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HELPSCOUT_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"HELPSCOUT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a helpscout secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Helpscout,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a helpscout secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Helpscout,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Helpscout.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Helpscout.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/helpscout/helpscout_test.go",
    "content": "package helpscout\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"HelpCrunch\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"helpscout_secret\": \"s442c6leu1e5hylj9ad0pouy2jphw42iyb54798e\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"s442c6leu1e5hylj9ad0pouy2jphw42iyb54798e\"\n)\n\nfunc TestHelpScout_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hereapi/hereapi.go",
    "content": "package hereapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"hereapi\"}) + `\\b([a-zA-Z0-9\\S]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"hereapi\"}\n}\n\n// FromData will find and optionally verify HereAPI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_HereAPI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://weather.ls.hereapi.com/weather/1.0/report.json?product=alerts&name=Argentina&apiKey=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_HereAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"HereAPI provides access to a wide range of location-based services including maps, geocoding, and traffic information. HereAPI keys can be used to authenticate and access these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/hereapi/hereapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage hereapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHereAPI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HEREAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"HEREAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hereapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HereAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hereapi secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HereAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"HereAPI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"HereAPI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hereapi/hereapi_test.go",
    "content": "package hereapi\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"HereAPI\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"hereapi_secret\": \"Lyg1xguJ4xkBHc2SO0yLUYUzpc9EzlnalubYW7DXQr8\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"Lyg1xguJ4xkBHc2SO0yLUYUzpc9EzlnalubYW7DXQr8\"\n)\n\nfunc TestHereAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/heroku/v1/heroku.go",
    "content": "package heroku\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"heroku\"}) + `\\b([0-9Aa-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\b`)\n)\n\nfunc (s Scanner) Version() int {\n\treturn 1\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"heroku\"}\n}\n\n// FromData will find and optionally verify Heroku secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Heroku,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := VerifyHerokuAPIKey(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Heroku\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Heroku is a cloud platform that lets companies build, deliver, monitor and scale apps. Heroku API keys can be used to access and manage Heroku applications and services.\"\n}\n\nfunc VerifyHerokuAPIKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.heroku.com/account\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Accept\", \"application/vnd.heroku+json; version=3\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/heroku/v1/heroku_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage heroku\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHeroku_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HEROKU\")\n\tinactiveSecret := testSecrets.MustGetField(\"HEROKU_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a heroku secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Heroku,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a heroku secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Heroku,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Heroku.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Heroku.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/heroku/v1/heroku_test.go",
    "content": "package heroku\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestHeroku_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `[{\n\t\t\t\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\t\t\t\"name\": \"Heroku\",\n\t\t\t\t\t\"type\": \"Detector\",\n\t\t\t\t\t\"api\": true,\n\t\t\t\t\t\"authentication_type\": \"\",\n\t\t\t\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\t\t\t\"test_secrets\": {\n\t\t\t\t\t\t\"heroku_secret\": \"bAf8bA7d-7088-07ce-3f87-7ec21653297d\"\n\t\t\t\t\t},\n\t\t\t\t\t\"expected_response\": \"200\",\n\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\"deprecated\": false\n\t\t\t\t}]`,\n\t\t\twant: []string{\"bAf8bA7d-7088-07ce-3f87-7ec21653297d\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/heroku/v2/heroku.go",
    "content": "package heroku\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\tv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/heroku/v1\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(HRKU-AA[0-9a-zA-Z_-]{58})\\b`)\n)\n\nfunc (s Scanner) Version() int {\n\treturn 2\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"HRKU-AA\"}\n}\n\n// FromData will find and optionally verify Heroku secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Heroku,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := v1.VerifyHerokuAPIKey(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Heroku\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Heroku is a cloud platform that lets companies build, deliver, monitor and scale apps. Heroku API keys can be used to access and manage Heroku applications and services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/heroku/v2/heroku_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage heroku\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHeroku_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HEROKU_V2_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"HEROKU_V2_TOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a heroku secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Heroku,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a heroku secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Heroku,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Heroku.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Heroku.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/heroku/v2/heroku_test.go",
    "content": "package heroku\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestHeroku_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `[{\n\t\t\t\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\t\t\t\"name\": \"MyApp\",\n\t\t\t\t\t\"type\": \"Detector\",\n\t\t\t\t\t\"api\": true,\n\t\t\t\t\t\"authentication_type\": \"\",\n\t\t\t\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\t\t\t\"test_secrets\": {\n\t\t\t\t\t\t\"secret1\": \"HRKU-AAlQ1aVoHDujJ9QsDHdHlHO0hbzhoERRSO45ZQusSYHg_____w4_hLrAym_u\",\n\t\t\t\t\t\t\"secret2\": \"HRKU-AAy9Ppr_HD2pPuTyIiTYInO0hbzhoERRSO93ZQusSYHgaD7_WQ07FnF7L9FX\"\n\t\t\t\t\t},\n\t\t\t\t\t\"expected_response\": \"200\",\n\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\"deprecated\": false\n\t\t\t\t}]`,\n\t\t\twant: []string{\"HRKU-AAlQ1aVoHDujJ9QsDHdHlHO0hbzhoERRSO45ZQusSYHg_____w4_hLrAym_u\", \"HRKU-AAy9Ppr_HD2pPuTyIiTYInO0hbzhoERRSO93ZQusSYHgaD7_WQ07FnF7L9FX\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `[{\n\t\t\t\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\t\t\t\"name\": \"MyApp\",\n\t\t\t\t\t\"type\": \"Detector\",\n\t\t\t\t\t\"api\": true,\n\t\t\t\t\t\"authentication_type\": \"\",\n\t\t\t\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\t\t\t\"test_secrets\": {\n\t\t\t\t\t\t\"secret1\": \"HRKU-AAlQ1aVoHDujJ9QsDHdHlHO0hbzoERRSO45ZQusSYHg_____w4_hLrAym_u\",\n\t\t\t\t\t\t\"secret2\": \"HRKU-AAy9Ppr_HD2pPuTyIiTYInO0h;zhoERRSO93ZQusSYHgaD7_WQ07FnF7L9FX\"\n\t\t\t\t\t},\n\t\t\t\t\t\"expected_response\": \"200\",\n\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\"deprecated\": false\n\t\t\t\t}]`,\n\t\t\twant: []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hive/hive.go",
    "content": "package hive\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"hive\"}) + `\\b([0-9A-Za-z]{17})\\b`)\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"hive\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"hive\"}\n}\n\n// FromData will find and optionally verify Hive secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\tkeyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range idMatches {\n\n\t\tidMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, match := range keyMatches {\n\t\t\tkeyMatch := strings.TrimSpace(match[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Hive,\n\t\t\t\tRaw:          []byte(idMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.hive.com/api/v1/testcredentials?user_id=\"+idMatch+\"&api_key=\"+keyMatch, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Hive\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Hive is a project management and collaboration tool. Hive API keys can be used to access and manage projects, tasks, and other data within a Hive workspace.\"\n}\n"
  },
  {
    "path": "pkg/detectors/hive/hive_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage hive\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHive_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tid := testSecrets.MustGetField(\"HIVE\")\n\tsecret := testSecrets.MustGetField(\"HIVE_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"HIVE_SECRET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hive secret %s within hive id %s but verified\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Hive,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hive secret %s within hive id %s but not valid\", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Hive,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Hive.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Hive.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hive/hive_test.go",
    "content": "package hive\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Hive\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"hive_id\": \"F1j6zMpLLcwZC3HM8\",\n\t\t\t\"hive_secret\": \"9b0mxjwzazdwjqvre1fj5ghy6gfw3nsy\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"F1j6zMpLLcwZC3HM8\"\n)\n\nfunc TestHive_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hiveage/hiveage.go",
    "content": "package hiveage\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"hiveage\"}) + `\\b([0-9A-Za-z\\_\\-]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"hiveage\"}\n}\n\n// FromData will find and optionally verify Hiveage secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Hiveage,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tdata := fmt.Sprintf(\"%s:\", resMatch)\n\t\t\tencoded := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://mltb8350.hiveage.com/api/network\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", encoded))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Hiveage\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Hiveage is an online invoicing and billing software. Hiveage API keys can be used to access and manage invoicing and billing data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/hiveage/hiveage_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage hiveage\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHiveage_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HIVEAGE\")\n\tinactiveSecret := testSecrets.MustGetField(\"HIVEAGE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hiveage secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Hiveage,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hiveage secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Hiveage,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Hiveage.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Hiveage.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hiveage/hiveage_test.go",
    "content": "package hiveage\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Hiveage\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"hiveage_secret\": \"nbBeM021WOTqocvdbBXR\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"nbBeM021WOTqocvdbBXR\"\n)\n\nfunc TestHiveage_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/holidayapi/holidayapi.go",
    "content": "package holidayapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"holidayapi\"}) + `\\b([a-z0-9-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"holidayapi\"}\n}\n\n// FromData will find and optionally verify HolidayAPI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_HolidayAPI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://holidayapi.com/v1/holidays?key=%s&country=PH&year=2020\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_HolidayAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"HolidayAPI keys are used to access holiday data for various countries and years. These keys can be used to query and retrieve holiday information programmatically.\"\n}\n"
  },
  {
    "path": "pkg/detectors/holidayapi/holidayapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage holidayapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHolidayAPI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HOLIDAYAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"HOLIDAYAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a holidayapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HolidayAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a holidayapi secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HolidayAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"HolidayAPI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"HolidayAPI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/holidayapi/holidayapi_test.go",
    "content": "package holidayapi\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"HolidayAPI\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"holidayapi_secret\": \"2l094-ldfa8kc3kxn4v6976q76k8z7h1hdro\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"2l094-ldfa8kc3kxn4v6976q76k8z7h1hdro\"\n)\n\nfunc TestHolidayAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/holistic/holistic.go",
    "content": "package holistic\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"holistic\"}) + `\\b([0-9a-f]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"holistic\"}\n}\n\n// FromData will find and optionally verify Holistic secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Holistic,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.holistic.dev/api/v1/project\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"x-api-key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\terrorResponse := strings.Contains(bodyString, `\"data\":[]`)\n\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && !errorResponse {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Holistic\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Holistic is a service that provides comprehensive project management solutions. Holistic API keys can be used to access and manage project data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/holistic/holistic_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage holistic\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHolistic_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HOLISTIC\")\n\tinactiveSecret := testSecrets.MustGetField(\"HOLISTIC_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a holistic secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Holistic,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a holistic secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Holistic,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Holistic.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Holistic.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/holistic/holistic_test.go",
    "content": "package holistic\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Holistic\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"holistic_secret\": \"5a65a90cd2dfa75cf21eeb16bb828bcec1911ebe0a2a11942b9d8be17261be1e\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"5a65a90cd2dfa75cf21eeb16bb828bcec1911ebe0a2a11942b9d8be17261be1e\"\n)\n\nfunc TestHolistic_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/honeycomb/honeycomb.go",
    "content": "package honeycomb\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\t// Older + Newer API key format. See pull#687 for discussion\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"Honeycomb\"}) + `\\b([0-9a-f]{32}|[0-9a-zA-Z]{22})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"Honeycomb\"}\n}\n\n// FromData will find and optionally verify Honeycomb secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Honeycomb,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.honeycomb.io/1/auth\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-Honeycomb-Team\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Honeycomb\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Honeycomb is a tool for introspecting and interrogating production systems to help users understand how their systems work in real-time. Honeycomb API keys can be used to authenticate and interact with the Honeycomb API.\"\n}\n"
  },
  {
    "path": "pkg/detectors/honeycomb/honeycomb_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage honeycomb\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHoneycomb_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HONEYCOMB\")\n\tinactiveSecret := testSecrets.MustGetField(\"HONEYCOMB_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a honeycomb secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Honeycomb,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a honeycomb secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Honeycomb,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Honeycomb.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Honeycomb.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/honeycomb/honeycomb_test.go",
    "content": "package honeycomb\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"HoneyComb\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"honeycomb_secret\": \"kZKcr8xUYHJQajRYQLuT8x\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"kZKcr8xUYHJQajRYQLuT8x\"\n)\n\nfunc TestHoneyComb_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/host/host.go",
    "content": "package host\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"host\"}) + `\\b([a-z0-9]{14})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"host.io\"}\n}\n\n// FromData will find and optionally verify Host secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Host,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://host.io/api/domains/ip/8.8.8.8?token=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Host\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Host.io provides domain data and related services. Host.io tokens can be used to access and manage domain information.\"\n}\n"
  },
  {
    "path": "pkg/detectors/host/host_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage host\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHost_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HOST\")\n\tinactiveSecret := testSecrets.MustGetField(\"HOST_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a host secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Host,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a host secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Host,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Host.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Host.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/host/host_test.go",
    "content": "package host\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Host.IO\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"host_secret\": \"cxr9i4q59p9za6\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"cxr9i4q59p9za6\"\n)\n\nfunc TestHost_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/html2pdf/html2pdf.go",
    "content": "package html2pdf\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\ntype html2pdfRequest struct {\n\tHTML   string `json:\"html\"`\n\tApiKey string `json:\"apiKey\"`\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"html2pdf\"}) + `\\b([a-zA-Z0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"html2pdf\"}\n}\n\n// FromData will find and optionally verify Html2Pdf secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Html2Pdf,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq := html2pdfRequest{\n\t\t\t\tHTML:   \"Helloworld\",\n\t\t\t\tApiKey: resMatch,\n\t\t\t}\n\t\t\treqJson, _ := json.Marshal(&req)\n\t\t\treqBuf := bytes.NewReader(reqJson)\n\t\t\tres, err := http.Post(\"https://api.html2pdf.app/v1/generate\", \"application/json\", reqBuf)\n\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Html2Pdf\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Html2Pdf is a service that converts HTML content into PDF documents. API keys for Html2Pdf can be used to authenticate and authorize access to this service.\"\n}\n"
  },
  {
    "path": "pkg/detectors/html2pdf/html2pdf_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage html2pdf\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHtml2Pdf_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HTML2PDF\")\n\tinactiveSecret := testSecrets.MustGetField(\"HTML2PDF_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a html2pdf secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Html2Pdf,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a html2pdf secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Html2Pdf,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Html2Pdf.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Html2Pdf.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/html2pdf/html2pdf_test.go",
    "content": "package html2pdf\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Html2Pdf\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"html2pdf_secret\": \"oFLqoMgM6I3h8WKYykz1zG86B4rR7wuPXYMKheO3jhuSzPz1B77HBorb18gV7dDv\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"oFLqoMgM6I3h8WKYykz1zG86B4rR7wuPXYMKheO3jhuSzPz1B77HBorb18gV7dDv\"\n)\n\nfunc TestHtml2Pdf_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/http.go",
    "content": "package detectors\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"net/http\"\n\t\"slices\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/feature\"\n)\n\nvar DetectorHttpClientWithNoLocalAddresses *http.Client\nvar DetectorHttpClientWithLocalAddresses *http.Client\n\n// DefaultResponseTimeout is the default timeout for HTTP requests.\nconst DefaultResponseTimeout = 10 * time.Second\n\nfunc userAgent() string {\n\tif len(feature.UserAgentSuffix.Load()) > 0 {\n\t\treturn \"TruffleHog \" + feature.UserAgentSuffix.Load()\n\t}\n\treturn \"TruffleHog\"\n}\n\nfunc init() {\n\tDetectorHttpClientWithLocalAddresses = NewDetectorHttpClient(\n\t\tWithTransport(NewDetectorTransport(nil)),\n\t\tWithTimeout(DefaultResponseTimeout),\n\t\tWithNoFollowRedirects(),\n\t)\n\tDetectorHttpClientWithNoLocalAddresses = NewDetectorHttpClient(\n\t\tWithTransport(NewDetectorTransport(nil)),\n\t\tWithTimeout(DefaultResponseTimeout),\n\t\tWithNoFollowRedirects(),\n\t\tWithNoLocalIP(),\n\t)\n}\n\nvar overrideOnce sync.Once\n\n// OverrideDetectorTimeout overrides the default timeout for the detector HTTP clients.\n// It is guaranteed to only run once, subsequent calls will have no effect.\n// This should be called before any scans are started.\nfunc OverrideDetectorTimeout(timeout time.Duration) {\n\toverrideOnce.Do(func() {\n\t\tDetectorHttpClientWithLocalAddresses.Timeout = timeout\n\t\tDetectorHttpClientWithNoLocalAddresses.Timeout = timeout\n\t})\n}\n\n// ClientOption defines a function type that modifies an http.Client.\ntype ClientOption func(*http.Client)\n\n// WithNoFollowRedirects allows disabling automatic following of redirects.\nfunc WithNoFollowRedirects() ClientOption {\n\treturn func(c *http.Client) {\n\t\tc.CheckRedirect = func(req *http.Request, via []*http.Request) error {\n\t\t\treturn http.ErrUseLastResponse\n\t\t}\n\t}\n}\n\ntype detectorTransport struct {\n\tT http.RoundTripper\n}\n\nfunc (t *detectorTransport) RoundTrip(req *http.Request) (*http.Response, error) {\n\treq.Header.Add(\"User-Agent\", userAgent())\n\treturn t.T.RoundTrip(req)\n}\n\nvar defaultDialer = &net.Dialer{\n\tTimeout:   2 * time.Second,\n\tKeepAlive: 5 * time.Second,\n}\n\nfunc NewDetectorTransport(T http.RoundTripper) http.RoundTripper {\n\tif T == nil {\n\t\tT = &http.Transport{\n\t\t\tProxy:                 http.ProxyFromEnvironment,\n\t\t\tDialContext:           defaultDialer.DialContext,\n\t\t\tMaxIdleConns:          100,\n\t\t\tMaxIdleConnsPerHost:   5,\n\t\t\tIdleConnTimeout:       90 * time.Second,\n\t\t\tTLSHandshakeTimeout:   3 * time.Second,\n\t\t\tExpectContinueTimeout: 1 * time.Second,\n\t\t}\n\t}\n\treturn &detectorTransport{T: T}\n}\n\nfunc isLocalIP(ip net.IP) bool {\n\tif ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsPrivate() || ip.IsUnspecified() {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nvar ErrNoLocalIP = errors.New(\"dialing local IP addresses is not allowed\")\n\nfunc WithNoLocalIP() ClientOption {\n\treturn func(c *http.Client) {\n\t\tif c.Transport == nil {\n\t\t\tc.Transport = &http.Transport{}\n\t\t}\n\n\t\t// Type assertion to get the underlying *http.Transport\n\t\ttransport, ok := c.Transport.(*http.Transport)\n\t\tif !ok {\n\t\t\t// If c.Transport is not *http.Transport, check if it is wrapped in a detectorTransport\n\t\t\tdt, ok := c.Transport.(*detectorTransport)\n\t\t\tif !ok {\n\t\t\t\tpanic(\"unsupported transport type\")\n\t\t\t}\n\t\t\ttransport, ok = dt.T.(*http.Transport)\n\t\t\tif !ok {\n\t\t\t\tpanic(\"underlying transport is not *http.Transport\")\n\t\t\t}\n\t\t}\n\n\t\t// If the original DialContext is nil, set it to the default dialer\n\t\tif transport.DialContext == nil {\n\t\t\ttransport.DialContext = defaultDialer.DialContext\n\t\t}\n\t\toriginalDialContext := transport.DialContext\n\t\ttransport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\thost, port, err := net.SplitHostPort(addr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tips, err := net.LookupIP(host)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tif slices.ContainsFunc(ips, isLocalIP) {\n\t\t\t\treturn nil, ErrNoLocalIP\n\t\t\t}\n\n\t\t\treturn originalDialContext(ctx, network, net.JoinHostPort(host, port))\n\t\t}\n\t}\n}\n\n// WithTransport sets a custom transport for the http.Client.\nfunc WithTransport(transport http.RoundTripper) ClientOption {\n\treturn func(c *http.Client) {\n\t\tc.Transport = transport\n\t}\n}\n\n// WithTimeout sets a timeout for the http.Client.\nfunc WithTimeout(timeout time.Duration) ClientOption {\n\treturn func(c *http.Client) {\n\t\tc.Timeout = timeout\n\t}\n}\n\nfunc NewDetectorHttpClient(opts ...ClientOption) *http.Client {\n\tclient := &http.Client{\n\t\tTransport: NewDetectorTransport(nil),\n\t\tTimeout:   DefaultResponseTimeout,\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(client)\n\t}\n\n\tclient.Transport = common.NewInstrumentedTransport(client.Transport)\n\treturn client\n}\n"
  },
  {
    "path": "pkg/detectors/http_test.go",
    "content": "package detectors\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestWithNoLocalIP(t *testing.T) {\n\tt.Run(\"Prevents dialing local IP\", func(t *testing.T) {\n\t\tclient := &http.Client{}\n\t\tWithNoLocalIP()(client)\n\n\t\ttransport, ok := client.Transport.(*http.Transport)\n\t\tassert.True(t, ok, \"Expected transport to be *http.Transport\")\n\n\t\t_, err := transport.DialContext(context.Background(), \"tcp\", \"127.0.0.1:8080\")\n\t\tassert.Error(t, err)\n\t\tassert.ErrorIs(t, err, ErrNoLocalIP)\n\t})\n\n\tt.Run(\"Prevents dialing wildcard IP\", func(t *testing.T) {\n\t\tclient := &http.Client{}\n\t\tWithNoLocalIP()(client)\n\n\t\ttransport, ok := client.Transport.(*http.Transport)\n\t\tassert.True(t, ok, \"Expected transport to be *http.Transport\")\n\n\t\t_, err := transport.DialContext(context.Background(), \"tcp\", \"0.0.0.0:8080\")\n\t\tassert.Error(t, err)\n\t\tassert.ErrorIs(t, err, ErrNoLocalIP)\n\t})\n\n\tt.Run(\"Prevents dialing IPv6 wildcard IP\", func(t *testing.T) {\n\t\tclient := &http.Client{}\n\t\tWithNoLocalIP()(client)\n\n\t\ttransport, ok := client.Transport.(*http.Transport)\n\t\tassert.True(t, ok, \"Expected transport to be *http.Transport\")\n\n\t\t_, err := transport.DialContext(context.Background(), \"tcp\", \"[::]:8080\")\n\t\tassert.Error(t, err)\n\t\tassert.ErrorIs(t, err, ErrNoLocalIP)\n\t})\n\n\tt.Run(\"Allows dialing non-local host\", func(t *testing.T) {\n\t\tclient := &http.Client{}\n\t\tWithNoLocalIP()(client)\n\n\t\ttransport, ok := client.Transport.(*http.Transport)\n\t\tassert.True(t, ok, \"Expected transport to be *http.Transport\")\n\n\t\tconn, err := transport.DialContext(context.Background(), \"tcp\", \"google.com:80\")\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, conn)\n\t\tconn.Close()\n\t})\n\n\tt.Run(\"Allows dialing non-local IP\", func(t *testing.T) {\n\t\tclient := &http.Client{}\n\t\tWithNoLocalIP()(client)\n\n\t\ttransport, ok := client.Transport.(*http.Transport)\n\t\tassert.True(t, ok, \"Expected transport to be *http.Transport\")\n\n\t\tconn, err := transport.DialContext(context.Background(), \"tcp\", \"1.1.1.1:80\")\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, conn)\n\t\tconn.Close()\n\t})\n\n\tt.Run(\"Handles invalid address\", func(t *testing.T) {\n\t\tclient := &http.Client{}\n\t\tWithNoLocalIP()(client)\n\n\t\ttransport, ok := client.Transport.(*http.Transport)\n\t\tassert.True(t, ok, \"Expected transport to be *http.Transport\")\n\n\t\t_, err := transport.DialContext(context.Background(), \"tcp\", \"invalid-address\")\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"Handles non-existent hostname\", func(t *testing.T) {\n\t\tclient := &http.Client{}\n\t\tWithNoLocalIP()(client)\n\n\t\ttransport, ok := client.Transport.(*http.Transport)\n\t\tassert.True(t, ok, \"Expected transport to be *http.Transport\")\n\n\t\t_, err := transport.DialContext(context.Background(), \"tcp\", \"non-existent-host.local:80\")\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestIsLocalIP(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\tip       net.IP\n\t\texpected bool\n\t}{\n\t\t{\"Loopback IPv4\", net.ParseIP(\"127.0.0.1\"), true},\n\t\t{\"Loopback IPv6\", net.ParseIP(\"::1\"), true},\n\t\t{\"Private IPv4\", net.ParseIP(\"192.168.1.1\"), true},\n\t\t{\"Private IPv6\", net.ParseIP(\"fd00::1\"), true},\n\t\t{\"Unspecified IPv4\", net.ParseIP(\"0.0.0.0\"), true},\n\t\t{\"Unspecified IPv6\", net.ParseIP(\"::\"), true},\n\t\t{\"Public IPv4\", net.ParseIP(\"8.8.8.8\"), false},\n\t\t{\"Public IPv6\", net.ParseIP(\"2001:4860:4860::8888\"), false},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tresult := isLocalIP(tc.ip)\n\t\t\tassert.Equal(t, tc.expected, result)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hubspot_apikey/v1/apikey.go",
    "content": "package v1\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ interface {\n\tdetectors.Detector\n\tdetectors.Versioner\n} = (*Scanner)(nil)\n\nfunc (s Scanner) Version() int { return 1 }\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"hubapi\", \"hapi_?key\", \"hubspot\"}) + `\\b([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})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"hubapi\", \"hapikey\", \"hapi_key\", \"hubspot\"}\n}\n\n// FromData will find and optionally verify HubSpotApiKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor token := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_HubSpotApiKey,\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tverified, verificationErr := verifyToken(ctx, client, token)\n\t\t\ts1.Verified = verified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\n// See https://legacydocs.hubspot.com/docs/methods/auth/oauth-overview\nfunc verifyToken(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.hubapi.com/contacts/v1/lists?hapikey=\"+token, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tcase http.StatusForbidden:\n\t\t// The token is valid but lacks permission for the endpoint.\n\t\treturn true, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_HubSpotApiKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"HubSpot is a CRM platform that provides tools for marketing, sales, and customer service. HubSpot API keys can be used to access and modify data within the HubSpot platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/hubspot_apikey/v1/apikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage v1\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHubSpotApiKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HUBSPOTAPIKEY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"HUBSPOTAPIKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hubspotapikey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HubSpotApiKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hubspotapikey secret %s within but unverified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HubSpotApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"HubSpotApiKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"HubSpotApiKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hubspot_apikey/v1/apikey_test.go",
    "content": "package v1\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestHubspotV1_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"hapikey\",\n\t\t\tinput: `// const hapikey = 'b714cac4-a45c-42af-9905-da4de8838d75';\nconst { HAPI_KEY } = process.env;\nconst hs = new HubSpotAPI({ hapikey: HAPI_KEY });`,\n\t\t\twant: []string{\"b714cac4-a45c-42af-9905-da4de8838d75\"},\n\t\t},\n\t\t// TODO: Doesn't work because it's more than 40 characters.\n\t\t//\t{\n\t\t//\t\tname: \"hubapi\",\n\t\t//\t\tinput: `curl https://api.hubapi.com/contacts/v1/lists/all/contacts/all \\\n\t\t// --header \"Authorization: Bearer b71aa2ed-9c76-417d-bd8e-c5f4980d21ef\"`,\n\t\t//\t\twant: []string{\"b71aa2ed-9c76-417d-bd8e-c5f4980d21ef\"},\n\t\t//\t},\n\t\t{\n\t\t\tname: \"hubspot_1\",\n\t\t\tinput: `const hs = new HubSpotAPI(\"76a836c8-469d-4426-8a3b-194ca930b7a1\");\n\nconst blogPosts = hs.blog.getPosts({ name: 'Inbound' });`,\n\t\t\twant: []string{\"76a836c8-469d-4426-8a3b-194ca930b7a1\"},\n\t\t},\n\t\t{\n\t\t\tname: \"hubspot_2\",\n\t\t\tinput: `\t'hubspot' => [\n\t       // 'api_key' => 'e9ff285d-6b7f-455a-a56d-9ec8c4abbd47',         // @ts dev`,\n\t\t\twant: []string{\"e9ff285d-6b7f-455a-a56d-9ec8c4abbd47\"},\n\t\t},\n\t\t{\n\t\t\tname: \"hubspot_3\",\n\t\t\tinput: `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"HubSpotAPIKey\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"hubspot_secret\": \"hDNxPGyQ-AOMZ-w9Sp-aw5t-TwKLBQjQ85go\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`,\n\t\t\twant: []string{\"hDNxPGyQ-AOMZ-w9Sp-aw5t-TwKLBQjQ85go\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hubspot_apikey/v2/apikey.go",
    "content": "package v2\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nfunc (s Scanner) Version() int { return 2 }\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ interface {\n\tdetectors.Detector\n\tdetectors.Versioner\n} = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(`\\b(pat-(?:eu|na)1-[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})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pat-na1-\", \"pat-eu1-\"}\n}\n\n// FromData will find and optionally verify HubSpotApiKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor token := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_HubSpotApiKey,\n\t\t\tRaw:          []byte(token),\n\t\t\tRedacted:     token[8:] + \"...\",\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tverified, verificationErr := verifyToken(ctx, client, token)\n\t\t\ts1.Verified = verified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\nfunc verifyToken(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.hubapi.com/account-info/v3/api-usage/daily/private-apps\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tcase http.StatusForbidden:\n\t\t// The token is valid but lacks permission for the endpoint.\n\t\treturn true, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_HubSpotApiKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"HubSpot is a CRM platform that provides tools for marketing, sales, and customer service. HubSpot API keys can be used to access and modify data within the HubSpot platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/hubspot_apikey/v2/apikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage v2\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHubSpotApiKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HUBSPOTAPIKEY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"HUBSPOTAPIKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hubspotapikey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HubSpotApiKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hubspotapikey secret %s within but unverified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HubSpotApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"HubSpotApiKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"HubSpotApiKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hubspot_apikey/v2/apikey_test.go",
    "content": "package v2\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestHubspotV2_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"eu key\",\n\t\t\tinput: `\nconst private_app_token = 'pat-eu1-1457aed5-04c6-40e2-83ad-a862d3cf19f2';\n\napp.get('/homepage', async (req, res) => {\n    const contactsEndpoint = 'https://api.hubspot.com/crm/v3/objects/contacts';`,\n\t\t\twant: []string{\"pat-eu1-1457aed5-04c6-40e2-83ad-a862d3cf19f2\"},\n\t\t},\n\t\t{\n\t\t\tname: \"na key\",\n\t\t\tinput: `hubspot:\n   api:\n      url: https://api.hubapi.com\n      auth-token: pat-na1-ffbb9f50-d96b-4abc-84f1-b986617be1b5\n   subscriptions:`,\n\t\t\twant: []string{\"pat-na1-ffbb9f50-d96b-4abc-84f1-b986617be1b5\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/huggingface/huggingface.go",
    "content": "package huggingface\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(?:hf_|api_org_)[a-zA-Z0-9]{34}\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"hf_\", \"api_org_\"} // Huggingface docs occasionally use \"hf\" instead of \"huggingface\"\n}\n\n// FromData will find and optionally verify Huggingface secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[0])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_HuggingFace,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, extraData, verificationErr := s.verifyResult(ctx, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\ts1.AnalysisInfo = map[string]string{\"key\": resMatch}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) verifyResult(ctx context.Context, apiKey string) (bool, map[string]string, error) {\n\tclient := s.client\n\tif client == nil {\n\t\tclient = defaultClient\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://huggingface.co/api/whoami-v2\", nil)\n\tif err != nil {\n\t\treturn false, nil, nil\n\t}\n\n\treq.Header.Set(\"Authorization\", \"Bearer \"+apiKey)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\tdefer res.Body.Close()\n\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\twhoamiRes := whoamiResponse{}\n\t\terr := json.NewDecoder(res.Body).Decode(&whoamiRes)\n\t\tif err != nil {\n\t\t\treturn true, nil, err\n\t\t}\n\n\t\tvar tokenInfo string\n\t\tswitch {\n\t\tcase whoamiRes.Auth.AccessToken.DisplayName != \"\" || whoamiRes.Auth.AccessToken.Role != \"\":\n\t\t\t// hf_xxxx token\n\t\t\tt := whoamiRes.Auth.AccessToken\n\t\t\ttokenInfo = fmt.Sprintf(\"%s (%s)\", t.DisplayName, t.Role)\n\n\t\tcase whoamiRes.Auth.Type != \"\":\n\t\t\t// api_org_xxxx token\n\t\t\ttokenInfo = whoamiRes.Auth.Type\n\n\t\tdefault:\n\t\t\ttokenInfo = \"Unknown Token Type\"\n\t\t}\n\n\t\textraData := map[string]string{\n\t\t\t\"Username\": whoamiRes.Name,\n\t\t\t\"Email\":    whoamiRes.Email,\n\t\t\t\"Token\":    tokenInfo,\n\t\t}\n\n\t\t// Condense a list of organizations + roles.\n\t\torgs := make([]string, 0, len(whoamiRes.Organizations))\n\t\tfor _, org := range whoamiRes.Organizations {\n\t\t\torgs = append(orgs, fmt.Sprintf(\"%s:%s\", org.Name, org.Role))\n\t\t}\n\t\tif len(orgs) > 0 {\n\t\t\textraData[\"Organizations\"] = strings.Join(orgs, \", \")\n\t\t}\n\t\treturn true, extraData, nil\n\t} else if res.StatusCode == 401 {\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\t} else {\n\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\treturn false, nil, err\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_HuggingFace\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Hugging Face is a platform for natural language processing tasks and model hosting. Hugging Face API keys can be used to access various services and resources on the platform.\"\n}\n\n// https://huggingface.co/docs/hub/api#get-apiwhoami-v2\ntype whoamiResponse struct {\n\tName          string         `json:\"name\"`\n\tEmail         string         `json:\"email\"`\n\tOrganizations []organization `json:\"orgs\"`\n\tAuth          auth           `json:\"auth\"`\n}\n\ntype organization struct {\n\tName string `json:\"name\"`\n\tRole string `json:\"roleInOrg\"`\n}\n\ntype auth struct {\n\tAccessToken struct {\n\t\tDisplayName string `json:\"displayName,omitempty\"`\n\t\tRole        string `json:\"role,omitempty\"`\n\t} `json:\"accessToken,omitempty\"`\n\tType string `json:\"type,omitempty\"`\n}\n"
  },
  {
    "path": "pkg/detectors/huggingface/huggingface_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage huggingface\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHuggingface_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HUGGINGFACE\")\n\tinactiveSecret := testSecrets.MustGetField(\"HUGGINGFACE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a huggingface secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HuggingFace,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"Email\":    \"zubair.khan@trufflesec.com\",\n\t\t\t\t\t\t\"Token\":    \"another_one (read)\",\n\t\t\t\t\t\t\"Username\": \"zubairkhan\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a huggingface secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HuggingFace,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a huggingface secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HuggingFace,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a huggingface secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HuggingFace,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\n\t// Add tests for invalid UTF-8 sequences.\n\tinvalidUTF8Sequences := [][]byte{\n\t\t{0x80},\n\t\t{0xC0, 0xAF},\n\t\t{0xFE},\n\t\t{0xE2, 0x82},\n\t\t{0xED, 0xA0, 0x80},\n\t\t{0xF0, 0x28},\n\t}\n\n\tfor _, invalidSeq := range invalidUTF8Sequences {\n\t\t// Inject invalid sequence into the test string\n\t\ttestData := fmt.Sprintf(\"you can find a huggingface secret %s%s%s more valid text\",\n\t\t\tstring(invalidSeq),\n\t\t\tsecret,\n\t\t\tstring(invalidSeq))\n\n\t\ttests = append(tests, struct {\n\t\t\tname                string\n\t\t\ts                   Scanner\n\t\t\targs                args\n\t\t\twant                []detectors.Result\n\t\t\twantErr             bool\n\t\t\twantVerificationErr bool\n\t\t}{\n\t\t\tname: fmt.Sprintf(\"Test with invalid UTF-8 sequence: %v\", invalidSeq),\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(testData),\n\t\t\t\tverify: true, // or true based on your test scenario\n\t\t\t},\n\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_HuggingFace,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"Email\":    \"zubair.khan@trufflesec.com\",\n\t\t\t\t\t\t\"Token\":    \"another_one (read)\",\n\t\t\t\t\t\t\"Username\": \"zubairkhan\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t})\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Huggingface.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\" wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Huggingface.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/huggingface/huggingface_test.go",
    "content": "package huggingface\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"HuggingFace\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"huggingface_secret\": \"hf_ZY1eSxaQdNSh6LSV1T1xkZ114inIxE57YK\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"hf_ZY1eSxaQdNSh6LSV1T1xkZ114inIxE57YK\"\n)\n\nfunc TestHuggingFace_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/humanity/humanity.go",
    "content": "package humanity\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"humanity\"}) + `\\b([0-9a-z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"humanity\"}\n}\n\n// FromData will find and optionally verify Humanity secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Humanity,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.humanity.com/api/v2/me?access_token=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\n\t\t\t\tvalidResponse := strings.Contains(body, \"name\")\n\n\t\t\t\tif validResponse {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Humanity\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Humanity is a workforce management platform. Humanity API keys can be used to access and manage workforce data and operations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/humanity/humanity_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage humanity\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHumanity_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HUMANITY\")\n\tinactiveSecret := testSecrets.MustGetField(\"HUMANITY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a humanity secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Humanity,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a humanity secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Humanity,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Humanity.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Humanity.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/humanity/humanity_test.go",
    "content": "package humanity\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Humanity\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"humanity_secret\": \"ys64ge0rcytbpzccvgat1p9ja2g94oe5ofnrkp5f\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"ys64ge0rcytbpzccvgat1p9ja2g94oe5ofnrkp5f\"\n)\n\nfunc TestHumanity_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hunter/hunter.go",
    "content": "package hunter\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"hunter\"}) + `\\b([a-z0-9_-]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"hunter\"}\n}\n\n// FromData will find and optionally verify Hunter secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Hunter,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.hunter.io/v2/leads_lists?api_key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Hunter\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Hunter is a service that helps find and verify professional email addresses. Hunter API keys can be used to access and retrieve data from the Hunter service.\"\n}\n"
  },
  {
    "path": "pkg/detectors/hunter/hunter_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage hunter\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHunter_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HUNTER_IO_API_KEY\")\n\tsecretInactive := testSecrets.MustGetField(\"HUNTER_IO_API_KEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hunter secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Hunter,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hunter secret %s within but not valid\", secretInactive)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Hunter,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Hunter.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Hunter.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hunter/hunter_test.go",
    "content": "package hunter\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Hunter\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"hunter_secret\": \"680o5ujd5r32jp2jf3s_xkhf8ls_99_oux07efq_\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"680o5ujd5r32jp2jf3s_xkhf8ls_99_oux07efq_\"\n)\n\nfunc TestHunter_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hybiscus/hybiscus.go",
    "content": "package hybiscus\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"hybiscus\"}) + `\\b([0-9a-zA-Z]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"hybiscus\"}\n}\n\n// FromData will find and optionally verify Hybiscus secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Hybiscus,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.hybiscus.dev/api/v1/get-remaining-quota\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.hybiscus+json; version=3\")\n\t\t\treq.Header.Add(\"X-API-KEY\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Hybiscus\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Hybiscus is a service that provides API keys to access its resources and perform operations. These keys should be kept secure to prevent unauthorized access.\"\n}\n"
  },
  {
    "path": "pkg/detectors/hybiscus/hybiscus_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage hybiscus\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHybiscus_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HYBISCUS\")\n\tinactiveSecret := testSecrets.MustGetField(\"HYBISCUS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hybiscus secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Hybiscus,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hybiscus secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Hybiscus,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Hybiscus.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Hybiscus.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hybiscus/hybiscus_test.go",
    "content": "package hybiscus\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"Hybiscus\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"hybiscus_secret\": \"gwM2c9pbPbM05OvhCZmStknNow5Ay0pWuxwG6HaAq9x\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"gwM2c9pbPbM05OvhCZmStknNow5Ay0pWuxwG6HaAq9x\"\n)\n\nfunc TestHybiscus_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hypertrack/hypertrack.go",
    "content": "package hypertrack\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.'\n\taccPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"hypertrack\"}) + `\\b([0-9a-zA-Z\\_\\-]{27})\\b`)\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"hypertrack\"}) + `\\b([0-9a-zA-Z\\_\\-]{54})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"hypertrack\"}\n}\n\n// FromData will find and optionally verify Hypertrack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\taccMatches := accPat.FindAllStringSubmatch(dataStr, -1)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, accMatch := range accMatches {\n\t\tresAccMatch := strings.TrimSpace(accMatch[1])\n\n\t\tfor _, match := range matches {\n\t\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Hypertrack,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://v3.api.hypertrack.com/trips/\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.SetBasicAuth(resAccMatch, resMatch)\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Hypertrack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Hypertrack is a service that provides live location tracking for fleets and other mobile workforce management. Hypertrack keys can be used to access and manage these tracking services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/hypertrack/hypertrack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage hypertrack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestHypertrack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"HYPERTRACK\")\n\tinactiveSecret := testSecrets.MustGetField(\"HYPERTRACK_INACTIVE\")\n\taccount := testSecrets.MustGetField(\"HYPERTRACK_ACCOUNT\")\n\tinactiveAccount := testSecrets.MustGetField(\"HYPERTRACK_ACCOUNT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hypertrack account %s with hypertrack secret %s within\", account, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Hypertrack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a hypertrack account %s with hypertrack secret %s within but not valid\", inactiveAccount, inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Hypertrack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Hypertrack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Hypertrack.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/hypertrack/hypertrack_test.go",
    "content": "package hypertrack\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `[{\n\t\t\"_id\": \"1a8d0cca-e1a9-4318-bc2f-f5658ab2dcb5\",\n\t\t\"name\": \"HyperTrack\",\n\t\t\"type\": \"Detector\",\n\t\t\"api\": true,\n\t\t\"authentication_type\": \"\",\n\t\t\"verification_url\": \"https://api.example.com/example\",\n\t\t\"test_secrets\": {\n\t\t\t\"hypertrack_account: \"7SzoSsoaDY-NMc9d4b-IbJm1sn1\",\n\t\t\t\"hypertrack_secret\": \"skVh58q2vSjeMPbyicWsyyxJ20NHu3WWj2lR0XnfwScEHO48CR96vV\"\n\t\t},\n\t\t\"expected_response\": \"200\",\n\t\t\"method\": \"GET\",\n\t\t\"deprecated\": false\n\t}]`\n\tsecret = \"skVh58q2vSjeMPbyicWsyyxJ20NHu3WWj2lR0XnfwScEHO48CR96vV\"\n)\n\nfunc TestHyperTrack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{secret},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ibmclouduserkey/ibmclouduserkey.go",
    "content": "package ibmclouduserkey\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ibm\"}) + `\\b([A-Za-z0-9_-]{44})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ibm\"}\n}\n\n// FromData will find and optionally verify IbmCloudUserKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_IbmCloudUserKey,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`apikey=` + resMatch + `&grant_type=urn%3Aibm%3Aparams%3Aoauth%3Agrant-type%3Aapikey`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://iam.cloud.ibm.com/identity/token\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\treq.Header.Add(\"Authorization\", \"Basic Yng6Yng=\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_IbmCloudUserKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"IBM Cloud is a suite of cloud computing services from IBM that offers both platform as a service (PaaS) and infrastructure as a service (IaaS). IBM Cloud API keys can be used to access and manage IBM Cloud services and resources.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ibmclouduserkey/ibmclouduserkey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ibmclouduserkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestIbmCloudUserKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"IBMCLOUDUSERKEY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"IBMCLOUDUSERKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ibmclouduserkey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_IbmCloudUserKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ibmclouduserkey secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_IbmCloudUserKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"IbmCloudUserKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"IbmCloudUserKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ibmclouduserkey/ibmclouduserkey_test.go",
    "content": "package ibmclouduserkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"0y0tceatuuwnIqFvzpfvb8Td6-07esplajvPznSvizma\"\n\tinvalidPattern = \"A0y0tceatuuwnIqFvzpfvb8Td6-07esplajvPznSvizmaF\"\n\tkeyword        = \"ibmclouduserkey\"\n)\n\nfunc TestIbmCloudUserKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword ibmclouduserkey\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/iconfinder/iconfinder.go",
    "content": "package iconfinder\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"iconfinder\"}) + `\\b([a-zA-Z0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"iconfinder\"}\n}\n\n// FromData will find and optionally verify IconFinder secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_IconFinder,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.iconfinder.com/v4/iconsets\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_IconFinder\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"IconFinder is a platform that provides a large collection of icons. IconFinder API keys can be used to access and retrieve icons from the platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/iconfinder/iconfinder_integreation_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage iconfinder\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestIconFinder_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ICONFINDER\")\n\tinactiveSecret := testSecrets.MustGetField(\"ICONFINDER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a iconfinder secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_IconFinder,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a iconfinder secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_IconFinder,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"IconFinder.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"IconFinder.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/iconfinder/iconfinder_test.go",
    "content": "package iconfinder\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"g48QWZXPpNeQjaUghu3aqKTxqcMtjVoY94Fv0lkYj4EGpbXTHd2GjbhWZ2uTmZLa\"\n\tinvalidPattern = \"g48QWZXPpNeQ-aUghu3aqKTxqcMtjVoY94Fv0lkY-4EGpbXTHd2GjbhWZ2uTmZLa\"\n\tkeyword        = \"iconfinder\"\n)\n\nfunc TestIconFinder_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword iconfinder\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/iexapis/iexapis.go",
    "content": "package iexapis\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"iexapis\"}) + `\\b(sk_[a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"iexapis\"}\n}\n\n// FromData will find and optionally verify Iexapis secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Iexapis,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://cloud.iexapis.com/stable/stock/aapl/quote?token=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Iexapis\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"IEX Cloud provides data services for financial markets. IEX Cloud API keys can be used to access and retrieve financial data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/iexapis/iexapis_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage iexapis\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestIexapis_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"IEXAPIS\")\n\tinactiveSecret := testSecrets.MustGetField(\"IEXAPIS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a iexapis secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Iexapis,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a iexapis secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Iexapis,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Iexapis.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Iexapis.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/iexapis/iexapis_test.go",
    "content": "package iexapis\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"sk_d21e2be3622049c7809f95cc02313821\"\n\tinvalidPattern = \"sg_h21e2be3622049c7809f95cc02313821\"\n\tkeyword        = \"iexapis\"\n)\n\nfunc TestIexapis_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword iexapis\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/iexcloud/iexcloud.go",
    "content": "package iexcloud\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"iexcloud\"}) + `\\b([a-z0-9_]{35})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"iexcloud\"}\n}\n\n// FromData will find and optionally verify Iexcloud secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Iexcloud,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://cloud.iexapis.com/v1/stock/aapl/quote?token=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Iexcloud\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"IEX Cloud is a financial data platform providing access to a wide range of financial data APIs. IEX Cloud tokens can be used to access and retrieve financial data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/iexcloud/iexcloud_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage iexcloud\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestIexcloud_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"IEXCLOUD\")\n\tinactiveSecret := testSecrets.MustGetField(\"IEXCLOUD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a iexcloud secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Iexcloud,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a iexcloud secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Iexcloud,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Iexcloud.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Iexcloud.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/iexcloud/iexcloud_test.go",
    "content": "package iexcloud\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"sk_e51a4bf78d4c4c6ab906bac4c783cb91\"\n\tinvalidPattern = \"Ask_e51a4bf78d4c4c6ab906bac4c783cb91F\"\n\tkeyword        = \"iexcloud\"\n)\n\nfunc TestIexcloud_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword iexcloud\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/imagekit/imagekit.go",
    "content": "package imagekit\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"imagekit\"}) + `\\b([a-zA-Z0-9_=]{36})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"imagekit\"}\n}\n\n// FromData will find and optionally verify Imagekit secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Imagekit,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.imagekit.io/v1/files\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.SetBasicAuth(resMatch, \"\")\n\t\t\tres, err := client.Do(req)\n\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Imagekit\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Imagekit is a real-time image optimization and transformation service. Imagekit keys can be used to access and manipulate images on the platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/imagekit/imagekit_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage imagekit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestImagekit_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"IMAGEKIT_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"IMAGEKIT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a imagekit secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Imagekit,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a imagekit secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Imagekit,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Imagekit.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Imagekit.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/imagekit/imagekit_test.go",
    "content": "package imagekit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"cHJpdmF0ZV9vNlM5MGxtbVE2Z0ZLbHFLS0xh\"\n\tinvalidPattern = \"-HJpdmF0ZV9vNlM5MGxtbVE2Z0ZLbHFLS0xh\"\n\tkeyword        = \"imagekit\"\n)\n\nfunc TestImagekit_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword imagekit\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/imagga/imagga.go",
    "content": "package imagga\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"imagga\"}) + `\\b([a-z0-9A-Z=]{72})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"imagga\"}\n}\n\n// FromData will find and optionally verify Imagga secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Imagga,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.imagga.com/v2/usage\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Imagga\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Imagga is a cloud-based image recognition and categorization service. Imagga API keys can be used to access and utilize these image processing services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/imagga/imagga_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage imagga\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestImagga_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"IMAGGA\")\n\tinactiveSecret := testSecrets.MustGetField(\"IMAGGA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a imagga secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Imagga,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a imagga secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Imagga,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Imagga.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Imagga.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/imagga/imagga_test.go",
    "content": "package imagga\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"2Lnr2CqKYvAzUrQ7mv0twEviqSq03Zq=w0F7EOJvY30J8oj5KSQZVxQmW5UYUI7bEEdjhmDK\"\n\tinvalidPattern = \"2Lnr2CqKYvAzUrQ7mv0twEviqSq03Zq=w0F7EOJvY30J8oj5KSQZVxQmW5UYUI7bEEdjhmD\"\n\tkeyword        = \"imagga\"\n)\n\nfunc TestImagga_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword imagga\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/impala/impala.go",
    "content": "package impala\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"impala\"}) + `\\b([0-9A-Za-z_]{46})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"impala\"}\n}\n\n// FromData will find and optionally verify Impala secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Impala,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://sandbox.impala.travel/v1/bookings\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"x-api-key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Impala\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Impala is a travel API service used for managing bookings and reservations. Impala API keys can be used to access and modify booking data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/impala/impala_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage impala\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestImpala_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"IMPALA\")\n\tinactiveSecret := testSecrets.MustGetField(\"IMPALA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a impala secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Impala,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a impala secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Impala,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Impala.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Impala.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/impala/impala_test.go",
    "content": "package impala\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"2MitCEv5ZAguR57EJkD7deNBssSC0lNr897akiBb9cEMoi\"\n\tinvalidPattern = \"2MitCEv5ZAguR57EJkD7deNBssSC0lNr897akiBb9cEMo\"\n\tkeyword        = \"impala\"\n)\n\nfunc TestImpala_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword impala\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/infura/infura.go",
    "content": "package infura\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"infura\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"infura\"}\n}\n\n// FromData will find and optionally verify Infura secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Infura,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\",\"params\":[],\"id\":1}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://mainnet.infura.io/v3/\"+resMatch, payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\t\t\t\tif strings.Contains(body, `\"result\"`) {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Infura\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Infura provides the infrastructure for decentralized applications to connect to the Ethereum blockchain. Infura keys can be used to interact with the Ethereum network.\"\n}\n"
  },
  {
    "path": "pkg/detectors/infura/infura_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage infura\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestInfura_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"INFURA\")\n\tinactiveSecret := testSecrets.MustGetField(\"INFURA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a infura secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Infura,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a infura secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Infura,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Infura.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Infura.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/infura/infura_test.go",
    "content": "package infura\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"8vr0j5458z4xq57qkozagbt2l6nnjetq\"\n\tinvalidPattern = \"8vr0j5458z4xq57qkozagbt2l6nnjet\"\n\tkeyword        = \"infura\"\n)\n\nfunc TestInfura_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword infura\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/insightly/insightly.go",
    "content": "package insightly\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"insightly\"}) + `\\b([a-z0-9-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"insightly\"}\n}\n\n// FromData will find and optionally verify Insightly secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Insightly,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.na1.insightly.com/v3.1/Contacts?top=2\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.SetBasicAuth(resMatch, \"\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Insightly\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Insightly is a customer relationship management (CRM) platform. Insightly API keys can be used to access and manage CRM data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/insightly/insightly_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage insightly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestInsightly_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"INSIGHTLY\")\n\tinactiveSecret := testSecrets.MustGetField(\"INSIGHTLY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a insightly secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Insightly,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a insightly secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Insightly,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Insightly.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Insightly.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/insightly/insightly_test.go",
    "content": "package insightly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"9ytdevl7jl75ylv06jx1m3qzktlr0x89m8fr\"\n\tinvalidPattern = \"9ytdevl7jl75ylv06jx1m3qzktlr0x89m8f\"\n\tkeyword        = \"insightly\"\n)\n\nfunc TestInsightly_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword insightly\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/instabot/instabot.go",
    "content": "package instabot\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"instabot\"}) + `\\b([0-9a-zA-Z=+\\/]{43}[0-9a-zA-Z+\\/=]{1})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"instabot\"}\n}\n\n// FromData will find and optionally verify Instabot secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Instabot,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.instabot.io/v1\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-Instabot-Api-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Instabot\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Instabot is a service that provides automated social media interactions. Instabot API keys can be used to access and interact with the service programmatically.\"\n}\n"
  },
  {
    "path": "pkg/detectors/instabot/instabot_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage instabot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestInstabot_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"INSTABOT\")\n\tinactiveSecret := testSecrets.MustGetField(\"INSTABOT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a instabot secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Instabot,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a instabot secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Instabot,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Instabot.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Instabot.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/instabot/instabot_test.go",
    "content": "package instabot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"7tB=6ZFzMLh7npsKURT3wqCRE0lAAn/4bLgkuR7Q=IFt\"\n\tinvalidPattern = \"7tB=6ZFzMLh7npsKURT3wqCRE0lAAn/4bLgkuR7Q=IF\"\n\tkeyword        = \"instabot\"\n)\n\nfunc TestInstabot_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword instabot\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/instamojo/instamojo.go",
    "content": "package instamojo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\t// KeyPat is client_id\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"instamojo\"}) + `\\b([0-9a-zA-Z]{40})\\b`)\n\t// Secretpat is Client_secret\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"instamojo\"}) + `\\b([0-9a-zA-Z]{128})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"instamojo\"}\n}\n\n// FromData will find and optionally verify Instamojo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\tclientIdmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range secretMatches {\n\t\tresSecret := strings.TrimSpace(match[1])\n\n\t\tfor _, clientIdMatch := range clientIdmatches {\n\t\t\tresClientId := strings.TrimSpace(clientIdMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Instamojo,\n\t\t\t\tRaw:          []byte(resClientId),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\t\t\t\tpayload := strings.NewReader(\"grant_type=client_credentials&client_id=\" + resClientId + \"&client_secret=\" + resSecret)\n\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.instamojo.com/oauth2/token/\", payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tbody := string(bodyBytes)\n\t\t\t\t\tif (res.StatusCode >= 200 && res.StatusCode < 300) && strings.Contains(body, \"access_token\") {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\t\ts1.SetVerificationError(err, resSecret)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ts1.SetVerificationError(err, resSecret)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Instamojo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"An Ecommerce service, API keys can be used to create and access customer data\"\n}\n"
  },
  {
    "path": "pkg/detectors/instamojo/instamojo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage instamojo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestInstamojo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tid := testSecrets.MustGetField(\"INSTAMOJO_CLIENT_ID\")\n\tsecret := testSecrets.MustGetField(\"INSTAMOJO_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"INSTAMOJO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a instamojo secret %s within id %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Instamojo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a instamojo secret %s within but not valid, within id %s\", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Instamojo,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Instamojo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Instamojo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/instamojo/instamojo_test.go",
    "content": "package instamojo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern      = \"ZrGOAl9jlTlAKxw4hXZXeRmd6wvndEr2pX0fqDv2\"\n\tinvalidKeyPattern    = \"VdZVfhpWN0O0FL0KKZauMMtgJytUPAmkNRO9Vwq\"\n\tvalidSecretPattern   = \"a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6q7R8s9T0u1V2w3X4y5Z6a7B8c9D0e1F2g3H4i5J6k7L8m9N0o1P2q3R4s5T6u7V8w9X0y1Z2a3B4c5D6e7F8g9H0D2e3F4g5\"\n\tinvalidSecretPattern = \"V2w3X4y5Z6a7!8c9D0e1F2g3H4i5J6k7L8m9N0o@P2q3R4s5T6u7V8w9X0y1Z2a3_4c5D6e7F8g9H0i1J2k3L4m5-6o7P8q9R0s1T2u3V4w5X6y7Z8a9B0c1D2e3F4+=\"\n\tkeyword              = \"instamojo\"\n)\n\nfunc TestInstamojo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword instamojo\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s' %s '%s'\", keyword, validKeyPattern, keyword, validSecretPattern),\n\t\t\twant:  []string{\"ZrGOAl9jlTlAKxw4hXZXeRmd6wvndEr2pX0fqDv2\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s' secret = '%s'\", keyword, validKeyPattern, validSecretPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' secret = '%s'\", keyword, invalidKeyPattern, invalidSecretPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/intercom/intercom.go",
    "content": "package intercom\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"intercom\"}) + `\\b([a-zA-Z0-9\\W\\S]{59}\\=)`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"intercom\"}\n}\n\n// FromData will find and optionally verify InterCom secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tdec, err := base64.StdEncoding.DecodeString(resMatch)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif !strings.HasPrefix(string(dec), \"tok:\") {\n\t\t\tcontinue\n\t\t}\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Intercom,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.intercom.io/contacts?per_page=5\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", \"Bearer \"+resMatch)\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Intercom\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Intercom is a customer messaging platform that allows businesses to communicate with customers. Intercom tokens can be used to access and manage customer data and communication.\"\n}\n"
  },
  {
    "path": "pkg/detectors/intercom/intercom_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage intercom\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestInterCom_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"INTERCOM_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"INTERCOM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a intercom secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Intercom,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a intercom secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Intercom,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"InterCom.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"InterCom.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/intercom/intercom_test.go",
    "content": "package intercom\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"dG9rOmE2EzJhNmQwXzdsOpFfNDcxNV84GPU2XzMnMWRiYTIxOTc2ZDevLcB=\"\n\tinvalidPattern = \"m2=L&7-I2',Ei.pC16@~s<w<r:7UeE2?jZ<l(<YdCLq,8<^o#v8woZ7wpv]=\"\n\tkeyword        = \"intercom\"\n)\n\nfunc TestInterCom_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword intercom\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/interseller/interseller.go",
    "content": "package interseller\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"interseller\"}) + `\\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"interseller\"}\n}\n\n// FromData will find and optionally verify Interseller secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Interseller,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://interseller.io/api/campaigns/list\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"X-API-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Interseller\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Interseller is a platform for automating email outreach. Interseller API keys can be used to manage and send email campaigns.\"\n}\n"
  },
  {
    "path": "pkg/detectors/interseller/interseller_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage interseller\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestInterseller_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"INTERSELLER\")\n\tinactiveSecret := testSecrets.MustGetField(\"INTERSELLER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a interseller secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Interseller,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a interseller secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Interseller,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Interseller.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Interseller.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/interseller/interseller_test.go",
    "content": "package interseller\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"7e1e818a-2bc5-5042-5b5d-8bf2465e1d81\"\n\tinvalidPattern = \"7e1e818a-2bc5-5042-5b5d-8bf2465e1d8\"\n\tkeyword        = \"interseller\"\n)\n\nfunc TestInterseller_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword interseller\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/intra42/intra42.go",
    "content": "package intra42\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nconst verifyURL = \"https://api.intra.42.fr/oauth/token\"\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(`\\b(s-s4t2(?:ud|af)-[a-f0-9]{64})\\b`)\n\tidPat  = regexp.MustCompile(`\\b(u-s4t2(?:ud|af)-[a-f0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"s-s4t2ud-\", \"s-s4t2af-\"}\n}\n\n// FromData will find and optionally verify Intra42 secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tif len(match) != 2 {\n\t\t\tcontinue\n\t\t}\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\t\t\tif len(idMatch) != 2 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Intra42,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.getClient()\n\t\t\t\tisVerified, verificationErr := verifyIntra42(ctx, client, resMatch, resIdMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\nfunc verifyIntra42(ctx context.Context, client *http.Client, resMatch string, resIdMatch string) (bool, error) {\n\tdata := url.Values{}\n\tdata.Set(\"client_id\", resIdMatch)\n\tdata.Set(\"client_secret\", resMatch)\n\tdata.Set(\"grant_type\", \"client_credentials\")\n\tencodedData := data.Encode()\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, verifyURL, strings.NewReader(encodedData))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\treq.Header.Add(\"Content-Length\", strconv.Itoa(len(data.Encode())))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected http response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Intra42\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Intra42 is an API service used to interact with the 42 school's internal systems. Intra42 API keys can be used to access and modify data within these systems.\"\n}\n"
  },
  {
    "path": "pkg/detectors/intra42/intra42_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage intra42\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestIntra42_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*10)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"INTRA42_SECRET\")\n\tid := testSecrets.MustGetField(\"INTRA42_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"INTRA42_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find an intra42 secret %s within intra42 %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Intra42,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find an intra42 secret %s within intra42 %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Intra42,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find an intra42 secret %s within intra42 %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Intra42,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find an intra42 secret %s within intra42 %s but not valid\", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Intra42,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Intra42.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Errorf(\"Intra42.FromData() verificationError = %v, wantVerificationErr %v\", got[i].VerificationError(), tt.wantVerificationErr)\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Intra42.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/intra42/intra42_test.go",
    "content": "package intra42\n\nimport (\n\t\"context\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"testing\"\n)\n\nfunc TestIntra42_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"typical pattern\",\n\t\t\tinput: `\nintra_client_id = 'u-s4t2ud-d91c558a2ba6b47f60f690efc20a33d28c252d5bed8400343246f3eb68f490d2'\nintra_client_secret = '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'\n`,\n\t\t\twant: []string{\n\t\t\t\t\"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\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tdetectorMatches := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(detectorMatches) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/intrinio/intrinio.go",
    "content": "package intrinio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"intrinio\"}) + `\\b([a-zA-Z0-9]{44})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"intrinio\"}\n}\n\n// FromData will find and optionally verify Intrinio secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Intrinio,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api-v2.intrinio.com/securities?api_key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Intrinio\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Intrinio provides financial data APIs. Intrinio API keys can be used to access various financial datasets and services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/intrinio/intrinio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage intrinio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestIntrinio_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"INTRINIO\")\n\tinactiveSecret := testSecrets.MustGetField(\"INTRINIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a intrinio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Intrinio,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a intrinio secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Intrinio,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Intrinio.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Intrinio.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/intrinio/intrinio_test.go",
    "content": "package intrinio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"RgqgmHdKJ4nQD7jk4whnZe6iox2sHSbcQf4UaLYE2UAw\"\n\tinvalidPattern = \"RgqgmHdKJ4nQD7jk4whnZe6iox2sHSbcQf4UaLYE2UA\"\n\tkeyword        = \"intrinio\"\n)\n\nfunc TestIntrinio_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword intrinio\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/invoiceocean/invoiceocean.go",
    "content": "package invoiceocean\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"invoiceocean\"}) + `\\b([0-9A-Za-z]{20})\\b`)\n\turlPat = regexp.MustCompile(`\\b([0-9a-z]{1,}\\.invoiceocean\\.com)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"invoiceocean\"}\n}\n\n// FromData will find and optionally verify Invoiceocean secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\turlMatches := urlPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, urlMatch := range urlMatches {\n\t\t\tresURL := strings.TrimSpace(urlMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_InvoiceOcean,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://%s/invoices.json?period=this_month&api_token=%s\", resURL, resMatch), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_InvoiceOcean\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"InvoiceOcean is an online invoicing service. InvoiceOcean API tokens can be used to access and manage invoices and other financial data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/invoiceocean/invoiceocean_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage invoiceocean\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestInvoiceocean_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"INVOICEOCEAN\")\n\turl := testSecrets.MustGetField(\"INVOICEOCEAN_URL\")\n\tinactiveSecret := testSecrets.MustGetField(\"INVOICEOCEAN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a invoiceocean secret %s within %s\", secret, url)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_InvoiceOcean,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a invoiceocean secret %s within %s but not valid\", inactiveSecret, url)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_InvoiceOcean,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Invoiceocean.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Invoiceocean.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/invoiceocean/invoiceocean_test.go",
    "content": "package invoiceocean\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern   = \"ZWd1e2141Q2R1ds23sdX\"\n\tinvalidKeyPattern = \"ZWd.e214-Q2R1ds$3sd=\"\n\tvalidUrlPattern   = \"4f2a9c.invoiceocean.com\"\n\tinvalidUrlPattern = \"4f2a9c.invoiceocean.net\"\n\tkeyword           = \"invoiceocean\"\n)\n\nfunc TestInvoiceocean_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword invoiceocean\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s' %s '%s'\", keyword, validKeyPattern, keyword, validUrlPattern),\n\t\t\twant:  []string{\"ZWd1e2141Q2R1ds23sdX\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s' secret = '%s'\", keyword, validKeyPattern, validUrlPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' secret = '%s'\", keyword, invalidKeyPattern, invalidUrlPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ip2location/ip2location.go",
    "content": "package ip2location\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ip2location\"}) + `\\b([0-9A-Z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ip2location\"}\n}\n\n// FromData will find and optionally verify Ip2location secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Ip2location,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.ip2location.io/?key=\"+resMatch, nil)\n\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Ip2location\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Ip2location is a service that provides IP geolocation data. Ip2location keys can be used to access this geolocation data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ip2location/ip2location_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ip2location\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestIp2location_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"IP2LOCATION\")\n\tinactiveSecret := testSecrets.MustGetField(\"IP2LOCATION_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ip2location secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ip2location,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ip2location secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ip2location,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Ip2location.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Ip2location.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ip2location/ip2location_test.go",
    "content": "package ip2location\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"UBENM81QDKGJ7GHB6Q68L2B4Z06DO3YD\"\n\tinvalidPattern = \"UBENM81QDKGJ7GHB6Q68L2B4Z06DO3Y\"\n\tkeyword        = \"ip2location\"\n)\n\nfunc TestIp2location_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword ip2location\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ipapi/ipapi.go",
    "content": "package ipapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ipapi\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ipapi\"}\n}\n\n// FromData will find and optionally verify Ipapi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Ipapi,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.ipapi.com/49.146.239.251?access_key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalid := strings.Contains(bodyString, \"continent_code\") || strings.Contains(bodyString, `\"info\":\"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption.\"`)\n\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif valid {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Ipapi\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Ipapi provides an API for IP address lookup and geolocation. Ipapi keys can be used to access this service and retrieve geolocation data for IP addresses.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ipapi/ipapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ipapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestIpapi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"IPAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"IPAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ipapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ipapi,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ipapi secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ipapi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Ipapi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Ipapi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ipapi/ipapi_test.go",
    "content": "package ipapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"af6dmku2agtwytrdfxxyf3gcszoa6one\"\n\tinvalidPattern = \"af6dmku2agtwytrdfxxyf3gcszoa6on\"\n\tkeyword        = \"ipapi\"\n)\n\nfunc TestIpapi_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword ipapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ipgeolocation/ipgeolocation.go",
    "content": "package ipgeolocation\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ipgeolocation\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ipgeolocation\"}\n}\n\n// FromData will find and optionally verify IPGeolocation secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_IPGeolocation,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.ipgeolocation.io/ipgeo?apiKey=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_IPGeolocation\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"IPGeolocation provides geolocation information about IP addresses. The API key can be used to access and retrieve this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ipgeolocation/ipgeolocation_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ipgeolocation\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestIPGeolocation_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"IPGEOLOCATION\")\n\tinactiveSecret := testSecrets.MustGetField(\"IPGEOLOCATION_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ipgeolocation secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_IPGeolocation,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ipgeolocation secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_IPGeolocation,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"IPGeolocation.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"IPGeolocation.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ipgeolocation/ipgeolocation_test.go",
    "content": "package ipgeolocation\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"08blecim3dji81ott4ipeh7kk3mcqs9f\"\n\tinvalidPattern = \"08blecim3dji81ott4ipeh7kk3mcqs9\"\n\tkeyword        = \"ipgeolocation\"\n)\n\nfunc TestIPGeolocation_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword ipgeolocation\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ipinfo/ipinfo.go",
    "content": "package ipinfo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ipinfo\"}) + `\\b([a-f0-9]{14})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ipinfo\"}\n}\n\n// FromData will find and optionally verify Ipinfo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_IPInfo,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://ipinfo.io/json?token=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 403 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_IPInfo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"IPInfo provides detailed information about IP addresses. IPInfo API keys can be used to access and retrieve this information.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ipinfo/ipinfo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ipinfo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestIpinfo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"IPINFO\")\n\tinactiveSecret := testSecrets.MustGetField(\"IPINFO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ipinfo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_IPInfo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ipinfo secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_IPInfo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ipinfo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_IPInfo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ipinfo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_IPInfo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Ipinfo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Ipinfo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ipinfo/ipinfo_test.go",
    "content": "package ipinfo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"3ff77bb6fe6885\"\n\tinvalidPattern = \"3ff77bb6fe688\"\n\tkeyword        = \"ipinfo\"\n)\n\nfunc TestIpinfo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword ipinfo\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ipinfodb/ipinfodb.go",
    "content": "package ipinfodb\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ipinfodb\"}) + `\\b([a-z0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ipinfodb\"}\n}\n\n// FromData will find and optionally verify IPinfoDB secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_IPinfoDB,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.ipinfodb.com/v3/ip-country/?key=%s&ip=8.8.8.8&format=json\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `\"statusCode\": \"OK\"`)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_IPinfoDB\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"IPinfoDB provides IP address geolocation services. IPinfoDB API keys can be used to access geolocation data for IP addresses.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ipinfodb/ipinfodb_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ipinfodb\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestIPinfoDB_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"IPINFODB\")\n\tinactiveSecret := testSecrets.MustGetField(\"IPINFODB_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ipinfodb secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_IPinfoDB,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ipinfodb secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_IPinfoDB,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"IPinfoDB.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"IPinfoDB.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ipinfodb/ipinfodb_test.go",
    "content": "package ipinfodb\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"5n2gzgwp07ttl1gsizaxcdkmb0osp0eiqi39nkzqq2mqqe94jrhwvhc2lvgqkta5\"\n\tinvalidPattern = \"5n2gzgwp07ttl1gsizaxcdkmb0osp0eiqi39nkzqq2mqqe94jrhwvhc2lvgqkta\"\n\tkeyword        = \"ipinfodb\"\n)\n\nfunc TestIPinfoDB_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword ipinfodb\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ipquality/ipquality.go",
    "content": "package ipquality\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ipquality\"}) + `\\b([0-9a-zA-Z]{32})\\b`)\n)\n\nconst (\n\t// response messages\n\tinvalidKeyMessage         = \"Invalid or unauthorized key\"\n\tinsufficientCreditMessage = \"insufficient credits\"\n)\n\ntype apiResponse struct {\n\tSuccess bool   `json:\"success\"`\n\tMessage string `json:\"message\"`\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ipquality\"}\n}\n\n// FromData will find and optionally verify Ipquality secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_IPQuality,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyIPQualityAPIKey(ctx, client, resMatch)\n\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_IPQuality\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"IPQualityScore provides tools to detect and prevent fraudulent activity. IPQualityScore API keys can be used to access their fraud prevention services.\"\n}\n\nfunc verifyIPQualityAPIKey(ctx context.Context, client *http.Client, apiKey string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://www.ipqualityscore.com/api/json/account/%s\", apiKey), nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tvar response apiResponse\n\n\t\tbodyBytes, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tif err = json.Unmarshal(bodyBytes, &response); err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tswitch response.Success {\n\t\tcase true:\n\t\t\treturn true, nil\n\t\tcase false:\n\t\t\t/*\n\t\t\t\tfor invalid api key and for a key which has insufficient credit the API returns the same response.\n\t\t\t\tThe scenario where we have correct API key but it has insufficient credit is rare than a scenario that we capture\n\t\t\t\tan invalid api key as the pattern is too common. Hence in case we get insufficient credit error message we mark the\n\t\t\t\tAPI Key as inactive and send back a verification error as well.\n\t\t\t*/\n\t\t\tif strings.Contains(response.Message, insufficientCreditMessage) {\n\t\t\t\treturn false, errors.New(\"couldn't verify; API Key has \" + insufficientCreditMessage)\n\t\t\t} else if strings.Contains(response.Message, invalidKeyMessage) {\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ipquality/ipquality_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ipquality\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestIpquality_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"IPQUALITY\")\n\tinactiveSecret := testSecrets.MustGetField(\"IPQUALITY_INACTIVE\")\n\n\tinvalidResult := detectors.Result{DetectorType: detectorspb.DetectorType_IPQuality, Verified: false}\n\tinvalidResult.SetVerificationError(errors.New(\"couldn't verify; API Key has \" + insufficientCreditMessage))\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ipquality secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_IPQuality,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ipquality secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\tinvalidResult,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Ipquality.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Ipquality.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ipquality/ipquality_test.go",
    "content": "package ipquality\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"p1b3ref1b3gzpn15gduecwvhaat0du5k\"\n\tinvalidPattern = \"p1b3ref1b3gzpn15gduecwvhaat0du5\"\n\tkeyword        = \"ipquality\"\n)\n\nfunc TestIpquality_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword ipquality\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ipstack/ipstack.go",
    "content": "package ipstack\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ipstack\"}) + `\\b([a-fA-F0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ipstack\"}\n}\n\n// FromData will find and optionally verify IpStack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_IpStack,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.ipstack.com/134.201.250.155?access_key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\t\t\t\tvalidResponse := strings.Contains(body, \"continent_code\") || strings.Contains(body, `\"info\":\"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption.\"`)\n\n\t\t\t\tif validResponse {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_IpStack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"IpStack provides a REST API for IP geolocation services. IpStack API keys can be used to access geolocation data for IP addresses.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ipstack/ipstack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ipstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestIpStack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"IPSTACK_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"IPSTACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a IPSTACK secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_IpStack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a IPSTACK secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_IpStack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"IpStack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"IPSTACK.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ipstack/ipstack_test.go",
    "content": "package ipstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"FA0E2B0be2fd6076b59c8696783eadeB\"\n\tinvalidPattern = \"FA0E2B0be2fd6076b59c8696783eade\"\n\tkeyword        = \"ipstack\"\n)\n\nfunc TestIpStack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword ipstack\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jdbc/jdbc.go",
    "content": "package jdbc\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tignorePatterns []regexp.Regexp\n}\n\nfunc New(opts ...func(*Scanner)) *Scanner {\n\tscanner := &Scanner{\n\t\tignorePatterns: []regexp.Regexp{},\n\t}\n\tfor _, opt := range opts {\n\t\topt(scanner)\n\t}\n\n\treturn scanner\n}\n\nfunc WithIgnorePattern(ignoreStrings []string) func(*Scanner) {\n\treturn func(s *Scanner) {\n\t\tvar ignorePatterns []regexp.Regexp\n\t\tfor _, ignoreString := range ignoreStrings {\n\t\t\tignorePattern, err := regexp.Compile(ignoreString)\n\t\t\tif err != nil {\n\t\t\t\tpanic(fmt.Sprintf(\"%s is not a valid regex, error received: %v\", ignoreString, err))\n\t\t\t}\n\t\t\tignorePatterns = append(ignorePatterns, *ignorePattern)\n\t\t}\n\n\t\ts.ignorePatterns = ignorePatterns\n\t}\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)\n\nvar (\n\t// Matches typical JDBC connection strings.\n\t// The terminal character class additionally excludes () and & to avoid\n\t// capturing surrounding delimiters (e.g. \"(jdbc:…)\" or \"…&user=x&\").\n\tkeyPat = regexp.MustCompile(`(?i)jdbc:[\\w]{3,10}:[^\\s\"'<>,{}[\\]]{10,511}[^\\s\"'<>,{}[\\]()&]`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"jdbc\"}\n}\n\n// FromData will find and optionally verify Jdbc secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tlogCtx := logContext.AddLogger(ctx)\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\nmatchLoop:\n\tfor _, match := range matches {\n\t\tif len(s.ignorePatterns) != 0 {\n\t\t\tfor _, ignore := range s.ignorePatterns {\n\t\t\t\tif ignore.MatchString(match[0]) {\n\t\t\t\t\tcontinue matchLoop\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tjdbcConn := match[0]\n\n\t\tresult := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_JDBC,\n\t\t\tRaw:          []byte(jdbcConn),\n\t\t\tRedacted:     tryRedactAnonymousJDBC(jdbcConn),\n\t\t}\n\n\t\tif verify {\n\t\t\tj, err := NewJDBC(logCtx, jdbcConn)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\t\t\tdefer cancel()\n\t\t\tpingRes := j.ping(ctx)\n\t\t\tresult.Verified = pingRes.err == nil\n\t\t\t// If there's a ping error that is marked as \"determinate\" we throw it away. We do this because this was the\n\t\t\t// behavior before tri-state verification was introduced and preserving it allows us to gradually migrate\n\t\t\t// detectors to use tri-state verification.\n\t\t\tif pingRes.err != nil && !pingRes.determinate {\n\t\t\t\terr = pingRes.err\n\t\t\t\tresult.SetVerificationError(err, jdbcConn)\n\t\t\t}\n\t\t\tresult.AnalysisInfo = map[string]string{\n\t\t\t\t\"connection_string\": jdbcConn,\n\t\t\t}\n\t\t\t// TODO: specialized redaction\n\t\t}\n\n\t\tresults = append(results, result)\n\t}\n\n\treturn\n}\n\nfunc (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {\n\treturn false, \"\"\n}\n\nfunc tryRedactAnonymousJDBC(conn string) string {\n\tif s, ok := tryRedactURLParams(conn); ok {\n\t\treturn s\n\t}\n\tif s, ok := tryRedactODBC(conn); ok {\n\t\treturn s\n\t}\n\tif s, ok := tryRedactBasicAuth(conn); ok {\n\t\treturn s\n\t}\n\tif s, ok := tryRedactRegex(conn); ok {\n\t\treturn s\n\t}\n\treturn conn\n}\n\n// Basic authentication \"username:password@host\" style\nfunc tryRedactBasicAuth(conn string) (string, bool) {\n\tuserPass, postfix, found := strings.Cut(conn, \"@\")\n\tif !found {\n\t\treturn \"\", false\n\t}\n\tindex := strings.LastIndex(userPass, \":\")\n\tif index == -1 {\n\t\treturn \"\", false\n\t}\n\tprefix, pass := userPass[:index], userPass[index+1:]\n\treturn prefix + \":\" + strings.Repeat(\"*\", len(pass)) + \"@\" + postfix, true\n}\n\n// URL param \"?password=password\" style\nfunc tryRedactURLParams(conn string) (string, bool) {\n\tprefix, paramString, found := strings.Cut(conn, \"?\")\n\tif !found {\n\t\treturn \"\", false\n\t}\n\tvar newParams []string\n\tfound = false\n\tfor _, param := range strings.Split(paramString, \"&\") {\n\t\tkey, val, _ := strings.Cut(param, \"=\")\n\t\tif strings.Contains(strings.ToLower(key), \"pass\") {\n\t\t\tnewParams = append(newParams, key+\"=\"+strings.Repeat(\"*\", len(val)))\n\t\t\tfound = true\n\t\t\tcontinue\n\t\t}\n\t\tnewParams = append(newParams, param)\n\t}\n\tif !found {\n\t\treturn \"\", false\n\t}\n\treturn prefix + \"?\" + strings.Join(newParams, \"&\"), true\n}\n\n// ODBC params \";password=password\" style\nfunc tryRedactODBC(conn string) (string, bool) {\n\tvar found bool\n\tvar newParams []string\n\tfor _, param := range strings.Split(conn, \";\") {\n\t\tkey, val, isKvp := strings.Cut(param, \"=\")\n\t\tif isKvp && strings.Contains(strings.ToLower(key), \"pass\") {\n\t\t\tnewParams = append(newParams, key+\"=\"+strings.Repeat(\"*\", len(val)))\n\t\t\tfound = true\n\t\t\tcontinue\n\t\t}\n\t\tnewParams = append(newParams, param)\n\t}\n\tif !found {\n\t\treturn \"\", false\n\t}\n\treturn strings.Join(newParams, \";\"), true\n}\n\n// Naively search the string for \"pass=\"\nfunc tryRedactRegex(conn string) (string, bool) {\n\tpattern := regexp.MustCompile(`(?i)pass.*?=(.+?)\\b`)\n\tvar found bool\n\tnewConn := pattern.ReplaceAllStringFunc(conn, func(s string) string {\n\t\tindex := strings.Index(s, \"=\")\n\t\tif index == -1 {\n\t\t\t// unreachable due to regex containing '='\n\t\t\treturn s\n\t\t}\n\t\tfound = true\n\t\treturn s[:index+1] + strings.Repeat(\"*\", len(s[index+1:]))\n\t})\n\tif !found {\n\t\treturn \"\", false\n\t}\n\treturn newConn, true\n}\n\nvar supportedSubprotocols = map[string]func(logContext.Context, string) (JDBC, error){\n\t\"mysql\":      parseMySQL,\n\t\"postgresql\": parsePostgres,\n\t\"sqlserver\":  parseSqlServer,\n}\n\nfunc NewJDBC(ctx logContext.Context, conn string) (JDBC, error) {\n\t// expected format: \"jdbc:{subprotocol}:{subname}\"\n\tif !strings.HasPrefix(strings.ToLower(conn), \"jdbc:\") {\n\t\treturn nil, errors.New(\"expected jdbc prefix\")\n\t}\n\tconn = conn[len(\"jdbc:\"):]\n\n\tsubprotocol, subname, found := strings.Cut(conn, \":\")\n\tif !found {\n\t\treturn nil, errors.New(\"expected a colon separated subprotocol and subname\")\n\t}\n\n\tparser, ok := supportedSubprotocols[strings.ToLower(subprotocol)]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unsupported subprotocol: %s\", subprotocol)\n\t}\n\n\treturn parser(ctx, subname)\n}\n\nfunc ping(ctx context.Context, driverName string, isDeterminate func(error) bool, candidateConns ...string) pingResult {\n\tvar indeterminateErrors []error\n\tfor _, c := range candidateConns {\n\t\terr := pingErr(ctx, driverName, c)\n\t\tif err == nil || isDeterminate(err) {\n\t\t\treturn pingResult{err, true}\n\t\t}\n\t\tindeterminateErrors = append(indeterminateErrors, err)\n\t}\n\treturn pingResult{errors.Join(indeterminateErrors...), false}\n}\n\nfunc pingErr(ctx context.Context, driverName, conn string) error {\n\tdb, err := sql.Open(driverName, conn)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer db.Close()\n\n\tif err := db.PingContext(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_JDBC\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"JDBC (Java Database Connectivity) is an API for connecting and executing queries with databases. JDBC connection strings can be used to access and manipulate databases.\"\n}\n"
  },
  {
    "path": "pkg/detectors/jdbc/jdbc_integration_test.go",
    "content": "//go:build detectors && integration\n// +build detectors,integration\n\npackage jdbc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/brianvoe/gofakeit/v7\"\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/testcontainers/testcontainers-go\"\n\t\"github.com/testcontainers/testcontainers-go/modules/mssql\"\n\t\"github.com/testcontainers/testcontainers-go/modules/mysql\"\n\t\"github.com/testcontainers/testcontainers-go/modules/postgres\"\n\t\"github.com/testcontainers/testcontainers-go/wait\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestJdbcVerified(t *testing.T) {\n\tctx := context.Background()\n\n\tpostgresUser := gofakeit.Username()\n\tpostgresPass := gofakeit.Password(true, true, true, false, false, 10)\n\tpostgresDB := gofakeit.Word()\n\tpostgresContainer, err := postgres.Run(ctx,\n\t\t\"postgres:13-alpine\",\n\t\tpostgres.WithDatabase(postgresDB),\n\t\tpostgres.WithUsername(postgresUser),\n\t\tpostgres.WithPassword(postgresPass),\n\t\ttestcontainers.WithWaitStrategy(\n\t\t\twait.ForLog(\"database system is ready to accept connections\").\n\t\t\t\tWithOccurrence(2).\n\t\t\t\tWithStartupTimeout(5*time.Second)),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer postgresContainer.Terminate(ctx)\n\n\tpostgresHost, err := postgresContainer.Host(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tpostgresPort, err := postgresContainer.MappedPort(ctx, \"5432\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tmysqlUser := gofakeit.Username()\n\tmysqlPass := gofakeit.Password(true, true, true, false, false, 10)\n\tmysqlDatabase := gofakeit.Word()\n\tmysqlC, err := mysql.Run(ctx,\n\t\t\"mysql:8.0.36\",\n\t\tmysql.WithUsername(mysqlUser),\n\t\tmysql.WithPassword(mysqlPass),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdefer mysqlC.Terminate(ctx)\n\n\tmysqlHost, err := mysqlC.Host(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tmysqlPort, err := mysqlC.MappedPort(ctx, \"3306\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsqlServerPass := gofakeit.Password(true, true, true, false, false, 10)\n\tsqlServerDatabase := \"master\"\n\n\tmssqlContainer, err := mssql.Run(ctx,\n\t\t\"mcr.microsoft.com/azure-sql-edge\",\n\t\tmssql.WithAcceptEULA(),\n\t\tmssql.WithPassword(sqlServerPass),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdefer mssqlContainer.Terminate(ctx)\n\n\tsqlServerHost, err := mssqlContainer.Host(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tsqlServerPort, err := mssqlContainer.MappedPort(ctx, \"1433\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"postgres verified\",\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(\"jdbc connection string: jdbc:postgresql://%s:%s/%s?sslmode=disable&password=%s&user=%s\",\n\t\t\t\t\tpostgresHost, postgresPort.Port(), postgresDB, postgresPass, postgresUser)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JDBC,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted: fmt.Sprintf(\"jdbc:postgresql://%s:%s/%s?sslmode=disable&password=%s&user=%s\",\n\t\t\t\t\t\tpostgresHost, postgresPort.Port(), postgresDB, strings.Repeat(\"*\", len(postgresPass)), postgresUser),\n\t\t\t\t\tAnalysisInfo: map[string]string{\n\t\t\t\t\t\t\"connection_string\": fmt.Sprintf(\"jdbc:postgresql://%s:%s/%s?sslmode=disable&password=%s&user=%s\",\n\t\t\t\t\t\t\tpostgresHost, postgresPort.Port(), postgresDB, postgresPass, postgresUser),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"mysql verified\",\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(`CONN=\"jdbc:mysql://%s:%s@tcp(%s:%s)/%s\"`,\n\t\t\t\t\tmysqlUser, mysqlPass, mysqlHost, mysqlPort.Port(), mysqlDatabase)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JDBC,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted: fmt.Sprintf(`jdbc:mysql://%s:%s@tcp(%s:%s)/%s`,\n\t\t\t\t\t\tmysqlUser, strings.Repeat(\"*\", len(mysqlPass)), mysqlHost, mysqlPort.Port(), mysqlDatabase),\n\t\t\t\t\tAnalysisInfo: map[string]string{\n\t\t\t\t\t\t\"connection_string\": fmt.Sprintf(`jdbc:mysql://%s:%s@tcp(%s:%s)/%s`,\n\t\t\t\t\t\t\tmysqlUser, mysqlPass, mysqlHost, mysqlPort.Port(), mysqlDatabase),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"sql server verified\",\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(\"jdbc:sqlserver://odbc:server=%s;port=%s;database=%s;password=%s\",\n\t\t\t\t\tsqlServerHost, sqlServerPort.Port(), sqlServerDatabase, sqlServerPass)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JDBC,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted: fmt.Sprintf(\"jdbc:sqlserver://odbc:server=%s;port=%s;database=%s;password=%s\",\n\t\t\t\t\t\tsqlServerHost, sqlServerPort.Port(), sqlServerDatabase, strings.Repeat(\"*\", len(sqlServerPass))),\n\t\t\t\t\tAnalysisInfo: map[string]string{\n\t\t\t\t\t\t\"connection_string\": fmt.Sprintf(\"jdbc:sqlserver://odbc:server=%s;port=%s;database=%s;password=%s\",\n\t\t\t\t\t\t\tsqlServerHost, sqlServerPort.Port(), sqlServerDatabase, sqlServerPass),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif tt.wantErr {\n\t\t\t\tassert.Error(t, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tassert.NoError(t, err)\n\t\t\tif os.Getenv(\"FORCE_PASS_DIFF\") == \"true\" {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Jdbc.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestJdbc_FromChunk(t *testing.T) {\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(`jdbc connection string: jdbc:mysql://hello.test.us-east-1.rds.amazonaws.com:3306/testdb?password=testpassword <-`),\n\t\t\t\tverify: false,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JDBC,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"jdbc:mysql://hello.test.us-east-1.rds.amazonaws.com:3306/testdb?password=************\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified numeric password\",\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(`jdbc connection string: jdbc:postgresql://host:5342/testdb?password=123456 <-`),\n\t\t\t\tverify: false,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JDBC,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"jdbc:postgresql://host:5342/testdb?password=******\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: false,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found double quoted string, unverified\",\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(`CONN=\"jdbc:postgres://hello.test.us-east-1.rds.amazonaws.com:3306/testdb?password=testpassword\"`),\n\t\t\t\tverify: false,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JDBC,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"jdbc:postgres://hello.test.us-east-1.rds.amazonaws.com:3306/testdb?password=************\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found single quoted string, unverified\",\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(`CONN='jdbc:postgres://hello.test.us-east-1.rds.amazonaws.com:3306/testdb?password=testpassword'`),\n\t\t\t\tverify: false,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JDBC,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"jdbc:postgres://hello.test.us-east-1.rds.amazonaws.com:3306/testdb?password=************\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"sqlserver, unverified\",\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(`jdbc:sqlserver://a.b.c.net;database=database-name;spring.datasource.password=super-secret-password`),\n\t\t\t\tverify: false,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JDBC,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"jdbc:sqlserver://a.b.c.net;database=database-name;spring.datasource.password=*********************\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Jdbc.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif os.Getenv(\"FORCE_PASS_DIFF\") == \"true\" {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Jdbc.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestJdbc_Redact(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tconn string\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"basic auth'\",\n\t\t\tconn: \"//user:secret@tcp(127.0.0.1:3306)/\",\n\t\t\twant: \"//user:******@tcp(127.0.0.1:3306)/\",\n\t\t},\n\t\t{\n\t\t\tname: \"basic auth including raw string 'pass'\",\n\t\t\tconn: \"//wrongUser:wrongPass@tcp(127.0.0.1:3306)/\",\n\t\t\twant: \"//wrongUser:*********@tcp(127.0.0.1:3306)/\",\n\t\t},\n\t\t{\n\t\t\tname: \"basic auth including raw string 'pass' with unfortunate db name\",\n\t\t\tconn: \"//wrongUser:wrongPass@tcp(127.0.0.1:3306)/passwords\",\n\t\t\twant: \"//wrongUser:*********@tcp(127.0.0.1:3306)/passwords\",\n\t\t},\n\t\t{\n\t\t\tname: \"url param-style\",\n\t\t\tconn: \"jdbc:postgresql://localhost:5432/foo?sslmode=disable&password=p@ssw04d\",\n\t\t\twant: \"jdbc:postgresql://localhost:5432/foo?sslmode=disable&password=********\",\n\t\t},\n\t\t{\n\t\t\tname: \"odbc-style without server\",\n\t\t\tconn: \"//odbc:server=localhost;user id=sa;database=master;password=/p?s=sw&rd\",\n\t\t\twant: \"//odbc:server=localhost;user id=sa;database=master;password=**********\",\n\t\t},\n\t\t{\n\t\t\tname: \"odbc-style with server\",\n\t\t\tconn: \"jdbc:sqlserver://a.b.c.net;database=database-name;spring.datasource.password=super-secret-password\",\n\t\t\twant: \"jdbc:sqlserver://a.b.c.net;database=database-name;spring.datasource.password=*********************\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := tryRedactAnonymousJDBC(tt.conn)\n\t\t\tassert.Equal(t, tt.want, got)\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jdbc/jdbc_test.go",
    "content": "package jdbc\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestJdbc_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\t// examples from: https://github.com/trufflesecurity/trufflehog/issues/3704\n\t\t\tname: \"valid patterns\",\n\t\t\tinput: `\n\t\t\t\t<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\t\t\t\t\t<project version=\"4\">\n\t\t\t\t\t\t<component name=\"DataSourceManagerImpl\" format=\"xml\" multifile-model=\"true\">\n\t\t\t\t\t\t\t<data-source source=\"LOCAL\" name=\"PostgreSQL - postgres@localhost\" uuid=\"18f0f64d-b804-471d-9351-e98a67c8389f\">\n\t\t\t\t\t\t\t<driver-ref>postgresql</driver-ref>\n\t\t\t\t\t\t\t<synchronize>true</synchronize>\n\t\t\t\t\t\t\t<jdbc-driver>org.postgresql.Driver</jdbc-driver>\n\t\t\t\t\t\t\t<jdbc-url>jdbc:postgresql://localhost:5432/postgres</jdbc-url>\n\t\t\t\t\t\t\t<jdbc-url>jdbc:sqlserver:</jdbc-url>\n\t\t\t\t\t\t\t<jdbc-url>jdbc:postgresql://#{uri.host}#{uri.path}?user=#{uri.user}</jdbc-url>\n\t\t\t\t\t\t\t<jdbc-url>postgresql://postgres:postgres@<your-connection-ip-address>:5432</jdbc-url>\n\t\t\t\t\t\t\t<jdbc-url>jdbc:mysql:localhost:3306/mydatabase</jdbc-url>\n\t\t\t\t\t\t\t<jdbc-url>jdbc:sqlserver://x.x.x.x:1433;databaseName=MY-DB;user=MY-USER;password=MY-PASSWORD;encrypt=false</jdbc-url>\n\t\t\t\t\t\t\t<jdbc-url>jdbc:sqlserver://localhost:1433;databaseName=AdventureWorks</jdbc-url>\n\t\t\t\t\t\t\t<jdbc-url>(jdbc:mysql://testuser:testpassword@tcp(localhost:1521)/testdb)</jdbc-url>\n\t\t\t\t\t\t\t<jdbc-url>jdbc:postgresql://localhost:1521/testdb?sslmode=disable&password=testpassword&user=testuser&</jdbc-url>\n\t\t\t\t\t\t\t<working-dir>$ProjectFileDir$</working-dir>\n\t\t\t\t\t\t\t</data-source>\n\t\t\t\t\t\t</component>\n\t\t\t\t\t</project>\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"jdbc:postgresql://localhost:5432/postgres\",\n\t\t\t\t\"jdbc:mysql:localhost:3306/mydatabase\",\n\t\t\t\t\"jdbc:sqlserver://x.x.x.x:1433;databaseName=MY-DB;user=MY-USER;password=MY-PASSWORD;encrypt=false\",\n\t\t\t\t\"jdbc:sqlserver://localhost:1433;databaseName=AdventureWorks\",\n\t\t\t\t\"jdbc:mysql://testuser:testpassword@tcp(localhost:1521)/testdb\",\n\t\t\t\t\"jdbc:postgresql://localhost:1521/testdb?sslmode=disable&password=testpassword&user=testuser\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - true positives\",\n\t\t\tinput: `\n\t\t\t\t{\n\t\t\t\t\t\"detector\": \"jdbc\",\n\t\t\t\t\t\"potential_matches\": [\n\t\t\t\t\t\t\"jdbc:postgresql://localhost:5432/mydb\",\n\t\t\t\t\t\t\"jdbc:mysql://user:pass@host:3306/db?param=1\",\n\t\t\t\t\t\t\"jdbc:sqlite:/data/test.db\",\n\t\t\t\t\t\t\"jdbc:oracle:thin:@host:1521:db\",\n\t\t\t\t\t\t\"jdbc:mysql://host:3306/db,other_param\",\n\t\t\t\t\t\t\"jdbc:db2://host:50000/db?param=1\"\n\t\t\t\t\t\t\"jdbc:postgresql://localhost:1521/testdb?sslmode=disable&password=testpassword&user=testuser\"\n\t\t\t\t\t\t\"jdbc:mysql://testuser:testpassword@tcp(localhost:1521)/testdb\"\n\t\t\t\t\t\t]\n\t\t\t\t\t\t}`,\n\t\t\twant: []string{\n\t\t\t\t\"jdbc:postgresql://localhost:5432/mydb\",\n\t\t\t\t\"jdbc:mysql://user:pass@host:3306/db?param=1\",\n\t\t\t\t\"jdbc:sqlite:/data/test.db\",\n\t\t\t\t\"jdbc:oracle:thin:@host:1521:db\",\n\t\t\t\t\"jdbc:mysql://host:3306/db\",\n\t\t\t\t\"jdbc:db2://host:50000/db?param=1\",\n\t\t\t\t\"jdbc:postgresql://localhost:1521/testdb?sslmode=disable&password=testpassword&user=testuser\",\n\t\t\t\t\"jdbc:mysql://testuser:testpassword@tcp(localhost:1521)/testdb\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"trailing non-alphanumeric characters in password\",\n\t\t\tinput: `jdbc:hive9://foo.example.com:10191/default;user=MyRoleUser;password=MyPa$$w0rd...`,\n\t\t\twant: []string{\n\t\t\t\t\"jdbc:hive9://foo.example.com:10191/default;user=MyRoleUser;password=MyPa$$w0rd...\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern - false positives\",\n\t\t\tinput: `\n\t\t\t\t{\n\t\t\t\t\t\"detector\": \"jdbc\",\n\t\t\t\t\t\"false_positives\": [\n\t\t\t\t\t\t\"jdbc:xyz:short\",\n\t\t\t\t\t\t\"somejdbc:mysql://host/db\",\n\t\t\t\t\t\t\"jdbc:invalid_driver:test\",\n\t\t\t\t\t\t\"jdbc:mysql://host/db>next\",\n\t\t\t\t\t\t\"adjdbc:mysql://host/db\",\n\t\t\t\t\t\t\"jdbc:my?ql:localhost:3306/my database\"\n\t\t\t\t\t]\n\t\t\t\t}`,\n\t\t\twant: []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestJdbc_FromDataWithIgnorePattern(t *testing.T) {\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname           string\n\t\targs           args\n\t\twant           []detectors.Result\n\t\tignorePatterns []string\n\t\twantErr        bool\n\t}{\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"jdbc:sqlite::secretpattern:\"),\n\t\t\t\tverify: false,\n\t\t\t},\n\t\t\twant: nil,\n\t\t\tignorePatterns: []string{\n\t\t\t\t\".*secretpattern.*\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := New(WithIgnorePattern(tt.ignorePatterns))\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Jdbc.FromDataWithConfig() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif os.Getenv(\"FORCE_PASS_DIFF\") == \"true\" {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Jdbc.FromDataWithConfig() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseJDBCURL_EdgeCases(t *testing.T) {\n\tt.Run(\"MySQL with special characters in password\", func(t *testing.T) {\n\t\t// Special chars: @ # $ % ^ & * ( )\n\t\tjdbcURL := \"jdbc:mysql://user:p@ss%23word@localhost:3306/testdb\"\n\t\tjdbc, err := NewJDBC(logContext.Background(), jdbcURL)\n\t\trequire.NoError(t, err)\n\n\t\tinfo := jdbc.GetConnectionInfo()\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, info)\n\t\tassert.Equal(t, \"user\", info.User)\n\t\t// URL encoding should be handled by url.Parse\n\t})\n\n\tt.Run(\"PostgreSQL with empty database\", func(t *testing.T) {\n\t\tjdbcURL := \"jdbc:postgresql://user:pass@localhost:5432\"\n\t\tjdbc, err := NewJDBC(logContext.Background(), jdbcURL)\n\t\trequire.NoError(t, err)\n\n\t\tinfo := jdbc.GetConnectionInfo()\n\t\tassert.Equal(t, \"postgres\", info.Database) // default\n\t})\n\n\tt.Run(\"SQL Server with multiple semicolon params\", func(t *testing.T) {\n\t\tjdbcURL := \"jdbc:sqlserver://localhost:1433;database=testdb;user=sa;password=Pass123;encrypt=true;trustServerCertificate=false\"\n\t\tjdbc, err := NewJDBC(logContext.Background(), jdbcURL)\n\t\trequire.NoError(t, err)\n\n\t\tinfo := jdbc.GetConnectionInfo()\n\t\tassert.Equal(t, \"testdb\", info.Database)\n\t\tassert.Equal(t, \"sa\", info.User)\n\t\tassert.Equal(t, \"Pass123\", info.Password)\n\t})\n\n\tt.Run(\"MySQL missing host\", func(t *testing.T) {\n\t\t// Missing // after prefix - will trigger error\n\t\tjdbcURL := \"jdbc:mysql:/testdb\"\n\t\t_, err := NewJDBC(logContext.Background(), jdbcURL)\n\t\tassert.Error(t, err)\n\t\tassert.Contains(t, err.Error(), \"expected host to start with //\")\n\t})\n}\n"
  },
  {
    "path": "pkg/detectors/jdbc/models.go",
    "content": "package jdbc\n\nimport (\n\t\"context\"\n)\n\ntype DatabaseType int\n\nconst (\n\tUnknown DatabaseType = iota\n\tMySQL\n\tPostgreSQL\n\tSQLServer\n)\n\nfunc (dt DatabaseType) String() string {\n\tswitch dt {\n\tcase MySQL:\n\t\treturn \"mysql\"\n\tcase PostgreSQL:\n\t\treturn \"postgresql\"\n\tcase SQLServer:\n\t\treturn \"sqlserver\"\n\tdefault:\n\t\treturn \"unknown\"\n\t}\n}\n\ntype pingResult struct {\n\terr         error\n\tdeterminate bool\n}\n\n// ConnectionInfo holds parsed connection information\ntype ConnectionInfo struct {\n\tHost     string // includes port if specified, e.g., \"host:port\"\n\tDatabase string\n\tUser     string\n\tPassword string\n\tParams   map[string]string\n}\n\ntype jdbcPinger interface {\n\tping(context.Context) pingResult\n}\n\n// public interfaces for analyzer\ntype JDBCParser interface {\n\tGetConnectionInfo() *ConnectionInfo\n\tGetDBType() DatabaseType\n\tBuildConnectionString() string\n}\ntype JDBC interface {\n\tjdbcPinger\n\tJDBCParser\n}\n"
  },
  {
    "path": "pkg/detectors/jdbc/mysql.go",
    "content": "package jdbc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"strings\"\n\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\n\t\"github.com/go-sql-driver/mysql\"\n)\n\ntype MysqlJDBC struct {\n\tConnectionInfo\n}\n\nvar _ JDBC = (*MysqlJDBC)(nil)\n\nfunc (s *MysqlJDBC) ping(ctx context.Context) pingResult {\n\treturn ping(ctx, \"mysql\", isMySQLErrorDeterminate,\n\t\tbuildMySQLConnectionString(s.Host, \"\", s.User, s.Password, s.Params))\n}\n\nfunc (s *MysqlJDBC) GetDBType() DatabaseType {\n\treturn MySQL\n}\n\nfunc (s *MysqlJDBC) GetConnectionInfo() *ConnectionInfo {\n\treturn &s.ConnectionInfo\n}\n\nfunc (s *MysqlJDBC) BuildConnectionString() string {\n\treturn buildMySQLConnectionString(s.Host, s.Database, s.User, s.Password, s.Params)\n}\n\nfunc buildMySQLConnectionString(host, database, user, password string, params map[string]string) string {\n\tconn := host + \"/\" + database\n\tuserPass := user\n\tif password != \"\" {\n\t\tuserPass = userPass + \":\" + password\n\t}\n\tif userPass != \"\" {\n\t\tconn = userPass + \"@\" + conn\n\t}\n\tif len(params) > 0 {\n\t\tvar paramList []string\n\t\tfor k, v := range params {\n\t\t\tparamList = append(paramList, fmt.Sprintf(\"%s=%s\", k, v))\n\t\t}\n\t\tconn = conn + \"?\" + strings.Join(paramList, \"&\")\n\t}\n\treturn conn\n}\n\nfunc isMySQLErrorDeterminate(err error) bool {\n\t// MySQL error numbers from https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html\n\tif mySQLErr, isMySQLErr := err.(*mysql.MySQLError); isMySQLErr {\n\t\tswitch mySQLErr.Number {\n\t\tcase 1044:\n\t\t\t// User access denied to a particular database\n\t\t\treturn false // \"Indeterminate\" so that other connection variations will be tried\n\t\tcase 1045:\n\t\t\t// User access denied\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc parseMySQL(ctx logContext.Context, subname string) (JDBC, error) {\n\t// expected form: [subprotocol:]//[user:password@]HOST[/DB][?key=val[&key=val]]\n\tif !strings.HasPrefix(subname, \"//\") {\n\t\treturn nil, errors.New(\"expected host to start with //\")\n\t}\n\n\t// need for hostnames that have tcp(host:port) format required by this database driver\n\tcfg, err := mysql.ParseDSN(strings.TrimPrefix(subname, \"//\"))\n\tif err != nil {\n\t\t// fall back to URI parsing\n\t\treturn parseMySQLURI(ctx, subname)\n\t}\n\n\tif cfg.Addr == \"\" || cfg.Passwd == \"\" {\n\t\tctx.Logger().WithName(\"jdbc\").\n\t\t\tV(2).\n\t\t\tInfo(\"Skipping invalid MySQL URL - no password or host found\")\n\t\treturn nil, fmt.Errorf(\"missing host or password in connection string\")\n\t}\n\treturn &MysqlJDBC{\n\t\tConnectionInfo: ConnectionInfo{\n\t\t\tUser:     cfg.User,\n\t\t\tPassword: cfg.Passwd,\n\t\t\tHost:     fmt.Sprintf(\"tcp(%s)\", cfg.Addr),\n\t\t\tParams:   map[string]string{\"timeout\": \"5s\"},\n\t\t\tDatabase: cfg.DBName,\n\t\t},\n\t}, nil\n}\n\nfunc parseMySQLURI(ctx logContext.Context, subname string) (JDBC, error) {\n\n\t// for standard URI format, which is all i've seen for JDBC\n\tu, err := url.Parse(subname)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tuser := \"root\"\n\tpass := \"\"\n\tif u.User != nil {\n\t\tuser = u.User.Username()\n\t\tpass, _ = u.User.Password()\n\t}\n\n\tif v := u.Query().Get(\"user\"); v != \"\" {\n\t\tuser = v\n\t}\n\tif v := u.Query().Get(\"password\"); v != \"\" {\n\t\tpass = v\n\t}\n\n\tif u.Host == \"\" || pass == \"\" {\n\t\tctx.Logger().WithName(\"jdbc\").\n\t\t\tV(2).\n\t\t\tInfo(\"Skipping invalid MySQL URL - no password or host found\")\n\t\treturn nil, fmt.Errorf(\"missing host or password in connection string\")\n\t}\n\n\t// Parse database name\n\tdbName := strings.TrimPrefix(u.Path, \"/\")\n\tif dbName == \"\" {\n\t\tdbName = \"mysql\" // default DB\n\t}\n\n\treturn &MysqlJDBC{\n\t\tConnectionInfo: ConnectionInfo{\n\t\t\tUser:     user,\n\t\t\tPassword: pass,\n\t\t\tHost:     fmt.Sprintf(\"tcp(%s)\", u.Host),\n\t\t\tParams:   map[string]string{\"timeout\": \"5s\"},\n\t\t\tDatabase: dbName,\n\t\t},\n\t}, nil\n\n}\n"
  },
  {
    "path": "pkg/detectors/jdbc/mysql_integration_test.go",
    "content": "//go:build detectors && integration\n// +build detectors,integration\n\npackage jdbc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/brianvoe/gofakeit/v7\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/testcontainers/testcontainers-go/modules/mysql\"\n\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestMySQL(t *testing.T) {\n\tmysqlUser := gofakeit.Username()\n\tmysqlPass := gofakeit.Password(true, true, true, false, false, 10)\n\tmysqlDatabase := gofakeit.Word()\n\n\tctx := context.Background()\n\n\tmysqlC, err := mysql.Run(ctx,\n\t\t\"mysql:8.0.36\",\n\t\tmysql.WithDatabase(mysqlDatabase),\n\t\tmysql.WithUsername(mysqlUser),\n\t\tmysql.WithPassword(mysqlPass),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdefer mysqlC.Terminate(ctx)\n\n\thost, err := mysqlC.Host(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tport, err := mysqlC.MappedPort(ctx, \"3306\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttype result struct {\n\t\tParseErr        bool\n\t\tPingOk          bool\n\t\tPingDeterminate bool\n\t}\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  result\n\t}{\n\t\t{\n\t\t\tname:  \"empty\",\n\t\t\tinput: \"\",\n\t\t\twant:  result{ParseErr: true},\n\t\t},\n\t\t{\n\t\t\tname:  \"all good\",\n\t\t\tinput: fmt.Sprintf(\"//%s:%s@tcp(%s:%s)/%s\", mysqlUser, mysqlPass, host, port.Port(), mysqlDatabase),\n\t\t\twant:  result{PingOk: true, PingDeterminate: true},\n\t\t},\n\t\t{\n\t\t\tname:  \"wrong creds\",\n\t\t\tinput: fmt.Sprintf(\"//wrongUser:wrongPassword@tcp(%s:%s)/%s\", host, port.Port(), mysqlDatabase),\n\t\t\twant:  result{PingOk: false, PingDeterminate: true},\n\t\t},\n\t\t{\n\t\t\tname:  \"wrong pass\",\n\t\t\tinput: fmt.Sprintf(\"//%s:wrongPass@tcp(%s:%s)/%s\", mysqlUser, host, port.Port(), mysqlDatabase),\n\t\t\twant:  result{PingOk: false, PingDeterminate: true},\n\t\t},\n\t\t{\n\t\t\tname:  \"no db\",\n\t\t\tinput: fmt.Sprintf(\"//%s:%s@tcp(%s:%s)/\", mysqlUser, mysqlPass, host, port.Port()),\n\t\t\twant:  result{PingOk: true, PingDeterminate: true},\n\t\t},\n\t\t{\n\t\t\tname:  \"wrong db\",\n\t\t\tinput: fmt.Sprintf(\"//%s:%s@tcp(%s:%s)/wrongDB\", mysqlUser, mysqlPass, host, port.Port()),\n\t\t\twant:  result{PingOk: true, PingDeterminate: true},\n\t\t},\n\t\t{\n\t\t\tname:  \"jdbc format\",\n\t\t\tinput: fmt.Sprintf(\"//%s:%s@%s:%s/%s\", mysqlUser, mysqlPass, host, port.Port(), mysqlDatabase),\n\t\t\twant:  result{PingOk: true, PingDeterminate: true},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input, func(t *testing.T) {\n\t\t\tj, err := parseMySQL(logContext.Background(), tt.input)\n\n\t\t\tif err != nil {\n\t\t\t\tgot := result{ParseErr: true}\n\t\t\t\tassert.Equal(t, tt.want, got)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tpr := j.ping(context.Background())\n\n\t\t\tgot := result{PingOk: pr.err == nil, PingDeterminate: pr.determinate}\n\n\t\t\tif diff := cmp.Diff(tt.want, got); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s: (-want +got)\\n%s\", tt.name, diff)\n\t\t\t\tt.Errorf(\"error is: %v\", pr.err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jdbc/mysql_test.go",
    "content": "package jdbc\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestParseMySQLMissingCredentials(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tsubname     string\n\t\tshouldBeNil bool\n\t\treason      string\n\t}{\n\t\t{\n\t\t\tname:        \"no password - should return nil\",\n\t\t\tsubname:     \"//examplehost.net:3306/dbname?user=admin\",\n\t\t\tshouldBeNil: true,\n\t\t\treason:      \"no password present\",\n\t\t},\n\t\t{\n\t\t\tname:        \"no password (tcp format) - should return nil\",\n\t\t\tsubname:     \"//tcp(examplehost.net:3306)/dbname?user=admin\",\n\t\t\tshouldBeNil: true,\n\t\t\treason:      \"no password present in tcp format\",\n\t\t},\n\t\t{\n\t\t\tname:        \"no host - should return nil\",\n\t\t\tsubname:     \"///dbname?user=admin&password=secret123\",\n\t\t\tshouldBeNil: true,\n\t\t\treason:      \"no host present\",\n\t\t},\n\t\t{\n\t\t\tname:        \"no host and no password - should return nil\",\n\t\t\tsubname:     \"///dbname\",\n\t\t\tshouldBeNil: true,\n\t\t\treason:      \"no host or password present\",\n\t\t},\n\t\t{\n\t\t\tname:        \"valid with host and password - should succeed\",\n\t\t\tsubname:     \"//examplehost.net:3306/dbname?user=root&password=secret123\",\n\t\t\tshouldBeNil: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"valid with tcp(host:port) format - should succeed\",\n\t\t\tsubname:     \"//root:secret123@tcp(examplehost.net:3306)/dbname\",\n\t\t\tshouldBeNil: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"valid with localhost - should succeed\",\n\t\t\tsubname:     \"//localhost/dbname?user=root&password=secret123\",\n\t\t\tshouldBeNil: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tctx := logContext.AddLogger(context.Background())\n\t\t\tj, err := parseMySQL(ctx, tt.subname)\n\n\t\t\tif tt.shouldBeNil {\n\t\t\t\tif j != nil {\n\t\t\t\t\tt.Errorf(\"parseMySQL() expected nil (%s), got: %v\", tt.reason, j)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif j == nil {\n\t\t\t\t\tt.Errorf(\"parseMySQL() returned nil, expected valid connection. err = %v\", err)\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"parseMySQL() unexpected error = %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseMySQLUsernameRecognition(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tsubname      string\n\t\twantUsername string\n\t}{\n\t\t{\n\t\t\tname:         \"user parameter specified\",\n\t\t\tsubname:      \"//localhost:3306/dbname?user=myuser&password=mypass\",\n\t\t\twantUsername: \"myuser\",\n\t\t},\n\t\t{\n\t\t\tname:         \"no user specified - default root\",\n\t\t\tsubname:      \"//localhost:3306/dbname?password=mypass\",\n\t\t\twantUsername: \"root\",\n\t\t},\n\t\t{\n\t\t\tname:         \"user specified (tcp format)\",\n\t\t\tsubname:      \"//myuser:secret123@tcp(localhost:3306)/dbname\",\n\t\t\twantUsername: \"myuser\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tctx := logContext.AddLogger(context.Background())\n\t\t\tj, err := parseMySQL(ctx, tt.subname)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"parseMySQL() error = %v\", err)\n\t\t\t}\n\n\t\t\tmysqlConn := j.(*MysqlJDBC)\n\t\t\tif mysqlConn.User != tt.wantUsername {\n\t\t\t\tt.Errorf(\"Connection string does not contain expected username '%s'\\nGot: %s\\nExpected: %s\",\n\t\t\t\t\ttt.wantUsername, mysqlConn.User, tt.wantUsername)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMySQL_ParseJDBCURL(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tjdbcURL  string\n\t\twantHost string\n\t\twantDB   string\n\t\twantUser string\n\t\twantPass string\n\t\twantErr  bool\n\t}{\n\t\t{\n\t\t\tname:     \"basic URL with all parts\",\n\t\t\tjdbcURL:  \"jdbc:mysql://root:password@localhost:3306/testdb\",\n\t\t\twantHost: \"tcp(localhost:3306)\",\n\t\t\twantDB:   \"testdb\",\n\t\t\twantUser: \"root\",\n\t\t\twantPass: \"password\",\n\t\t},\n\t\t{\n\t\t\tname:     \"URL with default port\",\n\t\t\tjdbcURL:  \"jdbc:mysql://user:pass@dbhost/mydb\",\n\t\t\twantHost: \"tcp(dbhost)\",\n\t\t\twantDB:   \"mydb\",\n\t\t\twantUser: \"user\",\n\t\t\twantPass: \"pass\",\n\t\t},\n\t\t{\n\t\t\tname:     \"URL with query params for credentials\",\n\t\t\tjdbcURL:  \"jdbc:mysql://dbhost:3307/testdb?user=admin&password=secret\",\n\t\t\twantHost: \"tcp(dbhost:3307)\",\n\t\t\twantDB:   \"testdb\",\n\t\t\twantUser: \"admin\",\n\t\t\twantPass: \"secret\",\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid URL - missing jdbc:mysql prefix\",\n\t\t\tjdbcURL: \"postgresql://user:pass@localhost/db\",\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid URL - missing //\",\n\t\t\tjdbcURL: \"jdbc:mysql:user:pass@localhost/db\",\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tjdbc, err := NewJDBC(logContext.Background(), tt.jdbcURL)\n\t\t\tif tt.wantErr {\n\t\t\t\tassert.Error(t, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\t\t\tinfo := jdbc.GetConnectionInfo()\n\t\t\tassert.Equal(t, tt.wantHost, info.Host)\n\t\t\tassert.Equal(t, tt.wantDB, info.Database)\n\t\t\tassert.Equal(t, tt.wantUser, info.User)\n\t\t\tassert.Equal(t, tt.wantPass, info.Password)\n\t\t})\n\t}\n}\n\nfunc TestMySQL_ParseJDBCURL_DSNAddressParsing(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tjdbcURL  string\n\t\twantHost string\n\t}{\n\t\t{\n\t\t\tname:     \"DSN format with explicit port\",\n\t\t\tjdbcURL:  \"jdbc:mysql://myuser:mypass@tcp(localhost:3307)/mydb\",\n\t\t\twantHost: \"tcp(localhost:3307)\",\n\t\t},\n\t\t{\n\t\t\tname:     \"DSN format with default port\",\n\t\t\tjdbcURL:  \"jdbc:mysql://myuser:mypass@tcp(db.example.com:3306)/testdb\",\n\t\t\twantHost: \"tcp(db.example.com:3306)\",\n\t\t},\n\t\t{\n\t\t\tname:     \"DSN format without port\",\n\t\t\tjdbcURL:  \"jdbc:mysql://myuser:mypass@tcp(myhost)/mydb\",\n\t\t\twantHost: \"tcp(myhost:3306)\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Simple host:port format\",\n\t\t\tjdbcURL:  \"jdbc:mysql://root:password@mysql.server.com:3308/database\",\n\t\t\twantHost: \"tcp(mysql.server.com:3308)\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tjdbc, err := NewJDBC(logContext.Background(), tt.jdbcURL)\n\t\t\trequire.NoError(t, err)\n\t\t\tinfo := jdbc.GetConnectionInfo()\n\t\t\tassert.Equal(t, tt.wantHost, info.Host)\n\t\t})\n\t}\n}\n\nfunc TestMySQL_BuildNativeConnectionString(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinfo     *ConnectionInfo\n\t\twantUser string\n\t\twantPass string\n\t\twantHost string\n\t\twantDB   string\n\t}{\n\t\t{\n\t\t\tname: \"basic connection\",\n\t\t\tinfo: &ConnectionInfo{\n\t\t\t\tHost:     \"localhost\",\n\t\t\t\tDatabase: \"testdb\",\n\t\t\t\tUser:     \"root\",\n\t\t\t\tPassword: \"secret\",\n\t\t\t},\n\t\t\twantUser: \"root\",\n\t\t\twantPass: \"secret\",\n\t\t\twantHost: \"localhost\",\n\t\t\twantDB:   \"testdb\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tmysqlJDBC := &MysqlJDBC{\n\t\t\t\tConnectionInfo: *tt.info,\n\t\t\t}\n\t\t\tconnStr := mysqlJDBC.BuildConnectionString()\n\n\t\t\t// MySQL format: [user[:password]@]tcp(host:port)/database?timeout=10s\n\t\t\tassert.Contains(t, connStr, tt.wantUser)\n\t\t\tassert.Contains(t, connStr, tt.wantPass)\n\t\t\tassert.Contains(t, connStr, tt.wantHost)\n\t\t\tassert.Contains(t, connStr, \"/\"+tt.wantDB)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jdbc/postgres.go",
    "content": "package jdbc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"strings\"\n\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\n\t\"github.com/lib/pq\"\n)\n\ntype PostgresJDBC struct {\n\tConnectionInfo\n}\n\nvar _ JDBC = (*PostgresJDBC)(nil)\n\nfunc (s *PostgresJDBC) ping(ctx context.Context) pingResult {\n\t// It is crucial that we try to build a connection string ourselves before using the one we found. This is because\n\t// if the found connection string doesn't include a username, the driver will attempt to connect using the current\n\t// user's name, which will fail in a way that looks like a determinate failure, thus terminating the waterfall. In\n\t// contrast, when we build a connection string ourselves, if there's no username, we try 'postgres' instead, which\n\t// actually has a chance of working.\n\treturn ping(ctx, \"postgres\", isPostgresErrorDeterminate,\n\t\tbuildPostgresConnectionString(s.Host, s.User, s.Password, \"postgres\", s.Params, true),\n\t\tbuildPostgresConnectionString(s.Host, s.User, s.Password, \"postgres\", s.Params, false),\n\t)\n}\n\nfunc (s *PostgresJDBC) GetDBType() DatabaseType {\n\treturn PostgreSQL\n}\n\nfunc (s *PostgresJDBC) GetConnectionInfo() *ConnectionInfo {\n\treturn &s.ConnectionInfo\n}\n\nfunc (s *PostgresJDBC) BuildConnectionString() string {\n\treturn buildPostgresConnectionString(s.Host, s.User, s.Password, s.Database, s.Params, true)\n}\n\nfunc isPostgresErrorDeterminate(err error) bool {\n\t// Postgres codes from https://www.postgresql.org/docs/current/errcodes-appendix.html\n\tif pqErr, isPostgresError := err.(*pq.Error); isPostgresError {\n\t\tswitch pqErr.Code {\n\t\tcase \"28P01\":\n\t\t\t// Invalid username/password\n\t\t\treturn true\n\t\tcase \"3D000\":\n\t\t\t// Unknown database\n\t\t\treturn false // \"Indeterminate\" so that other connection variations will be tried\n\t\tcase \"3F000\":\n\t\t\t// Unknown schema\n\t\t\treturn false // \"Indeterminate\" so that other connection variations will be tried\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc joinKeyValues(m map[string]string, sep string) string {\n\tvar data []string\n\tfor k, v := range m {\n\t\tif v == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tdata = append(data, fmt.Sprintf(\"%s=%s\", k, v))\n\t}\n\treturn strings.Join(data, sep)\n}\n\nfunc parsePostgres(ctx logContext.Context, subname string) (JDBC, error) {\n\t// expected form: [subprotocol:]//[user:password@]HOST[/DB][?key=val[&key=val]]\n\n\tif !strings.HasPrefix(subname, \"//\") {\n\t\treturn nil, errors.New(\"expected host to start with //\")\n\t}\n\n\tu, err := url.Parse(subname)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdbName := strings.TrimPrefix(u.Path, \"/\")\n\tif dbName == \"\" {\n\t\tdbName = \"postgres\"\n\t}\n\n\tparams := map[string]string{\n\t\t\"connect_timeout\": \"5\",\n\t}\n\n\tpostgresJDBC := &PostgresJDBC{\n\t\tConnectionInfo: ConnectionInfo{\n\t\t\tHost:     u.Host,\n\t\t\tDatabase: dbName,\n\t\t\tParams:   params,\n\t\t},\n\t}\n\n\tif u.User != nil {\n\t\tpostgresJDBC.User = u.User.Username()\n\t\tpass, set := u.User.Password()\n\t\tif set {\n\t\t\tpostgresJDBC.Password = pass\n\t\t}\n\t}\n\n\tif v := u.Query()[\"sslmode\"]; len(v) > 0 {\n\t\tswitch v[0] {\n\t\t// https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-PROTECTION\n\t\tcase \"disable\", \"allow\", \"prefer\",\n\t\t\t\"require\", \"verify-ca\", \"verify-full\":\n\t\t\tpostgresJDBC.Params[\"sslmode\"] = v[0]\n\t\t}\n\t}\n\n\tif v := u.Query().Get(\"user\"); v != \"\" {\n\t\tpostgresJDBC.User = v\n\t}\n\n\tif v := u.Query().Get(\"password\"); v != \"\" {\n\t\tpostgresJDBC.Password = v\n\t}\n\n\tif postgresJDBC.Host == \"\" || postgresJDBC.Password == \"\" {\n\t\tctx.Logger().WithName(\"jdbc\").\n\t\t\tV(2).\n\t\t\tInfo(\"Skipping invalid Postgres URL - no password or host found\")\n\t\treturn nil, fmt.Errorf(\"missing host or password in connection string\")\n\t}\n\n\treturn postgresJDBC, nil\n}\n\nfunc buildPostgresConnectionString(host string, user string, password string, dbName string, params map[string]string, includeDbName bool) string {\n\tdata := map[string]string{\n\t\t// default user\n\t\t\"user\":     \"postgres\",\n\t\t\"password\": password,\n\t\t\"host\":     host,\n\t}\n\tif user != \"\" {\n\t\tdata[\"user\"] = user\n\t}\n\tif h, p, ok := strings.Cut(host, \":\"); ok {\n\t\tdata[\"host\"] = h\n\t\tdata[\"port\"] = p\n\t}\n\tfor key, val := range params {\n\t\tdata[key] = val\n\t}\n\n\tif includeDbName {\n\t\tdata[\"dbname\"] = \"postgres\"\n\t\tif dbName != \"\" {\n\t\t\tdata[\"dbname\"] = dbName\n\t\t}\n\t}\n\n\tconnStr := joinKeyValues(data, \" \")\n\n\treturn connStr\n}\n"
  },
  {
    "path": "pkg/detectors/jdbc/postgres_integration_test.go",
    "content": "//go:build detectors && integration\n// +build detectors,integration\n\npackage jdbc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/brianvoe/gofakeit/v7\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/testcontainers/testcontainers-go\"\n\t\"github.com/testcontainers/testcontainers-go/modules/postgres\"\n\t\"github.com/testcontainers/testcontainers-go/wait\"\n\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestPostgres(t *testing.T) {\n\ttype result struct {\n\t\tParseErr        bool\n\t\tPingOk          bool\n\t\tPingDeterminate bool\n\t}\n\n\tuser := gofakeit.Username()\n\tpass := gofakeit.Password(true, true, true, false, false, 32)\n\tdbName := gofakeit.Word()\n\n\tt.Log(\"user: \", user)\n\tt.Log(\"dbName: \", dbName)\n\n\tctx := context.Background()\n\tpostgresContainer, err := postgres.Run(ctx,\n\t\t\"postgres:13-alpine\",\n\t\tpostgres.WithDatabase(dbName),\n\t\tpostgres.WithUsername(user),\n\t\tpostgres.WithPassword(pass),\n\t\ttestcontainers.WithWaitStrategy(\n\t\t\twait.ForLog(\"database system is ready to accept connections\").\n\t\t\t\tWithOccurrence(2).\n\t\t\t\tWithStartupTimeout(5*time.Second)),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\thost, err := postgresContainer.Host(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tport, err := postgresContainer.MappedPort(ctx, \"5432\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer postgresContainer.Terminate(ctx)\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  result\n\t}{\n\t\t{\n\t\t\tname:  \"invalid password\",\n\t\t\tinput: fmt.Sprintf(\"//%s:%s/foo?sslmode=disable&password=foo\", host, port.Port()),\n\t\t\twant:  result{PingOk: false, PingDeterminate: true},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid password - no user name\",\n\t\t\tinput: fmt.Sprintf(\"//%s:%s/foo?sslmode=disable&password=%s\", host, port.Port(), pass),\n\t\t\twant:  result{PingOk: false, PingDeterminate: true},\n\t\t},\n\t\t{\n\t\t\tname:  \"exact username and password, wrong db\",\n\t\t\tinput: fmt.Sprintf(\"//%s:%s@%s:%s/foo?sslmode=disable\", user, pass, host, port.Port()),\n\t\t\twant:  result{PingOk: true, PingDeterminate: true},\n\t\t},\n\t\t{\n\t\t\tname:  \"exact username and password, no db\",\n\t\t\tinput: fmt.Sprintf(\"//%s:%s@%s:%s?sslmode=disable\", user, pass, host, port.Port()),\n\t\t\twant:  result{PingOk: true, PingDeterminate: true},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid user name\",\n\t\t\tinput: fmt.Sprintf(\"//%s:%s/foo?sslmode=disable&user=foo&password=%s\", host, port.Port(), pass),\n\t\t\twant:  result{PingOk: false, PingDeterminate: true},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid hostname\",\n\t\t\tinput: fmt.Sprintf(\"//badhost:%s/foo?sslmode=disable&user=foo&password=%s\", port.Port(), pass),\n\t\t\twant:  result{PingOk: false, PingDeterminate: false},\n\t\t},\n\t\t{\n\t\t\tname:  \"no username, password\",\n\t\t\tinput: fmt.Sprintf(\"//%s:%s/foo?password=%s\", host, port.Port(), pass),\n\t\t\twant:  result{PingOk: false, PingDeterminate: false},\n\t\t},\n\t\t{\n\t\t\tname:  \"db, password - no username\",\n\t\t\tinput: fmt.Sprintf(\"//%s:%s/foo?sslmode=disable&password=%s\", host, port.Port(), pass),\n\t\t\twant:  result{PingOk: false, PingDeterminate: true},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid format\",\n\t\t\tinput: \"invalid\",\n\t\t\twant:  result{ParseErr: true},\n\t\t},\n\t\t{\n\t\t\tname:  \"normal connect with generated username and password\",\n\t\t\tinput: fmt.Sprintf(\"//%s:%s@%s:%s/%s?sslmode=disable\", user, pass, host, port.Port(), dbName),\n\t\t\twant:  result{PingOk: true, PingDeterminate: true},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tj, err := parsePostgres(logContext.Background(), tt.input)\n\t\t\tif err != nil {\n\t\t\t\tgot := result{ParseErr: true}\n\n\t\t\t\tif diff := cmp.Diff(tt.want, got); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"%s: (-want +got)\\n%s\", tt.name, diff)\n\t\t\t\t\tt.Errorf(\"error is: %v\", err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tpr := j.ping(ctx)\n\n\t\t\tgot := result{PingOk: pr.err == nil, PingDeterminate: pr.determinate}\n\n\t\t\tif diff := cmp.Diff(tt.want, got); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s: (-want +got)\\n%s\", tt.name, diff)\n\t\t\t\tt.Errorf(\"error is: %v\", pr.err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jdbc/postgres_test.go",
    "content": "package jdbc\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestParsePostgresMissingCredentials(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tsubname     string\n\t\tshouldBeNil bool\n\t\treason      string\n\t}{\n\t\t{\n\t\t\tname:        \"no password - should return nil (nothing to verify)\",\n\t\t\tsubname:     \"//examplehost.net:5432/dbname?user=admin\",\n\t\t\tshouldBeNil: true,\n\t\t\treason:      \"no password present\",\n\t\t},\n\t\t{\n\t\t\tname:        \"no host - should return nil (invalid connection)\",\n\t\t\tsubname:     \"///dbname?user=admin&password=secret123\",\n\t\t\tshouldBeNil: true,\n\t\t\treason:      \"no host present\",\n\t\t},\n\t\t{\n\t\t\tname:        \"no host and no password - should return nil\",\n\t\t\tsubname:     \"///dbname\",\n\t\t\tshouldBeNil: true,\n\t\t\treason:      \"no host or password present\",\n\t\t},\n\t\t{\n\t\t\tname:        \"valid with host and password - should succeed\",\n\t\t\tsubname:     \"//examplehost.net:5432/dbname?user=admin&password=secret123\",\n\t\t\tshouldBeNil: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"valid with localhost and password - should succeed\",\n\t\t\tsubname:     \"//localhost/dbname?user=postgres&password=secret123\",\n\t\t\tshouldBeNil: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tctx := logContext.AddLogger(context.Background())\n\t\t\tj, err := parsePostgres(ctx, tt.subname)\n\n\t\t\tif tt.shouldBeNil {\n\t\t\t\tif j != nil {\n\t\t\t\t\tt.Errorf(\"parsePostgres() expected nil (%s), got: %v\", tt.reason, j)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif j == nil {\n\t\t\t\t\tt.Errorf(\"parsePostgres() returned nil, expected valid connection. err = %v\", err)\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"parsePostgres() unexpected error = %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParsePostgresUsernameRecognition(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tsubname      string\n\t\twantUsername string\n\t}{\n\t\t{\n\t\t\tname:         \"user parameter specified\",\n\t\t\tsubname:      \"//localhost:5432/dbname?user=myuser&password=mypass\",\n\t\t\twantUsername: \"myuser\",\n\t\t},\n\t\t{\n\t\t\tname:         \"user and password specified\",\n\t\t\tsubname:      \"//myuser:mypassword@localhost:5432/dbname\",\n\t\t\twantUsername: \"myuser\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tctx := logContext.AddLogger(context.Background())\n\t\t\tj, err := parsePostgres(ctx, tt.subname)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"ParsePostgres() error = %v\", err)\n\t\t\t}\n\n\t\t\tpgConn := j.(*PostgresJDBC)\n\t\t\tif pgConn.User != tt.wantUsername {\n\t\t\t\tt.Errorf(\"expected username '%s', got '%s'\", tt.wantUsername, pgConn.User)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPostgreSQLHandler_ParseJDBCURL(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tjdbcURL     string\n\t\twantHost    string\n\t\twantDB      string\n\t\twantUser    string\n\t\twantPass    string\n\t\twantSSLMode string\n\t\twantErr     bool\n\t}{\n\t\t{\n\t\t\tname:     \"basic URL with all parts\",\n\t\t\tjdbcURL:  \"jdbc:postgresql://postgres:secret@localhost:5432/mydb\",\n\t\t\twantHost: \"localhost:5432\",\n\t\t\twantDB:   \"mydb\",\n\t\t\twantUser: \"postgres\",\n\t\t\twantPass: \"secret\",\n\t\t},\n\t\t{\n\t\t\tname:     \"URL with default port\",\n\t\t\tjdbcURL:  \"jdbc:postgresql://user:pass@dbhost/testdb\",\n\t\t\twantHost: \"dbhost\",\n\t\t\twantDB:   \"testdb\",\n\t\t\twantUser: \"user\",\n\t\t\twantPass: \"pass\",\n\t\t},\n\t\t{\n\t\t\tname:     \"URL with default database\",\n\t\t\tjdbcURL:  \"jdbc:postgresql://user:pass@dbhost:5433\",\n\t\t\twantHost: \"dbhost:5433\",\n\t\t\twantDB:   \"postgres\",\n\t\t\twantUser: \"user\",\n\t\t\twantPass: \"pass\",\n\t\t},\n\t\t{\n\t\t\tname:        \"URL with SSL mode\",\n\t\t\tjdbcURL:     \"jdbc:postgresql://user:pass@dbhost:5432/mydb?sslmode=require\",\n\t\t\twantHost:    \"dbhost:5432\",\n\t\t\twantDB:      \"mydb\",\n\t\t\twantUser:    \"user\",\n\t\t\twantPass:    \"pass\",\n\t\t\twantSSLMode: \"require\",\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid URL - missing jdbc:postgresql prefix\",\n\t\t\tjdbcURL: \"mysql://user:pass@localhost/db\",\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid URL - missing //\",\n\t\t\tjdbcURL: \"jdbc:postgresql:user:pass@localhost/db\",\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tjdbc, err := NewJDBC(logContext.Background(), tt.jdbcURL)\n\t\t\tif tt.wantErr {\n\t\t\t\tassert.Error(t, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\t\t\tinfo := jdbc.GetConnectionInfo()\n\t\t\tassert.Equal(t, tt.wantHost, info.Host)\n\t\t\tassert.Equal(t, tt.wantDB, info.Database)\n\t\t\tassert.Equal(t, tt.wantUser, info.User)\n\t\t\tassert.Equal(t, tt.wantPass, info.Password)\n\n\t\t\tif tt.wantSSLMode != \"\" {\n\t\t\t\tassert.Equal(t, tt.wantSSLMode, info.Params[\"sslmode\"])\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPostgreSQLHandler_BuildNativeConnectionString(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinfo *ConnectionInfo\n\t\twant map[string]string // key-value pairs that should be in the connection string\n\t}{\n\t\t{\n\t\t\tname: \"basic connection\",\n\t\t\tinfo: &ConnectionInfo{\n\t\t\t\tHost:     \"localhost\",\n\t\t\t\tDatabase: \"testdb\",\n\t\t\t\tUser:     \"postgres\",\n\t\t\t\tPassword: \"secret\",\n\t\t\t\tParams: map[string]string{\n\t\t\t\t\t\"connect_timeout\": \"10\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: map[string]string{\n\t\t\t\t\"host\":            \"localhost\",\n\t\t\t\t\"dbname\":          \"testdb\",\n\t\t\t\t\"user\":            \"postgres\",\n\t\t\t\t\"password\":        \"secret\",\n\t\t\t\t\"connect_timeout\": \"10\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"with SSL mode\",\n\t\t\tinfo: &ConnectionInfo{\n\t\t\t\tHost:     \"dbhost:5433\",\n\t\t\t\tDatabase: \"mydb\",\n\t\t\t\tUser:     \"user\",\n\t\t\t\tPassword: \"pass\",\n\t\t\t\tParams:   map[string]string{\"sslmode\": \"require\"},\n\t\t\t},\n\t\t\twant: map[string]string{\n\t\t\t\t\"host\":     \"dbhost\",\n\t\t\t\t\"port\":     \"5433\",\n\t\t\t\t\"dbname\":   \"mydb\",\n\t\t\t\t\"sslmode\":  \"require\",\n\t\t\t\t\"user\":     \"user\",\n\t\t\t\t\"password\": \"pass\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tjdbc := &PostgresJDBC{\n\t\t\t\tConnectionInfo: *tt.info,\n\t\t\t}\n\n\t\t\tconnStr := jdbc.BuildConnectionString()\n\t\t\t// Verify all expected key-value pairs are in the connection string\n\t\t\tfor key, expectedValue := range tt.want {\n\t\t\t\texpectedPair := key + \"=\" + expectedValue\n\t\t\t\tassert.Contains(t, connStr, expectedPair)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jdbc/sqlserver.go",
    "content": "package jdbc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\n\tmssql \"github.com/microsoft/go-mssqldb\"\n)\n\ntype SqlServerJDBC struct {\n\tConnectionInfo\n}\n\nvar _ JDBC = (*SqlServerJDBC)(nil)\n\nfunc (s *SqlServerJDBC) ping(ctx context.Context) pingResult {\n\treturn ping(ctx, \"mssql\", isSqlServerErrorDeterminate,\n\t\tbuildSQLServerConnectionString(s.Host, s.User, s.Password, \"master\", map[string]string{\"connection+timeout\": \"5\"}))\n}\n\nfunc (s *SqlServerJDBC) GetDBType() DatabaseType {\n\treturn SQLServer\n}\n\nfunc (s *SqlServerJDBC) GetConnectionInfo() *ConnectionInfo {\n\treturn &s.ConnectionInfo\n}\n\nfunc (s *SqlServerJDBC) BuildConnectionString() string {\n\treturn buildSQLServerConnectionString(s.Host, s.User, s.Password, s.Database, s.Params)\n}\n\nfunc isSqlServerErrorDeterminate(err error) bool {\n\t// Error numbers from https://learn.microsoft.com/en-us/sql/relational-databases/errors-events/database-engine-events-and-errors?view=sql-server-ver16\n\tvar sqlErr mssql.Error\n\tif errors.As(err, &sqlErr) {\n\t\tswitch sqlErr.Number {\n\t\tcase 18456:\n\t\t\t// Login failed\n\t\t\t// This is a determinate failure iff we tried to use a real user\n\t\t\treturn sqlErr.Message != \"login error: Login failed for user ''.\"\n\t\t}\n\t}\n\treturn false\n}\n\nfunc parseSqlServer(ctx logContext.Context, subname string) (JDBC, error) {\n\tif !strings.HasPrefix(subname, \"//\") {\n\t\treturn nil, errors.New(\"expected connection to start with //\")\n\t}\n\tconn := strings.TrimPrefix(subname, \"//\")\n\n\tport := \"1433\"\n\tuser := \"sa\"\n\tdatabase := \"master\"\n\tvar password, host string\n\tparams := make(map[string]string)\n\n\tfor i, param := range strings.Split(conn, \";\") {\n\t\tkey, value, found := strings.Cut(param, \"=\")\n\t\tif !found && i == 0 {\n\t\t\t//  String connectionUrl = \"jdbc:sqlserver://<server>:<port>;encrypt=true;databaseName=AdventureWorks;user=<user>;password=<password>\";\n\t\t\tif split := strings.Split(param, \":\"); len(split) > 1 {\n\t\t\t\thost = split[0]\n\t\t\t\tport = split[1]\n\t\t\t} else {\n\t\t\t\thost = param\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// incase there is a bridge between jdbc and some driver like \"odbc\", and conn string looks like this odbc:server\n\t\tif split := strings.Split(key, \":\"); len(split) > 1 {\n\t\t\tkey = split[1]\n\t\t}\n\n\t\tswitch strings.ToLower(key) {\n\t\tcase \"password\", \"spring.datasource.password\", \"pwd\":\n\t\t\tpassword = value\n\t\tcase \"server\":\n\t\t\thost = value\n\t\tcase \"port\":\n\t\t\tport = value\n\t\tcase \"user\", \"uid\", \"user id\", \"userid\":\n\t\t\tuser = value\n\t\tcase \"database\", \"databasename\":\n\t\t\tdatabase = value\n\t\tdefault:\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\n\tif password == \"\" || host == \"\" {\n\t\tctx.Logger().WithName(\"jdbc\").\n\t\t\tV(2).\n\t\t\tInfo(\"Skipping invalid SQL Server URL - no password or host found\")\n\t\treturn nil, fmt.Errorf(\"missing host or password in connection string\")\n\t}\n\n\treturn &SqlServerJDBC{\n\t\tConnectionInfo: ConnectionInfo{\n\t\t\tHost:     host + \":\" + port,\n\t\t\tUser:     user,\n\t\t\tPassword: password,\n\t\t\tDatabase: database,\n\t\t\tParams:   params,\n\t\t},\n\t}, nil\n}\n\nfunc buildSQLServerConnectionString(host, user, password, database string, params map[string]string) string {\n\tconn := fmt.Sprintf(\"sqlserver://%s:%s@%s?database=%s\", user, password, host, database)\n\tif len(params) > 0 {\n\t\tfor k, v := range params {\n\t\t\tconn += fmt.Sprintf(\"&%s=%s\", k, v)\n\t\t}\n\t}\n\treturn conn\n}\n"
  },
  {
    "path": "pkg/detectors/jdbc/sqlserver_integration_test.go",
    "content": "//go:build detectors && integration\n// +build detectors,integration\n\npackage jdbc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/brianvoe/gofakeit/v7\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/testcontainers/testcontainers-go/modules/mssql\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestSqlServer(t *testing.T) {\n\tctx := context.Background()\n\n\tsqlServerUser := \"sa\"\n\tsqlServerPass := gofakeit.Password(true, true, true, false, false, 10)\n\tsqlServerDB := \"master\"\n\n\tmssqlContainer, err := mssql.Run(ctx,\n\t\t\"mcr.microsoft.com/azure-sql-edge\",\n\t\tmssql.WithAcceptEULA(),\n\t\tmssql.WithPassword(sqlServerPass),\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdefer mssqlContainer.Terminate(ctx)\n\n\tmssqlHost, err := mssqlContainer.Host(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tmssqlPort, err := mssqlContainer.MappedPort(ctx, \"1433\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttype result struct {\n\t\tparseErr        bool\n\t\tpingOk          bool\n\t\tpingDeterminate bool\n\t}\n\ttests := []struct {\n\t\tinput string\n\t\twant  result\n\t}{\n\t\t{\n\t\t\tinput: \"\",\n\t\t\twant:  result{parseErr: true},\n\t\t},\n\t\t{\n\t\t\tinput: fmt.Sprintf(\"//server=%s;port=%s;user id=%s;database=%s;password=%s\",\n\t\t\t\tmssqlHost, mssqlPort.Port(), sqlServerUser, sqlServerDB, sqlServerPass),\n\t\t\twant: result{pingOk: true, pingDeterminate: true},\n\t\t},\n\t\t{\n\t\t\tinput: fmt.Sprintf(\"//server=badhost;user id=sa;database=master;password=%s\", sqlServerPass),\n\t\t\twant:  result{pingOk: false, pingDeterminate: false},\n\t\t},\n\t\t{\n\t\t\tinput: fmt.Sprintf(\"//%s;database=master;spring.datasource.password=%s;port=%s\",\n\t\t\t\tmssqlHost, sqlServerPass, mssqlPort.Port()),\n\t\t\twant: result{pingOk: true, pingDeterminate: true},\n\t\t},\n\t\t{\n\t\t\tinput: fmt.Sprintf(\"//%s;database=master;spring.datasource.password=badpassword;port=%s\", mssqlHost, mssqlPort.Port()),\n\t\t\twant:  result{pingOk: false, pingDeterminate: true},\n\t\t},\n\t\t{\n\t\t\tinput: fmt.Sprintf(\"//%s:%s;databaseName=master;user=%s;password=%s\",\n\t\t\t\tmssqlHost, mssqlPort.Port(), sqlServerUser, sqlServerPass),\n\t\t\twant: result{pingOk: true, pingDeterminate: true},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input, func(t *testing.T) {\n\t\t\tctx := logContext.AddLogger(context.Background())\n\t\t\tj, err := ParseSqlServer(ctx, tt.input)\n\n\t\t\tif err != nil {\n\t\t\t\tgot := result{parseErr: true}\n\t\t\t\tassert.Equal(t, tt.want, got)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tpr := j.ping(context.Background())\n\n\t\t\tgot := result{pingOk: pr.err == nil, pingDeterminate: pr.determinate}\n\t\t\tassert.Equal(t, tt.want, got)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jdbc/sqlserver_test.go",
    "content": "package jdbc\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestParseSqlServerMissingCredentials(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tsubname     string\n\t\tshouldBeNil bool\n\t\treason      string\n\t}{\n\t\t{\n\t\t\tname:        \"no password - should return nil (nothing to verify)\",\n\t\t\tsubname:     \"//examplehost.net;databaseName=QRG1;sendStringParametersAsUnicode=false\",\n\t\t\tshouldBeNil: true,\n\t\t\treason:      \"no password present\",\n\t\t},\n\t\t{\n\t\t\tname:        \"no host - should return nil (invalid connection)\",\n\t\t\tsubname:     \"//;password=secret123\",\n\t\t\tshouldBeNil: true,\n\t\t\treason:      \"no host present\",\n\t\t},\n\t\t{\n\t\t\tname:        \"no host and no password - should return nil\",\n\t\t\tsubname:     \"//;databaseName=QRG1\",\n\t\t\tshouldBeNil: true,\n\t\t\treason:      \"no host or password present\",\n\t\t},\n\t\t{\n\t\t\tname:        \"csm-1584-example\",\n\t\t\tsubname:     \"//examplehost.net;databaseName=QRG1;sendStringParametersAsUnicode=false;loginTimeout=4;applicationName=pdal-cat-hierarchy-loader-v1;\",\n\t\t\tshouldBeNil: true,\n\t\t\treason:      \"no password present\",\n\t\t},\n\t\t{\n\t\t\tname:        \"valid with both host and password - should succeed\",\n\t\t\tsubname:     \"//examplehost.net;password=secret123\",\n\t\t\tshouldBeNil: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"valid with host:port and password - should succeed\",\n\t\t\tsubname:     \"//examplehost.net:1433;password=secret123\",\n\t\t\tshouldBeNil: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tctx := logContext.AddLogger(context.Background())\n\n\t\t\tj, err := parseSqlServer(ctx, tt.subname)\n\n\t\t\tif tt.shouldBeNil {\n\t\t\t\tif j != nil {\n\t\t\t\t\tt.Errorf(\"parseSqlServer() expected nil (%s), but got connection: %v\",\n\t\t\t\t\t\ttt.reason, j)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif j == nil {\n\t\t\t\t\tt.Errorf(\"parseSqlServer() returned nil, expected valid connection. err = %v\", err)\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"parseSqlServer() unexpected error = %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// This test demonstrates the username is ignored when parsing the JDBC URL. Instead the default username \"sa\" is always used.\nfunc TestParseSqlServerUserIgnoredBug2(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tsubname      string // the part after \"jdbc:sqlserver:\"\n\t\twantUsername string\n\t}{\n\t\t{\n\t\t\tname:         \"user parameter specified\",\n\t\t\tsubname:      \"//localhost:1433;user=myuser;password=mypass\",\n\t\t\twantUsername: \"myuser\",\n\t\t},\n\t\t{\n\t\t\tname:         \"user id parameter specified\",\n\t\t\tsubname:      \"//localhost:1433;user id=admin;password=secret\",\n\t\t\twantUsername: \"admin\",\n\t\t},\n\t\t{\n\t\t\tname:         \"no user specified - should default to sa\",\n\t\t\tsubname:      \"//localhost:1433;password=mypass\",\n\t\t\twantUsername: \"sa\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tctx := logContext.AddLogger(context.Background())\n\n\t\t\tj, err := parseSqlServer(ctx, tt.subname)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"parseSqlServer() error = %v\", err)\n\t\t\t}\n\n\t\t\tsqlServerConn := j.(*SqlServerJDBC)\n\n\t\t\tif sqlServerConn.User != tt.wantUsername {\n\t\t\t\tt.Errorf(\"Connection string does not contain expected username '%s'\\nGot: %s\\nExpected to contain: %s\",\n\t\t\t\t\ttt.wantUsername, sqlServerConn.User, tt.wantUsername)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseSqlServerWithJdbcAndOdbcBridgeString(t *testing.T) {\n\tsubname := \"//odbc:server=localhost;port=1433;database=testdb;password=testpassword\"\n\n\twantHost := \"localhost\"\n\twantPort := \"1433\"\n\twantPassword := \"testpassword\"\n\twantDatabase := \"testdb\"\n\n\tctx := logContext.AddLogger(context.Background())\n\n\tj, err := parseSqlServer(ctx, subname)\n\tif err != nil {\n\t\tt.Fatalf(\"parseSqlServer() error = %v\", err)\n\t}\n\n\tif j == nil {\n\t\tt.Fatalf(\"parseSqlServer() returned nil, expected valid connection.\")\n\t}\n\n\tsqlServerConn, ok := j.(*SqlServerJDBC)\n\tif !ok {\n\t\tt.Fatalf(\"parseSqlServer() returned unexpected type %T, expected *SqlServerJDBC\", j)\n\t}\n\n\tif sqlServerConn.Host != wantHost+\":\"+wantPort {\n\t\tt.Errorf(\"Host mismatch. Got: %s, Want: %s\", sqlServerConn.Host, wantHost+\":\"+wantPort)\n\t}\n\n\tif sqlServerConn.Password != wantPassword {\n\t\tt.Errorf(\"Password mismatch. Got: %s, Want: %s\", sqlServerConn.Password, wantPassword)\n\t}\n\n\tif sqlServerConn.Database != wantDatabase {\n\t\tt.Errorf(\"Database mismatch. Got: %s, Want: %s\", sqlServerConn.Database, wantDatabase)\n\t}\n}\nfunc TestSQLServerHandler_ParseJDBCURL(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tjdbcURL  string\n\t\twantHost string\n\t\twantDB   string\n\t\twantUser string\n\t\twantPass string\n\t\twantErr  bool\n\t}{\n\t\t{\n\t\t\tname:     \"basic URL with semicolon params\",\n\t\t\tjdbcURL:  \"jdbc:sqlserver://localhost:1433;database=testdb;user=sa;password=Pass123\",\n\t\t\twantHost: \"localhost:1433\",\n\t\t\twantDB:   \"testdb\",\n\t\t\twantUser: \"sa\",\n\t\t\twantPass: \"Pass123\",\n\t\t},\n\t\t{\n\t\t\tname:     \"URL with default port and database\",\n\t\t\tjdbcURL:  \"jdbc:sqlserver://dbhost;user=testuser;password=secret\",\n\t\t\twantHost: \"dbhost:1433\",\n\t\t\twantDB:   \"master\",\n\t\t\twantUser: \"testuser\",\n\t\t\twantPass: \"secret\",\n\t\t},\n\t\t{\n\t\t\tname:     \"URL with port in host\",\n\t\t\tjdbcURL:  \"jdbc:sqlserver://server.example.com:1434;databaseName=mydb;userId=admin;pwd=admin123\",\n\t\t\twantHost: \"server.example.com:1434\",\n\t\t\twantDB:   \"mydb\",\n\t\t\twantUser: \"admin\",\n\t\t\twantPass: \"admin123\",\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid URL - missing jdbc:sqlserver prefix\",\n\t\t\tjdbcURL: \"jdbc:mysql://localhost/db\",\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid URL - missing //\",\n\t\t\tjdbcURL: \"jdbc:sqlserver:localhost;database=test\",\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tjdbc, err := NewJDBC(logContext.Background(), tt.jdbcURL)\n\t\t\tif tt.wantErr {\n\t\t\t\tassert.Error(t, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\t\t\tinfo := jdbc.GetConnectionInfo()\n\t\t\tassert.Equal(t, tt.wantHost, info.Host)\n\t\t\tassert.Equal(t, tt.wantDB, info.Database)\n\t\t\tassert.Equal(t, tt.wantUser, info.User)\n\t\t\tassert.Equal(t, tt.wantPass, info.Password)\n\t\t})\n\t}\n}\n\nfunc TestSQLServerHandler_BuildNativeConnectionString(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinfo     *ConnectionInfo\n\t\twantUser string\n\t\twantPass string\n\t\twantHost string\n\t\twantDB   string\n\t}{\n\t\t{\n\t\t\tname: \"basic connection\",\n\t\t\tinfo: &ConnectionInfo{\n\t\t\t\tHost:     \"localhost\",\n\t\t\t\tDatabase: \"testdb\",\n\t\t\t\tUser:     \"sa\",\n\t\t\t\tPassword: \"Pass123\",\n\t\t\t},\n\t\t\twantUser: \"sa\",\n\t\t\twantPass: \"Pass123\",\n\t\t\twantHost: \"localhost\",\n\t\t\twantDB:   \"testdb\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tjdbc := &SqlServerJDBC{\n\t\t\t\tConnectionInfo: *tt.info,\n\t\t\t}\n\t\t\tconnStr := jdbc.BuildConnectionString()\n\n\t\t\t// SQL Server format: sqlserver://user:password@host:port?database=db&connection+timeout=10\n\t\t\tassert.Contains(t, connStr, tt.wantUser)\n\t\t\tassert.Contains(t, connStr, tt.wantPass)\n\t\t\tassert.Contains(t, connStr, tt.wantHost)\n\t\t\tassert.Contains(t, connStr, \"database=\"+tt.wantDB)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jiratoken/v1/jiratoken.go",
    "content": "package jiratoken\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nfunc (Scanner) Version() int { return 1 }\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\ttokenPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"atlassian\", \"confluence\", \"jira\"}) + `\\b([a-zA-Z-0-9]{24})\\b`)\n\tdomainPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"atlassian\", \"confluence\", \"jira\"}) + `\\b((?:[a-zA-Z0-9-]{1,24}\\.)+[a-zA-Z0-9-]{2,24}\\.[a-zA-Z0-9-]{2,16})\\b`)\n\temailPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"atlassian\", \"confluence\", \"jira\"}) + common.EmailPattern)\n\n\tinvalidHosts = simple.NewCache[struct{}]()\n\n\terrNoHost = errors.New(\"no such host\")\n)\n\ntype JIRAGraphQLResponse struct {\n\tData struct {\n\t\tMe struct {\n\t\t\tUser struct {\n\t\t\t\tName string `json:\"name\"`\n\t\t\t} `json:\"user\"`\n\t\t} `json:\"me\"`\n\t} `json:\"data\"`\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"atlassian\", \"confluence\", \"jira\"}\n}\n\n// FromData will find and optionally verify JiraToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueTokens, uniqueDomains, uniqueEmails = make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, token := range tokenPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokens[token[1]] = struct{}{}\n\t}\n\n\tfor _, domain := range domainPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueDomains[domain[1]] = struct{}{}\n\t}\n\n\tfor _, email := range emailPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueEmails[strings.ToLower(email[1])] = struct{}{}\n\t}\n\n\tif len(uniqueDomains) == 0 {\n\t\t// reason: https://community.atlassian.com/forums/Jira-Product-Discovery-questions/Authorization-issues-with-GRAPHQL/qaq-p/2640943\n\t\t// In case we don't find any domain matches we can use this as the graphql API works with this domain if our authentication is valid\n\t\tuniqueDomains[\"api.atlassian.com\"] = struct{}{}\n\t}\n\n\tfor email := range uniqueEmails {\n\t\tfor token := range uniqueTokens {\n\t\t\tfor domain := range uniqueDomains {\n\t\t\t\tif invalidHosts.Exists(domain) {\n\t\t\t\t\tdelete(uniqueDomains, domain)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JiraToken,\n\t\t\t\t\tRaw:          []byte(token),\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s:%s\", email, token, domain)),\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/atlassian/\",\n\t\t\t\t\t\t\"version\":        fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tclient := s.getClient()\n\t\t\t\t\tisVerified, verificationErr := VerifyJiraToken(ctx, client, email, domain, token)\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\tif verificationErr != nil {\n\t\t\t\t\t\tif errors.Is(verificationErr, errNoHost) {\n\t\t\t\t\t\t\tinvalidHosts.Set(domain, struct{}{})\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\ts1.SetVerificationError(verificationErr, token)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc VerifyJiraToken(ctx context.Context, client *http.Client, email, domain, token string) (bool, error) {\n\t// wrap the query in a JSON body\n\tbody := map[string]string{\n\t\t\"query\": `query verify { me { user { name } } }`,\n\t}\n\n\t// encode the body as JSON\n\tjsonBody, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// api docs: https://developer.atlassian.com/platform/atlassian-graphql-api/graphql/#authentication\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://\"+domain+\"/gateway/api/graphql\", bytes.NewBuffer(jsonBody))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Accept\", \"application/json\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.SetBasicAuth(email, token)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\t// lookup foo.test.net: no such host\n\t\tif strings.Contains(err.Error(), \"no such host\") {\n\t\t\treturn false, errNoHost\n\t\t}\n\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\t// the API returns 200 if the token is valid\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tvar jiraResp JIRAGraphQLResponse\n\t\tif err := json.NewDecoder(resp.Body).Decode(&jiraResp); err != nil {\n\t\t\treturn false, nil // can't decode response in case of 200 OK = not valid JIRA domain\n\t\t}\n\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_JiraToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Jira is a proprietary issue tracking product developed by Atlassian that allows bug tracking and agile project management. Jira tokens can be used to authenticate and interact with Jira's API.\"\n}\n"
  },
  {
    "path": "pkg/detectors/jiratoken/v1/jiratoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage jiratoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestJiraToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"JIRA_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"JIRA_INACTIVE\")\n\temail := testSecrets.MustGetField(\"JIRA_EMAIL\")\n\tdomain := testSecrets.MustGetField(\"JIRA_DOMAIN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a jira secret %s within jira %s with jira %s\", secret, email, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JiraToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/atlassian/\",\n\t\t\t\t\t\t\"version\":        \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a jira secret %s within jira %s but not jira %s valid\", inactiveSecret, email, domain)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JiraToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/atlassian/\",\n\t\t\t\t\t\t\"version\":        \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a jira secret %s within jira %s with jira %s\", secret, email, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JiraToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/atlassian/\",\n\t\t\t\t\t\t\"version\":        \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a jira secret %s within jira %s with jira %s\", secret, email, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JiraToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/atlassian/\",\n\t\t\t\t\t\t\"version\":        \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"JiraToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"JiraToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jiratoken/v1/jiratoken_test.go",
    "content": "package jiratoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidTokenPattern    = \"Z7VoIYJ0K4rFWLBfkhOsLAWX\"\n\tinvalidTokenPattern  = \"Z7VoI?J0K4rF#LBfkhO&LAWX\"\n\tvalidDomainPattern   = \"hereisavalidsubdomain.heresalongdomain.com\"\n\tinvalidDomainPattern = \"?y4r3fs1ewqec12v1e3tl.5Hcsrcehic89saXd.ro@\"\n\tvalidEmailPattern    = \"xfkf_bz7@grum.com\"\n\tinvalidEmailPattern  = \"xfKF_BZq7/grum.com\"\n\tkeyword              = \"jira\"\n)\n\nfunc TestJiraToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword jira\",\n\t\t\tinput: fmt.Sprintf(\"%s %s          \\n%s %s\\n%s %s\", keyword, validTokenPattern, keyword, validDomainPattern, keyword, validEmailPattern),\n\t\t\twant:  []string{validEmailPattern + \":\" + validTokenPattern + \":\" + validDomainPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s' domain = '%s' email = '%s'\", keyword, validTokenPattern, validDomainPattern, validEmailPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' domain = '%s' email = '%s'\", keyword, invalidTokenPattern, invalidDomainPattern, invalidEmailPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jiratoken/v2/jiratoken_v2.go",
    "content": "package jiratoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\tv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jiratoken/v1\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nfunc (Scanner) Version() int { return 2 }\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithLocalAddresses\n\n\t// https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/\n\t// Tokens created after Jan 18 2023 use a variable length\n\ttokenPat  = regexp.MustCompile(`\\b(ATATT[A-Za-z0-9+/=_-]+=[A-Za-z0-9]{8})\\b`)\n\tdomainPat = regexp.MustCompile(`\\b((?:[a-zA-Z0-9-]{1,24}\\.)+[a-zA-Z0-9-]{2,24}\\.[a-zA-Z0-9-]{2,16})\\b`)\n\temailPat  = regexp.MustCompile(common.EmailPattern)\n)\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"atlassian\", \"confluence\", \"jira\"}\n}\n\n// FromData will find and optionally verify JiraToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueTokens, uniqueDomains, uniqueEmails = make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, token := range tokenPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokens[token[1]] = struct{}{}\n\t}\n\n\tfor _, domain := range domainPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueDomains[domain[1]] = struct{}{}\n\t}\n\n\tfor _, email := range emailPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueEmails[strings.ToLower(email[1])] = struct{}{}\n\t}\n\n\tif len(uniqueDomains) == 0 {\n\t\t// reason: https://community.atlassian.com/forums/Jira-Product-Discovery-questions/Authorization-issues-with-GRAPHQL/qaq-p/2640943\n\t\t// In case we don't find any domain matches we can use this as the graphql API works with this domain if our authentication is valid\n\t\tuniqueDomains[\"api.atlassian.com\"] = struct{}{}\n\t}\n\n\tfor email := range uniqueEmails {\n\t\tfor token := range uniqueTokens {\n\t\t\tfor domain := range uniqueDomains {\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JiraToken,\n\t\t\t\t\tRaw:          []byte(token),\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s:%s\", email, token, domain)),\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/atlassian/\",\n\t\t\t\t\t\t\"version\":        fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tclient := s.getClient()\n\t\t\t\t\tisVerified, verificationErr := v1.VerifyJiraToken(ctx, client, email, domain, token)\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\ts1.SetVerificationError(verificationErr, token)\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_JiraToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Jira is a proprietary issue tracking product developed by Atlassian that allows bug tracking and agile project management. Jira tokens can be used to authenticate API requests.\"\n}\n"
  },
  {
    "path": "pkg/detectors/jiratoken/v2/jiratoken_v2_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage jiratoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestJiraToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"JIRA_V2_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"JIRA_V2_INACTIVE\")\n\temail := testSecrets.MustGetField(\"JIRA_V2_EMAIL\")\n\tdomain := testSecrets.MustGetField(\"JIRA_V2_DOMAIN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a jira secret %s within jira %s with jira %s\", secret, email, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JiraToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/atlassian/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a jira secret %s within jira %s but not jira %s valid\", inactiveSecret, email, domain)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JiraToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/atlassian/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a jira secret %s within jira %s with jira %s\", secret, email, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JiraToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/atlassian/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a jira secret %s within jira %s with jira %s\", secret, email, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JiraToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/atlassian/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"JiraToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"JiraToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jiratoken/v2/jiratoken_v2_test.go",
    "content": "package jiratoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestJiraToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname       string\n\t\tinput      string\n\t\twant       []string\n\t\tnoKeywords bool\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t\t{\n\t\t\t\t\t\"expand\": \"schema,names\",\n\t\t\t\t\t\"startAt\": 0,\n\t\t\t\t\t\"maxResults\": 50,\n\t\t\t\t\t\"total\": 1,\n\t\t\t\t\t\"issues\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"expand\": \"operations,versionedRepresentations,editmeta,changelog,renderedFields\",\n\t\t\t\t\t\t\t\"id\": \"fake454\",\n\t\t\t\t\t\t\t\"self\": \"https://example.atlassian.net/rest/api/2/issue/fake454\",\n\t\t\t\t\t\t\t\"key\": \"ESI-5555\",\n\t\t\t\t\t\t\t\"fields\": {\n\t\t\t\t\t\t\t\t\"statuscategorychangedate\": \"2016-06-01T01:25:35.807-0700\",\n\t\t\t\t\t\t\t\t\"issuetype\": {\n\t\t\t\t\t\t\t\t\"self\": \"https://example.atlassian.net/rest/api/2/issuetype/09090\",\n\t\t\t\t\t\t\t\t\"id\": \"09090\",\n\t\t\t\t\t\t\t\t\"description\": \"This is an example ticket. Here's the token to test JIRA APIs: ATATThktLkSzzcXi1xt19IlU6gAchV1TS83w11YOqAXqFUeA2=Yx3ssoNC\",\n\t\t\t\t\t\t\t\t\"name\": \"Example Pattern test\",\n\t\t\t\t\t\t\t\t\"subtask\": false,\n\t\t\t\t\t\t\t\t\"avatarId\": 1298,\n\t\t\t\t\t\t\t\t\"entityId\": \"93a51c1d-fake-4673-a71d-0889fake1238\",\n\t\t\t\t\t\t\t\t\"hierarchyLevel\": 0,\n\t\t\t\t\t\t\t\t\"emailAddress\": \"trufflesecurity@example.com\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t]}`,\n\t\t\twant: []string{\"trufflesecurity@example.com\" + \":\" + \"ATATThktLkSzzcXi1xt19IlU6gAchV1TS83w11YOqAXqFUeA2=Yx3ssoNC\" + \":\" + \"example.atlassian.net\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - without domain\",\n\t\t\tinput: `\n\t\t\t\t\t{\n\t\t\t\t\t\"expand\": \"schema,names\",\n\t\t\t\t\t\"startAt\": 0,\n\t\t\t\t\t\"maxResults\": 50,\n\t\t\t\t\t\"total\": 1,\n\t\t\t\t\t\"issues\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"expand\": \"operations,versionedRepresentations,editmeta,changelog,renderedFields\",\n\t\t\t\t\t\t\t\"id\": \"fake454\",\n\t\t\t\t\t\t\t\"key\": \"ESI-5555\",\n\t\t\t\t\t\t\t\"fields\": {\n\t\t\t\t\t\t\t\t\"statuscategorychangedate\": \"2016-06-01T01:25:35.807-0700\",\n\t\t\t\t\t\t\t\t\"issuetype\": {\n\t\t\t\t\t\t\t\t\"id\": \"09090\",\n\t\t\t\t\t\t\t\t\"description\": \"This is an example ticket. Here's the token to test JIRA APIs: ATATThktLkSzzcXi1xt19IlU6gAchV1TS83w11YOqAXqFUeA2=Yx3ssoNC\",\n\t\t\t\t\t\t\t\t\"name\": \"Example Pattern test 2\",\n\t\t\t\t\t\t\t\t\"subtask\": false,\n\t\t\t\t\t\t\t\t\"avatarId\": 1298,\n\t\t\t\t\t\t\t\t\"entityId\": \"93a51c1d-fake-4673-a71d-0889fake1238\",\n\t\t\t\t\t\t\t\t\"hierarchyLevel\": 0,\n\t\t\t\t\t\t\t\t\"emailAddress\": \"trufflesecurity@example.com\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t]}`,\n\t\t\twant: []string{\"trufflesecurity@example.com\" + \":\" + \"ATATThktLkSzzcXi1xt19IlU6gAchV1TS83w11YOqAXqFUeA2=Yx3ssoNC\" + \":\" + \"api.atlassian.com\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - without token\",\n\t\t\tinput: `\n\t\t\t\t\t{\n\t\t\t\t\t\"expand\": \"schema,names\",\n\t\t\t\t\t\"startAt\": 0,\n\t\t\t\t\t\"maxResults\": 50,\n\t\t\t\t\t\"total\": 1,\n\t\t\t\t\t\"issues\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"expand\": \"operations,versionedRepresentations,editmeta,changelog,renderedFields\",\n\t\t\t\t\t\t\t\"id\": \"fake454\",\n\t\t\t\t\t\t\t\"key\": \"ESI-5555\",\n\t\t\t\t\t\t\t\"fields\": {\n\t\t\t\t\t\t\t\t\"statuscategorychangedate\": \"2016-06-01T01:25:35.807-0700\",\n\t\t\t\t\t\t\t\t\"issuetype\": {\n\t\t\t\t\t\t\t\t\"id\": \"09090\",\n\t\t\t\t\t\t\t\t\"self\": \"https://example.atlassian.net/rest/api/2/issuetype/09090\",\n\t\t\t\t\t\t\t\t\"description\": \"This is an example ticket\",\n\t\t\t\t\t\t\t\t\"name\": \"Example Pattern test 2\",\n\t\t\t\t\t\t\t\t\"subtask\": false,\n\t\t\t\t\t\t\t\t\"avatarId\": 1298,\n\t\t\t\t\t\t\t\t\"entityId\": \"93a51c1d-fake-4673-a71d-0889fake1238\",\n\t\t\t\t\t\t\t\t\"hierarchyLevel\": 0,\n\t\t\t\t\t\t\t\t\"emailAddress\": \"trufflesecurity@example.com\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t]}`,\n\t\t\twant: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - without email\",\n\t\t\tinput: `\n\t\t\t\t\t{\n\t\t\t\t\t\"expand\": \"schema,names\",\n\t\t\t\t\t\"startAt\": 0,\n\t\t\t\t\t\"maxResults\": 50,\n\t\t\t\t\t\"total\": 1,\n\t\t\t\t\t\"issues\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"expand\": \"operations,versionedRepresentations,editmeta,changelog,renderedFields\",\n\t\t\t\t\t\t\t\"id\": \"fake454\",\n\t\t\t\t\t\t\t\"key\": \"ESI-5555\",\n\t\t\t\t\t\t\t\"fields\": {\n\t\t\t\t\t\t\t\t\"statuscategorychangedate\": \"2016-06-01T01:25:35.807-0700\",\n\t\t\t\t\t\t\t\t\"issuetype\": {\n\t\t\t\t\t\t\t\t\"id\": \"09090\",\n\t\t\t\t\t\t\t\t\"self\": \"https://example.atlassian.net/rest/api/2/issuetype/09090\",\n\t\t\t\t\t\t\t\t\"description\": \"This is an example ticket. Here's the token to test JIRA APIs: ATATThktLkSzzcXi1xt19IlU6gAchV1TS83w11YOqAXqFUeA2=Yx3ssoNC\",\n\t\t\t\t\t\t\t\t\"name\": \"Example Pattern test 2\",\n\t\t\t\t\t\t\t\t\"subtask\": false,\n\t\t\t\t\t\t\t\t\"avatarId\": 1298,\n\t\t\t\t\t\t\t\t\"entityId\": \"93a51c1d-fake-4673-a71d-0889fake1238\",\n\t\t\t\t\t\t\t\t\"hierarchyLevel\": 0,\n\t\t\t\t\t\t\t\t\"emailAddress\": \"\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t]}`,\n\t\t\twant: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - without keywords\",\n\t\t\tinput: `\n\t\t\t\t\t{\n\t\t\t\t\t\"expand\": \"schema,names\",\n\t\t\t\t\t\"startAt\": 0,\n\t\t\t\t\t\"maxResults\": 50,\n\t\t\t\t\t\"total\": 1,\n\t\t\t\t\t\"issues\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"expand\": \"operations,versionedRepresentations,editmeta,changelog,renderedFields\",\n\t\t\t\t\t\t\t\"id\": \"fake454\",\n\t\t\t\t\t\t\t\"key\": \"ESI-5555\",\n\t\t\t\t\t\t\t\"fields\": {\n\t\t\t\t\t\t\t\t\"statuscategorychangedate\": \"2016-06-01T01:25:35.807-0700\",\n\t\t\t\t\t\t\t\t\"issuetype\": {\n\t\t\t\t\t\t\t\t\"id\": \"09090\",\n\t\t\t\t\t\t\t\t\"description\": \"ATATThktLkSzzcXi1xt19IlU6gAchV1TS83w11YOqAXqFUeA2=Yx3ssoNC\",\n\t\t\t\t\t\t\t\t\"name\": \"Example Pattern test 2\",\n\t\t\t\t\t\t\t\t\"subtask\": false,\n\t\t\t\t\t\t\t\t\"avatarId\": 1298,\n\t\t\t\t\t\t\t\t\"entityId\": \"93a51c1d-fake-4673-a71d-0889fake1238\",\n\t\t\t\t\t\t\t\t\"hierarchyLevel\": 0,\n\t\t\t\t\t\t\t\t\"emailAddress\": \"trufflesecurity@example.com\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t]}`,\n\t\t\twant:       []string{},\n\t\t\tnoKeywords: true,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t\t{\n\t\t\t\t\t\"expand\": \"schema,names\",\n\t\t\t\t\t\"startAt\": 0,\n\t\t\t\t\t\"maxResults\": 50,\n\t\t\t\t\t\"total\": 1,\n\t\t\t\t\t\"issues\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"expand\": \"operations,versionedRepresentations,editmeta,changelog,renderedFields\",\n\t\t\t\t\t\t\t\"id\": \"fake454\",\n\t\t\t\t\t\t\t\"key\": \"ESI-5555\",\n\t\t\t\t\t\t\t\"fields\": {\n\t\t\t\t\t\t\t\t\"statuscategorychangedate\": \"2016-06-01T01:25:35.807-0700\",\n\t\t\t\t\t\t\t\t\"issuetype\": {\n\t\t\t\t\t\t\t\t\"id\": \"09090\",\n\t\t\t\t\t\t\t\t\"description\": \"This is an example ticket. Here's the token to test JIRA APIs: ATATTA9nsCA?a7812Z7VoI%YJ0K4rFWLBfk91rhOsLAW=Yx3ssoNC\",\n\t\t\t\t\t\t\t\t\"name\": \"Example Pattern test 2\",\n\t\t\t\t\t\t\t\t\"subtask\": false,\n\t\t\t\t\t\t\t\t\"avatarId\": 1298,\n\t\t\t\t\t\t\t\t\"entityId\": \"93a51c1d-fake-4673-a71d-0889fake1238\",\n\t\t\t\t\t\t\t\t\"hierarchyLevel\": 0,\n\t\t\t\t\t\t\t\t\"emailAddress\": \"?y4r3fs1ewqec12v1e3tl.5Hcsrcehic89saXd.ro@\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t]}`,\n\t\t\twant: []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\t// if intentionally no keywords are passed\n\t\t\t\tif test.noKeywords {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jotform/jotform.go",
    "content": "package jotform\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"jotform\"}) + `\\b([0-9Aa-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"jotform\"}\n}\n\n// FromData will find and optionally verify Jotform secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Jotform,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.jotform.com/user?apiKey=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Jotform\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Jotform is an online form builder and survey tool. Jotform API keys can be used to access and manage form submissions and data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/jotform/jotform_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage jotform\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestJotform_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"JOTFORM\")\n\tinactiveSecret := testSecrets.MustGetField(\"JOTFORM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a jotform secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Jotform,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a jotform secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Jotform,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Jotform.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Jotform.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jotform/jotform_test.go",
    "content": "package jotform\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"4q5fbhf90noexp56sxr86mhf3gAf31en\"\n\tinvalidPattern = \"4q5fbhf90noexp56sxr86mhf3gAf31e\"\n\tkeyword        = \"jotform\"\n)\n\nfunc TestJotform_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword jotform\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jumpcloud/jumpcloud.go",
    "content": "package jumpcloud\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"jumpcloud\"}) + `\\b([a-zA-Z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"jumpcloud\"}\n}\n\n// FromData will find and optionally verify Jumpcloud secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Jumpcloud,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://console.jumpcloud.com/api/v2/systemgroups\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"x-api-key\", resMatch)\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Jumpcloud\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Jumpcloud is a cloud-based directory service platform that offers user and device management, single sign-on, and other identity and access management (IAM) features. Jumpcloud API keys can be used to access and manage these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/jumpcloud/jumpcloud_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage jumpcloud\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestJumpcloud_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"JUMPCLOUD_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"JUMPCLOUD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a jumpcloud secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Jumpcloud,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a jumpcloud secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Jumpcloud,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Jumpcloud.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Jumpcloud.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jumpcloud/jumpcloud_test.go",
    "content": "package jumpcloud\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"ugys2uNNSDMylExbF0n4ttJrxL4ghI4YWK1nr0HM\"\n\tinvalidPattern = \"ugys2uNNSDMylExbF0n4ttJrxL4ghI4YWK1nr0H\"\n\tkeyword        = \"jumpcloud\"\n)\n\nfunc TestJumpcloud_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword jumpcloud\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jupiterone/jupiterone.go",
    "content": "package jupiterone\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"jupiterone\"}) + `\\b([0-9a-zA-Z]{76})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"jupiterone\"}\n}\n\n// FromData will find and optionally verify Jupiterone secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_JupiterOne,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tpayload := strings.NewReader(`{\n\t\t\t\t\"query\": \"query J1QL($query: String! = \\\"find jupiterone_account\\\", $variables: JSON, $cursor: String, $scopeFilters: [JSON!], $flags: QueryV1Flags) { queryV1(query: $query, variables: $variables, cursor: $cursor, scopeFilters: $scopeFilters, flags: $flags) { type data cursor }}\"\n\t\t\t  }`,\n\t\t\t)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://graphql.us.jupiterone.io/\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treq.Header.Add(\"Authorization\", \"Bearer \"+resMatch)\n\t\t\treq.Header.Add(\"JupiterOne-Account\", \"12345678-1234-1234-1234-123412341234\") // dummy account number\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode == 200 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\ts1.SetVerificationError(fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode), resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_JupiterOne\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"JupiterOne is a cloud security management platform. JupiterOne API keys can be used to access and manage security data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/jupiterone/jupiterone_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage jupiterone\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestJupiterone_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"JUPITERONE\")\n\tinactiveSecret := testSecrets.MustGetField(\"JUPITERONE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a jupiterone secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JupiterOne,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a jupiterone secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JupiterOne,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a jupiterone secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JupiterOne,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a jupiterone secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_JupiterOne,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Jupiterone.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Jupiterone.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jupiterone/jupiterone_test.go",
    "content": "package jupiterone\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"k62e5DggsqyrvjehnWM11M6h4kznOBmLYPcV1MfjNOncXis7cX90yAUyNXnlGNld1KT8iHYZoQxm\"\n\tinvalidPattern = \"k62e5DggsqyrvjehnWM11M6h4kznOBmLYPcV1MfjNOncXis7cX90yAUyNXnlGNld1KT8iHYZoQx\"\n\tkeyword        = \"jupiterone\"\n)\n\nfunc TestJupiterone_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword jupiterone\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/juro/juro.go",
    "content": "package juro\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"juro\"}) + `\\b([a-zA-Z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"juro\"}\n}\n\n// FromData will find and optionally verify Juro secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Juro,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.juro.com/v3/templates\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"x-api-key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Juro\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Juro is a contract automation platform. Juro API keys can be used to access and manage contract templates and other resources in Juro.\"\n}\n"
  },
  {
    "path": "pkg/detectors/juro/juro_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage juro\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestJuro_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"JURO\")\n\tinactiveSecret := testSecrets.MustGetField(\"JURO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a juro secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Juro,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a juro secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Juro,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Juro.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Juro.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/juro/juro_test.go",
    "content": "package juro\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"b2hKUaFGc0nyBmk3xRr0cyPCzRJOGW97G9WIhBtQ\"\n\tinvalidPattern = \"b2hKUaFGc0nyBmk3xRr0cyPCzRJOGW97G9WIhBt\"\n\tkeyword        = \"juro\"\n)\n\nfunc TestJuro_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword juro\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/jwt/jwt.go",
    "content": "package jwt\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/golang-jwt/jwt/v5\"\n\t\"github.com/lestrrat-go/jwx/v3/jwk\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies expected interfaces at compile time.\nvar _ interface {\n\tdetectors.Detector\n\tdetectors.MaxSecretSizeProvider\n} = (*Scanner)(nil)\n\nvar keyPat = regexp.MustCompile(`\\b((?:eyJ|ewogIC|ewoid)[A-Za-z0-9_-]{12,}={0,2}\\.(?:eyJ|ewo)[A-Za-z0-9_-]{12,}={0,2}\\.[A-Za-z0-9_-]{12,})\\b`)\n\n// The default max secret size value for this detector must be overridden or JWTs with lots of claims will get missed.\nfunc (s Scanner) MaxSecretSize() int64 {\n\treturn 4096\n}\n\n// These keywords are derived from prefixes of the base64url-encoded versions of JSON object strings like the following:\n//\n// `{\"typ\":\"`\n// `{\"alg\":\"`\n// `{\\n  \"typ\":\"`\n// `{\\n    \"typ\":\"`\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\n\t\t\"ewogIC\",\n\t\t\"ewoid\",\n\t\t\"eyJ\",\n\t}\n}\n\nvar jwtOptions = []jwt.ParserOption{\n\tjwt.WithValidMethods([]string{\n\t\t// HMAC-based algorithms\n\t\t// jwt.SigningMethodHS256.Alg(),\n\t\t// jwt.SigningMethodHS384.Alg(),\n\t\t// jwt.SigningMethodHS512.Alg(),\n\n\t\t// Public key-based algorithms\n\t\tjwt.SigningMethodRS256.Alg(),\n\t\tjwt.SigningMethodRS384.Alg(),\n\t\tjwt.SigningMethodRS512.Alg(),\n\t\tjwt.SigningMethodEdDSA.Alg(),\n\t\tjwt.SigningMethodES256.Alg(),\n\t\tjwt.SigningMethodES384.Alg(),\n\t\tjwt.SigningMethodES512.Alg(),\n\t\tjwt.SigningMethodPS256.Alg(),\n\t\tjwt.SigningMethodPS384.Alg(),\n\t\tjwt.SigningMethodPS512.Alg(),\n\t}),\n\tjwt.WithIssuedAt(),\n\tjwt.WithPaddingAllowed(),\n\tjwt.WithLeeway(time.Minute),\n}\n\n// FromData will find and optionally verify JWT secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tjwtParser := jwt.NewParser(jwtOptions...)\n\tclient := detectors.DetectorHttpClientWithNoLocalAddresses\n\n\tseenMatches := make(map[string]struct{})\n\n\tfor _, matchGroups := range keyPat.FindAllStringSubmatch(string(data), -1) {\n\t\tmatch := matchGroups[1]\n\n\t\tif _, ok := seenMatches[match]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tseenMatches[match] = struct{}{}\n\n\t\tclaims := jwt.MapClaims{}\n\t\tparsedToken, tokenParts, err := jwtParser.ParseUnverified(match, claims)\n\t\tif err != nil || len(tokenParts) != 3 {\n\t\t\t// skip malformed tokens; no need to do claims validation or signature verification\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch parsedToken.Method.Alg() {\n\t\tcase \"HS256\", \"HS384\", \"HS512\":\n\t\t\t// The JWT *might* be valid, but we can't in general do signature verification on HMAC-based algorithms.\n\t\t\t// We don't have a suitable status to represent this situation in trufflehog.\n\t\t\t// (The `unknown` status is intended to indicate that an error occurred due to external environmental conditions, like transient network errors.)\n\t\t\t// So instead, to avoid possible false positives, totally skip HMAC-based JWTs; don't even create results for them.\n\t\t\tcontinue\n\t\t}\n\n\t\t// Decode signature\n\t\tparsedToken.Signature, err = jwtParser.DecodeSegment(tokenParts[2])\n\t\tif err != nil {\n\t\t\t// skip JWTs with malformed signatures\n\t\t\tcontinue\n\t\t}\n\n\t\tissString, _ := claims.GetIssuer()\n\n\t\tiatString := \"\"\n\t\tiat, err := claims.GetIssuedAt()\n\t\tif err == nil && iat != nil {\n\t\t\tiatString = iat.String()\n\t\t}\n\n\t\texpString := \"\"\n\t\texp, err := claims.GetExpirationTime()\n\t\tif err == nil && exp != nil {\n\t\t\texpString = exp.String()\n\t\t}\n\n\t\textraData := map[string]string{\n\t\t\t\"alg\": parsedToken.Method.Alg(),\n\t\t\t\"iss\": issString,\n\t\t\t\"iat\": iatString,\n\t\t\t\"exp\": expString,\n\t\t}\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_JWT,\n\t\t\tRaw:          []byte(match),\n\t\t\tExtraData:    extraData,\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyJWT(ctx, client, tokenParts, parsedToken)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\n// Parse a string into a URL and check that it is an HTTPS URL.\nfunc parseHttpsUrl(urlString string) (*url.URL, error) {\n\turl, err := url.ParseRequestURI(urlString)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif url.Scheme != \"https\" {\n\t\treturn nil, fmt.Errorf(\"only https scheme is supported\")\n\t}\n\n\treturn url, nil\n}\n\nfunc performHttpRequest(ctx context.Context, client *http.Client, method string, url string) (*http.Response, error) {\n\treq, err := http.NewRequestWithContext(ctx, method, url, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create request: %w\", err)\n\t}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to perform request: %w\", err)\n\t}\n\treturn resp, nil\n}\n\n// Wrap an `io.Reader` with a reasonable limit as an additional measure against DoS from a malicious JWKS issuer\nfunc limitReader(reader io.Reader) io.Reader {\n\treturn io.LimitReader(reader, 1024*1024)\n}\n\n// Attempt to verify a JWT\n//\n// This cannot be done in general, but in a few special cases we can get definitive answers.\n//\n// In particular:\n//\n// - If the JWT uses public key cryptography and the OIDC Discovery protocol, we can fetch the public key and perform signature verification\n// - In all cases, we can perform claims validation (e.g., checking expiration time) and sometimes get a definite answer that a JWT is *not* live\nfunc verifyJWT(ctx context.Context, client *http.Client, tokenParts []string, parsedToken *jwt.Token) (bool, error) {\n\tjwtValidator := jwt.NewValidator(jwtOptions...)\n\n\tif err := jwtValidator.Validate(parsedToken.Claims); err != nil {\n\t\t// though we have not checked the signature, the token is definitely invalid\n\t\treturn false, nil\n\t}\n\n\t// Use the OIDC Discovery protocol to fetch the public signing key,\n\t// being careful to avoid possible DoS from a potentially malicious JWKS server.\n\tissuer, err := parsedToken.Claims.GetIssuer()\n\tif err != nil || issuer == \"\" {\n\t\t// missing or invalid issuer\n\t\treturn false, nil\n\t}\n\tissuerURL, err := parseHttpsUrl(issuer)\n\tif err != nil {\n\t\t// unsupported issuer\n\t\treturn false, nil\n\t}\n\n\toidcDiscoveryURL := issuerURL.JoinPath(\".well-known/openid-configuration\")\n\n\t// Check for a proper key id before making any network requests\n\tkid, ok := parsedToken.Header[\"kid\"].(string)\n\tif !ok {\n\t\t// invalid key id\n\t\treturn false, nil\n\t}\n\n\t// Fetch the OIDC discovery document\n\tresp, err := performHttpRequest(ctx, client, \"GET\", oidcDiscoveryURL.String())\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to perform OIDC discovery: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != 200 {\n\t\treturn false, fmt.Errorf(\"bad status for OIDC discovery document: %v\", resp.Status)\n\t}\n\n\t// Get the JWKS URL from the OIDC discovery document\n\tvar discoveryDoc struct {\n\t\tJWKSUri string `json:\"jwks_uri\"`\n\t}\n\tif err := json.NewDecoder(limitReader(resp.Body)).Decode(&discoveryDoc); err != nil {\n\t\treturn false, fmt.Errorf(\"failed to decode OIDC discovery document: %w\", err)\n\t}\n\n\tjwksURL, err := parseHttpsUrl(discoveryDoc.JWKSUri)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"invalid JWKS URL: %w\", err)\n\t}\n\n\tif jwksURL.Host != issuerURL.Host {\n\t\treturn false, fmt.Errorf(\"JWKS host does not match issuer host: %q\", discoveryDoc.JWKSUri)\n\t}\n\n\t// Fetch the JWKS\n\tresp, err = performHttpRequest(ctx, client, \"GET\", discoveryDoc.JWKSUri)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to fetch JWKS: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != 200 {\n\t\treturn false, fmt.Errorf(\"bad status for JWKS: %v\", resp.Status)\n\t}\n\n\t// Parse the JWKS and find the first matching key\n\tkeySet, err := jwk.ParseReader(limitReader(resp.Body))\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to parse JWKS: %w\", err)\n\t}\n\tmatchingKey, found := keySet.LookupKeyID(kid)\n\tif !found {\n\t\t// this is a determinate failure indicating rotation\n\t\treturn false, nil\n\t}\n\n\t// Parse matching key to the \"raw\" key type needed for signature verification\n\tvar rawMatchingKey any\n\terr = jwk.Export(matchingKey, &rawMatchingKey)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to export matching key: %w\", err)\n\t}\n\n\terr = parsedToken.Method.Verify(strings.Join(tokenParts[0:2], \".\"), parsedToken.Signature, rawMatchingKey)\n\tif err != nil {\n\t\t// signature invalid\n\t\treturn false, nil\n\t}\n\n\t// signature valid and claims check out\n\treturn true, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_JWT\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"A JSON Web Token (JWT) is an approach to authentication or authorization that does not depend on server-side data. It may allow access to protected resources.\"\n}\n"
  },
  {
    "path": "pkg/detectors/jwt/jwt_test.go",
    "content": "package jwt\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\n// This tests the JWT detector for a number of different cases (mostly HMAC-based) without verification enabled.\nfunc TestJwt_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"HS256/valid\",\n\t\t\tinput: `\n\t\t\t\t// secret is \"a-string-secret-at-least-256-bits-long\"\n\t\t\t\teyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"HS256/valid-verbose-header\",\n\t\t\tinput: `\n\t\t\t\t// secret is \"a-string-secret-at-least-256-bits-long\"\n\t\t\t\tewogICJhbGciOiJIUzI1NiIsCiIgIHR5cCI6IkpXVCIKfQo.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\n\t\t{\n\t\t\tname: \"HS384/valid/no-expiration\",\n\t\t\tinput: `\n\t\t\t\t// secret is \"a-string-secret-at-least-256-bits-long\"\n\t\t\t\teyJhbGciOiJIUzM4NCJ9.eyJtc2ciOiJoZWxsbyBoYWNrZXIsIHRoZXJlJ3Mgbm90aGluZyBmb3IgeW91IGhlcmUg8J-YhiJ9.NArdGjJ9DjXwGCLdNXDVjlwlvI_tUa2B3H44dvrZfKliBNTUL0YyKi8q4Al0Wl8u\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\n\t\t{\n\t\t\tname: \"HS512/valid/no-expiration\",\n\t\t\tinput: `\n\t\t\t\t// secret is \"a-string-secret-at-least-256-bits-long\"\n\t\t\t\teyJhbGciOiJIUzUxMiJ9.eyJtc2ciOiJoZWxsbyBoYWNrZXIsIHRoZXJlJ3Mgbm90aGluZyBmb3IgeW91IGhlcmUg8J-YhiJ9.SiKgg2-kq7kVXhe5uLMakzlygHsJ70aTyXGhdbqG2SfkUC_fwk8MZ3JAWXrCIEJAUi_QMmQm-7qMU0SCMFRQug\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\n\t\t{\n\t\t\tname: \"HS256/padding-in-verbose-header/invalid-sig\",\n\t\t\tinput: `\n\t\t\t\t// secret is \"a-string-secret-at-least-256-bits-long\"\n\t\t\t\tewogICJhbGciOiJIUzI1NiIsCiIgIHR5cCI6IkpXVCIKfQ==.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"HS256/padding-in-claims/invalid-sig\",\n\t\t\tinput: `\n\t\t\t\t// secret is \"a-string-secret-at-least-256-bits-long\"\n\t\t\t\tewogICJhbGciOiJIUzI1NiIsCiAgInR5cCI6IkpXVCIKfQ.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyfQo=.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\n\t\t{\n\t\t\tname: \"HS256/expired\",\n\t\t\tinput: `\n\t\t\t\t// secret is \"a-string-secret-at-least-256-bits-long\"\n\t\t\t\teyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTQxNjIzOTAyMiwiZXhwIjoxNDE2MjM5MTIyfQ.EwRkAg9uOr6kVajMdMvB6KWGvIdDlGNRAH3lsZ2qQHI\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\n\t\t{\n\t\t\tname: \"HS256/not-yet-valid\",\n\t\t\tinput: `\n\t\t\t\t// secret is \"a-string-secret-at-least-256-bits-long\"\n\t\t\t\teyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MjQxNjIzOTAyMiwibmJmIjozNDE2MjM5MDIyLCJleHAiOjQ0MTYyMzkwMjJ9.rVQaCey3ETfhn8AeiC_EmFjp6_X2Dq8QY_AzBAF2ZzQ\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\n\t\t{\n\t\t\tname: \"PS384/expired/invalid-issuer\",\n\t\t\tinput: `\n\t\t\t\teyJhbGciOiJQUzM4NCIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMeDFGbWF5UDJZQnR4YXFTMVNLSlJKR2lYUktudzJvdjVXbVlJTUctQkxFIn0.eyJleHAiOjE2MTU0MDY5ODIsImlhdCI6MTYxNTQwNjkyMiwianRpIjoiMGY2NGJjYTktYjU4OC00MWFhLWFkNDEtMmFmZDM2OGRmNTFkIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJhZDEyOGRmMS0xMTQwLTRlNGMtYjA5Ny1hY2RjZTcwNWJkOWIiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ0b2tlbmRlbG1lIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiY2xpZW50SG9zdCI6IjE3Mi4yMC4wLjEiLCJjbGllbnRJZCI6InRva2VuZGVsbWUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC10b2tlbmRlbG1lIiwiY2xpZW50QWRkcmVzcyI6IjE3Mi4yMC4wLjEifQ.Rxrq41AxbWKIQHWv-Tkb7rqwel3sKT_R_AGvn9mPIHqhw1m7nsQWcL9t2a_8MI2hCwgWtYdgTF1xxBNmb2IW3CZkML5nGfcRrFvNaBHd3UQEqbFKZgnIX29h5VoxekyiwFaGD-0RXL83jF7k39hytEzTatwoVjZ-frga0KFl-nLce3OwncRXVCGmxoFzUsyu9TQFS2Mm_p0AMX1y1MAX1JmLC3WFhH3BohhRqpzBtjSfs_f46nE1-HKjqZ1ERrAc2fmiVJjmG7sT702JRuuzrgUpHlMy2juBG4DkVcMlj4neJUmCD1vZyZBRggfaIxNkwUhHtmS2Cp9tOcwNu47tSg\n\t\t\t`,\n\t\t\twant: []string{\"eyJhbGciOiJQUzM4NCIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJMeDFGbWF5UDJZQnR4YXFTMVNLSlJKR2lYUktudzJvdjVXbVlJTUctQkxFIn0.eyJleHAiOjE2MTU0MDY5ODIsImlhdCI6MTYxNTQwNjkyMiwianRpIjoiMGY2NGJjYTktYjU4OC00MWFhLWFkNDEtMmFmZDM2OGRmNTFkIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJhZDEyOGRmMS0xMTQwLTRlNGMtYjA5Ny1hY2RjZTcwNWJkOWIiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ0b2tlbmRlbG1lIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiY2xpZW50SG9zdCI6IjE3Mi4yMC4wLjEiLCJjbGllbnRJZCI6InRva2VuZGVsbWUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC10b2tlbmRlbG1lIiwiY2xpZW50QWRkcmVzcyI6IjE3Mi4yMC4wLjEifQ.Rxrq41AxbWKIQHWv-Tkb7rqwel3sKT_R_AGvn9mPIHqhw1m7nsQWcL9t2a_8MI2hCwgWtYdgTF1xxBNmb2IW3CZkML5nGfcRrFvNaBHd3UQEqbFKZgnIX29h5VoxekyiwFaGD-0RXL83jF7k39hytEzTatwoVjZ-frga0KFl-nLce3OwncRXVCGmxoFzUsyu9TQFS2Mm_p0AMX1y1MAX1JmLC3WFhH3BohhRqpzBtjSfs_f46nE1-HKjqZ1ERrAc2fmiVJjmG7sT702JRuuzrgUpHlMy2juBG4DkVcMlj4neJUmCD1vZyZBRggfaIxNkwUhHtmS2Cp9tOcwNu47tSg\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/kanban/kanban.go",
    "content": "package kanban\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"kanban\"}) + `\\b([0-9A-Z]{12})\\b`)\n\turlPat = regexp.MustCompile(`\\b([0-9a-z]{1,}\\.kanbantool\\.com)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"kanban\"}\n}\n\n// FromData will find and optionally verify Kanban secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\turlMatches := urlPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, urlMatch := range urlMatches {\n\t\t\tresURL := strings.TrimSpace(urlMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Kanban,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://%s/api/v3/users/current.json\", resURL), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Accept\", \"application/vnd.kanban+json; version=3\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Kanban\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"KanbanTool is a project management software that helps visualize and optimize workflow. Kanban API keys can be used to access and modify project data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/kanban/kanban_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage kanban\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestKanban_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"KANBAN\")\n\turl := testSecrets.MustGetField(\"KANBAN_URL\")\n\tinactiveSecret := testSecrets.MustGetField(\"KANBAN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a kanban secret %s within %s\", secret, url)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Kanban,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a kanban secret %s within %s but not valid\", inactiveSecret, url)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Kanban,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Kanban.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Kanban.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/kanban/kanban_test.go",
    "content": "package kanban\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern   = \"11XQ98FVS8WI\"\n\tinvalidKeyPattern = \"11?Q98FV=8WI\"\n\tvalidUrlPattern   = \"tn1vjduk6fjhoqscmqx9hpa.kanbantool.com\"\n\tinvalidUrlPattern = \"Tn1vjduk6fjhoqscmqx9hpa.kanbantool.com\"\n\tkeyword           = \"kanban\"\n)\n\nfunc TestKanban_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword kanban\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s' %s '%s'\", keyword, validKeyPattern, keyword, validUrlPattern),\n\t\t\twant:  []string{validKeyPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s' url = '%s'\", keyword, validKeyPattern, validUrlPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' url = '%s'\", keyword, invalidKeyPattern, invalidUrlPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/kanbantool/kanbantool.go",
    "content": "package kanbantool\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"kanbantool\"}) + `\\b([0-9A-Z]{12})\\b`)\n\tdomainPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"kanbantool\"}) + `\\b([a-z0-9A-Z]{2,22})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"kanbantool\"}\n}\n\n// FromData will find and optionally verify Kanbantool secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := domainPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Kanbantool,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\t\t\tif verify {\n\t\t\t\turl := fmt.Sprintf(\"https://%s.kanbantool.com/api/v3/users/current.json\", resIdMatch)\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", url, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Kanbantool\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Kanbantool is a project management software that uses API keys for accessing and modifying project data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/kanbantool/kanbantool_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage kanbantool\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestKanbantool_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"KANBANTOOL\")\n\tinactiveSecret := testSecrets.MustGetField(\"KANBANTOOL_INACTIVE\")\n\tdomain := testSecrets.MustGetField(\"KANBANTOOL_DOMAIN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a kanbantool secret %s within kanbantool %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Kanbantool,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a kanbantool secret %s within but not valid kanbantool %s\", inactiveSecret, domain)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Kanbantool,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Kanbantool.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\n\t\t\t\tif diff := pretty.Compare(got[i], tt.want[0]); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"Kanbantool.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t\t}\n\t\t\t}\n\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/kanbantool/kanbantool_test.go",
    "content": "package kanbantool\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern      = \"H1EC2BUPZ22S\"\n\tinvalidKeyPattern    = \"?1EC2BU=Z22S\"\n\tvalidDomainPattern   = \"86DzV1nrzA53SPRijUoX2o\"\n\tinvalidDomainPattern = \"?6DzV1nrA53SPRijUoX2o=\"\n\tkeyword              = \"kanbantool\"\n)\n\nfunc TestKanbantool_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword kanbantool\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s' %s '%s'\", keyword, validKeyPattern, keyword, validDomainPattern),\n\t\t\twant:  []string{validKeyPattern, validKeyPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n key = '%s' domain: '%s'\", keyword, validKeyPattern, validDomainPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' secret = '%s'\", keyword, invalidKeyPattern, invalidDomainPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/karmacrm/karmacrm.go",
    "content": "package karmacrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"karma\"}) + `\\b([a-zA-Z0-9]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"karmacrm\"}\n}\n\n// FromData will find and optionally verify KarmaCRM secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_KarmaCRM,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://app.karmacrm.com/api/v3/contacts.json?api_token=%s&page=1\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.karmacrm+json; version=3\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_KarmaCRM\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"KarmaCRM is a customer relationship management tool. API tokens can be used to access and modify CRM data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/karmacrm/karmacrm_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage karmacrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestKarmaCRM_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"KARMACRM\")\n\tinactiveSecret := testSecrets.MustGetField(\"KARMACRM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a karmacrm secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_KarmaCRM,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a karmacrm secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_KarmaCRM,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"KarmaCRM.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"KarmaCRM.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/karmacrm/karmacrm_test.go",
    "content": "package karmacrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"ThGCnHSUKBY9OBnpIZ59\"\n\tinvalidPattern = \"ThGCnHSUKBY9OBnpIZ5\"\n\tkeyword        = \"karmacrm\"\n)\n\nfunc TestKarmaCRM_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword karmacrm\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/keenio/keenio.go",
    "content": "package keenio\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"keen\"}) + `\\b([0-9A-Z]{64})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"keen\"}) + `\\b([0-9a-z]{24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"keen\"}\n}\n\n// FromData will find and optionally verify KeenIO secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_KeenIO,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.keen.io/3.0/organizations/\"+resIdMatch+\"/projects\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\treq.Header.Add(\"Authorization\", resMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_KeenIO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"KeenIO is an analytics platform that allows you to collect, analyze, and visualize event data. KeenIO API keys can be used to access and manage this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/keenio/keenio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage keenio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestKeenIO_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"KEENIO\")\n\tinactiveSecret := testSecrets.MustGetField(\"KEENIO_INACTIVE\")\n\torgId := testSecrets.MustGetField(\"KEENIO_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a keenio secret %s within keenid %s\", secret, orgId)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_KeenIO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a keenio secret %s within keenid %s but not valid\", inactiveSecret, orgId)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_KeenIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"KeenIO.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"KeenIO.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/keenio/keenio_test.go",
    "content": "package keenio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern   = \"15AP0WODU2PTXS7AMCVEKXVHHNCOTJTJKMOXYQWFY649VTW5E8DNO5JAN3WIZDNY\"\n\tinvalidKeyPattern = \"?dsa0WODU2PTXS7AMCVEKXVHHNCOTJTJKMOXYQWFY649VTW5E8dsf3JAN3WIZDN=\"\n\tvalidIdPattern    = \"u95zu2ka660bfte1gj2u14s3\"\n\tinvalidIdPattern  = \"?95ZU2ka660BftE1Gj2u14s=\"\n\tkeyword           = \"keenio\"\n)\n\nfunc TestKeenIO_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword keenio\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s' %s '%s'\", keyword, validKeyPattern, keyword, validIdPattern),\n\t\t\twant:  []string{validKeyPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n key = '%s' domain: '%s'\", keyword, validKeyPattern, validIdPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' %s secret = '%s'\", keyword, invalidKeyPattern, keyword, invalidIdPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/kickbox/kickbox.go",
    "content": "package kickbox\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"kickbox\"}) + `\\b([a-zA-Z0-9_]+[a-zA-Z0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"kickbox\"}\n}\n\n// FromData will find and optionally verify Kickbox secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Kickbox,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.kickbox.com/v2/verify?email=kickbox@example.com&apikey=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Kickbox\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Kickbox is an email verification service that allows users to verify email addresses in real-time. Kickbox API keys can be used to access and utilize the email verification services provided by Kickbox.\"\n}\n"
  },
  {
    "path": "pkg/detectors/kickbox/kickbox_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage kickbox\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestKickbox_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"KICKBOX\")\n\tinactiveSecret := testSecrets.MustGetField(\"KICKBOX_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a kickbox secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Kickbox,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a kickbox secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Kickbox,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Kickbox.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Kickbox.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/kickbox/kickbox_test.go",
    "content": "package kickbox\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"_DN4IVERUcAbdO2ibFCriJAyfyuaDys7u08ilesdQxON2oUsobOusDu1UFkclGq8P\"\n\tinvalidPattern = \"?DN4IVERUcAbdO2ibFCriJAyfyuaDys7u08ilesdQxON2oUsobOusDu1UFkclGq8=\"\n\tkeyword        = \"kickbox\"\n)\n\nfunc TestKickbox_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword kickbox\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/klaviyo/klaviyo.go",
    "content": "package klaviyo\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(pk_[[:alnum:]]{34})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pk_\"}\n}\n\ntype response struct {\n\tErrors []struct {\n\t\tId     string `json:\"id\"`\n\t\tStatus int    `json:\"status\"`\n\t\tCode   string `json:\"code\"`\n\t\tTitle  string `json:\"title\"`\n\t\tDetail string `json:\"detail\"`\n\t\tSource struct {\n\t\t\tPointer string `json:\"pointer\"`\n\t\t} `json:\"source\"`\n\t} `json:\"errors\"`\n}\n\n// FromData will find and optionally verify Klaviyo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Klaviyo,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://a.klaviyo.com/api/profiles\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Set(\"Revision\", \"2023-02-22\")\n\t\t\treq.Header.Set(\"Authorization\", \"Klaviyo-API-Key \"+resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 403 {\n\t\t\t\t\tvar apiResp response\n\t\t\t\t\t// Klaviyo responds with 403 when the API-key does not have permissions to hit /api/profiles.\n\t\t\t\t\t// Ensure that the 403 is from Klaviyo: https://developers.klaviyo.com/en/docs/rate_limits_and_error_handling\n\t\t\t\t\tif err = json.NewDecoder(res.Body).Decode(&apiResp); err == nil {\n\t\t\t\t\t\t// valid JSON response\n\t\t\t\t\t\tif len(apiResp.Errors) > 0 {\n\t\t\t\t\t\t\t// Thus, the key is verified, but it is up to the user to determine what scopes the key has.\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.SetVerificationError(fmt.Errorf(\"errors expected\"), resMatch)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts1.SetVerificationError(fmt.Errorf(\"unexpected API JSON response\"), resMatch)\n\t\t\t\t\t}\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Klaviyo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Klaviyo is a marketing automation platform. Klaviyo API keys can be used to access and modify marketing data and configurations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/klaviyo/klaviyo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage klaviyo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestKlaviyo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"KLAVIYO\")\n\tinactiveSecret := testSecrets.MustGetField(\"KLAVIYO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a klaviyo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Klaviyo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a klaviyo secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Klaviyo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a klaviyo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Klaviyo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a klaviyo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Klaviyo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Klaviyo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Klaviyo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/klaviyo/klaviyo_test.go",
    "content": "package klaviyo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"pk_1234567890abcdefghijklmnopqrstuvwx\"\n\tinvalidPattern = \"pk_1234567890abcdefghijklmnopqrstu-_=\"\n\tkeyword        = \"klaviyo\"\n)\n\nfunc TestKlaviyo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword klaviyo\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/klipfolio/klipfolio.go",
    "content": "package klipfolio\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"klipfolio\"}) + `\\b([0-9a-f]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"klipfolio\"}\n}\n\n// FromData will find and optionally verify Klipfolio secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Klipfolio,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.klipfolio.com/api/1.0/users\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"kf-api-key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Klipfolio\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Klipfolio is a cloud-based business intelligence platform that provides dashboards and reporting tools. Klipfolio API keys can be used to access and manage data and visualizations within the platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/klipfolio/klipfolio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage klipfolio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestKlipfolio_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"KLIPFOLIO\")\n\tinactiveSecret := testSecrets.MustGetField(\"KLIPFOLIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a klipfolio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Klipfolio,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a klipfolio secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Klipfolio,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Klipfolio.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Klipfolio.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/klipfolio/klipfolio_test.go",
    "content": "package klipfolio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"8fd61e953ce814446c36975623c3d77aa97cc35c\"\n\tinvalidPattern = \"8fd61e953ce814446c36975623c3d77aa97cc35\"\n\tkeyword        = \"klipfolio\"\n)\n\nfunc TestKlipfolio_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword klipfolio\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/knapsackpro/knapsackpro.go",
    "content": "package knapsackpro\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"knapsackpro\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"knapsackpro\"}\n}\n\n// FromData will find and optionally verify KnapsackPro secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_KnapsackPro,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.knapsackpro.com/v1/builds?page=1\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"KNAPSACK-PRO-TEST-SUITE-TOKEN\", resMatch)\n\t\t\tres, err := client.Do(req)\n\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_KnapsackPro\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"KnapsackPro is a tool for optimal test suite parallelization. Its tokens can be used to access and manage test suite runs and configurations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/knapsackpro/knapsackpro_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage knapsackpro\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestKnapsackPro_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"KNAPSACKPRO\")\n\tinactiveSecret := testSecrets.MustGetField(\"KNAPSACKPRO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a knapsackpro secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_KnapsackPro,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a knapsackpro secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_KnapsackPro,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"KnapsackPro.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"KnapsackPro.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/knapsackpro/knapsackpro_test.go",
    "content": "package knapsackpro\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"njei162j3zfnwzx97yezow5ck2fg2mr1\"\n\tinvalidPattern = \"njei162j3zfnwzx97yezow5ck2fg2mr\"\n\tkeyword        = \"knapsackpro\"\n)\n\nfunc TestKnapsackPro_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword knapsackpro\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/kontent/kontent.go",
    "content": "package kontent\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tapiKeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"kontent\"}) + common.BuildRegexJWT(\"30,34\", \"200,400\", \"40,43\"))\n\tenvIDPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"kontent\", \"env\"}) + common.UUIDPattern)\n\n\t// API return this error when the environment does not exist or the api key does not have the permission to access that environment\n\tenvErr = \"The specified API key does not provide the permissions required to access the environment\"\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"kontent\"}\n}\n\n// FromData will find and optionally verify Kontent secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueAPIKeys, uniqueEnvIDs = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, apiKey := range apiKeyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueAPIKeys[apiKey[1]] = struct{}{}\n\t}\n\n\tfor _, envID := range envIDPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueEnvIDs[envID[1]] = struct{}{}\n\t}\n\n\tfor envID := range uniqueEnvIDs {\n\t\tif _, ok := detectors.UuidFalsePositives[detectors.FalsePositive(envID)]; ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tif detectors.StringShannonEntropy(envID) < 3 {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor apiKey := range uniqueAPIKeys {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Kontent,\n\t\t\t\tRaw:          []byte(envID),\n\t\t\t\tRawV2:        []byte(envID + apiKey),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, verificationErr := verifyKontentAPIKey(client, envID, apiKey)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Kontent\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Kontent is a headless CMS (Content Management System) that allows users to manage and deliver content to any device or application. Kontent API keys can be used to access and manage this content.\"\n}\n\n// api docs: https://kontent.ai/learn/docs/apis/openapi/management-api-v2/#operation/retrieve-environment-information\nfunc verifyKontentAPIKey(client *http.Client, envID, apiKey string) (bool, error) {\n\treq, err := http.NewRequest(http.MethodGet, fmt.Sprintf(\"https://manage.kontent.ai/v2/projects/%s\", envID), nil)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\n\treq.Header.Add(\"Authorization\", \"Bearer \"+apiKey)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusForbidden:\n\t\tbodyBytes, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tif strings.Contains(string(bodyBytes), envErr) {\n\t\t\treturn true, nil\n\t\t}\n\n\t\treturn false, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/kontent/kontent_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage kontent\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestKontent_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tenvID := testSecrets.MustGetField(\"KONTENT_ENV_ID\")\n\tsecret := testSecrets.MustGetField(\"KONTENT_API_KEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"KONTENT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a kontent env id: %s and kontent secret %s within\", envID, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Kontent,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a kontent env id: %s and kontent secret %s within but not valid\", envID, inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Kontent,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Kontent.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Kontent.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/kontent/kontent_test.go",
    "content": "package kontent\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestKontent_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern - with keyword kontent\",\n\t\t\tinput: `\n\t\t\t\t// the following are credentials for kontent.ai APIs - do not share with anyone\n\t\t\t\tkontent_personal_api_key = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjOTE4OThlMWZlMGI0NDcwOTczOGM0ZmE0YzVlYzk0MyIsImlhdCI6MTc0NjUyNzQyNSwibmJmIjoxNzQ2NTI3NDI1LCJleHAiOjE3NjI0MjQ5NDAsInZlciI6IjMuMC4wIiwidWlkIjoidmlydHVhbF8zNTI4OGIxNC00YmE3LTQ5MzgtODZiNC1lYjFhYjczMDBiZTciLCJzY29wZV9pZCI6IjAyYmYxZDg5NzYzMjQ3ZWE4MTFkYjkwMjVhYjc0MTRhIiwicHJvamVjdF9jb250YWluZXJfaWQiOiI0MDFkMzg1NmMyYzUwMGZlOTYwMTE5YzFhMThkNWY4OCIsImF1ZCI6Im1hbmFnZS5rZW50aWNvY2xvdWQuY29tIn0.yfZTic9Zba6Dui8N6UO6t-SGbZYf17bKAd-uJ9enYPw\n\t\t\t\tkontent_env_id = 3d5f4d88-0511-00b3-37f1-31bb55c25ab4`,\n\t\t\twant: []string{\"3d5f4d88-0511-00b3-37f1-31bb55c25ab4eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjOTE4OThlMWZlMGI0NDcwOTczOGM0ZmE0YzVlYzk0MyIsImlhdCI6MTc0NjUyNzQyNSwibmJmIjoxNzQ2NTI3NDI1LCJleHAiOjE3NjI0MjQ5NDAsInZlciI6IjMuMC4wIiwidWlkIjoidmlydHVhbF8zNTI4OGIxNC00YmE3LTQ5MzgtODZiNC1lYjFhYjczMDBiZTciLCJzY29wZV9pZCI6IjAyYmYxZDg5NzYzMjQ3ZWE4MTFkYjkwMjVhYjc0MTRhIiwicHJvamVjdF9jb250YWluZXJfaWQiOiI0MDFkMzg1NmMyYzUwMGZlOTYwMTE5YzFhMThkNWY4OCIsImF1ZCI6Im1hbmFnZS5rZW50aWNvY2xvdWQuY29tIn0.yfZTic9Zba6Dui8N6UO6t-SGbZYf17bKAd-uJ9enYPw\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t// the following are credentials for kontent.ai APIs - do not share with anyone\n\t\t\t\tkontent_personal_api_key = eyJhbGciOiJIUzI1NiIsInR5cCVCJ9.eyJqdGkiOiJjOTE4OThlMWZlMGI0NDcwOTczOGM0ZmE0YzVlYzk0MyIsImlhdCI6MTc0NjUyNzQyNSwibmJmIjoxNzQ2NTI3NDI1LCJleHAiOjE3NjI0MjQ5NDAsInZlciI6IjMuMC4wIiwidWlkIjoidmlydHVhbF8zNTI4OGIxNC00YmE3LTQ5MzgtODZiNC1lYjFhYjczMDBiZTciLCJzY29wZV9pZCI6IjAyYmYxZDg5NzYzMjQ3ZWE4MTFkYjkwMjVhYjc0MTRhIiwicHJvamVjdF9jb250YWluZXJfaWQiOiI0MDFkMzg1NmMyYzUwMGZlOTYwMTE5YzFhMThkNWY4OCIsImF1ZCI6Im1hbmFnZS5rZW50aWNvY2xvdWQuY29tIn0.yfZTic9Zba6Dui8N6UO6t-SGbZYf17bKAd-uJ9enYPw\n\t\t\t\tkontent_env_id = 3d5f4d88-051-00b3-37f1-31bb55c25ab4`,\n\t\t\twant: []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/kraken/kraken.go",
    "content": "package kraken\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"crypto/sha512\"\n\t\"encoding/base64\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\t// Bounds have been removed because there are some cases that tokens have trailing frontslash(/) or plus sign (+)\n\tkeyPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"kraken\"}) + `\\b([0-9A-Za-z\\/\\+=]{56}[ \"'\\r\\n]{1})`)\n\tprivKeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"kraken\"}) + `\\b([0-9A-Za-z\\/\\+=]{86,88}[ \"'\\r\\n]{1})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"kraken\"}\n}\n\n// FromData will find and optionally verify Kraken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tprivKeyMatches := privKeyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, privKeyMatch := range privKeyMatches {\n\t\t\tresPrivKeyMatch := strings.TrimSpace(privKeyMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Kraken,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resPrivKeyMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\n\t\t\t\t// Increasing 64-bit integer, for each request that is made with a particular API key.\n\t\t\t\tapiNonce := strconv.FormatInt(time.Now().Unix(), 10)\n\n\t\t\t\tpayload := url.Values{}\n\t\t\t\tpayload.Add(\"nonce\", apiNonce)\n\n\t\t\t\tb64DecodedSecret, _ := base64.StdEncoding.DecodeString(resPrivKeyMatch)\n\t\t\t\tsignature := getKrakenSignature(\"/0/private/Balance\", payload, b64DecodedSecret)\n\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.kraken.com/0/private/Balance\", strings.NewReader(payload.Encode()))\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\t\treq.Header.Add(\"API-Key\", resMatch)\n\t\t\t\treq.Header.Add(\"API-Sign\", signature)\n\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tout, _ := io.ReadAll(res.Body)\n\t\t\t\t\tif !strings.Contains(string(out), \"Invalid key\") {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\n// Code from https://docs.kraken.com/rest/#section/Authentication/Headers-and-Signature\nfunc getKrakenSignature(url_path string, values url.Values, secret []byte) string {\n\n\tsha := sha256.New()\n\tsha.Write([]byte(values.Get(\"nonce\") + values.Encode()))\n\tshasum := sha.Sum(nil)\n\n\tmac := hmac.New(sha512.New, secret)\n\tmac.Write(append([]byte(url_path), shasum...))\n\tmacsum := mac.Sum(nil)\n\treturn base64.StdEncoding.EncodeToString(macsum)\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Kraken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Kraken is a cryptocurrency exchange that allows trading of various digital assets. Kraken API keys can be used to access and manage account information and perform trading operations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/kraken/kraken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage kraken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestKraken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"KRAKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"KRAKEN_INACTIVE\")\n\tprivate := testSecrets.MustGetField(\"KRAKEN_PRIVATE\")\n\tinactivePrivate := testSecrets.MustGetField(\"KRAKEN_PRIVATE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a kraken secret %s with kraken private key %s within\", secret, private)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Kraken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a kraken secret %s with kraken private key %s within but not valid\", inactiveSecret, inactivePrivate)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Kraken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Kraken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Kraken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/kraken/kraken_test.go",
    "content": "package kraken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern       = \"m=MN/0yYJ/5xqpE15JYDJtCFdDF7RDLuiXtTiSF1FU1H9waiub1kgwI= \"\n\tinvalidKeyPattern     = \"m=MN/0yYJ/5xqpE15JYDJtCFdDF7RDLuiXtTiSF1FU1H9waiub1kgwI=\"\n\tvalidPrivKeyPattern   = \"Oe1xUe+sNT7F5SboHSpfCubMhJlAaghB3SZ=NMmkIHTSzWVoF3uTOnxv32cgI+WuEDXYS+z5MvX+q9IUJ1cYo=+ \"\n\tinvalidPrivKeyPattern = \"Oe1xUe+sNT7F5SboHSpfCubMhJlAaghB3SZ=NMmkIHTSzWVoF3uTOnxv32cgI+WuEDXYS+z5MvX+q9IUJ1cYo=+\"\n\tkeyword               = \"kraken\"\n)\n\nfunc TestKraken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword kraken\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s' %s '%s'\", keyword, validKeyPattern, keyword, validPrivKeyPattern),\n\t\t\twant:  []string{strings.TrimSpace(validKeyPattern) + strings.TrimSpace(validPrivKeyPattern)},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n key = '%s' domain: '%s'\", keyword, validKeyPattern, validPrivKeyPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' secret = '%s'\", keyword, invalidKeyPattern, invalidPrivKeyPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/kucoin/kucoin.go",
    "content": "package kucoin\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat        = regexp.MustCompile(detectors.PrefixRegex([]string{\"kucoin\"}) + `\\b([0-9a-f]{24})\\b`)\n\tsecretPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"kucoin\"}) + `\\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\b`)\n\tpassphrasePat = regexp.MustCompile(detectors.PrefixRegex([]string{\"kucoin\"}) + `([ \\r\\n]{1}[!-~]{7,32}[ \\r\\n]{1})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"kucoin\"}\n}\n\n// FromData will find and optionally verify KuCoin secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\tpassphraseMatches := passphrasePat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, keyMatch := range keyMatches {\n\t\tresKeyMatch := strings.TrimSpace(keyMatch[1])\n\n\t\tfor _, secretMatch := range secretMatches {\n\t\t\tresSecretMatch := strings.TrimSpace(secretMatch[1])\n\n\t\t\tfor _, passphraseMatch := range passphraseMatches {\n\t\t\t\tresPassphraseMatch := strings.TrimSpace(passphraseMatch[1])\n\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_KuCoin,\n\t\t\t\t\tRaw:          []byte(resKeyMatch),\n\t\t\t\t\tRawV2:        []byte(resKeyMatch + resPassphraseMatch),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\n\t\t\t\t\ttimestamp := strconv.FormatInt(time.Now().Unix()*1000, 10)\n\t\t\t\t\tmethod := \"GET\"\n\t\t\t\t\tendpoint := \"/api/v1/accounts\"\n\t\t\t\t\tbodyStr := \"\"\n\t\t\t\t\tapiVersion := \"2\"\n\n\t\t\t\t\tsignature := getKucoinSignature(resSecretMatch, timestamp, method, endpoint, bodyStr)\n\t\t\t\t\tpassPhrase := getKucoinPassphrase(resSecretMatch, resPassphraseMatch)\n\n\t\t\t\t\treq, err := http.NewRequestWithContext(ctx, method, \"https://api.kucoin.com\"+endpoint, nil)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\treq.Header.Add(\"KC-API-KEY\", resKeyMatch)\n\t\t\t\t\treq.Header.Add(\"KC-API-SIGN\", signature)\n\t\t\t\t\treq.Header.Add(\"KC-API-TIMESTAMP\", timestamp)\n\t\t\t\t\treq.Header.Add(\"KC-API-PASSPHRASE\", passPhrase)\n\t\t\t\t\treq.Header.Add(\"KC-API-KEY-VERSION\", apiVersion)\n\n\t\t\t\t\tres, err := client.Do(req)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc getKucoinPassphrase(apiSecret string, apiPassphrase string) string {\n\n\tmac := hmac.New(sha256.New, []byte(apiSecret))\n\tmac.Write([]byte(apiPassphrase))\n\tmacsum := mac.Sum(nil)\n\treturn base64.StdEncoding.EncodeToString(macsum)\n}\n\nfunc getKucoinSignature(apiSecret string, timestamp string, method string, endpoint string, bodyStr string) string {\n\n\tpreHashStr := timestamp + method + endpoint + bodyStr\n\tmac := hmac.New(sha256.New, []byte(apiSecret))\n\tmac.Write([]byte(preHashStr))\n\tmacsum := mac.Sum(nil)\n\treturn base64.StdEncoding.EncodeToString(macsum)\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_KuCoin\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"KuCoin is a cryptocurrency exchange that provides various digital asset trading services. KuCoin API keys can be used to access and manage trading accounts, execute trades, and retrieve market data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/kucoin/kucoin_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage kucoin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestKuCoin_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"KUCOIN\")\n\tinactiveSecret := testSecrets.MustGetField(\"KUCOIN_INACTIVE\")\n\tkey := testSecrets.MustGetField(\"KUCOIN_KEY\")\n\tinactiveKey := testSecrets.MustGetField(\"KUCOIN_KEY_INACTIVE\")\n\tpassphrase := testSecrets.MustGetField(\"KUCOIN_PASSPHRASE\")\n\tinactivePassphrase := testSecrets.MustGetField(\"KUCOIN_PASSPHRASE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a kucoin key %s with kucoin secret %s and kucoin passphrase %s within\", key, secret, passphrase)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_KuCoin,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a kucoin key %s with kucoin secret %s and kucoin passphrase %s within but not valid\", inactiveKey, inactiveSecret, inactivePassphrase)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_KuCoin,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"KuCoin.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"KuCoin.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/kucoin/kucoin_test.go",
    "content": "package kucoin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern          = \"c19c2539c53367c726a5c04c\"\n\tinvalidKeyPattern        = \"X19c2539c53367c726a5c04c\"\n\tvalidSecretPattern       = \"2313b299-7ba8-66b6-5b30-3e94050324ff\"\n\tinvalidSecretPattern     = \"X313b299-7ba8-66b6-5b30-3e94050324ff\"\n\tvalidPassphrasePattern   = \"_4K~W4|RHH--o}:BiwRhY9J2pl/5x,\"\n\tinvalidPassphrasePattern = \"_4Kix,\"\n\tkeyword                  = \"kucoin\"\n)\n\nfunc TestKuCoin_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword kucoin\",\n\t\t\tinput: fmt.Sprintf(\"%s %s %s %s %s %s\", keyword, validPassphrasePattern, keyword, validSecretPattern, keyword, validKeyPattern),\n\t\t\twant:  []string{validKeyPattern + validPassphrasePattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s' domain = '%s' email = '%s'\", keyword, validKeyPattern, validSecretPattern, validPassphrasePattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' domain = '%s' email = '%s'\", keyword, invalidKeyPattern, invalidSecretPattern, invalidPassphrasePattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/kylas/kylas.go",
    "content": "package kylas\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"kylas\"}) + `\\b([a-z0-9-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"kylas\"}\n}\n\n// FromData will find and optionally verify Kylas secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Kylas,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.kylas.io/v1/contacts\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"api-key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Kylas\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Kylas is a sales CRM platform. Kylas API keys can be used to access and manage sales data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/kylas/kylas_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage kylas\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestKylas_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"KYLAS\")\n\tinactiveSecret := testSecrets.MustGetField(\"KYLAS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a kylas secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Kylas,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a kylas secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Kylas,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Kylas.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Kylas.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/kylas/kylas_test.go",
    "content": "package kylas\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"dozvko9mlnqqxg5ontcanklqzvw2-lo0jvge\"\n\tinvalidPattern = \"dozvko9mlnqqxg5ontcanklqzvw2-lo0jvg\"\n\tkeyword        = \"kylas\"\n)\n\nfunc TestKylas_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword kylas\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/langfuse/langfuse.go",
    "content": "package langfuse\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\tpublicKey = regexp.MustCompile(detectors.PrefixRegex([]string{\"langfuse\"}) + `\\b(pk-lf-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\b`)\n\tsecretKey = regexp.MustCompile(detectors.PrefixRegex([]string{\"langfuse\"}) + `\\b(sk-lf-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\b`)\n)\n\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pk-lf-\", \"sk-lf-\"}\n}\n\n// FromData will find and optionally verify Langfuse secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tpublicKeyMatches := make(map[string]struct{})\n\tfor _, match := range publicKey.FindAllStringSubmatch(dataStr, -1) {\n\t\tpublicKeyMatches[match[1]] = struct{}{}\n\t}\n\n\tsecretKeyMatches := make(map[string]struct{})\n\tfor _, match := range secretKey.FindAllStringSubmatch(dataStr, -1) {\n\t\tsecretKeyMatches[match[1]] = struct{}{}\n\t}\n\n\tfor pkMatch := range publicKeyMatches {\n\t\tfor skMatch := range secretKeyMatches {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Langfuse,\n\t\t\t\tRaw:          []byte(skMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, pkMatch, skMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\tif verificationErr != nil {\n\t\t\t\t\ts1.SetVerificationError(verificationErr, pkMatch)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, pkMatch string, skMatch string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://cloud.langfuse.com/api/public/projects\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.SetBasicAuth(pkMatch, skMatch)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Langfuse\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Langfuse is a platform for building and scaling AI applications. Langfuse API keys can be used to access these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/langfuse/langfuse_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage langfuse\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLangfuse_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tpublicKey := testSecrets.MustGetField(\"LANGFUSE_PUBLIC_KEY\")\n\tsecretKey := testSecrets.MustGetField(\"LANGFUSE_SECRET_KEY\")\n\tinactivePublicKey := testSecrets.MustGetField(\"LANGFUSE_INACTIVE_PUBLIC_KEY\")\n\tinactiveSecretKey := testSecrets.MustGetField(\"LANGFUSE_INACTIVE_SECRET_KEY\")\n\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a langfuse public key %s and langfuse secret key %s within\", publicKey, secretKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Langfuse,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a langfuse public key %s and langfuse secret key %s within but not valid\", inactivePublicKey, inactiveSecretKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Langfuse,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a langfuse public key %s and langfuse secret key %s within\", publicKey, secretKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Langfuse,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a langfuse public key %s and langfuse secret key %s within\", publicKey, secretKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Langfuse,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Langfuse.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Langfuse.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/langfuse/langfuse_test.go",
    "content": "package langfuse\n\nimport (\n\t\"context\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"testing\"\n)\n\nfunc TestLangfuse_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: `langfuse_public_key = pk-lf-00000000-0000-0000-0000-000000000000\n                    langfuse_secret_key = sk-lf-00000000-0000-0000-0000-000000000000`,\n\t\t\twant:  []string{\"sk-lf-00000000-0000-0000-0000-000000000000\"},\n\t\t},\n\t\t{\n\t\t\tname: \"finds all matches\",\n\t\t\tinput: `langfuse_public_key1 = pk-lf-00000000-0000-0000-0000-000000000000\n                    langfuse_secret_key1 = sk-lf-00000000-0000-0000-0000-000000000000\n\t\t\t\t\tlangfuse_public_key2 = pk-lf-11111111-1111-1111-1111-111111111111\n                    langfuse_secret_key2 = sk-lf-11111111-1111-1111-1111-111111111111`,\n\t\t\twant: []string{\"sk-lf-00000000-0000-0000-0000-000000000000\",\n\t\t\t \"sk-lf-11111111-1111-1111-1111-111111111111\",\n\t\t\t \"sk-lf-11111111-1111-1111-1111-111111111111\",\n\t\t\t \"sk-lf-00000000-0000-0000-0000-000000000000\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: `langfuse_public_key1 = pk-lf-invalid\n                    langfuse_secret_key1 = sk-lf-invalid`,\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/langsmith/langsmith.go",
    "content": "package langsmith\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(lsv2_(?:pt|sk)_[a-f0-9]{32}_[a-f0-9]{10})\\b`) // personal api token and service keys\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"lsv2_pt_\", \"lsv2_sk_\"}\n}\n\n// FromData will find and optionally verify Langsmith secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueAPIKeys := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueAPIKeys[match[1]] = struct{}{}\n\t}\n\n\tfor apiKey := range uniqueAPIKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_LangSmith,\n\t\t\tRaw:          []byte(apiKey),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, apiKey)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, apiKey)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, apiKey string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.smith.langchain.com/api/v1/api-key\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Set(\"X-API-Key\", apiKey)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_LangSmith\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"LangSmith is a unified observability & evals platform where teams can debug, test, and monitor AI app performance — whether building with LangChain or not\"\n}\n"
  },
  {
    "path": "pkg/detectors/langsmith/langsmith_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage langsmith\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLangsmith_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LANGSMITH\")\n\tinactiveSecret := testSecrets.MustGetField(\"LANGSMITH_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a langsmith secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LangSmith,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a langsmith secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LangSmith,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Langsmith.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Langsmith.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/langsmith/langsmith_test.go",
    "content": "package langsmith\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestLangsmith_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: \"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\",\n\t\t\twant:  []string{\"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\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"finds all matches\",\n\t\t\tinput: `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 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`,\n\t\t\twant:  []string{\"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\", \"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\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: \"lsv2_pt_1e0430d40fc14d3fj03397b9e6z46289_2b9036edd2\",\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/languagelayer/languagelayer.go",
    "content": "package languagelayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"languagelayer\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"languagelayer\"}\n}\n\n// FromData will find and optionally verify LanguageLayer secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_LanguageLayer,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.languagelayer.com/languages?access_key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `results`) || strings.Contains(bodyString, `\"info\":\"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption.\"`)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_LanguageLayer\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"LanguageLayer is an API service for language detection. The access key can be used to authenticate and use the API.\"\n}\n"
  },
  {
    "path": "pkg/detectors/languagelayer/languagelayer_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage languagelayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLanguageLayer_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LANGUAGELAYER\")\n\tinactiveSecret := testSecrets.MustGetField(\"LANGUAGELAYER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a languagelayer secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LanguageLayer,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a languagelayer secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LanguageLayer,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"LanguageLayer.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"LanguageLayer.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/languagelayer/languagelayer_test.go",
    "content": "package languagelayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"ze1z7svucm5z17kyizcve7gin44socz5\"\n\tinvalidPattern = \"ze1z7svucm5z17kyizcve7gin44socz\"\n\tkeyword        = \"languagelayer\"\n)\n\nfunc TestLanguageLayer_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword languagelayer\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/larksuite/larksuite.go",
    "content": "package larksuite\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"path\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\n// Define error code constants for better maintainability\nconst (\n\t// Success codes\n\tCodeSuccess        = 0\n\tCodeSpecialSuccess = 99991672 // Based on old code - couldn't find documentation for this code\n\n\t// HTTP 200 response codes - token invalidity\n\tCodeInvalidToken      = 20005 // The user access token passed is invalid\n\tCodeUserNotExist      = 20008 // User not exist\n\tCodeUserResigned      = 20021 // User resigned\n\tCodeUserFrozen        = 20022 // User frozen\n\tCodeUserNotRegistered = 20023 // User not registered\n\n\t// Undocumented error codes - these are found in the API responses but not in the documentation\n\tCodeUndocumentedInvalid1 = 99991663 // Invalid access token for authorization. Please make a request with token attached.\n\tCodeUndocumentedInvalid2 = 99991668 // Invalid access token for authorization. Please make a request with token attached.\n)\n\n// VerificationResult represents the outcome of token verification\ntype VerificationResult int\n\nconst (\n\tVerificationValid   VerificationResult = iota // Token is valid\n\tVerificationInvalid                           // Token is definitively invalid\n\tVerificationError                             // Error occurred during verification\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\n// Check that the LarkSuite scanner implements the SecretScanner interface at compile time.\nvar _ detectors.Detector = Scanner{}\nvar _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)\n\ntype tokenType string\n\nconst (\n\tTenantAccessToken tokenType = \"Tenant Access Token\"\n\tUserAccessToken   tokenType = \"User Access Token\"\n\tAppAccessToken    tokenType = \"App Access Token\"\n)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\ttokenPats     = map[tokenType]*regexp.Regexp{\n\t\tTenantAccessToken: regexp.MustCompile(detectors.PrefixRegex([]string{\"lark\", \"larksuite\", \"tenant\"}) + `(?:^|[^-])\\b(t-[a-z0-9A-Z_.]{14,50})\\b(?:[^-]|$)`),\n\t\tUserAccessToken:   regexp.MustCompile(detectors.PrefixRegex([]string{\"lark\", \"larksuite\", \"user\"}) + `(?:^|[^-])\\b(u-[a-z0-9A-Z_.]{14,50})\\b(?:[^-]|$)`),\n\t\tAppAccessToken:    regexp.MustCompile(detectors.PrefixRegex([]string{\"lark\", \"larksuite\", \"app\"}) + `(?:^|[^-])\\b(a-[a-z0-9A-Z_.]{14,50})\\b(?:[^-]|$)`),\n\t}\n\n\tverificationUrls = map[tokenType]string{\n\t\tTenantAccessToken: \"https://open.larksuite.com/open-apis/tenant/v2/tenant/query\",\n\t\tUserAccessToken:   \"https://open.larksuite.com/open-apis/authen/v1/user_info\",\n\t\tAppAccessToken:    \"https://open.larksuite.com/open-apis/calendar/v4/calendars\",\n\t}\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"lark\", \"larksuite\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_LarkSuite\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"LarkSuite is a collaborative suite that includes chat, calendar, and cloud storage features. The detected token can be used to access and interact with these services.\"\n}\n\n// FromData will find and optionally verify Larksuite secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tfor key, tokenPat := range tokenPats {\n\t\tuniqueMatches := make(map[string]struct{})\n\t\tfor _, match := range tokenPat.FindAllStringSubmatch(dataStr, -1) {\n\t\t\tuniqueMatches[match[1]] = struct{}{}\n\t\t}\n\n\t\tfor token := range uniqueMatches {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_LarkSuite,\n\t\t\t\tRaw:          []byte(token),\n\t\t\t}\n\t\t\ts1.ExtraData = map[string]string{\n\t\t\t\t\"token_type\": string(key),\n\t\t\t}\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif s.client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tvar (\n\t\t\t\t\tisVerified bool\n\t\t\t\t\terr        error\n\t\t\t\t)\n\n\t\t\t\tisVerified, err = verifyAccessToken(ctx, client, verificationUrls[key], token)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(err, token)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyAccessToken(ctx context.Context, client *http.Client, url string, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\t// For LarkSuite API, we expect JSON responses for all documented status codes\n\tvar bodyResponse verificationResponse\n\tif err := json.NewDecoder(res.Body).Decode(&bodyResponse); err != nil {\n\t\treturn false, fmt.Errorf(\"failed to decode response (status %d): %w\", res.StatusCode, err)\n\t}\n\n\t// Handle based on error code classification\n\tresult := classifyErrorCode(bodyResponse.Code)\n\n\tswitch result {\n\tcase VerificationValid:\n\t\treturn true, nil\n\n\tcase VerificationInvalid:\n\t\treturn false, nil\n\n\tcase VerificationError:\n\t\t// All other cases: system errors, rate limits, permission issues, etc.\n\t\t// Return error so token is marked as \"unverified\" (couldn't verify)\n\t\treturn false, fmt.Errorf(\"verification failed (status %d, code %d): %s\",\n\t\t\tres.StatusCode, bodyResponse.Code, bodyResponse.Message)\n\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\ntype verificationResponse struct {\n\tCode    int    `json:\"code\"`\n\tMessage string `json:\"msg\"`\n}\n\n// classifyErrorCode determines how to handle different error codes based on their meaning\nfunc classifyErrorCode(code int) VerificationResult {\n\t// based on the documentation, API fails if response code is not zero\n\t// https://open.larksuite.com/document/uAjLw4CM/ukTMukTMukTM/reference/authen-v1/user_info/get\n\t// https://open.larksuite.com/document/server-docs/calendar-v4/calendar/list\n\t// https://open.larksuite.com/document/server-docs/tenant-v2/tenant/query\n\tswitch code {\n\tcase CodeSuccess, CodeSpecialSuccess:\n\t\treturn VerificationValid\n\n\t// HTTP 200 codes that indicate definitive token invalidity\n\tcase CodeInvalidToken, CodeUserNotExist, CodeUserResigned, CodeUserFrozen, CodeUserNotRegistered, CodeUndocumentedInvalid1,\n\t\tCodeUndocumentedInvalid2:\n\t\treturn VerificationInvalid\n\n\t// All other error codes are treated as verification errors\n\t// This includes:\n\t// - Invalid requests (20001)\n\t// - System errors (20050, 190003)\n\t// - Rate limits (190004, 190005, 190010)\n\t// - Permission issues (190006, 191002, 191003, 191004, 1184001)\n\t// - Resource not found (190007, 191000, 195100, 1184000)\n\t// - Parameter issues (190002, 190008, 190009, 191001)\n\tdefault:\n\t\treturn VerificationError\n\t}\n}\n\nfunc (s Scanner) IsFalsePositive(result detectors.Result) (bool, string) {\n\tif _, ok := commonFileExtensions[strings.ToLower(path.Ext(string(result.Raw)))]; ok {\n\t\treturn ok, \"\"\n\t}\n\n\t// back to the default false positive checks\n\treturn detectors.IsKnownFalsePositive(string(result.Raw), detectors.DefaultFalsePositives, true)\n}\n\nvar commonFileExtensions = map[string]struct{}{\n\t// Web files\n\t\".html\": {},\n\t\".htm\":  {},\n\t\".css\":  {},\n\t\".js\":   {},\n\t\".json\": {},\n\t\".xml\":  {},\n\t\".svg\":  {},\n\t\".php\":  {},\n\t\".asp\":  {},\n\t\".aspx\": {},\n\t\".jsp\":  {},\n\n\t// Document files\n\t\".txt\":  {},\n\t\".md\":   {},\n\t\".pdf\":  {},\n\t\".doc\":  {},\n\t\".docx\": {},\n\t\".rtf\":  {},\n\n\t// Data files\n\t\".csv\":  {},\n\t\".xlsx\": {},\n\t\".xls\":  {},\n\t\".sql\":  {},\n\t\".db\":   {},\n\n\t// Config files\n\t\".conf\":   {},\n\t\".config\": {},\n\t\".ini\":    {},\n\t\".yaml\":   {},\n\t\".yml\":    {},\n\t\".toml\":   {},\n\n\t// Log files\n\t\".log\": {},\n\t\".out\": {},\n\t\".err\": {},\n\n\t// Archive files\n\t\".zip\": {},\n\t\".tar\": {},\n\t\".gz\":  {},\n\t\".rar\": {},\n\n\t// Image files\n\t\".png\":  {},\n\t\".jpg\":  {},\n\t\".jpeg\": {},\n\t\".gif\":  {},\n\t\".bmp\":  {},\n\t\".ico\":  {},\n\n\t// Source code files\n\t\".go\":   {},\n\t\".py\":   {},\n\t\".java\": {},\n\t\".cpp\":  {},\n\t\".c\":    {},\n\t\".h\":    {},\n\t\".rb\":   {},\n\t\".rs\":   {},\n\t\".ts\":   {},\n\n\t// Other common files\n\t\".tmp\":  {},\n\t\".bak\":  {},\n\t\".old\":  {},\n\t\".lock\": {},\n}\n"
  },
  {
    "path": "pkg/detectors/larksuite/larksuite_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage larksuite\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLarksuite_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\ttenantToken := testSecrets.MustGetField(\"LARKSUITE_TENANT\")\n\tuserToken := testSecrets.MustGetField(\"LARKSUITE_USER\")\n\tappToken := testSecrets.MustGetField(\"LARKSUITE_APP\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found tenant token, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a larksuite token %s within\", tenantToken)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LarkSuite,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found user token, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a larksuite token %s within\", userToken)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LarkSuite,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found app token, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a larksuite app %s within\", appToken)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LarkSuite,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Larksuite.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].ExtraData = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Larksuite.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/larksuite/larksuite_test.go",
    "content": "package larksuite\n\nimport (\n\t\"context\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"testing\"\n)\n\nfunc TestLarksuite_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"tenant token pattern\",\n\t\t\tinput: \"larksuite_token = 't-KkBmh6TUBIcyFAp20XXa'\",\n\t\t\twant:  []string{\"t-KkBmh6TUBIcyFAp20XXa\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"user token pattern\",\n\t\t\tinput: \"larksuite_token = 'u-fM_lEWSNhfFqE.dZU6YZ28SRlnWR4hk59Pow05gg00DFA'\",\n\t\t\twant:  []string{\"u-fM_lEWSNhfFqE.dZU6YZ28SRlnWR4hk59Pow05gg00DFA\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"app token pattern\",\n\t\t\tinput: \"larksuite_token = 'a-KkBmh6TUBIcyFAp20XXa'\",\n\t\t\twant:  []string{\"a-KkBmh6TUBIcyFAp20XXa\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/larksuiteapikey/larksuiteapikey.go",
    "content": "package larksuiteapikey\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"lark\", \"larksuite\"}) + `\\b(cli_[a-z0-9A-Z]{16})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"lark\", \"larksuite\"}) + `\\b([a-z0-9A-Z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"lark\", \"larksuite\", \"cli_\"}\n}\n\n// FromData will find and optionally verify larksuite secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// find for app id + secrets\n\tidMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tidMatches[match[1]] = struct{}{}\n\t}\n\tsecretMatches := make(map[string]struct{})\n\tfor _, match := range secretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tsecretMatches[match[1]] = struct{}{}\n\t}\n\n\tfor appId := range idMatches {\n\t\tfor appSecret := range secretMatches {\n\t\t\tresMatch := strings.TrimSpace(appId)\n\t\t\tresSecretMatch := strings.TrimSpace(appSecret)\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_LarkSuiteApiKey,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resSecretMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\t\t\t\tisVerified, verificationErr := verifyCredentials(ctx, client, resMatch, resSecretMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_LarkSuiteApiKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"LarkSuite is a collaboration platform that provides tools for communication, calendar, and cloud storage. LarkSuite API keys can be used to access and manage these services programmatically.\"\n}\n\nfunc verifyCredentials(ctx context.Context, client *http.Client, appId, appSecret string) (bool, error) {\n\tpayload := strings.NewReader(fmt.Sprintf(`{\"app_id\": \"%s\", \"app_secret\": \"%s\"}`, appId, appSecret))\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://open.larksuite.com/open-apis/auth/v3/tenant_access_token/internal\", payload)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tvar bodyResponse verificationResponse\n\t\tif err := json.NewDecoder(res.Body).Decode(&bodyResponse); err != nil {\n\t\t\terr = fmt.Errorf(\"failed to decode response: %w\", err)\n\t\t\treturn false, err\n\t\t} else {\n\t\t\tif bodyResponse.Code == 0 {\n\t\t\t\treturn true, nil\n\t\t\t} else {\n\t\t\t\treturn false, fmt.Errorf(\"Verification failed code %d, message %s\", bodyResponse.Code, bodyResponse.Message)\n\t\t\t}\n\t\t}\n\tdefault:\n\t\t// 500 larksuite was unable to generate a result\n\t\treturn false, err\n\t}\n}\n\ntype verificationResponse struct {\n\tCode    int    `json:\"code\"`\n\tMessage string `json:\"msg\"`\n}\n"
  },
  {
    "path": "pkg/detectors/larksuiteapikey/larksuiteapikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage larksuiteapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLarksuiteApiKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tid := testSecrets.MustGetField(\"LARKSUITE_APP_ID\")\n\tsecret := testSecrets.MustGetField(\"LARKSUITE_APP_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"LARKSUITE_APP_SECRET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a larksuite appid %s and secret %s within\", id, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LarkSuiteApiKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRaw:          []byte(id),\n\t\t\t\t\tRawV2:        []byte(id + secret),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a larksuite appid %s and larksuite secret %s within\", id, inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LarkSuiteApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(id),\n\t\t\t\t\tRawV2:        []byte(id + inactiveSecret),\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"Verification failed code 10014, message app secret invalid\"))\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Larksuite.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].ExtraData = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Larksuite.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/larksuiteapikey/larksuiteapikey_test.go",
    "content": "package larksuiteapikey\n\nimport (\n\t\"context\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"testing\"\n)\n\nfunc TestLarksuiteApiKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"Secrets Pattern\",\n\t\t\tinput: `{\n\t\t\t\t\"lark_app_id\": \"cli_1234567890123456\",\n\t\t\t\t\"lark_app_secret\": \"2nuq0H1dZUqRpHMzKMvbbwlgNWaJiokl\",\n\t\t\t}`,\n\t\t\twant: []string{\"cli_12345678901234562nuq0H1dZUqRpHMzKMvbbwlgNWaJiokl\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/launchdarkly/launchdarkly.go",
    "content": "package launchdarkly\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Launchdarkly keys are UUIDv4s with either api- or sdk- prefixes.\n\t// mob- keys are possible, but are not sensitive credentials.\n\tkeyPat = regexp.MustCompile(`\\b((?:api|sdk)-[a-z0-9]{8}-[a-z0-9]{4}-4[a-z0-9]{3}-[a-z0-9]{4}-[a-z0-9]{12})\\b`)\n)\n\ntype callerIdentity struct {\n\tAccountId       string `json:\"accountId,omitempty\"`\n\tEnvironmentId   string `json:\"environmentId,omitempty\"`\n\tProjectId       string `json:\"projectId,omitempty\"`\n\tEnvironmentName string `json:\"environmentName,omitempty\"`\n\tProjectName     string `json:\"projectName,omitempty\"`\n\tAuthKind        string `json:\"authKind,omitempty\"`\n\tTokenKind       string `json:\"tokenKind,omitempty\"`\n\tClientID        string `json:\"clientId,omitempty\"`\n\tTokenName       string `json:\"tokenName,omitempty\"`\n\tTokenId         string `json:\"tokenId,omitempty\"`\n\tMemberId        string `json:\"memberId,omitempty\"`\n\tServiceToken    bool   `json:\"serviceToken\"`\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client == nil {\n\t\treturn defaultClient\n\t}\n\n\treturn s.client\n}\n\n// We are not including \"mob-\" because client keys are not sensitive.\n// They are expected to be public.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"api-\", \"sdk-\"}\n}\n\n// FromData will find and optionally verify LaunchDarkly secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_LaunchDarkly,\n\t\t\tRaw:          []byte(resMatch),\n\t\t\tExtraData:    make(map[string]string),\n\t\t}\n\n\t\tif verify {\n\t\t\textraData, isVerified, verificationErr := verifyLaunchDarklyKey(ctx, s.getClient(), resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\ts1.ExtraData = extraData\n\n\t\t\t// only api keys can be analyzed\n\t\t\tif strings.HasPrefix(resMatch, \"api-\") {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\"key\": resMatch,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_LaunchDarkly\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"LaunchDarkly is a feature management platform that allows teams to control the visibility of features to users. LaunchDarkly API keys can be used to access and modify feature flags and other resources within a LaunchDarkly account.\"\n}\n\nfunc verifyLaunchDarklyKey(ctx context.Context, client *http.Client, key string) (map[string]string, bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://app.launchdarkly.com/api/v2/caller-identity\", http.NoBody)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tvar callerIdentity callerIdentity\n\t\tvar extraData = make(map[string]string)\n\n\t\tif err := json.NewDecoder(resp.Body).Decode(&callerIdentity); err != nil {\n\t\t\treturn nil, false, err\n\t\t}\n\n\t\textraData[\"type\"] = callerIdentity.AccountId\n\t\textraData[\"account\"] = callerIdentity.AccountId\n\t\textraData[\"environment_id\"] = callerIdentity.EnvironmentId\n\t\textraData[\"project_id\"] = callerIdentity.ProjectId\n\t\textraData[\"environment_name\"] = callerIdentity.EnvironmentName\n\t\textraData[\"project_name\"] = callerIdentity.ProjectName\n\t\textraData[\"auth_kind\"] = callerIdentity.AuthKind\n\t\textraData[\"token_kind\"] = callerIdentity.TokenKind\n\t\textraData[\"client_id\"] = callerIdentity.ClientID\n\t\textraData[\"token_name\"] = callerIdentity.TokenName\n\t\textraData[\"member_id\"] = callerIdentity.MemberId\n\t\tif callerIdentity.TokenKind == \"auth\" {\n\t\t\tif callerIdentity.ServiceToken {\n\t\t\t\textraData[\"token_type\"] = \"service\"\n\t\t\t} else {\n\t\t\t\textraData[\"token_type\"] = \"personal\"\n\t\t\t}\n\t\t}\n\n\t\treturn extraData, true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn nil, false, nil\n\tdefault:\n\t\treturn nil, false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/launchdarkly/launchdarkly_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage launchdarkly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLaunchDarkly_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsdkSecret := testSecrets.MustGetField(\"LAUNCHDARKLY_SDK_TOKEN\")\n\tsecret := testSecrets.MustGetField(\"LAUNCHDARKLY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"LAUNCHDARKLY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a launchdarkly secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LaunchDarkly,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a launchdarkly secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LaunchDarkly,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, valid but unexpected api response\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a launchdarkly secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LaunchDarkly,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, valid sdk token\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a launchdarkly sdk secret %s within\", sdkSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LaunchDarkly,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"LaunchDarkly.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].AnalysisInfo = nil\n\n\t\t\t\t// Do we expect to be comparing the ExtraData?\n\t\t\t\tgot[i].ExtraData = nil\n\n\t\t\t\t// If we expect a verification error, copy over the one we got so the later comparison can work\n\t\t\t\tif tt.wantVerificationErr && len(tt.want) > i {\n\t\t\t\t\ttt.want[i].SetVerificationError(got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"LaunchDarkly.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/launchdarkly/launchdarkly_test.go",
    "content": "package launchdarkly\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestLaunchDarkly_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - launchdarkly api key\",\n\t\t\tinput: `api-5gykyl1b-wgrq-41lc-z6q1-h7zn5fdlcybw`,\n\t\t\twant:  []string{\"api-5gykyl1b-wgrq-41lc-z6q1-h7zn5fdlcybw\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - launchdarkly sdk key\",\n\t\t\tinput: `sdk-5gykyl1b-wgrq-41lc-z6q1-h7zn5fdlcybw`,\n\t\t\twant:  []string{\"sdk-5gykyl1b-wgrq-41lc-z6q1-h7zn5fdlcybw\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern - invalid length\",\n\t\t\tinput: `sdk-5gykyl1b-wgrq-41lc-z6q1-h7zn5fdlcyb`,\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern - invalid character\",\n\t\t\tinput: `api-5Gykyl1b-Wgrq-41lC-z6q1-h7zn5fdlcybw`,\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ldap/ldap.go",
    "content": "package ldap\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\tldap \"github.com/mariduv/ldap-verify\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nfunc init() {\n\tldap.DefaultTimeout = 5 * time.Second\n}\n\nvar (\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\turiPat = regexp.MustCompile(`\\b(?i)ldaps?://[\\S]+\\b`)\n\t// ldap://127.0.0.1:389\n\t// ldap://127.0.0.1\n\t// ldap://mydomain.test\n\t// ldaps://[fe80:4049:92ff:fe44:4bd1]:5060\n\t// ldap://[fe80::4bd1]:5060\n\t// ldap://ds.example.com:389/dc=example,dc=com?givenName,sn,cn?sub?(uid=john.doe)\n\tusernamePat = regexp.MustCompile(detectors.PrefixRegex([]string{\"user\", \"bind\"}) + `[\"']([a-zA-Z=,]{4,150})[\"']`)\n\tpasswordPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pass\"}) + `[\"']([\\S]{4,48})[\"']`)\n\n\t// https://learn.microsoft.com/en-us/windows/win32/api/iads/nf-iads-iadsopendsobject-opendsobject?redirectedfrom=MSDN\n\t// I.E. Set ou = dso.OpenDSObject(\"LDAP://DC.business.com/OU=IT,DC=Business,DC=com\", \"Business\\administrator\", \"Pa$$word01\", 1)\n\tiadPat = regexp.MustCompile(`OpenDSObject\\(\\\"(?i)(ldaps?://[\\S]+)\\\", ?\\\"([\\S]+)\\\", ?\\\"([\\S]+)\\\",[ \\d]+\\)`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ldaps://\", \"ldap://\"}\n}\n\n// FromData will find and optionally verify Ldap secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// Check for matches in the URI + username + password format\n\turiMatches := uriPat.FindAllString(dataStr, -1)\n\tfor _, uri := range uriMatches {\n\t\tldapURL, err := url.Parse(uri)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tusernameMatches := usernamePat.FindAllStringSubmatch(dataStr, -1)\n\t\tfor _, username := range usernameMatches {\n\t\t\tpasswordMatches := passwordPat.FindAllStringSubmatch(dataStr, -1)\n\t\t\tfor _, password := range passwordMatches {\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LDAP,\n\t\t\t\t\tRaw:          []byte(strings.Join([]string{ldapURL.String(), username[1], password[1]}, \"\\t\")),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tverificationErr := verifyLDAP(ctx, username[1], password[1], ldapURL)\n\t\t\t\t\ts1.Verified = verificationErr == nil\n\t\t\t\t\tif !isErrDeterminate(verificationErr) {\n\t\t\t\t\t\ts1.SetVerificationError(verificationErr, password[1])\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check for matches for the IAD library format\n\tiadMatches := iadPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, iad := range iadMatches {\n\t\turi := iad[1]\n\t\tusername := iad[2]\n\t\tpassword := iad[3]\n\n\t\tldapURL, err := url.Parse(uri)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_LDAP,\n\t\t\tRaw:          []byte(strings.Join([]string{ldapURL.String(), username, password}, \"\\t\")),\n\t\t}\n\n\t\tif verify {\n\t\t\tverificationError := verifyLDAP(ctx, username, password, ldapURL)\n\n\t\t\ts1.Verified = verificationError == nil\n\t\t\tif !isErrDeterminate(verificationError) {\n\t\t\t\ts1.SetVerificationError(verificationError, password)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc isErrDeterminate(err error) bool {\n\tvar neterr *net.OpError\n\n\tif errors.As(err, &neterr) ||\n\t\terrors.Is(err, context.DeadlineExceeded) ||\n\t\terrors.Is(err, context.Canceled) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc verifyLDAP(ctx context.Context, username, password string, ldapURL *url.URL) error {\n\t// Tests with non-TLS, TLS, and STARTTLS\n\n\turi := ldapURL.String()\n\n\tswitch ldapURL.Scheme {\n\tcase \"ldap\":\n\t\t// Non-TLS dial\n\t\tl, err := ldap.DialURL(uri, ldap.DialWithContext(ctx))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer l.Close()\n\t\t// Non-TLS verify\n\t\terr = l.BindContext(ctx, username, password)\n\t\tif err == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\t// STARTTLS\n\t\terr = l.StartTLS(&tls.Config{InsecureSkipVerify: true})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// STARTTLS verify\n\t\treturn l.BindContext(ctx, username, password)\n\tcase \"ldaps\":\n\t\t// TLS dial\n\t\tl, err := ldap.DialURL(\n\t\t\turi,\n\t\t\tldap.DialWithContext(ctx),\n\t\t\tldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer l.Close()\n\t\t// TLS verify\n\t\treturn l.BindContext(ctx, username, password)\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown ldap scheme %q\", ldapURL.Scheme)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_LDAP\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"LDAP (Lightweight Directory Access Protocol) is an open, vendor-neutral, industry standard application protocol for accessing and maintaining distributed directory information services over an Internet Protocol (IP) network.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ldap/ldap_integration_test.go",
    "content": "//go:build detectors && integration\n// +build detectors,integration\n\npackage ldap\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMain(m *testing.M) {\n\tcode, err := runMain(m)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tos.Exit(code)\n}\n\nfunc runMain(m *testing.M) (int, error) {\n\tif err := startOpenLDAP(); err != nil {\n\t\treturn 0, err\n\t}\n\tdefer stopOpenLDAP()\n\treturn m.Run(), nil\n}\n\nfunc dockerLogLine(hash string, needle string) chan struct{} {\n\tch := make(chan struct{}, 1)\n\tgo func() {\n\t\tfor {\n\t\t\tout, err := exec.Command(\"docker\", \"logs\", hash).CombinedOutput()\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tif strings.Contains(string(out), needle) {\n\t\t\t\tch <- struct{}{}\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t}\n\t}()\n\treturn ch\n}\n\nfunc TestLdap_Integration_FromChunk(t *testing.T) {\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found with URI and separate user+password usage, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(`\n\t\tldap://localhost:1389\n\t\tbinddn=\"cn=admin,dc=example,dc=org\"\n\t\tpass=\"P@55w0rd\"`),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LDAP,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found with URI and separate user+password usage, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(`\n\t\tldap://localhost:1389\n\t\tbinddn=\"cn=someuser,dc=example,dc=org\"\n\t\tpass=\"P@55w0rd\"`),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LDAP,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found with IAD lib usage, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(`Set ou = dso.OpenDSObject(\"LDAP://localhost:1389\", \"cn=admin,dc=example,dc=org\", \"P@55w0rd\", 1)`),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LDAP,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found with IAD lib usage, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(`Set ou = dso.OpenDSObject(\"LDAP://localhost:1389\", \"cn=admin,dc=example,dc=org\", \"P@55w0rd\", 1)`),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LDAP,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found with IAD lib usage, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(`Set ou = dso.OpenDSObject(\"LDAP://localhost:1389\", \"cn=admin,dc=example,dc=org\", \"invalid\", 1)`),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LDAP,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"inaccessible host\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(`\n\t\tldap://badhost:1389\n\t\tbinddn=\"cn=admin,dc=example,dc=org\"\n\t\tpass=\"P@55w0rd\"`),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LDAP,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Ldap.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Ldap.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nvar containerID string\n\nfunc startOpenLDAP() error {\n\tcmd := exec.Command(\n\t\t\"docker\", \"run\", \"--rm\", \"-p\", \"1389:1389\",\n\t\t\"-e\", \"LDAP_ROOT=dc=example,dc=org\",\n\t\t\"-e\", \"LDAP_ADMIN_USERNAME=admin\",\n\t\t\"-e\", \"LDAP_ADMIN_PASSWORD=P@55w0rd\",\n\t\t\"-d\", \"bitnami/openldap:latest\",\n\t)\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn err\n\t}\n\tcontainerID = string(bytes.TrimSpace(out))\n\tselect {\n\tcase <-dockerLogLine(containerID, \"slapd starting\"):\n\t\treturn nil\n\tcase <-time.After(30 * time.Second):\n\t\tstopOpenLDAP()\n\t\treturn errors.New(\"timeout waiting for ldap service to be ready\")\n\t}\n}\n\nfunc stopOpenLDAP() {\n\texec.Command(\"docker\", \"kill\", containerID).Run()\n}\n\nfunc TestLdap_FromChunk(t *testing.T) {\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found with IAD lib usage, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(`Set ou = dso.OpenDSObject(\"LDAP://DC.business.com/OU=IT,DC=Business,DC=com\", \"Business\\administrator\", \"Pa$$word01\", 1)`),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LDAP,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Ldap.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Ldap.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ldap/ldap_test.go",
    "content": "package ldap\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\tldap \"github.com/mariduv/ldap-verify\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidUriPattern        = \"ldaps://127.0.0.1:389\"\n\tinvalidUriPattern      = \"idaps://127.0.0.1:389\"\n\tvalidUsernamePattern   = \"cCOfHuyrVdDdcWCAbKLCSAlospWAdCmfKwr=\"\n\tinvalidUsernamePattern = \"0fHuy2VdDdcWCAbKLC412lospWAdCmfKwr9=\"\n\tvalidPasswordPattern   = \"A:J$NL9~6:u:L$_:VO4tf))h#v0i}O\"\n\tinvalidPasswordPattern = \"A:J$NL9~6:u:L$_:VO4tf))h#v0i}O\"\n\tvalidIadPattern        = \"OpenDSObject(\\\"ldaps://www\\\", \\\"ABC\\\", \\\"XYZ\\\", 123)\"\n\tinvalidIadPattern      = \"OpenDSObject(\\\"ldaps://www\\\", \\\"ABC\\\", \\\"XYZ\\\", ?)\"\n)\n\nfunc TestLdap_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s bind '%s' pass '%s' %s\", validUriPattern, validIadPattern, validPasswordPattern, validUsernamePattern),\n\t\t\twant:  []string{\"ldaps://www\\tABC\\tXYZ\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = bind '%s' pass '%s %s\", invalidUriPattern, invalidUsernamePattern, invalidPasswordPattern, invalidIadPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_isErrDeterminate(t *testing.T) {\n\tif isErrDeterminate(fmt.Errorf(\"anything\")) != true {\n\t\tt.Errorf(\"general errors should be determinate\")\n\t}\n\n\tif isErrDeterminate(&ldap.Error{Err: fmt.Errorf(\"anything\")}) != true {\n\t\tt.Errorf(\"ldap general errors should be determinate\")\n\t}\n\n\tif isErrDeterminate(&ldap.Error{Err: &net.OpError{}}) == true {\n\t\tt.Errorf(\"ldap net.OpError{} should be indeterminate\")\n\t}\n\n\tif isErrDeterminate(&ldap.Error{Err: context.DeadlineExceeded}) == true {\n\t\tt.Errorf(\"ldap context deadline should be indeterminate\")\n\t}\n\n\tif isErrDeterminate(&ldap.Error{Err: context.Canceled}) == true {\n\t\tt.Errorf(\"ldap context deadline should be indeterminate\")\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/leadfeeder/leadfeeder.go",
    "content": "package leadfeeder\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"leadfeeder\"}) + `\\b([a-zA-Z0-9-]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"leadfeeder\"}\n}\n\n// FromData will find and optionally verify Leadfeeder secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Leadfeeder,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.leadfeeder.com/accounts\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Token token=%s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Leadfeeder\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Leadfeeder is a tool that shows you the companies visiting your website, how they found you, and what they’re interested in. Leadfeeder API keys can be used to access and manage this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/leadfeeder/leadfeeder_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage leadfeeder\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLeadfeeder_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LEADFEEDER\")\n\tinactiveSecret := testSecrets.MustGetField(\"LEADFEEDER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a leadfeeder secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Leadfeeder,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a leadfeeder secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Leadfeeder,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Leadfeeder.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Leadfeeder.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/leadfeeder/leadfeeder_test.go",
    "content": "package leadfeeder\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"7zy0rgEn3QnW3oGW8PCuWWlDkLOBMpz307kKsm9OJa4\"\n\tinvalidPattern = \"7zy0rgEn3QnW3oGW8PCuWWlDkLOBMpz307kKsm9OJa=\"\n\tkeyword        = \"leadfeeder\"\n)\n\nfunc TestLeadfeeder_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword leadfeeder\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/lemlist/lemlist.go",
    "content": "package lemlist\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"lemlist\"}) + `\\b([a-f0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"lemlist\"}\n}\n\n// FromData will find and optionally verify Lemlist secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Lemlist,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tdata := fmt.Sprintf(\":%s\", resMatch)\n\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.lemlist.com/api/team\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Lemlist\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Lemlist is an email outreach platform used for sending personalized emails. Lemlist API keys can be used to access and manage email campaigns and contacts.\"\n}\n"
  },
  {
    "path": "pkg/detectors/lemlist/lemlist_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage lemlist\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLemlist_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LEMLIST\")\n\tinactiveSecret := testSecrets.MustGetField(\"LEMLIST_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a lemlist secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Lemlist,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a lemlist secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Lemlist,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Lemlist.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Lemlist.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/lemlist/lemlist_test.go",
    "content": "package lemlist\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"24203d9f149dd02d1d944ee799cfb57e\"\n\tinvalidPattern = \"24203d9f149dd02d1d944ee799cfb57\"\n\tkeyword        = \"lemlist\"\n)\n\nfunc TestLemlist_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword lemlist\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/lemonsqueezy/lemonsqueezy.go",
    "content": "package lemonsqueezy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// JWT token\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"lemonsqueezy\"}) + `\\b(eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9\\.[0-9A-Za-z]{314}\\.[0-9A-Za-z-_]{512})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"lemonsqueezy\"}\n}\n\n// FromData will find and optionally verify Lemonsqueezy secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_LemonSqueezy,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.lemonsqueezy.com/v1/products/\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_LemonSqueezy\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"LemonSqueezy is a platform for selling digital products. Its API tokens can be used to manage products, orders, and other resources.\"\n}\n"
  },
  {
    "path": "pkg/detectors/lemonsqueezy/lemonsqueezy_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage lemonsqueezy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLemonsqueezy_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LEMONSQUEEZY\")\n\tinactiveSecret := testSecrets.MustGetField(\"LEMONSQUEEZY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a lemonsqueezy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LemonSqueezy,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a lemonsqueezy secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LemonSqueezy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Lemonsqueezy.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Lemonsqueezy.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/lemonsqueezy/lemonsqueezy_test.go",
    "content": "package lemonsqueezy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.TkOQyjzXNs1ExektRLyW2wxKeFk7VWEE0NVQwaBSKjO5EKaXBwZciTfpIPOFKupmt0PkIChZLcI8fX5XOHQK6hy1e2f7gWW0A00ixqbQUAUyfOTBXSHdrZrK6QNd9xi7q7B2m7ei3rfSipMMod7oHyxvVKwKckwcdfDlZ5OwtDy1lhBFeYZWcGiTM2qvTOWQkBMezkwhz23YONwYK2MOP0PaasJhryNui98LbiiXju20dV2tlxslqJD6i856axkolvQRhJWM7y2Jp37iDgIABh6b13LadPbWJgKiOKkrow4UyzYCrcDOQ5Y6to.c8zXA41FY2GgWUWXjwqoem5A6q46CgLicuYZ4M2XuGZ747WQz1ZmtbnLZn4nclSWLpJUEgdxQpNt8GBVBB2_3B4on1m2HkOHBqjrfn5kHuYSeR_zHNPdLXZBER4tpUPx7Dijl1T8WO6cri32vj8oM2o4ihLeFD1Ewd_OYpP-CIzC4jOKn4DFbgtr7CWaE4vf4XEFSn4B4v-XEjgjmSRDcw_a-wRXRnSZCL8UoiN9k0cMyxqXFHxfiFrMcghwFIKHt37fhHEidYh8SwJy3XdJzusRpynUtoHcpfNhgts9Ik3W7jg_HAhMvbg5XxMUYhtQty32sonozf5cVuoXUD0HOe7gbLNMxaHNT8RVYRSHTqzV1FXLdtGBsZMke-6pKuhC1erbPpLB57os6bKetCkwswk9yZI_eNi4MtR8KnhO8aeWUrz2QJUMY6xXI0a1E0yES8yxGQsSe3CHlVhGgjQuLZf9p2_30YB-yu3NIjJTkamgsaWA41H0eX_SuDci35uO\"\n\tinvalidPattern = \"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.TkOQyjzXNs1ExektRLyW2wxKeFk7VWEE0NVQwaBSKjO5EKaXBwZciTfpIPOFKupmt0PkIChZLcI8fX5XOHQK6hy1e2f7gWW0A00ixqbQUAUyfOTBXSHdrZrK6QNd9xi7q7B2m7ei3rfSipMMod7oHyxvVKwKc.c8zXA41FY2GgWUWXjwqoem5A6q46CgLicuYZ4M2XuGZ747WQz1ZmtbnLZn4nclSWLpJUEgdxQpNt8GBVBB2_3B4on1m2HkOHBqjrfn5kHuYSeR_zHNPdLXZBER4tpUPx7Dijl1T8WO6cri32vj8oM2o4ihLeFD1Ewd_OYpP-CIzC4jOKn4DFbgtr7CWaE4vf4XEFSn4B4v-XEjgjmSRDcw_a-wRXRnSZCL8UoiN9k0cMyxqXFHxfiFrMcghwFIKH\"\n\tkeyword        = \"lemonsqueezy\"\n)\n\nfunc TestLemonsqueezy_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword lemonsqueezy\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/lendflow/lendflow.go",
    "content": "package lendflow\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"lendflow\"}) + `\\b([a-zA-Z0-9]{36}\\.[a-zA-Z0-9]{235}\\.[a-zA-Z0-9]{32}\\-[a-zA-Z0-9]{47}\\-[a-zA-Z0-9_]{162}\\-[a-zA-Z0-9]{42}\\-[a-zA-Z0-9_]{40}\\-[a-zA-Z0-9_]{66}\\-[a-zA-Z0-9_]{59}\\-[a-zA-Z0-9]{7}\\-[a-zA-Z0-9_]{220})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"lendflow\"}\n}\n\n// FromData will find and optionally verify Lendflow secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Lendflow,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := 10 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.lendflow.io/api/v1/deals\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Lendflow\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Lendflow is a platform for accessing financial data and services. Lendflow API keys can be used to access and manipulate this financial data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/lendflow/lendflow_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage lendflow\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLendflow_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LENDFLOW\")\n\tinactiveSecret := testSecrets.MustGetField(\"LENDFLOW_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a lendflow secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Lendflow,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a lendflow secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Lendflow,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Lendflow.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Lendflow.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/lendflow/lendflow_test.go",
    "content": "package lendflow\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"RODlvb3v108LswHD7gCZcyHdCIK8T90Hgg12.VF24VyD6akOZS1NmAZ4tVRmtyhyLeNELvCqZnBuiAtJqyMUN2JQHC6vQgzrcAMwPglMjiAZ8gBPIVsQSRiHDvJA89BPQpjAOCxGQynOoSvECoSH5WCldSnl8lN0BNKyeK9J0DSKitEaS9G00qVY9emKcNEQWCWXZeZXLsDXReCcOUEr1Cmi1tpsueqxT34TG4zvaPa2npM10TCKLqKsJrdfZL4oWtZVnZob0QBBkU7k.nQcGbtHUBtYXGUIQYZ1Et1ROaE4Rg0gM-Kw3YtvcVgoV5ro02tydPtPGqSAzf5SBt3LYqQriHmcmW5cx-tASACVQwoT1dpJtxrUP1sa5mHuvstgxDFVe1DxDpMjXYVNL3immaAxqnD6NHs4FzKdNvXvz7p969aIe9q7YdpdyDbFL6x2FINKkqrJ2uMIUNvECITzDjHLGQP3hDcbfJUQ47bDAi3XpbqtFhZTlDd29VwxfEMoA3xi-6rKIS9xXsRxCsLdEt3mhwIxEW89oXKLFHxjXYMGNvl-91H2hw0TIvhzXJHrG3VE1NKXq9E1WoC6OfQPa7qV-lz26nq1jGSW7ENR9HWfc4ppiHj20PGczp6YZPRUIbaTcrOi64F09uOKLInZCIIKI94-bqjlEK5dAune7Fm8pmRqOUPIEnRydJi4ilWiG2cm1xDgU4nb3F507dsjHVi-V0TFyrI-69lrF0TEh7jb8sBjU6yusaRGdannDKBFSnz2d3LVVAn7udGuk2ZNIjYi6equNRYhQ3wJnKQCGIqLaGGQdgH9ILiDB0T9e5aED0taqYpvMFgby6UJGJeNatazX1KMj1NMwMWd3ZIBia7Dlc1lkbWlePlQEh2mZTBcOsvh7lOzLmIu1NKDzByZI3VUhKq5wY3JXJdGeiD3EBPphUt5eaz9Mt0nrK8J\"\n\tinvalidPattern = \"?ODlvb3v108LswHD7gCZcyHdCIK8T90Hgg12.VF24VyD6akOZS1NmAZ4tVRmtyhyLeNELvCqZnBuiAtJqyMUN2JQHC6vQgzrcAMwPglMjiAZ8gBPIVsQSRiHDvJA89BPQpjAOCxGQynOoSvECoSH5WCldSnl8lN0BNKyeK9J0DSKitEaS9G00qVY9emKcNEQWCWXZeZXLsDXReCcOUEr1Cmi1tpsueqxT34TG4zvaPa2npM10TCKLqKsJrdfZL4oWtZVnZob0QBBkU7k.nQcGbtHUBtYXGUIQYZ1Et1ROaE4Rg0gM-Kw3YtvcVgoV5ro02tydPtPGqSAzf5SBt3LYqQriHmcmW5cx-tASACVQwoT1dpJtxrUP1sa5mHuvstgxDFVe1DxDpMjXYVNL3immaAxqnD6NHs4FzKdNvXvz7p969aIe9q7YdpdyDbFL6x2FINKkqrJ2uMIUNvECITzDjHLGQP3hDcbfJUQ47bDAi3XpbqtFhZTlDd29VwxfEMoA3xi-6rKIS9xXsRxCsLdEt3mhwIxEW89oXKLFHxjXYMGNvl-91H2hw0TIvhzXJHrG3VE1NKXq9E1WoC6OfQPa7qV-lz26nq1jGSW7ENR9HWfc4ppiHj20PGczp6YZPRUIbaTcrOi64F09uOKLInZCIIKI94-bqjlEK5dAune7Fm8pmRqOUPIEnRydJi4ilWiG2cm1xDgU4nb3F507dsjHVi-V0TFyrI-69lrF0TEh7jb8sBjU6yusaRGdannDKBFSnz2d3LVVAn7udGuk2ZNIjYi6equNRYhQ3wJnKQCGIqLaGGQdgH9ILiDB0T9e5aED0taqYpvMFgby6UJGJeNatazX1KMj1NMwMWd3ZIBia7Dlc1lkbWlePlQEh2mZTBcOsvh7lOzLmIu1NKDzByZI3VUhKq5wY3JXJdGeiD3EBPphUt5eaz9Mt0nrK8J\"\n\tkeyword        = \"lendflow\"\n)\n\nfunc TestLendflow_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword lendflow\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/lessannoyingcrm/lessannoyingcrm.go",
    "content": "package lessannoyingcrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"less\"}) + `\\b([a-zA-Z0-9-]{57})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"lessannoyingcrm\"}\n}\n\n// FromData will find and optionally verify LessAnnoyingCRM secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_LessAnnoyingCRM,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tuserCode := strings.Split(resMatch, \"-\")\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.lessannoyingcrm.com?UserCode=%s&APIToken=%s&Function=GetUserInfo\", userCode, resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.lessannoyingcrm+json; version=3\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_LessAnnoyingCRM\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Less Annoying CRM is a customer relationship management system. The API token can be used to access and manage customer data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/lessannoyingcrm/lessannoyingcrm_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage lessannoyingcrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLessAnnoyingCRM_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LESSANNOYINGCRM_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"LESSANNOYINGCRM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a lessannoyingcrm secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LessAnnoyingCRM,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a lessannoyingcrm secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LessAnnoyingCRM,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"LessAnnoyingCRM.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"LessAnnoyingCRM.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/lessannoyingcrm/lessannoyingcrm_test.go",
    "content": "package lessannoyingcrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"xiPi5OZDPlnS6WmR0JropSAMlcHCSCRWVN2D1sVttofuZQpdYuPKmWD81\"\n\tinvalidPattern = \"xiPi5OZDPlnS6WmR0JropSAMlcHCSCRWVN2D1sVttofuZQpdYuPKmWD8\"\n\tkeyword        = \"lessannoyingcrm\"\n)\n\nfunc TestLessAnnoyingCRM_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword lessannoyingcrm\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/lexigram/lexigram.go",
    "content": "package lexigram\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"lexigram\"}) + `\\b([a-zA-Z0-9\\S]{301})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"lexigram\"}\n}\n\n// FromData will find and optionally verify Lexigram secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Lexigram,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.lexigram.io/v1/lexigraph/search?q=diabetes\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Lexigram\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Lexigram is a healthcare data service that provides an API for medical terminology and data. Lexigram API keys can be used to access and retrieve medical data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/lexigram/lexigram_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage lexigram\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLexigram_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LEXIGRAM\")\n\tinactiveSecret := testSecrets.MustGetField(\"LEXIGRAM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a lexigram secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Lexigram,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a lexigram secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Lexigram,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Lexigram.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Lexigram.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/lexigram/lexigram_test.go",
    "content": "package lexigram\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"FyIcB4LRVZ4iEJVky0JvzVS6yzreDuFgz8zSmfXNlWuEIE29ad6Wmc9sQtUzWBp8dV960IeSdWk5V182naPy0uAty27gdFtyl6TvCz0gJdprxKyZfNzxVeAy5Qlghzl4Ivk9jXe2p7gnafM3iUvK2vemq5FroXXgUJTZ0SHqR6QGv0wk9WGEa5aAxDUiQ91e2xI6leUbTIoxXQyF1P1rxYZswimIbGTHy6H7oMuTbXFrX3XDdphMqyHYjGlzYDfY3nREvC83XsgIlawdTV0FdPXzXxqB4ELorpo3z8kCsWm1H\"\n\tinvalidPattern = \"=yIcB4LRVZ4iEJVky0JvzVS6yzreDuFgz8zSmfXNlWuEIE29ad6Wmc9sQtUzWBp8dV960IeSdWk5V182naPy0uAty27gdFtyl6TvCz0gJdprxKyZfNzxVeAy5Qlghzl4Ivk9jXe2p7gnafM3iUvK2vemq5FroXXgUJTZ0SHqR6QGv0wk9WGEa5aAxDUiQ91e2xI6leUbTIoxXQyF1P1rxYZswimIbGTHy6H7oMuTbXFrX3XDdphMqyHYjGlzYDfY3nREvC83XsgIlawdTV0FdPXzXxqB4ELorpo3z8kCsWm1H\"\n\tkeyword        = \"lexigram\"\n)\n\nfunc TestLexigram_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword lexigram\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/linearapi/linearapi.go",
    "content": "package linearapi\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(lin_api_[0-9A-Za-z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"lin_api_\"}\n}\n\n// FromData will find and optionally verify LinearAPI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_LinearAPI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treqDataQuery := strings.NewReader(`{ \"query\": \"{ viewer { id name } }\" }`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.linear.app/graphql\", reqDataQuery)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", resMatch)\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && strings.Contains(body, `\"data\":`) {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_LinearAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"LinearAPI is a project management software. LinearAPI keys can be used to access and modify project data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/linearapi/linearapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage linearapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLinearAPI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LINEARAPI_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"LINEARAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a linearapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LinearAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a linearapi secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LinearAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"LinearAPI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"LinearAPI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/linearapi/linearapi_test.go",
    "content": "package linearapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"lin_api_AMBNSMuZbO3NIH4EFgpBg9weewxyU8fG0u3RwEDC\"\n\tinvalidPattern = \"lin_api_=MBNSMuZbO3NIH4EFgpBg9weewxyU8fG0u3RwEDC\"\n\tkeyword        = \"linearapi\"\n)\n\nfunc TestLinearAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/linemessaging/linemessaging.go",
    "content": "package linemessaging\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"line\"}) + `\\b([A-Za-z0-9+/]{171,172})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"line\"}\n}\n\n// FromData will find and optionally verify LineMessaging secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_LineMessaging,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.line.me/v2/bot/info\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_LineMessaging\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Line Messaging is a communication app that allows users to send messages, photos, videos, and audio. Line Messaging API keys can be used to access and modify this data and communication.\"\n}\n"
  },
  {
    "path": "pkg/detectors/linemessaging/linemessaging_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage linemessaging\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLineMessaging_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LINEMESSAGING_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"LINEMESSAGING_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a line secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LineMessaging,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a line secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LineMessaging,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"LineMessaging.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"LineMessaging.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/linemessaging/linemessaging_test.go",
    "content": "package linemessaging\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"daNigdC0Z4fsCucz3i+KzudCr26xF5EN7xsyvLYLADR+lo3KTNtNJ4B0cDw30J0Ju1HyC6oNkX/OoZniUvJTNZAVe2Jf4B+Fu3/bVvz609P0cZhpzc0VHc4bdzbBQCjv4Nz/SKZwqjfbg9DdmNwzq1i8hSimX6a+t39neP247rF\"\n\tinvalidPattern = \"=aNigdC0Z4fsCucz3i+KzudCr26xF5EN7xsyvLYLADR+lo3KTNtNJ4B0cDw30J0Ju1HyC6oNkX/OoZniUvJTNZAVe2Jf4B+Fu3/bVvz609P0cZhpzc0VHc4bdzbBQCjv4Nz/SKZwqjfbg9DdmNwzq1i8hSimX6a+t39neP247rF\"\n\tkeyword        = \"line\"\n)\n\nfunc TestLineMessaging_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword line\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/linenotify/linenotify.go",
    "content": "package linenotify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"linenotify\"}) + `\\b([0-9A-Za-z]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"linenotify\"}\n}\n\n// FromData will find and optionally verify LineNotify secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_LineNotify,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://notify-api.line.me/api/notify?message=Notification Successful\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_LineNotify\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Line Notify is a service provided by LINE Corporation that allows you to send notifications to your LINE app. The API keys can be used to send messages to users or groups.\"\n}\n"
  },
  {
    "path": "pkg/detectors/linenotify/linenotify_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage linenotify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLineNotify_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LINENOTIFY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"LINENOTIFY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a linenotify secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LineNotify,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a linenotify secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LineNotify,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"LineNotify.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"LineNotify.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/linenotify/linenotify_test.go",
    "content": "package linenotify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"xddIUmDwi9BoOOlSVgXSzwABg5R1icstGke6gp9P85B\"\n\tinvalidPattern = \"xddIUmDwi9BoOOlSVgXSzwABg5R1icstGke6gp9P85\"\n\tkeyword        = \"linenotify\"\n)\n\nfunc TestLineNotify_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword linenotify\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/linkpreview/linkpreview.go",
    "content": "package linkpreview\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"linkpreview\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"linkpreview\"}\n}\n\n// FromData will find and optionally verify LinkPreview secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_LinkPreview,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.linkpreview.net/?key=%s&q=https://google.com\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tresults = append(results, s1)\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_LinkPreview\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"LinkPreview is a service that provides information about a web page when given a URL. LinkPreview API keys can be used to access this service and retrieve metadata about web pages.\"\n}\n"
  },
  {
    "path": "pkg/detectors/linkpreview/linkpreview_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage linkpreview\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLinkPreview_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LINKPREVIEW\")\n\tinactiveSecret := testSecrets.MustGetField(\"LINKPREVIEW_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a linkpreview secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LinkPreview,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a linkpreview secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LinkPreview,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"LinkPreview.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"LinkPreview.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/linkpreview/linkpreview_test.go",
    "content": "package linkpreview\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"EKHHAPdXrzqKExMfz0f1fxC1oVlDP0E4\"\n\tinvalidPattern = \"EKHHAPdXrzqKExMfz0f1fxC1oVlDP0E\"\n\tkeyword        = \"linkpreview\"\n)\n\nfunc TestLinkPreview_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword linkpreview\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/liveagent/liveagent.go",
    "content": "package liveagent\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tdomainPat = regexp.MustCompile(`\\b(https?://[A-Za-z0-9-]+\\.ladesk\\.com)\\b`)\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"liveagent\", \"apikey\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"liveagent\", \"ladesk\"}\n}\n\ntype response struct {\n\tMessage string `json:\"message\"`\n}\n\n// FromData will find and optionally verify LiveAgent secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tdomainMatches := domainPat.FindAllStringSubmatch(dataStr, -1)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, domainMatch := range domainMatches {\n\t\t\tdomainRes := strings.TrimSpace(domainMatch[0])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_LiveAgent,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\"domain\": domainRes,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", domainRes+\"/api/v3/agents\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"apikey\", resMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else if res.StatusCode == 403 {\n\t\t\t\t\t\tvar r response\n\t\t\t\t\t\tif err := json.NewDecoder(res.Body).Decode(&r); err != nil {\n\t\t\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// If the message is \"You do not have sufficient privileges\", then the key is valid, but does not have access to the `/agents` endpoint.\n\t\t\t\t\t\tif r.Message == \"You do not have sufficient privileges\" {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_LiveAgent\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"LiveAgent is a help desk software that provides a customer service platform. The API key allows access to various functionalities of the LiveAgent service.\"\n}\n"
  },
  {
    "path": "pkg/detectors/liveagent/liveagent_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage liveagent\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLiveAgent_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tdeskUrl := testSecrets.MustGetField(\"LIVEAGENT_URL\")\n\tsecret := testSecrets.MustGetField(\"LIVEAGENT_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"LIVEAGENT_INACTIVE\")\n\tu, err := url.Parse(deskUrl)\n\tif err != nil {\n\t\tt.Fatalf(\"could not parse LIVEAGENT_URL: %s\", err)\n\t}\n\twantUrl := u.Scheme + \"://\" + u.Hostname()\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a liveagent secret %s within for %s\", secret, deskUrl)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LiveAgent,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"domain\": wantUrl,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a liveagent secret %s within but not valid for %s\", inactiveSecret, deskUrl)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LiveAgent,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"domain\": wantUrl,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"LiveAgent.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"LiveAgent.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/liveagent/liveagent_test.go",
    "content": "package liveagent\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern      = \"S1vAf8FVzS8g52avAG78sANc01XaHWIx\"\n\tinvalidKeyPattern    = \"?1vAf8FVzS8g52avAG78sANc01XaHWIx\"\n\tvalidDomainPattern   = \"https://eg-te.ladesk.com\"\n\tinvalidDomainPattern = \"https://?.ladesk.com\"\n\tkeyword              = \"liveagent\"\n)\n\nfunc TestLiveAgent_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword liveagent\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s' '%s'\", keyword, validKeyPattern, validDomainPattern),\n\t\t\twant:  []string{validKeyPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validKeyPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' url = '%s'\", keyword, invalidKeyPattern, invalidDomainPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/livestorm/livestorm.go",
    "content": "package livestorm\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"livestorm\"}) + `\\b(eyJhbGciOiJIUzI1NiJ9\\.eyJhdWQiOiJhcGkubGl2ZXN0b3JtLmNvIiwianRpIjoi[0-9A-Z-a-z]{134}\\.[0-9A-Za-z\\-\\_]{43}[ \\r\\n]{1})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"livestorm\"}\n}\n\n// FromData will find and optionally verify Livestorm secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Livestorm,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.livestorm.co/v1/ping\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Livestorm\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Livestorm is a video communication platform for webinars, meetings, and virtual events. Livestorm API keys can be used to interact with Livestorm services programmatically.\"\n}\n"
  },
  {
    "path": "pkg/detectors/livestorm/livestorm_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage livestorm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLivestorm_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LIVESTORM\")\n\tinactiveSecret := testSecrets.MustGetField(\"LIVESTORM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a livestorm secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Livestorm,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a livestorm secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Livestorm,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Livestorm.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Livestorm.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/livestorm/livestorm_test.go",
    "content": "package livestorm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJhcGkubGl2ZXN0b3JtLmNvIiwianRpIjoiOaFI2p8Vo2dnHiy5OxrjkJ8wRE1LW4rTyWzChczEu3lAmcD1maSawW7xKeZ1GEfjJRSelieqydpF8eVp619RMtYPGwY3XmOPrBatssI2RSBDM5srACqiTOcg3sQgkeBBtA4bUJ.O6UuKenKIAUh60CeGWQRTMVAA2JcFlvz1GRTliyZ448 \"\n\tinvalidPattern = \"eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJhcGkubGl2ZXN0b3JtLmNvIiwianRpIjoiOaFI2p8Vo2dnHiy5OxrjkJ8wRE1LW4rTyWzChczEu3lAmcD1maSawW7xKeZ1GEfjJRSelieqydpF8eVp619RMtYPGwY3XmOPrBatssI2RSBDM5srACqiTOcg3sQgkeBBtA4bUJ.O6UuKenKIAUh60CeGWQRTMVAA2JcFlvz1GRTliyZ448\"\n\tkeyword        = \"livestorm\"\n)\n\nfunc TestLivestorm_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword livestorm\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{strings.TrimSpace(validPattern)},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{strings.TrimSpace(validPattern)},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/loadmill/loadmill.go",
    "content": "package loadmill\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"loadmill\"}) + `\\b([0-9a-zA-Z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"loadmill\"}\n}\n\n// FromData will find and optionally verify Loadmill secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Loadmill,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.loadmill.com/api/v1/labels?filter=CI_enable\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.loadmill+json; version=3\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Loadmill\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Loadmill is a testing service for APIs and web applications. Loadmill API keys can be used to access and manage test scenarios and results.\"\n}\n"
  },
  {
    "path": "pkg/detectors/loadmill/loadmill_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage loadmill\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLoadmill_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LOADMILL\")\n\tinactiveSecret := testSecrets.MustGetField(\"LOADMILL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a loadmill secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Loadmill,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a loadmill secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Loadmill,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Loadmill.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Loadmill.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/loadmill/loadmill_test.go",
    "content": "package loadmill\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"YbQOeyGBV5J1ri9hUGn5WfRfZrmGGQRWQblsyesK\"\n\tinvalidPattern = \"YbQOeyGBV5J1ri9hUGn5WfRfZrmGGQRWQblsyes\"\n\tkeyword        = \"loadmill\"\n)\n\nfunc TestLoadmill_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword loadmill\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/lob/lob.go",
    "content": "package lob\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"lob\"}) + `\\b([a-zA-Z0-9_]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"lob\"}\n}\n\n// FromData will find and optionally verify Lob secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Lob,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.lob.com/v1/addresses\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.SetBasicAuth(resMatch, \"\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Lob\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Lob is a service for automating the creation and sending of letters, checks, and postcards. Lob API keys can be used to access and manage these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/lob/lob_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage lob\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLob_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LOB\")\n\tinactiveSecret := testSecrets.MustGetField(\"LOB_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a lob secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Lob,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a lob secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Lob,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Lob.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Lob.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/lob/lob_test.go",
    "content": "package lob\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"XUTbwiuF1_qmP5BvXi4hMeDafM2VoNz5yMH__rI5\"\n\tinvalidPattern = \"XUTbwiuF1_qmP5BvXi4hMeDafM2VoNz5yMH__rI\"\n\tkeyword        = \"lob\"\n)\n\nfunc TestLob_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword lob\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/locationiq/locationiq.go",
    "content": "package locationiq\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(pk\\.[a-zA-Z-0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"locationiq\"}\n}\n\n// FromData will find and optionally verify LocationIQ secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_LocationIQ,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://us1.locationiq.com/v1/reverse.php?key=\"+resMatch+\"&lat=-37.870662&lon=144.9803321&format=json\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_LocationIQ\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"LocationIQ provides location-based services such as geocoding and reverse geocoding. LocationIQ API keys can be used to access these services and retrieve location data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/locationiq/locationiq_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage locationiq\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLocationIQ_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LOCATIONIQ\")\n\tinactiveSecret := testSecrets.MustGetField(\"LOCATIONIQ_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a locationiq secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LocationIQ,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a locationiq secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LocationIQ,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"LocationIQ.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"LocationIQ.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/locationiq/locationiq_test.go",
    "content": "package locationiq\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"pk.yRfkay-UQ1g6ywFnbsJT3IhS1U-Rdhtf\"\n\tinvalidPattern = \"pk.yRfkay-UQ1g6ywFnbsJT3IhS1U-Rdht\"\n\tkeyword        = \"locationiq\"\n)\n\nfunc TestLocationIQ_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword locationiq\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/loggly/loggly.go",
    "content": "package loggly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tdomainPat = regexp.MustCompile(`\\b([a-zA-Z0-9-]+\\.loggly\\.com)\\b`)\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"loggly\"}) + `\\b([a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"loggly\"}\n}\n\n// FromData will find and optionally verify Loggly secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tdomainMatches := domainPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range keyMatches {\n\t\tkey := strings.TrimSpace(match[1])\n\n\t\tfor _, domainMatch := range domainMatches {\n\n\t\t\tdomainRes := strings.TrimSpace(domainMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Loggly,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s\", domainRes, key)),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://\"+domainRes+\"/apiv2/customer\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\t\ts1.SetVerificationError(err, key)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ts1.SetVerificationError(err, key)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Loggly\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Loggly is a cloud-based log management service. Loggly API keys can be used to send, search, and analyze log data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/loggly/loggly_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage loggly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLoggly_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LOGGLY\")\n\tinactiveSecret := testSecrets.MustGetField(\"LOGGLY_INACTIVE\")\n\tdomain := testSecrets.MustGetField(\"LOGGLY_DOMAIN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a loggly secret %s within loggly Domain %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Loggly,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a loggly secret %s within loggly Domain %s but not valid\", inactiveSecret, domain)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Loggly,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a loggly secret %s within %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Loggly,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a loggly secret %s within %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Loggly,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Loggly.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Loggly.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/loggly/loggly_test.go",
    "content": "package loggly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern      = \"096somlu-xkif-4qyy-e02a-j29tr5v0z574\"\n\tinvalidKeyPattern    = \"A96somlu-xkif-4qyy-e02a-j29tr5v0z57Z\"\n\tvalidDomainPattern   = \"w8wW1-tiwRdoa.loggly.com\"\n\tinvalidDomainPattern = \"w8wW1-tiwRdoa.loggy.com\"\n\tkeyword              = \"loggly\"\n)\n\nfunc TestLoggly_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword loggly\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s' '%s'\", keyword, validKeyPattern, validDomainPattern),\n\t\t\twant:  []string{validDomainPattern + \":\" + validKeyPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validKeyPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' url = '%s'\", keyword, invalidKeyPattern, invalidDomainPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/loginradius/loginradius.go",
    "content": "package loginradius\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"loginradius\"}) + `\\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"loginradius\"}\n}\n\n// FromData will find and optionally verify Loginradius secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Loginradius,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.loginradius.com/identity/v2/serverinfo?apikey=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Loginradius\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Loginradius is a customer identity and access management (CIAM) platform. The API keys can be used to manage user identities, authentication, and access control.\"\n}\n"
  },
  {
    "path": "pkg/detectors/loginradius/loginradius_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage loginradius\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLoginradius_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LOGINRADIUS\")\n\tinactiveSecret := testSecrets.MustGetField(\"LOGINRADIUS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a loginradius secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Loginradius,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a loginradius secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Loginradius,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Loginradius.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Loginradius.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/loginradius/loginradius_test.go",
    "content": "package loginradius\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"fb4e966d-7321-6974-b2e7-2525a9554d6f\"\n\tinvalidPattern = \"fb4e966d-7321-6974-b2e7-2525a9554d6\"\n\tkeyword        = \"loginradius\"\n)\n\nfunc TestLoginradius_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword loginradius\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/logzio/logzio.go",
    "content": "package logzio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"logz\"}) + `\\b([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"logz\"}\n}\n\n// FromData will find and optionally verify Logzio secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_LogzIO,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.logz.io/v2/whoami\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-API-TOKEN\", resMatch)\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json; charset=utf-8\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_LogzIO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Logz.io is a cloud observability platform for log analysis and monitoring. Logz.io API tokens can be used to access and manage log data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/logzio/logzio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage logzio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLogzIO_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LOGZIO\")\n\tinactiveSecret := testSecrets.MustGetField(\"LOGZIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a logzio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LogzIO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a logzio secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LogzIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a logzio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LogzIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a logzio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LogzIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"LogzIO.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"LogzIO.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/logzio/logzio_test.go",
    "content": "package logzio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"s1k5frmd-hqx6-h27w-oho2-2jv1k7ww5k48\"\n\tinvalidPattern = \"s1k5frmd-hqx6-h27w-oho2-2jv1k7ww5k4\"\n\tkeyword        = \"logzio\"\n)\n\nfunc TestLogzIO_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword logzio\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/lokalisetoken/lokalisetoken.go",
    "content": "package lokalisetoken\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"lokalise\"}) + `\\b([a-z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"lokalise\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_LokaliseToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Lokalise is a translation management system that helps teams to manage and automate their localization process. Lokalise tokens can be used to access its API and modify project data.\"\n}\n\n// FromData will find and optionally verify LokaliseToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_LokaliseToken,\n\t\t\tRaw:          []byte(resMatch),\n\t\t\tExtraData:    make(map[string]string),\n\t\t}\n\n\t\tif verify {\n\t\t\tprojectCreators, isVerified, verificationErr := verifyLokaliseKey(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\tif len(projectCreators) > 0 {\n\t\t\t\ts1.ExtraData[\"project creators\"] = strings.Join(projectCreators, \", \")\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\ntype projectsResponse struct {\n\tProjects []struct {\n\t\tCreatedByEmail string `json:\"created_by_email\"`\n\t} `json:\"projects\"`\n}\n\nfunc verifyLokaliseKey(ctx context.Context, client *http.Client, token string) ([]string, bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.lokalise.com/api2/projects\", http.NoBody)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\n\treq.Header.Add(\"X-Api-Token\", token)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tvar projectCreators projectsResponse\n\t\tvar creatorsList = make([]string, 0)\n\n\t\tif err := json.NewDecoder(resp.Body).Decode(&projectCreators); err != nil {\n\t\t\t// if failed to decode in case of 200 OK - return true without list\n\t\t\treturn nil, true, nil\n\t\t}\n\n\t\tfor _, project := range projectCreators.Projects {\n\t\t\tcreatorsList = append(creatorsList, project.CreatedByEmail)\n\t\t}\n\n\t\treturn creatorsList, true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil, false, nil\n\tdefault:\n\t\treturn nil, false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/lokalisetoken/lokalisetoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage lokalisetoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLokaliseToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LOKALISE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"LOKALISE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a lokalisetoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LokaliseToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a lokalisetoken secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LokaliseToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"LokaliseToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"LokaliseToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/lokalisetoken/lokalisetoken_test.go",
    "content": "package lokalisetoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"c4ebwpmg9id4vk1kj6skeatz3wdsl5gl62imf9we\"\n\tinvalidPattern = \"c4ebwpmg9id4vk1kj6skeatz3wdsl5gl62imf9w\"\n\tkeyword        = \"lokalisetoken\"\n)\n\nfunc TestLokaliseToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword lokalisetoken\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/loyverse/loyverse.go",
    "content": "package loyverse\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"loyverse\"}) + `\\b([0-9-a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"loyverse\"}\n}\n\n// FromData will find and optionally verify Loyverse secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Loyverse,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.loyverse.com/v1.0/merchant/\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Loyverse\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Loyverse is a point of sale (POS) system that allows businesses to manage sales, inventory, and customer relationships. Loyverse API keys can be used to access and modify this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/loyverse/loyverse_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage loyverse\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLoyverse_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LOYVERSE\")\n\tinactiveSecret := testSecrets.MustGetField(\"LOYVERSE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a loyverse secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Loyverse,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a loyverse secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Loyverse,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Loyverse.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Loyverse.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/loyverse/loyverse_test.go",
    "content": "package loyverse\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"g7w41cfy8n6z7mrlku18wouolg9dt0nh\"\n\tinvalidPattern = \"g7w41cfy8n6z7mrlku18wouolg9dt0n\"\n\tkeyword        = \"loyverse\"\n)\n\nfunc TestLoyverse_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword loyverse\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/lunchmoney/lunchmoney.go",
    "content": "package lunchmoney\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"lunchmoney\"}) + `\\b([a-f0-9]{50})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"lunchmoney\"}\n}\n\n// FromData will find and optionally verify LunchMoney secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_LunchMoney,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://dev.lunchmoney.app/v1/categories\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_LunchMoney\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"LunchMoney is a personal finance tool that helps users manage their finances. LunchMoney API keys can be used to access and modify financial data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/lunchmoney/lunchmoney_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage lunchmoney\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLunchMoney_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LUNCHMONEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"LUNCHMONEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a lunchmoney secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LunchMoney,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a lunchmoney secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_LunchMoney,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"LunchMoney.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"LunchMoney.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/lunchmoney/lunchmoney_test.go",
    "content": "package lunchmoney\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"c7d3c5b3bb87a43f6ecb66b93b467248a7914b9c8bbe135d3d\"\n\tinvalidPattern = \"c7d3c5b3bb87a43f6ecb66b93b467248a7914b9c8bbe135d3\"\n\tkeyword        = \"lunchmoney\"\n)\n\nfunc TestLunchMoney_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword lunchmoney\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/luno/luno.go",
    "content": "package luno\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"luno\"}) + `\\b([a-zA-Z0-9_-]{43})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"luno\"}) + `\\b([a-z0-9]{13})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"luno\"}\n}\n\n// FromData will find and optionally verify Luno secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\ttokenPatMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tuserPatMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Luno,\n\t\t\t\tRaw:          []byte(tokenPatMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.luno.com/api/1/balance\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.SetBasicAuth(userPatMatch, tokenPatMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Luno\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Luno is a cryptocurrency exchange platform that allows users to buy, sell, and store digital currencies. Luno API keys can be used to access account information and perform transactions.\"\n}\n"
  },
  {
    "path": "pkg/detectors/luno/luno_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage luno\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestLuno_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"LUNO\")\n\tuser := testSecrets.MustGetField(\"LUNO_USER\")\n\tinactiveSecret := testSecrets.MustGetField(\"LUNO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a luno secret %s within luno %s\", secret, user)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Luno,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a luno secret %s within  luno %s but not valid\", inactiveSecret, user)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Luno,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Luno.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Luno.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/luno/luno_test.go",
    "content": "package luno\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern   = \"1XyDP7rTIaP622RZTmvzM29VsNc9fubPjk3_M-xFP-F\"\n\tinvalidKeyPattern = \"=XyDP7rTIaP622RZTmvzM29VsNc9fubPjk3_M-xFP-F\"\n\tvalidIdPattern    = \"s5ru6kuyx1vwp\"\n\tinvalidIdPattern  = \"s-ru6kuyx1vwp\"\n\tkeyword           = \"luno\"\n)\n\nfunc TestLuno_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword luno\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s' %s '%s'\", keyword, validKeyPattern, keyword, validIdPattern),\n\t\t\twant:  []string{validKeyPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s' url = '%s'\", keyword, validKeyPattern, validIdPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' url = '%s'\", keyword, invalidKeyPattern, invalidIdPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/m3o/m3o.go",
    "content": "package m3o\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"m3o\"}) + `\\b([0-9A-Za-z]{48})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"m3o\"}\n}\n\n// FromData will find and optionally verify M3o secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_M3o,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\n\t\t\t\t\"days\": 2,\n\t\t\t\t\"location\": \"London\"\n\t\t\t\t}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.m3o.com/v1/weather/Forecast\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_M3o\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"M3o is a cloud platform that provides a set of microservices for building applications. M3o API keys can be used to access these microservices.\"\n}\n"
  },
  {
    "path": "pkg/detectors/m3o/m3o_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage m3o\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestM3o_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"M3O\")\n\tinactiveSecret := testSecrets.MustGetField(\"M3O_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a m3o secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_M3o,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a m3o secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_M3o,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"M3o.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"M3o.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/m3o/m3o_test.go",
    "content": "package m3o\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"Li0HPrJXNlfTJ93qOz606jBnnPpetC4M7ni5i5SRdL9kDA7Z\"\n\tinvalidPattern = \"Li0HPrJXNlfTJ93qOz606jBnnPpetC4M7ni5i5SRdL9kDA7\"\n\tkeyword        = \"m3o\"\n)\n\nfunc TestM3o_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword m3o\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/madkudu/madkudu.go",
    "content": "package madkudu\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"madkudu\"}) + `\\b([0-9a-f]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"madkudu\"}\n}\n\n// FromData will find and optionally verify MadKudu secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_MadKudu,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.madkudu.com/v1/ping\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.SetBasicAuth(resMatch, \"\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_MadKudu\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"MadKudu is a predictive lead scoring service that helps B2B companies identify the best-fit leads based on data. MadKudu API keys can be used to access and manipulate lead scoring data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/madkudu/madkudu_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage madkudu\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMadKudu_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MADKUDU\")\n\tinactiveSecret := testSecrets.MustGetField(\"MADKUDU_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a madkudu secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MadKudu,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a madkudu secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MadKudu,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"MadKudu.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"MadKudu.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/madkudu/madkudu_test.go",
    "content": "package madkudu\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"2c57b42d056b3409b664000fec80af90\"\n\tinvalidPattern = \"2c57b42d056b3409b664000fec80af9\"\n\tkeyword        = \"madkudu\"\n)\n\nfunc TestMadKudu_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword madkudu\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/magicbell/magicbell.go",
    "content": "package magicbell\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"magicbell\"}) + `\\b([a-zA-Z-0-9]{40})\\b`)\n\temailPat = regexp.MustCompile(common.EmailPattern)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"magicbell\"}\n}\n\n// FromData will find and optionally verify MagicBell secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tapiKeyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tuniqueEmailMatches := make(map[string]struct{})\n\tfor _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor _, keyMatch := range apiKeyMatches {\n\t\tapiKeyRes := strings.TrimSpace(keyMatch[1])\n\n\t\tfor emailMatch := range uniqueEmailMatches {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_MagicBell,\n\t\t\t\tRaw:          []byte(apiKeyRes),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.magicbell.com/notification_preferences\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"X-MAGICBELL-API-KEY\", apiKeyRes)\n\t\t\t\treq.Header.Add(\"X-MAGICBELL-USER-EMAIL\", emailMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_MagicBell\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"MagicBell is a notification service. API keys can be used to manage and send notifications.\"\n}\n"
  },
  {
    "path": "pkg/detectors/magicbell/magicbell_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage magicbell\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMagicBell_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MAGICBELL\")\n\tinactiveSecret := testSecrets.MustGetField(\"MAGICBELL_INACTIVE\")\n\tuserEmail := testSecrets.MustGetField(\"MAGICBELL_USER_EMAIL\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a magicbell secret %s with email %s within\", secret, userEmail)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MagicBell,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a magicbell secret %s with email %s within but not valid\", inactiveSecret, userEmail)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MagicBell,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"MagicBell.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"MagicBell.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/magicbell/magicbell_test.go",
    "content": "package magicbell\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"abcde12345-67890fghijklmnopqrs-tuvwxyzYu/ magicbell_email = testuser1005@example.com\"\n\tinvalidPattern = \"abcde12345-67890fghijklmnopqrs#tuvwxyz/testing@go\"\n)\n\nfunc TestMagicBell_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: fmt.Sprintf(\"magicbell: %s\", validPattern),\n\t\t\twant:  []string{\"abcde12345-67890fghijklmnopqrs-tuvwxyzYu\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"magicbell keyword is not close to the real key and id = %s\", validPattern),\n\t\t\twant:  nil,\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"magicbell: %s\", invalidPattern),\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 && test.want != nil {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"expected %d results, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/magnetic/magnetic.go",
    "content": "package magnetic\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"magnetic\"}) + `\\b([0-9Aa-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"magnetic\"}\n}\n\n// FromData will find and optionally verify Magnetic secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Magnetic,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.magnetichq.com/Magnetic/rest/accountsAPI/itemTypes?token=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Magnetic\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Magnetic is a service used for managing accounts and item types. Magnetic tokens can be used to access and modify these resources.\"\n}\n"
  },
  {
    "path": "pkg/detectors/magnetic/magnetic_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage magnetic\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMagnetic_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MAGNETIC_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"MAGNETIC_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a magnetic secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Magnetic,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a magnetic secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Magnetic,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Magnetic.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Magnetic.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/magnetic/magnetic_test.go",
    "content": "package magnetic\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"n8825xxn-pc8e-t4ri-62bo-as2laynut4lu\"\n\tinvalidPattern = \"n8825xxn-pc8e-t4ri-62bo-as2laynut4l\"\n\tkeyword        = \"magnetic\"\n)\n\nfunc TestMagnetic_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword magnetic\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mailboxlayer/mailboxlayer.go",
    "content": "package mailboxlayer\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mailboxlayer\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mailboxlayer\"}\n}\n\n// FromData will find and optionally verify Mailboxlayer secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Mailboxlayer,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://apilayer.net/api/check?access_key=\"+resMatch+\"&email=support@email.com\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\t\t\t\tvalidResponse := strings.Contains(body, `email`) || strings.Contains(body, `\"info\":\"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption.\"`)\n\n\t\t\t\t// if client_id and client_secret is valid -> 403 {\"error\":\"invalid_grant\",\"error_description\":\"Invalid authorization code\"}\n\t\t\t\t// if invalid -> 401 {\"error\":\"access_denied\",\"error_description\":\"Unauthorized\"}\n\t\t\t\t// ingenious!\n\n\t\t\t\tif validResponse {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Mailboxlayer\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mailboxlayer is an email validation and verification service. Mailboxlayer API keys can be used to validate and verify email addresses.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mailboxlayer/mailboxlayer_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mailboxlayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMailboxplayer_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MAILBOXPLAYER_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"MAILBOXPLAYER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailboxlayer secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mailboxlayer,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailboxlayer secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mailboxlayer,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Mailboxplayer.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Mailboxplayer.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mailboxlayer/mailboxlayer_test.go",
    "content": "package mailboxlayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"n0rsfta3vmx8fgdnvx6qmzlfbzr1ras2\"\n\tinvalidPattern = \"n0rsfta3vmx8fgdnvx6qmzlfbzr1ras\"\n\tkeyword        = \"mailboxlayer\"\n)\n\nfunc TestMailboxplayer_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mailboxlayer\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mailchimp/mailchimp.go",
    "content": "package mailchimp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\tkeyPat = regexp.MustCompile(`[0-9a-f]{32}-us[0-9]{1,2}`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"-us\"}\n}\n\n// FromData will find and optionally verify Mailchimp secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// pretty standard regex match\n\tmatches := keyPat.FindAllString(dataStr, -1)\n\n\tfor _, match := range matches {\n\n\t\tresult := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Mailchimp,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\t\tresult.ExtraData = map[string]string{\n\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/mailchimp/\",\n\t\t}\n\n\t\tif verify {\n\t\t\tdatacenter := strings.Split(match, \"-\")[1]\n\n\t\t\t// https://mailchimp.com/developer/guides/marketing-api-conventions/\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://%s.api.mailchimp.com/3.0/\", datacenter), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.SetBasicAuth(\"anystring\", match)\n\t\t\treq.Header.Add(\"accept\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\tresult.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult.AnalysisInfo = map[string]string{\n\t\t\t\t\"key\": match,\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, result)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Mailchimp\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mailchimp is a marketing automation platform and email marketing service. Mailchimp API keys can be used to access and manage email campaigns, audience data, and other marketing resources.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mailchimp/mailchimp_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mailchimp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMailchimp_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MAILCHIMP\")\n\tsecretInactive := testSecrets.MustGetField(\"MAILCHIMP_INACTIVE\")\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailchimp secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mailchimp,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailchimp secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mailchimp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Mailchimp.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t\tgot[i].ExtraData = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Mailchimp.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mailchimp/mailchimp_test.go",
    "content": "package mailchimp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"eda4fd37283490e9d99bcd633627063c-us5\"\n\tinvalidPattern = \"eda4fd37283490e9d99bcd633627063c-us\"\n\tkeyword        = \"mailchimp\"\n)\n\nfunc TestMailchimp_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mailchimp\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mailerlite/mailerlite.go",
    "content": "package mailerlite\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mailerlite\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mailerlite\"}\n}\n\n// FromData will find and optionally verify Mailerlite secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Mailerlite,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.mailerlite.com/api/v2/campaigns\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-MailerLite-ApiKey\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Mailerlite\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"MailerLite is an email marketing service that allows users to create and manage email campaigns. MailerLite API keys can be used to access and modify email campaign data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mailerlite/mailerlite_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mailerlite\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMailerlite_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MAILERLITE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"MAILERLITE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailerlite secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mailerlite,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailerlite secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mailerlite,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Mailerlite.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Mailerlite.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mailerlite/mailerlite_test.go",
    "content": "package mailerlite\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"5avyoep7wl6gua2edxnd6d9bk9up8tp6\"\n\tinvalidPattern = \"5avyoep7wl6gua2edxnd6d9bk9up8tp\"\n\tkeyword        = \"mailerlite\"\n)\n\nfunc TestMailerlite_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mailerlite\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mailgun/mailgun.go",
    "content": "package mailgun\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\ttokenPats = map[string]*regexp.Regexp{\n\t\t\"Original MailGun Token\": regexp.MustCompile(detectors.PrefixRegex([]string{\"mailgun\"}) + `\\b([a-zA-Z0-9-]{72})\\b`),\n\t\t\"Key-MailGun Token\":      regexp.MustCompile(`\\b(key-[a-z0-9]{32})\\b`),\n\t\t\"Hex MailGun Token\":      regexp.MustCompile(`\\b([a-f0-9]{32}-[a-f0-9]{8}-[a-f0-9]{8})\\b`),\n\t}\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mailgun\", \"key-\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Mailgun secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, tokenPat := range tokenPats {\n\t\tfor _, match := range tokenPat.FindAllStringSubmatch(dataStr, -1) {\n\t\t\tuniqueMatches[match[1]] = struct{}{}\n\t\t}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: s.Type(),\n\t\t\tRaw:          []byte(match),\n\t\t\tAnalysisInfo: map[string]string{\"key\": match},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.getClient()\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\t// https://documentation.mailgun.com/docs/mailgun/api-reference/openapi-final/tag/Domains/\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.mailgun.net/v3/domains\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\tif len(token) == 72 {\n\t\t// This matches prior logic, but may not be correct.\n\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", token))\n\t} else {\n\t\t// https://documentation.mailgun.com/docs/mailgun/api-reference/authentication/\n\t\treq.SetBasicAuth(\"api\", token)\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tif res.StatusCode == http.StatusOK {\n\t\tvar domains domainResponse\n\t\tif err := json.NewDecoder(res.Body).Decode(&domains); err != nil {\n\t\t\treturn false, nil, fmt.Errorf(\"error decoding response body: %w\", err)\n\t\t}\n\n\t\tvar extraData map[string]string\n\t\tif len(domains.Items) > 0 {\n\t\t\tsb := strings.Builder{}\n\t\t\tfor i, item := range domains.Items {\n\t\t\t\tif i != 0 {\n\t\t\t\t\tsb.WriteString(\", \")\n\t\t\t\t}\n\t\t\t\tsb.WriteString(item.Name)\n\t\t\t\tsb.WriteString(\" (\")\n\t\t\t\tsb.WriteString(item.State)\n\t\t\t\tsb.WriteString(\",\")\n\t\t\t\tsb.WriteString(item.Type)\n\t\t\t\tif item.IsDisabled {\n\t\t\t\t\tsb.WriteString(\",disabled\")\n\t\t\t\t}\n\t\t\t\tsb.WriteString(\")\")\n\t\t\t}\n\t\t\textraData = map[string]string{\n\t\t\t\t\"Domains\": sb.String(),\n\t\t\t}\n\t\t}\n\n\t\treturn true, extraData, nil\n\t} else if res.StatusCode == http.StatusUnauthorized {\n\t\treturn false, nil, nil\n\t} else {\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\ntype domainResponse struct {\n\tTotalCount int    `json:\"total_count\"`\n\tItems      []item `json:\"items\"`\n}\n\ntype item struct {\n\tID         string `json:\"id\"`\n\tIsDisabled bool   `json:\"is_disabled\"`\n\tName       string `json:\"name\"`\n\tState      string `json:\"state\"`\n\tType       string `json:\"type\"`\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Mailgun\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mailgun is an email automation service. Mailgun tokens can be used to send, receive, and track emails.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mailgun/mailgun_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mailgun\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMailgun_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MAILGUN_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"MAILGUN_INACTIVE\")\n\tkeyDashSecret := testSecrets.MustGetField(\"NEW_MAILGUN_TOKEN_ACTIVE\")\n\tinactiveHexEncodedSecret := testSecrets.MustGetField(\"NEW_MAILGUN_TOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailgun secret %s within https://api.mailgun.net/v3/domains\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mailgun,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified key-dash mailgun pattern token\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailgun secret %s within https://api.mailgun.net/v3/domains\", keyDashSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mailgun,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified key-dash mailgun pattern token\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailgun secret %s within https://api.mailgun.net/v3/domains\", inactiveHexEncodedSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mailgun,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailgun secret %s within but unverified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mailgun,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Mailgun.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t\tgot[i].ExtraData = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Mailgun.FromData() %s  diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mailgun/mailgun_test.go",
    "content": "package mailgun\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidTokenPattern   = \"gRtZTX7RaarK8pXdNcUBDcgvmIP6qiX5QCjxGlABBFJwHO3531XAFxCzlaSZS6KNkBQH7Lmk\"\n\tinvalidTokenPattern = \"=RtZTX7RaarK8pXdNcUBDcgvmIP6qi-5QCjxGlABBFJwHO353-1AFxCzlaSZS6KNkBQH7Lmk\"\n\tvalidKeyPattern     = \"key-dirirygl04i92ww1uuvz9owuc9sb0fpm\"\n\tinvalidKeyPattern   = \"key-dirirygl04i92ww1uuvz9owuc9sb0fp=\"\n\tvalidHexPattern     = \"55c3a879d7efe24929506f368dcff6d4-5eeb6406-757a4247\"\n\tinvalidHexPattern   = \"G5c3a879d7efe24929506f368dcff6d4-5eeb6406-757a4247\"\n\tkeyword             = \"mailgun\"\n)\n\nfunc TestMailgun_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mailgun\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s' pass '%s' %s\", keyword, validTokenPattern, validHexPattern, validKeyPattern),\n\t\t\twant:  []string{validTokenPattern, validHexPattern, validKeyPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s' '%s' '%s\", keyword, invalidTokenPattern, invalidKeyPattern, invalidHexPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mailjetbasicauth/mailjetbasicauth.go",
    "content": "package mailjetbasicauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mailjet\"}) + `\\b([A-Za-z0-9]{87}\\=)`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mailjet\"}\n}\n\n// FromData will find and optionally verify MailJetBasicAuth secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_MailJetBasicAuth,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.mailjet.com/v3/REST/message\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_MailJetBasicAuth\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mailjet is an email delivery service that allows you to send transactional and marketing emails. Mailjet Basic Auth credentials can be used to authenticate and send emails through the Mailjet API.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mailjetbasicauth/mailjetbasicauth_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mailjetbasicauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMailJetBasicAuth_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MAILJETBASICAUTH_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"MAILJETBASICAUTH_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailjetbasicauth secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MailJetBasicAuth,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailjetbasicauth secret %s within but unverified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MailJetBasicAuth,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"MailJetBasicAuth.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"MailJetBasicAuth.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mailjetbasicauth/mailjetbasicauth_test.go",
    "content": "package mailjetbasicauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"49mCNOTU9XTijsrY1QjHFkfjxy9KkCA0g3KuZPTujdKStmVgarAqe2vZvchjuzIEyOjOcANYiZhGnStpSfg5p5i=\"\n\tinvalidPattern = \"49mCNOTU9XTijsrY1QjHFkfjxy9KkCA0g3KuZPTujdKStmVgarAqe2vZvchjuzIEyOjOcANYiZhGnStpSfg5p5i\"\n\tkeyword        = \"mailjetbasicauth\"\n)\n\nfunc TestMailJetBasicAuth_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mailjetbasicauth\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mailjetsms/mailjetsms.go",
    "content": "package mailjetsms\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mailjet\"}) + `\\b([A-Za-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mailjet\"}\n}\n\n// FromData will find and optionally verify MailJetSMS secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_MailJetSMS,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.mailjet.com/v4/sms\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.mailjetsms+json; version=3\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_MailJetSMS\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mailjet is a cloud-based email service provider. Mailjet API keys can be used to send SMS messages and manage email campaigns.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mailjetsms/mailjetsms_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mailjetsms\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMailJetSMS_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MAILJETSMS_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"MAILJETSMS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailjetsms secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MailJetSMS,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailjetsms secret %s within but unverified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MailJetSMS,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"MailJetSMS.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"MailJetSMS.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mailjetsms/mailjetsms_test.go",
    "content": "package mailjetsms\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"EGeBEbLBwAdeY9ectt6hwNKAu2rUEeBw\"\n\tinvalidPattern = \"EGeBEbLBwAdeY9ectt6hwNKAu2rUEeB\"\n\tkeyword        = \"mailjetsms\"\n)\n\nfunc TestMailJetSMS_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mailjetsms\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mailmodo/mailmodo.go",
    "content": "package mailmodo\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mailmodo\"}) + `\\b([A-Z0-9]{7}-[A-Z0-9]{7}-[A-Z0-9]{7}-[A-Z0-9]{7})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mailmodo\"}\n}\n\n// FromData will find and optionally verify Mailmodo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Mailmodo,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.mailmodo.com/api/v1/campaigns?type=CONTACT_LIST\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"mmApiKey\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Mailmodo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mailmodo is a platform for creating and sending interactive emails. Mailmodo API keys can be used to access and manage email campaigns and contacts.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mailmodo/mailmodo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mailmodo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMailmodo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MAILMODO_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"MAILMODO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailmodo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mailmodo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailmodo secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mailmodo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Mailmodo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Mailmodo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mailmodo/mailmodo_test.go",
    "content": "package mailmodo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"D5OZWF8-QBDDBEL-14Z7B49-AHZ06BF\"\n\tinvalidPattern = \"D5OZWF8-QBDDBEL-14Z7B49-AHZ06B\"\n\tkeyword        = \"mailmodo\"\n)\n\nfunc TestMailmodo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mailmodo\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mailsac/mailsac.go",
    "content": "package mailsac\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mailsac\"}) + `\\b(k_[0-9A-Za-z]{36,})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mailsac\"}\n}\n\n// FromData will find and optionally verify Mailsac secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Mailsac,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://mailsac.com/api/addresses\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Mailsac-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Mailsac\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mailsac is a disposable email service used for testing and temporary email purposes. Mailsac API keys can be used to access and manage email addresses and messages.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mailsac/mailsac_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mailsac\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMailsac_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MAILSAC\")\n\tinactiveSecret := testSecrets.MustGetField(\"MAILSAC_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailsac secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mailsac,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mailsac secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mailsac,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Mailsac.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Mailsac.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mailsac/mailsac_test.go",
    "content": "package mailsac\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"k_0Jzm3Vsm2Lm9z3OomKv2Bn1TScRhoKfX1OJk0Oezz6W0nCSLsaXb7XJ0B8ElHnUzASzffdL7LIzhSCWPOqScIG\"\n\tinvalidPattern = \"?_0Jzm3Vsm2Lm9z3OomKv2Bn1TScRhoKfX1OJk0Oezz6W0nCSLsaXb7XJ0B8ElHnUzASzffdL7LIzhSCWPOqScIG\"\n\tkeyword        = \"mailsac\"\n)\n\nfunc TestMailsac_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mailsac\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mandrill/mandrill.go",
    "content": "package mandrill\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mandrill\"}) + `\\b([A-Za-z0-9_-]{22})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mandrill\"}\n}\n\n// FromData will find and optionally verify Mandrill secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Mandrill,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(fmt.Sprintf(`{\"key\": \"%s\"}`, resMatch))\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://mandrillapp.com/api/1.0/users/info\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Mandrill\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mandrill is a transactional email API for Mailchimp users. Mandrill API keys can be used to send and track email messages.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mandrill/mandrill_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mandrill\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMandrill_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MANDRILL_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"MANDRILL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mandrill secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mandrill,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mandrill secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mandrill,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Mandrill.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Mandrill.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mandrill/mandrill_test.go",
    "content": "package mandrill\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"z8A3Owe1WRrHmg4AHQZlwK\"\n\tinvalidPattern = \"z8A3Owe1WRrHmg4AHQZlw\"\n\tkeyword        = \"mandrill\"\n)\n\nfunc TestMandrill_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mandrill\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/manifest/manifest.go",
    "content": "package manifest\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"manifest\"}) + `\\b([a-zA-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"manifest\"}\n}\n\n// FromData will find and optionally verify Manifest secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Manifest,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.manifest.ly/api/v1/checklists/?api_key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Manifest\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Manifest API keys are used to interact with the Manifest service, which provides checklist management and other features.\"\n}\n"
  },
  {
    "path": "pkg/detectors/manifest/manifest_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage manifest\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestManifest_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MANIFEST\")\n\tinactiveSecret := testSecrets.MustGetField(\"MANIFEST_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a manifest secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Manifest,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a manifest secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Manifest,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Manifest.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Manifest.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/manifest/manifest_test.go",
    "content": "package manifest\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"0Nubbkxtphh1c3YHbmB8MipsgpbmczRT\"\n\tinvalidPattern = \"0Nubbkxtphh1c3YHbmB8MipsgpbmczR=\"\n\tkeyword        = \"manifest\"\n)\n\nfunc TestManifest_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword manifest\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mapbox/mapbox.go",
    "content": "package mapbox\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\tidPat  = regexp.MustCompile(`([a-zA-Z-0-9]{4,32})`)\n\tkeyPat = regexp.MustCompile(`\\b(sk\\.[a-zA-Z-0-9\\.]{80,240})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mapbox\"}\n}\n\n// FromData will find and optionally verify MapBox secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor i, idMatch := range idMatches {\n\t\t\tif i == 11 {\n\t\t\t\tresId := strings.TrimSpace(idMatch[1])\n\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MapBox,\n\t\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.mapbox.com/tokens/v2/\"+resId+\"?access_token=\"+resMatch, nil)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tres, err := client.Do(req)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_MapBox\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mapbox provides location-based services and APIs. Mapbox access tokens can be used to interact with these services and modify data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mapbox/mapbox_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mapbox\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMapBox_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MAPBOX_API_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"MAPBOX_API_INACTIVE\")\n\tid := testSecrets.MustGetField(\"MAPBOX_USER\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mapbox secret %s within https://api.mapbox.com/tokens/v2/%s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MapBox,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mapbox secret %s within https://api.mapbox.com/tokens/v2/%s but unverified\", inactiveSecret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MapBox,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"MapBox.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"MapBox.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mapbox/mapbox_test.go",
    "content": "package mapbox\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern   = \"sk.nc7.R5t.P5a.bnM.kvG.s1Q.Gms.RO7.f6q.LMz.YZK.arr.PXw.dmM.Gqt.urp.Oaw.5O8.JCY.grH.x\"\n\tinvalidKeyPattern = \"sk.nc7.R5t.P5a.bnM.kvG.s1Q.Gms.RO7.f6q.coz.YZK.arr.PXw.dmM.Gqt.urp.Oaw.5O8.JCY.grH.=\"\n\tvalidIdPattern    = \"A3xF A3xF A3xF A3xF A3xF A3xF A3xF A3xF A3xF A3xF A3xF\"\n\tinvalidIdPattern  = \"A3x=\"\n\tkeyword           = \"mapbox\"\n)\n\nfunc TestMapBox_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s %s %s\", keyword, validKeyPattern, validIdPattern),\n\t\t\twant:  []string{validKeyPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' id = '%s'\", keyword, invalidKeyPattern, invalidIdPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mapquest/mapquest.go",
    "content": "package mapquest\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mapquest\"}) + `\\b([0-9A-Za-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mapquest\"}\n}\n\n// FromData will find and optionally verify Mapquest secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Mapquest,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.mapquestapi.com/datamanager/v2/get-column-types?key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Mapquest\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mapquest is a web mapping service. Mapquest API keys can be used to access and manipulate mapping data and services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mapquest/mapquest_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mapquest\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMapquest_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MAPQUEST\")\n\tinactiveSecret := testSecrets.MustGetField(\"MAPQUEST_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mapquest secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mapquest,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mapquest secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mapquest,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Mapquest.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Mapquest.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mapquest/mapquest_test.go",
    "content": "package mapquest\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"l8FuOZVcR9DMCukVKAVXHAUx9LmquzuZ\"\n\tinvalidPattern = \"l8FuOZVcR9DMCukVKAVXHAUx9Lmquzu\"\n\tkeyword        = \"mapquest\"\n)\n\nfunc TestMapquest_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mapquest\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/marketstack/marketstack.go",
    "content": "package marketstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"marketstack\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"marketstack\"}\n}\n\n// FromData will find and optionally verify Marketstack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Marketstack,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.marketstack.com/v1/eod?access_key=%s&symbols=AAPL\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Marketstack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Marketstack is a service that provides real-time market data. Marketstack API keys can be used to access this data and perform various operations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/marketstack/marketstack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage marketstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMarketstack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MARKETSTACK\")\n\tinactiveSecret := testSecrets.MustGetField(\"MARKETSTACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a marketstack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Marketstack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a marketstack secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Marketstack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Marketstack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Marketstack.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/marketstack/marketstack_test.go",
    "content": "package marketstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"5f6g9mh0zl6awmdyx8gjnkn1ybuqrvfv\"\n\tinvalidPattern = \"5f6g9mh0zl6awmdyx8gjnkn1ybuqrvf\"\n\tkeyword        = \"marketstack\"\n)\n\nfunc TestMarketstack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword marketstack\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mattermostpersonaltoken/mattermostpersonaltoken.go",
    "content": "package mattermostpersonaltoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"mattermost\"}) + `\\b([a-z0-9]{26})\\b`)\n\tserverPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mattermost\"}) + `\\b([A-Za-z0-9-_]{1,}.cloud.mattermost.com)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mattermost\"}\n}\n\n// FromData will find and optionally verify MattermostPersonalToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tserverMatches := serverPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, serverMatch := range serverMatches {\n\t\t\tserverRes := strings.TrimSpace(serverMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_MattermostPersonalToken,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + serverRes),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://\"+serverRes+\"/api/v4/users/stats\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_MattermostPersonalToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mattermost is an open-source, self-hostable online chat service with file sharing, search, and integrations. Mattermost Personal Tokens can be used to authenticate API requests to a Mattermost server.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mattermostpersonaltoken/mattermostpersonaltoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mattermostpersonaltoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMattermostPersonalToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MATTERMOSTPERSONALTOKEN_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"MATTERMOSTPERSONALTOKEN_INACTIVE\")\n\tserver := testSecrets.MustGetField(\"MATTERMOSTPERSONALTOKEN_SERVER\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mattermost secret %s within mattermost server %s\", secret, server)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MattermostPersonalToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mattermost secret %s within mattermost server %s but not valid\", inactiveSecret, server)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MattermostPersonalToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"MattermostPersonalToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no RawV2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"MattermostPersonalToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mattermostpersonaltoken/mattermostpersonaltoken_test.go",
    "content": "package mattermostpersonaltoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern      = \"e2rgfqdg7xpzbq0nmj8l68gmuz\"\n\tinvalidKeyPattern    = \"=2rgfqdg7xpzbq0nmj8l68gmuz\"\n\tvalidServerPattern   = \"example.cloud.mattermost.com\"\n\tinvalidServerPattern = \"?xample.cloud.mattermost.com\"\n\tkeyword              = \"mattermost\"\n)\n\nfunc TestMattermostPersonalToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mattermost\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s' %s '%s'\", keyword, validKeyPattern, keyword, validServerPattern),\n\t\t\twant:  []string{validKeyPattern + validServerPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s' '%s'\", keyword, validKeyPattern, validServerPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' url = '%s'\", keyword, invalidKeyPattern, invalidServerPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mavenlink/mavenlink.go",
    "content": "package mavenlink\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mavenlink\"}) + `\\b([0-9a-z]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mavenlink\"}\n}\n\n// FromData will find and optionally verify Mavenlink secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Mavenlink,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.mavenlink.com/api/v1/workspaces.json\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Mavenlink\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mavenlink is a project management software that provides tools to plan, manage, and analyze projects. Mavenlink API keys can be used to access and modify project data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mavenlink/mavenlink_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mavenlink\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMavenlink_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MAVENLINK\")\n\tinactiveSecret := testSecrets.MustGetField(\"MAVENLINK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mavenlink secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mavenlink,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mavenlink secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mavenlink,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Mavenlink.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Mavenlink.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mavenlink/mavenlink_test.go",
    "content": "package mavenlink\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"e18w9n0upom99bzram7826tce87yp3d4mulnicn8eballopb2xh4r8m8da2p7bus\"\n\tinvalidPattern = \"e18w9n0upom99bzram7826tce87yp3d4mulnicn8eballopb2xh4r8m8da2p7bu\"\n\tkeyword        = \"mavenlink\"\n)\n\nfunc TestMavenlink_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mavenlink\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/maxmindlicense/v1/maxmindlicense.go",
    "content": "package maxmindlicense\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"maxmind\", \"geoip\"}) + `\\b([0-9]{2,7})\\b`)\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"maxmind\", \"geoip\"}) + `\\b([0-9A-Za-z]{16})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"maxmind\", \"geoip\"}\n}\n\nfunc (Scanner) Version() int { return 1 }\n\n// FromData will find and optionally verify MaxMindLicense secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, keyMatch := range keyMatches {\n\t\tkeyRes := strings.TrimSpace(keyMatch[1])\n\n\t\tfor _, idMatch := range idMatches {\n\t\t\tidRes := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_MaxMindLicense,\n\t\t\t\tRedacted:     idRes,\n\t\t\t\tRaw:          []byte(keyRes),\n\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/maxmind/\",\n\t\t\t\t\t\"version\":        fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://geoip.maxmind.com/geoip/v2.1/country/8.8.8.8\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.SetBasicAuth(idRes, keyRes)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_MaxMindLicense\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"MaxMind provides IP intelligence through their GeoIP service which can be used to determine geographical location and other information about an IP address.\"\n}\n"
  },
  {
    "path": "pkg/detectors/maxmindlicense/v1/maxmindlicense_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage maxmindlicense\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMaxMindLicense_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MAXMIND_LICENSE\")\n\tuser := testSecrets.MustGetField(\"MAXMIND_USER\")\n\tinactiveSecret := testSecrets.MustGetField(\"MAXMIND_LICENSE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a geoip secret %s within with maxmind user %s\", secret, user)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MaxMindLicense,\n\t\t\t\t\tRedacted:     \"510124\",\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/maxmind/\",\n\t\t\t\t\t\t\"version\":        \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a maxmind secret %s within with maxmind user %s\", inactiveSecret, user)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MaxMindLicense,\n\t\t\t\t\tRedacted:     \"510124\",\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/maxmind/\",\n\t\t\t\t\t\t\"version\":        \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"MaxMindLicense.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"MaxMindLicense.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/maxmindlicense/v1/maxmindlicense_test.go",
    "content": "package maxmindlicense\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern   = \"LAwdpRpHOnYE0Wyc\"\n\tinvalidKeyPattern = \"=AwdpRpHOnYE0Wyc\"\n\tvalidIdPattern    = \"42\"\n\tinvalidIdPattern  = \"4R\"\n\tkeyword           = \"geoip\"\n)\n\nfunc TestMaxMindLicense_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword geoip\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s' %s '%s'\", keyword, validKeyPattern, keyword, validIdPattern),\n\t\t\twant:  []string{validKeyPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s' '%s'\", keyword, validKeyPattern, validIdPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' url = '%s'\", keyword, invalidKeyPattern, invalidIdPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/maxmindlicense/v2/maxmindlicense_v2.go",
    "content": "package maxmindlicense\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\tkeyPat        = regexp.MustCompile(`\\b([a-zA-Z0-9]{6}_[a-zA-Z0-9]{29}_mmk)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"_mmk\"}\n}\n\nfunc (Scanner) Version() int { return 2 }\n\n// FromData will find and optionally verify MaxMindLicense secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, key := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[key[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueMatches {\n\t\tr := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_MaxMindLicense,\n\t\t\tRaw:          []byte(key),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/maxmind/\",\n\t\t\t\t\"version\":        fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tverified, vErr := s.verify(ctx, client, key)\n\t\t\tr.Verified = verified\n\t\t\tr.SetVerificationError(vErr, key)\n\t\t}\n\n\t\tresults = append(results, r)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) verify(ctx context.Context, client *http.Client, key string) (bool, error) {\n\tdata := url.Values{}\n\tdata.Add(\"license_key\", key)\n\n\t// https://dev.maxmind.com/license-key-validation-api\n\treq, err := http.NewRequestWithContext(\n\t\tctx, http.MethodPost, \"https://secret-scanning.maxmind.com/secrets/validate-license-key\",\n\t\tstrings.NewReader(data.Encode()))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusNoContent:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_MaxMindLicense\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"MaxMind provides IP intelligence through its GeoIP databases and services. MaxMind license keys are used to access these services and databases.\"\n}\n"
  },
  {
    "path": "pkg/detectors/maxmindlicense/v2/maxmindlicense_v2_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage maxmindlicense\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMaxMindLicense(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MAXMIND_LICENSE_V2\")\n\tinactiveSecret := testSecrets.MustGetField(\"MAXMIND_LICENSE_INACTIVE_V2\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a geoip secret %s\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MaxMindLicense,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/maxmind/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a maxmind secret %s\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MaxMindLicense,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/maxmind/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"MaxMindLicense.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"MaxMindLicense.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/maxmindlicense/v2/maxmindlicense_v2_test.go",
    "content": "package maxmindlicense\n\nimport (\n\t\"context\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"testing\"\n)\n\nfunc TestMaxMind_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"key in URL\",\n\t\t\tinput: `#cd /home/xtreamcodes/iptv_xtream_codes/\n#wget \"https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=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&suffix=tar.gz\" -qO /home/xtreamcodes/iptv_xtream_codes/GeoLite2-City.mmdb.tar.gz\n#tar -xf /home/xtreamcodes/iptv_xtream_codes/GeoLite2-City.mmdb.tar.gz`,\n\t\t\twant: []string{\"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\"},\n\t\t},\n\t\t{\n\t\t\tname: \"ENV VAR\",\n\t\t\tinput: `BASE_URL=https://plausible.example.com\nSECRET_KEY_BASE=GLVzDZW04FzuS1gMcmBRVhwgd4Gu9YmSl/k/TqfTUXti7FLBd7aflXeQDdwCj6Cz\nTOTP_VAULT_KEY=dsxvbn3jxDd16az2QpsX5B8O+llxjQ2SJE2i5Bzx38I=\nMAXMIND_LICENSE_KEY=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\nMAXMIND_EDITION=GeoLite2-City\nGOOGLE_CLIENT_ID=...`,\n\t\t\twant: []string{\"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\"},\n\t\t},\n\t\t{\n\t\t\tname: \"Random .conf\",\n\t\t\tinput: `# LicenseKey is from your MaxMind account\nLicenseKey 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`,\n\t\t\twant: []string{\"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\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/meaningcloud/meaningcloud.go",
    "content": "package meaningcloud\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"meaningcloud\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"meaningcloud\"}\n}\n\ntype response struct {\n\tDeepTime float64 `json:\"deepTime\"`\n}\n\n// FromData will find and optionally verify MeaningCloud secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_MeaningCloud,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tbody := &bytes.Buffer{}\n\t\t\twriter := multipart.NewWriter(body)\n\t\t\tfw, err := writer.CreateFormField(\"key\")\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t_, err = io.Copy(fw, strings.NewReader(resMatch))\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfw, err = writer.CreateFormField(\"txt\")\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t_, err = io.Copy(fw, strings.NewReader(\"test\"))\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\twriter.Close()\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.meaningcloud.com/lang-4.0/identification\", bytes.NewReader(body.Bytes()))\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", writer.FormDataContentType())\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\tvar r response\n\t\t\t\t\tif err := json.NewDecoder(res.Body).Decode(&r); err != nil {\n\t\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif r.DeepTime > 0 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_MeaningCloud\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"MeaningCloud is a text analytics service used to extract insights from unstructured content. MeaningCloud API keys can be used to access and utilize these text analytics services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/meaningcloud/meaningcloud_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage meaningcloud\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMeaningCloud_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MEANINGCLOUD\")\n\tinactiveSecret := testSecrets.MustGetField(\"MEANINGCLOUD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a meaningcloud secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MeaningCloud,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a meaningcloud secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MeaningCloud,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"MeaningCloud.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"MeaningCloud.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/meaningcloud/meaningcloud_test.go",
    "content": "package meaningcloud\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"srs9jhxnzvodflr77ze5ela384fwx3gr\"\n\tinvalidPattern = \"srs9jhxnzvodflr77ze5ela384fwx3g\"\n\tkeyword        = \"meaningcloud\"\n)\n\nfunc TestMeaningCloud_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword meaningcloud\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mediastack/mediastack.go",
    "content": "package mediastack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mediastack\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mediastack\"}\n}\n\n// FromData will find and optionally verify MediaStack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_MediaStack,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := 10 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.mediastack.com/v1/news?access_key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_MediaStack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"MediaStack is a service that provides real-time news data. MediaStack API keys can be used to access this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mediastack/mediastack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mediastack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMediaStack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MEDIASTACK\")\n\tinactiveSecret := testSecrets.MustGetField(\"MEDIASTACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mediastack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MediaStack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mediastack secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MediaStack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"MediaStack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"MediaStack.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mediastack/mediastack_test.go",
    "content": "package mediastack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"3cadij78xzo69hzopa70dtqfjivd497o\"\n\tinvalidPattern = \"3cadij78xzo69hzopa70dtqfjivd497\"\n\tkeyword        = \"mediastack\"\n)\n\nfunc TestMediaStack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mediastack\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/meistertask/meistertask.go",
    "content": "package meistertask\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"meistertask\"}) + `\\b([a-zA-Z0-9]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"meistertask\"}\n}\n\n// FromData will find and optionally verify Meistertask secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Meistertask,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.meistertask.com/api/projects\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Meistertask\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Meistertask is a task management tool that allows users to manage projects and tasks. Meistertask API keys can be used to access and modify project and task data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/meistertask/meistertask_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage meistertask\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMeistertask_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MEISTERTASK\")\n\tinactiveSecret := testSecrets.MustGetField(\"MEISTERTASK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a meistertask secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Meistertask,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a meistertask secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Meistertask,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Meistertask.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Meistertask.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/meistertask/meistertask_test.go",
    "content": "package meistertask\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"dFZqvIPCmhFhwNuGSZyboxnWjFLrzDrSimrUF8UaYbY\"\n\tinvalidPattern = \"dFZqvIPCmhFhwNuGSZyboxnWjFLrzDrSimrUF8UaYb\"\n\tkeyword        = \"meistertask\"\n)\n\nfunc TestMeistertask_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword meistertask\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/meraki/meraki.go",
    "content": "package meraki\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// merakiOrganizations is the partial response from the /organizations api of cisco Meraki.\n// api docs: https://developer.cisco.com/meraki/api-v1/get-organizations/\ntype merakiOrganizations struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\tapiKey = regexp.MustCompile(detectors.PrefixRegex([]string{\"meraki\"}) + `([0-9a-f]{40})`)\n)\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"meraki\"}\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Cisco Meraki is a cloud-managed IT solution that provides networking, security, and device management through an easy-to-use interface.\" +\n\t\t\"Meraki APIs make it possible to rapidly deploy and manage networks at scale, build on a platform of intelligent, cloud-connected IT products, and engage with users in powerful new ways.\"\n}\n\n// FromData will find and optionally verify Meraki API Key secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// uniqueMatches will hold unique match values and ensure we only process unique matches found in the data string\n\tvar uniqueMatches = make(map[string]struct{})\n\n\tfor _, match := range apiKey.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Meraki,\n\t\t\tRaw:          []byte(match),\n\t\t\tExtraData:    make(map[string]string),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.getClient()\n\t\t\torganizations, isVerified, verificationErr := verifyMerakiApiKey(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\tif verificationErr != nil {\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t}\n\n\t\t\t// if organizations are not nil, which means token was verified.\n\t\t\tfor _, org := range organizations {\n\t\t\t\t// format: ExtraData{\"organization_1\": \"Example\", organization_2\": \"Example\"}\n\t\t\t\ts1.ExtraData[fmt.Sprintf(\"organization_%s\", org.ID)] = org.Name\n\t\t\t}\n\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Meraki\n}\n\n/*\nverifyMerakiApiKey verifies if the passed matched api key for meraki is active or not.\ndocs: https://developer.cisco.com/meraki/api-v1/authorization/#authorization\n*/\nfunc verifyMerakiApiKey(ctx context.Context, client *http.Client, match string) ([]merakiOrganizations, bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.meraki.com/api/v1/organizations\", http.NoBody)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\n\t// set the required auth header\n\treq.Header.Set(\"X-Cisco-Meraki-API-Key\", match)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\t// in case token is verified, capture the organization id's and name which are accessible via token.\n\t\tvar organizations []merakiOrganizations\n\t\tif err = json.NewDecoder(resp.Body).Decode(&organizations); err != nil {\n\t\t\treturn nil, false, err\n\t\t}\n\n\t\treturn organizations, true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn nil, false, nil\n\tdefault:\n\t\treturn nil, false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/meraki/meraki_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage meraki\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMeraki_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MERAKI\")\n\tinactiveSecret := testSecrets.MustGetField(\"MERAKI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a meraki apikey %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Meraki,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a meraki apikey secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Meraki,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a meraki apikey %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Meraki,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a meraki secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Meraki,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Meraki.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Meraki.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/meraki/meraki_test.go",
    "content": "package meraki\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\t// example picked from: https://github.com/CiscoLearning/ciscolive-ltrcrt-1100/blob/cd0b8f14883ccd70e2db370f8f7a36534bdfe073/02-intro-python/code/meraki_api_info.md?plain=1#L8\n\tvalidPattern = `Information used in API calls for meraki\nVariable name | Initial Value\napiKey |e9e0f062f587b423bb6cc6328eb786d75b45783e\nbaseUrl |https://api.meraki.com/api/v1\norganizationId |646829496481091262\nnetworkId |L_646829496481117067\nserial |`\n\n\tvalidPatternWithNoKeyword = `Information used in API calls\nVariable name | Initial Value\napiKey |e9e0f062f587b423bb6cc6328eb786d75b45783e\nbaseUrl |https://api.meraki.com/api/v1\norganizationId |646829496481091262\nnetworkId |L_646829496481117067\nserial |`\n\n\tinvalidPattern = \"001A1E0092C7a711d7679d%d0d442d59b05ce65D\"\n)\n\nfunc TestMeraki_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: fmt.Sprintf(\"meraki token = '%s'\", validPattern),\n\t\t\twant:  []string{\"e9e0f062f587b423bb6cc6328eb786d75b45783e\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"meraki token keyword is not close to the real token = '%s'\", validPatternWithNoKeyword),\n\t\t\twant:  nil,\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"meraki = '%s'\", invalidPattern),\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mesibo/mesibo.go",
    "content": "package mesibo\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mesibo\"}) + `\\b([0-9A-Za-z]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mesibo\"}\n}\n\n// FromData will find and optionally verify Mesibo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Mesibo,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.mesibo.com/api.php?op=useradd&token=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\n\t\t\t\tif !strings.Contains(body, \"AUTHFAIL\") {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Mesibo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mesibo is a real-time communication platform that allows developers to add messaging, voice, and video calls to their apps. Mesibo tokens can be used to access and interact with the Mesibo API.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mesibo/mesibo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mesibo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMesibo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MESIBO\")\n\tinactiveSecret := testSecrets.MustGetField(\"MESIBO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mesibo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mesibo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mesibo secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mesibo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Mesibo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Mesibo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mesibo/mesibo_test.go",
    "content": "package mesibo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"Q07RXEk41f4vgUqGsYu1AZJSLheG42pgyna2lf12FomyL1w80ZBfDL9f6Mbb66JU\"\n\tinvalidPattern = \"Q07RXEk41f4vgUqGsYu1AZJSLheG42pgyna2lf12FomyL1w80ZBfDL9f6Mbb66J\"\n\tkeyword        = \"mesibo\"\n)\n\nfunc TestMesibo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mesibo\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/messagebird/messagebird.go",
    "content": "package messagebird\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"messagebird\"}) + `\\b([A-Za-z0-9_-]{25})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"messagebird\"}\n}\n\n// FromData will find and optionally verify MessageBird secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_MessageBird,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://rest.messagebird.com/messages\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"AccessKey %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_MessageBird\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"MessageBird is a cloud communications platform. MessageBird API keys can be used to send and receive SMS, voice, and chat messages.\"\n}\n"
  },
  {
    "path": "pkg/detectors/messagebird/messagebird_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage messagebird\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMessageBird_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MESSAGEBIRD_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"MESSAGEBIRD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a messagebird secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MessageBird,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a messagebird secret %s within but unverified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MessageBird,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"MessageBird.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"MessageBird.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/messagebird/messagebird_test.go",
    "content": "package messagebird\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"w9BKrNMo9pO-mkKpUr9i7ypGd\"\n\tinvalidPattern = \"w9BKrNMo9pO-mkKpUr9i7ypG\"\n\tkeyword        = \"messagebird\"\n)\n\nfunc TestMessageBird_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword messagebird\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/metaapi/metaapi.go",
    "content": "package metaapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"metaapi\", \"meta-api\"}) + `\\b([0-9a-f]{64})\\b`)\n\tspellPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"metaapi\", \"meta-api\"}) + `\\b([0-9a-f]{24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"metaapi\", \"meta-api\"}\n}\n\n// FromData will find and optionally verify MetaAPI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tspellMatches := spellPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, spellMatch := range spellMatches {\n\t\tresSpellMatch := strings.TrimSpace(spellMatch[1])\n\n\t\tfor _, match := range matches {\n\t\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_MetaAPI,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.meta-api.io/api/spells/%s/runSync\", resSpellMatch), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\treq.Header.Add(\"apikey\", resMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tbody, errBody := io.ReadAll(res.Body)\n\n\t\t\t\t\tif errBody == nil {\n\t\t\t\t\t\tbodyStr := string(body)\n\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && strings.Contains(bodyStr, `\"success\":true`) {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_MetaAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Detects MetaAPI credentials, which are typically API keys used for accessing the MetaAPI service.\"\n}\n"
  },
  {
    "path": "pkg/detectors/metaapi/metaapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage metaapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMetaAPI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"METAAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"METAAPI_INACTIVE\")\n\tspell := testSecrets.MustGetField(\"METAAPI_SPELL\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a meta-api spell %s with meta-api secret %s within\", spell, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MetaAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a meta-api spell %s with meta-api secret %s within but not valid\", spell, inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MetaAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"MetaAPI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"MetaAPI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/metaapi/metaapi_test.go",
    "content": "package metaapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern     = \"2ee2a64a2db2a508cd457856d2694f4399f9525bd71a7f2fbd4e155e13d0c4ec\"\n\tinvalidKeyPattern   = \"=ee2a64a2db2a508cd457856d2694f4399f9525bd71a7f2fbd4e155e13d0c4ec\"\n\tvalidSpellPattern   = \"622ac3b6f4fce35975d66841\"\n\tinvalidSpellPattern = \"=22ac3b6f4fce35975d66841\"\n\tkeyword             = \"metaapi\"\n)\n\nfunc TestMetaAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid key pattern - with keyword metaapi\",\n\t\t\tinput: fmt.Sprintf(\"%s %s %s %s\", keyword, validKeyPattern, keyword, validSpellPattern),\n\t\t\twant:  []string{validKeyPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s' '%s'\", keyword, validKeyPattern, validSpellPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' url = '%s'\", keyword, invalidKeyPattern, invalidSpellPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/metabase/metabase.go",
    "content": "package metabase\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"metabase\"}) + `\\b([a-zA-Z0-9-]{36})\\b`)\n\n\tbaseURL = regexp.MustCompile(detectors.PrefixRegex([]string{\"metabase\"}) + `\\b(https?:\\/\\/[-a-zA-Z0-9@:%._\\+~#=]{7,256})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"metabase\"}\n}\n\n// FromData will find and optionally verify Metabase secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\turlMatches := baseURL.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, urlMatch := range urlMatches {\n\t\t\tresURLMatch := strings.TrimSpace(urlMatch[1])\n\n\t\t\tu, err := detectors.ParseURLAndStripPathAndParams(resURLMatch)\n\t\t\tif err != nil {\n\t\t\t\t// if the URL is invalid just move onto the next one\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Metabase,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resURLMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tu.Path = \"/api/user/current\"\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"X-Metabase-Session\", resMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tbody, err := io.ReadAll(res.Body)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif res.StatusCode == http.StatusOK && json.Valid(body) {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Metabase\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Metabase is an open-source business intelligence tool. Metabase session tokens can be used to access and interact with the Metabase API.\"\n}\n"
  },
  {
    "path": "pkg/detectors/metabase/metabase_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage metabase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMetabase_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"METABASE_SESSION_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"INACTIVE_METABASE_SESSION_TOKEN\")\n\tlocalUrl := testSecrets.MustGetField(\"METABASE_URL\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a metabase secret %s with metabase url %s\", secret, localUrl)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Metabase,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a metabase secret %s within but not valid with metabase url %s\", inactiveSecret, localUrl)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Metabase,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Metabase.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no RawV2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Metabase.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/metabase/metabase_test.go",
    "content": "package metabase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern       = \"Y49ftoUer6aYZOzdkRzlENnW8PHnD9zf9dP9\"\n\tinvalidKeyPattern     = \"=49ftoUer6aYZOzdkRzlENnW8PHnD9zf9dP9\"\n\tvalidBaseUrlPattern   = \"https://tiwRdoa.metabase.com\"\n\tinvalidBaseUrlPattern = \"https://tiwRdo^.metabase.com\"\n\tkeyword               = \"metabase\"\n)\n\nfunc TestMetabase_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword metabase\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s' %s '%s'\", keyword, validKeyPattern, keyword, validBaseUrlPattern),\n\t\t\twant:  []string{validKeyPattern + validBaseUrlPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s' '%s'\", keyword, validKeyPattern, validBaseUrlPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' url = '%s'\", keyword, invalidKeyPattern, invalidBaseUrlPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/metrilo/metrilo.go",
    "content": "package metrilo\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"metrilo\"}) + `\\b([a-z0-9]{16})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"metrilo\"}\n}\n\n// FromData will find and optionally verify Metrilo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Metrilo,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\"time\":1518004715732,\"token\": \"` + resMatch + `\" ,\"platform\":\"Wordpress 4.2.7 / Woocommerce 3.5\",\"pluginVersion\":\"1.1.0\",\"params\":{\"id\":\"12\",\"name\":\"Clothing\",\"url\":\"https://dummysite.com\"}}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://trk.mtrl.me/category\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Content-Length\", `0`)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Metrilo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Metrilo is an analytics and CRM platform for e-commerce. Metrilo API keys can be used to access and manage e-commerce data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/metrilo/metrilo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage metrilo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMetrilo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"METRILO\")\n\tinactiveSecret := testSecrets.MustGetField(\"METRILO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a metrilo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Metrilo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a metrilo secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Metrilo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Metrilo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Metrilo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/metrilo/metrilo_test.go",
    "content": "package metrilo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"yzmil2yrmu5qfd09\"\n\tinvalidPattern = \"yzmil2yrmu5qfd0\"\n\tkeyword        = \"metrilo\"\n)\n\nfunc TestMetrilo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword metrilo\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/microsoftteamswebhook/microsoftteamswebhook.go",
    "content": "package microsoftteamswebhook\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`(https:\\/\\/[a-zA-Z-0-9]+\\.webhook\\.office\\.com\\/webhookb2\\/[a-zA-Z-0-9]{8}-[a-zA-Z-0-9]{4}-[a-zA-Z-0-9]{4}-[a-zA-Z-0-9]{4}-[a-zA-Z-0-9]{12}\\@[a-zA-Z-0-9]{8}-[a-zA-Z-0-9]{4}-[a-zA-Z-0-9]{4}-[a-zA-Z-0-9]{4}-[a-zA-Z-0-9]{12}\\/IncomingWebhook\\/[a-zA-Z-0-9]{32}\\/[a-zA-Z-0-9]{8}-[a-zA-Z-0-9]{4}-[a-zA-Z-0-9]{4}-[a-zA-Z-0-9]{4}-[a-zA-Z-0-9]{12})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"webhook.office.com\"}\n}\n\n// FromData will find and optionally verify MicrosoftTeamsWebhook secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_MicrosoftTeamsWebhook,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\t\ts1.ExtraData = map[string]string{\n\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/microsoftteams/\",\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyWebhook(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyWebhook(ctx context.Context, client *http.Client, webhookURL string) (bool, error) {\n\tpayload := strings.NewReader(`{'text':''}`)\n\treq, err := http.NewRequestWithContext(ctx, \"POST\", webhookURL, payload)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tswitch {\n\tcase res.StatusCode == http.StatusBadRequest:\n\t\tif strings.Contains(string(body), \"Text is required\") {\n\t\t\treturn true, nil\n\t\t}\n\t\treturn false, fmt.Errorf(\"unexpected response body: %s\", string(body))\n\tcase res.StatusCode < 200 || res.StatusCode >= 500:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status: %d\", res.StatusCode)\n\tdefault:\n\t\treturn false, nil\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_MicrosoftTeamsWebhook\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Microsoft Teams Webhooks allow external services to communicate with Teams channels by sending messages to a unique URL.\"\n}\n"
  },
  {
    "path": "pkg/detectors/microsoftteamswebhook/microsoftteamswebhook_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage microsoftteamswebhook\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMicrosoftTeamsWebhook_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MICROSOFT_TEAMS_WEBHOOK\")\n\tinactiveSecret := testSecrets.MustGetField(\"MICROSOFT_TEAMS_WEBHOOK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a microsoftteamswebhook secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MicrosoftTeamsWebhook,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected response body\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(400, \"nope\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a microsoftteamswebhook secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MicrosoftTeamsWebhook,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a microsoftteamswebhook secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MicrosoftTeamsWebhook,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a microsoftteamswebhook secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MicrosoftTeamsWebhook,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a microsoftteamswebhook secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MicrosoftTeamsWebhook,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"MicrosoftTeamsWebhook.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Errorf(\"MicrosoftTeamsWebhook.FromData() verificationError = %v, wantVerificationErr %v\", got[i].VerificationError(), tt.wantVerificationErr)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"MicrosoftTeamsWebhook.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/microsoftteamswebhook/microsoftteamswebhook_test.go",
    "content": "package microsoftteamswebhook\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"https://uQu9muaokn.webhook.office.com/webhookb2/8IoQDUDl-eqlp-q0zl-tlhi-HTxVfS864oTv@b4infsZa-qpMp-wOV0-T-3g-44wVII0y5vF6/IncomingWebhook/SnuHwf1vaX7oNK32hSnCcLSNYFD8qtfW/uiqxkMLH-gw-a-RxJ7-9rpT-3W85tSlbX4nv\"\n\tinvalidPattern = \"https://uQu9muaokn.webhook.office.com/webhookb2/8IoQDUDl-eqlp-q0zl-tlhi-HTxVfS864oTv@b4infsZa-qpMp-wOV0-T-3g-44wVII0y5vF6/IncomingWebhook/SnuHwf1vaX7oNK32hSnCcLSNYFD8qtfW/uiqxkMLH-gw-a-RxJ7-9rpT-3W85tSlbX4n\"\n\tkeyword        = \"microsoftteamswebhook\"\n)\n\nfunc TestMicrosoftTeamsWebhook_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword microsoftteamswebhook\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mindmeister/mindmeister.go",
    "content": "package mindmeister\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mindmeister\"}) + `\\b([a-zA-Z0-9]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mindmeister\"}\n}\n\n// FromData will find and optionally verify Mindmeister secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Mindmeister,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.mindmeister.com/services/rest/oauth2?method=mm.maps.getList\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `\"stat\":\"ok\"`)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t} \n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Mindmeister\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mindmeister is a mind mapping tool that helps users organize and visualize their thoughts. Mindmeister API keys can be used to access and manipulate mind maps and other data within the platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mindmeister/mindmeister_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mindmeister\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMindmeister_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MINDMEISTER\")\n\tinactiveSecret := testSecrets.MustGetField(\"MINDMEISTER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mindmeister secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mindmeister,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mindmeister secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mindmeister,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Mindmeister.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Mindmeister.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mindmeister/mindmeister_test.go",
    "content": "package mindmeister\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"yXzajuNRDuuophXvznVAnzPO7lIFOd8RAAAzsSwZ79z\"\n\tinvalidPattern = \"yXzajuNRDuuophXvznVAnzPO7lIFOd8RAAAzsSwZ79\"\n\tkeyword        = \"mindmeister\"\n)\n\nfunc TestMindmeister_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mindmeister\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/miro/miro.go",
    "content": "package miro\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"miro\"}) + `\\b([0-9a-zA-Z]{27})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"miro\"}\n}\n\n// FromData will find and optionally verify Miro secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Miro,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.miro.com/v1/users/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Miro\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Miro is an online collaborative whiteboarding platform. Miro API keys can be used to access and modify data and collaborate on whiteboards.\"\n}\n"
  },
  {
    "path": "pkg/detectors/miro/miro_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage miro\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMiro_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MIRO\")\n\tinactiveSecret := testSecrets.MustGetField(\"MIRO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a miro secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Miro,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a miro secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Miro,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Miro.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Miro.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/miro/miro_test.go",
    "content": "package miro\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"1S9ppaQalVHeB3rGJRjNJ7G0HEA\"\n\tinvalidPattern = \"1S9ppaQalVHeB3rGJRjNJ7G0HE\"\n\tkeyword        = \"miro\"\n)\n\nfunc TestMiro_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword miro\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mite/mite.go",
    "content": "package mite\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mite\"}) + `\\b([0-9a-z]{16})\\b`)\n\turlPat = regexp.MustCompile(`\\b([0-9a-z-]{1,}.mite.yo.lk)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mite\"}\n}\n\n// FromData will find and optionally verify Mite secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\turlMatches := urlPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, urlMatch := range urlMatches {\n\t\t\tresURL := strings.TrimSpace(urlMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Mite,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://%s/account.json\", resURL), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\treq.Header.Add(\"X-MiteApiKey\", resMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Mite\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mite is a time tracking tool. Mite API keys can be used to access and modify time tracking data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mite/mite_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mite\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMite_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MITE\")\n\turl := testSecrets.MustGetField(\"MITE_URL\")\n\tinactiveSecret := testSecrets.MustGetField(\"MITE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mite secret %s within %s\", secret, url)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mite,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mite secret %s within %s but not valid\", inactiveSecret, url)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mite,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Mite.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Mite.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mite/mite_test.go",
    "content": "package mite\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern   = \"2w3933xk6uav9ff2\"\n\tinvalidKeyPattern = \"Xw3933xk6uav9ff2\"\n\tvalidUrlPattern   = \"www.mite.yo.lk\"\n\tinvalidUrlPattern = \"WWW.mite.yo.lk\"\n\tkeyword           = \"mite\"\n)\n\nfunc TestMite_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mite\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s' '%s'\", keyword, validKeyPattern, validUrlPattern),\n\t\t\twant:  []string{validKeyPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validKeyPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' url = '%s'\", keyword, invalidKeyPattern, invalidUrlPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mixmax/mixmax.go",
    "content": "package mixmax\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mixmax\"}) + `\\b([a-zA-Z0-9_-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mixmax\"}\n}\n\n// FromData will find and optionally verify Mixmax secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Mixmax,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.mixmax.com/v1/users/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treq.Header.Add(\"X-API-Token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Mixmax\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mixmax is a communication platform for email enhancement and automation. Mixmax API tokens can be used to access and manage email communication and automation features.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mixmax/mixmax_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mixmax\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMixmax_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MIXMAX\")\n\tinactiveSecret := testSecrets.MustGetField(\"MIXMAX_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mixmax secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mixmax,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mixmax secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mixmax,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Mixmax.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Mixmax.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mixmax/mixmax_test.go",
    "content": "package mixmax\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"en0h_MAbEdRtEPRN-3CDREyU2E9s2lhogDbZ\"\n\tinvalidPattern = \"en0h_MAbEdRtEPRN-3CDREyU2E9s2lhogDb\"\n\tkeyword        = \"mixmax\"\n)\n\nfunc TestMixmax_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mixmax\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mixpanel/mixpanel.go",
    "content": "package mixpanel\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mixpanel\"}) + `\\b([a-zA-Z0-9-]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"mixpanel\"}) + `\\b([a-zA-Z0-9.-]{30,40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mixpanel\"}\n}\n\n// FromData will find and optionally verify Mixpanel secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\ttokenPatMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tuserPatMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Mixpanel,\n\t\t\t\tRaw:          []byte(tokenPatMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://mixpanel.com/api/app/me\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.SetBasicAuth(userPatMatch, tokenPatMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Mixpanel\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mixpanel is an analytics service that tracks user interactions with web and mobile applications. Mixpanel keys can be used to access and analyze user data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mixpanel/mixpanel_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mixpanel\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMixpanel_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MIXPANEL\")\n\tid := testSecrets.MustGetField(\"MIXPANEL_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"MIXPANEL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mixpanel secret %s within  mixpanel %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mixpanel,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mixpanel secret %s within  mixpanel %s but not valid\", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mixpanel,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Mixpanel.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Mixpanel.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mixpanel/mixpanel_test.go",
    "content": "package mixpanel\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern   = \"M0UXRQ2DBF5rEJ1qlJTgJvwGqUJ7D8TQ\"\n\tinvalidKeyPattern = \"=0UXRQ2DBF5rEJ-qlJTgJvwGqUJ\"\n\tvalidIdPattern    = \"1bTaEGIzJRnBN77woEGPwMiuLS68HN\"\n\tinvalidIdPattern  = \"=bTaEGIzJRnBN7.woEGPwMiuLS68HN\"\n\tkeyword           = \"mixpanel\"\n)\n\nfunc TestMixpanel_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mixpanel\",\n\t\t\tinput: fmt.Sprintf(\"%s %s %s\", keyword, validKeyPattern, validIdPattern),\n\t\t\twant:  []string{validKeyPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s' '%s'\", keyword, validKeyPattern, validIdPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' url = '%s'\", keyword, invalidKeyPattern, invalidIdPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mockaroo/mockaroo.go",
    "content": "package mockaroo\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mockaroo\"}) + `\\b([0-9a-z]{8})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mockaroo\"}\n}\n\n// FromData will find and optionally verify Mockaroo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Mockaroo,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.mockaroo.com/api/types?key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\tvar t typeRes\n\t\t\t\t\terr = json.NewDecoder(res.Body).Decode(&t)\n\t\t\t\t\tif err == nil && len(t.Types) > 0 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Mockaroo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mockaroo is a tool for generating realistic data for testing and development. Mockaroo API keys can be used to access and generate this data.\"\n}\n\ntype typeRes struct {\n\tTypes []struct {\n\t\tName       string `json:\"name\"`\n\t\tType       any    `json:\"type\"`\n\t\tParameters []any  `json:\"parameters\"`\n\t} `json:\"types\"`\n}\n"
  },
  {
    "path": "pkg/detectors/mockaroo/mockaroo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mockaroo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMockaroo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MOCKAROO\")\n\tinactiveSecret := testSecrets.MustGetField(\"MOCKAROO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mockaroo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mockaroo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mockaroo secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mockaroo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Mockaroo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Mockaroo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mockaroo/mockaroo_test.go",
    "content": "package mockaroo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"mlm9iqkm\"\n\tinvalidPattern = \"mlm9iqk\"\n\tkeyword        = \"mockaroo\"\n)\n\nfunc TestMockaroo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mockaroo\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/moderation/moderation.go",
    "content": "package moderation\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"moderation\"}) + `\\b([a-zA-Z0-9]{36}\\.[a-zA-Z0-9]{115}\\.[a-zA-Z0-9_]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"moderation\"}\n}\n\n// FromData will find and optionally verify Moderation secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Moderation,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := 5 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\tpayload := strings.NewReader(`{\"value\": \"This is an english text\"}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://moderationapi.com/api/v1/analyze/language\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\treq.Header.Add(\"Content-type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Moderation\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Moderation API keys are used to access the Moderation API, which provides services for analyzing and moderating content.\"\n}\n"
  },
  {
    "path": "pkg/detectors/moderation/moderation_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage moderation\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestModeration_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MODERATION\")\n\tinactiveSecret := testSecrets.MustGetField(\"MODERATION_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a moderation secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Moderation,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a moderation secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Moderation,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Moderation.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Moderation.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/moderation/moderation_test.go",
    "content": "package moderation\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"JZxuB8KrC1jO7tANvLp2D6oWF5Mq9yBaL3Hg.PZxyW0aTuBvNcK9rfsav3Lm1jOq8Fg2DtE7oX5uAM3yBC4HsQqLmN5aQ9vWjvNcK9Sfv45rLmvNcK9rLBC4m2yB8oC3uD7tda14E6rAf0gHsMJdLnOp.JZxuB8KrC1jO7tANvLp2D6oWF5Mq9yBaL3HgEpX1zK4\"\n\tinvalidPattern = \"=ZxuB8KrC1jO7tANvLp2D6oWF5Mq9yBaL3Hg.PZxyW0aTuBvNcK9rfsav3Lm1jOq8Fg2DtE7oX5uAM3yBC4HsQqLmN5aQ9vWjvNcK9Sfv45rLmvNcK9rLBC4m2yB8oC3uD7tda14E6rAf0gHsMJdLnOp.JZxuB8KrC1jO7tANvLp2D6oWF5Mq9yBaL3HgEpX1zK4\"\n\tkeyword        = \"moderation\"\n)\n\nfunc TestModeration_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword moderation\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/monday/monday.go",
    "content": "package monday\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"monday\"}) + `\\b(eyJ[A-Za-z0-9-_]{15,100}\\.eyJ[A-Za-z0-9-_]{100,300}\\.[A-Za-z0-9-_]{25,100})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"monday\"}\n}\n\n// FromData will find and optionally verify Monday secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Monday,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyMondayPAT(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\n\t\t\tif s1.Verified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\"key\": resMatch,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Monday\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Monday.com is a work operating system that powers teams to run projects and workflows with confidence. Monday API keys can be used to access and modify data and workflows on the platform.\"\n}\n\nfunc verifyMondayPAT(ctx context.Context, client *http.Client, token string) (bool, error) {\n\tpayload := strings.NewReader(`{\"query\":\"{boards(limit:1){id name}}\"}`)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://api.monday.com/v2\", payload)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Authorization\", token)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/monday/monday_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage monday\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMonday_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MONDAY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"MONDAY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a monday secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Monday,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a monday secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Monday,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Monday.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Monday.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/monday/monday_test.go",
    "content": "package monday\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"eyJ4tw1Jd1PM49YlUdnNZGN3zlQ3HnKpTT2Nf6WPEPuPP_ZyH37eh.eyJE8Rw930nGgPXjJFKD0LHFqyaEg1yEFxTCGBTWiI9aPsWdrg4iR_DLit6QOeUN7I_Qgm3Wrl5udWmh3n3Gy-x5UCbuuEWnVugOJH0.pH9QMElnn4DhIkvdaVxRaa_g12E3tfV4LSTgXI7WWjs6BtdPu8l6GL5s3\"\n\tinvalidPattern = \"?yJ4tw1Jd1PM49YlUdnNZGN3zlQ3HnKpTT2Nf6WPEPuPP_ZyH37eh.eyJE8Rw930nGgPXjJFKD0LHFqyaEg1yEFxTCGBTWiI9aPsWdrg4iR_DLit6QOeUN7I_Qgm3Wrl5udWmh3n3Gy-x5UCbuuEWnVugOJH0.pH9QMElnn4DhIkvdaVxRaa_g12E3tfV4LSTgXI7WWjs6BtdPu8l6GL5s3\"\n\tkeyword        = \"monday\"\n)\n\nfunc TestMonday_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword monday\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mongodb/mongodb.go",
    "content": "package mongodb\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"go.mongodb.org/mongo-driver/mongo\"\n\t\"go.mongodb.org/mongo-driver/mongo/options\"\n\t\"go.mongodb.org/mongo-driver/mongo/readpref\"\n\t\"go.mongodb.org/mongo-driver/x/mongo/driver/auth\"\n)\n\ntype Scanner struct {\n\ttimeout time.Duration // Zero value means \"default timeout\"\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)\n\nvar (\n\tdefaultTimeout = 5 * time.Second\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tconnStrPat = regexp.MustCompile(`\\b(mongodb(?:\\+srv)?://(?P<username>\\S{3,50}):(?P<password>\\S{3,88})@(?P<host>[-.%\\w]+(?::\\d{1,5})?(?:,[-.%\\w]+(?::\\d{1,5})?)*)(?:/(?P<authdb>[\\w-]+)?(?P<options>\\?\\w+=[\\w@/.$-]+(?:&(?:amp;)?\\w+=[\\w@/.$-]+)*)?)?)(?:\\b|$)`)\n\t// TODO: Add support for sharded cluster, replica set and Atlas Deployment.\n\tplaceholderPasswordPat = regexp.MustCompile(`^[xX]+|\\*+$`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mongodb\"}\n}\n\n// FromData will find and optionally verify MongoDB secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tlogger := logContext.AddLogger(ctx).Logger().WithName(\"mongodb\")\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]string)\n\tfor _, match := range connStrPat.FindAllStringSubmatch(dataStr, -1) {\n\t\t// Filter out common placeholder passwords.\n\t\tpassword := match[3]\n\t\tif password == \"\" || placeholderPasswordPat.MatchString(password) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// If the query string contains `&amp;` the options will not be parsed.\n\t\tconnStr := strings.Replace(strings.TrimSpace(match[1]), \"&amp;\", \"&\", -1)\n\t\tconnUrl, err := url.Parse(connStr)\n\t\tif err != nil {\n\t\t\tlogger.V(3).Info(\"Skipping invalid URL\", \"err\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tparams := connUrl.Query()\n\t\tfor k, v := range connUrl.Query() {\n\t\t\tif len(v) > 0 {\n\t\t\t\tswitch k {\n\t\t\t\tcase \"tls\":\n\t\t\t\t\tif v[0] == \"false\" {\n\t\t\t\t\t\tparams.Set(\"tls\", \"false\")\n\t\t\t\t\t} else {\n\t\t\t\t\t\tparams.Set(\"tls\", \"true\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconnUrl.RawQuery = params.Encode()\n\t\tconnStr = connUrl.String()\n\n\t\tuniqueMatches[connStr] = password\n\t}\n\n\tfor connStr, password := range uniqueMatches {\n\t\tr := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_MongoDB,\n\t\t\tRaw:          []byte(connStr),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/mongo/\",\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := s.timeout\n\t\t\tif timeout == 0 {\n\t\t\t\ttimeout = defaultTimeout\n\t\t\t}\n\n\t\t\tisVerified, vErr := verifyUri(ctx, connStr, timeout)\n\t\t\tr.Verified = isVerified\n\t\t\tif isErrDeterminate(vErr) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tr.SetVerificationError(vErr, password)\n\n\t\t\tif isVerified {\n\t\t\t\tr.AnalysisInfo = map[string]string{\n\t\t\t\t\t\"key\": connStr,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tresults = append(results, r)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {\n\treturn false, \"\"\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"MongoDB is a NoSQL database that uses a document-oriented data model. MongoDB credentials can be used to access and manipulate the database.\"\n}\n\nfunc isErrDeterminate(err error) bool {\n\tvar authErr *auth.Error\n\treturn errors.As(err, &authErr)\n}\n\nfunc verifyUri(ctx context.Context, connStr string, timeout time.Duration) (bool, error) {\n\tctx, cancel := context.WithTimeout(ctx, timeout)\n\tdefer cancel()\n\n\tclientOptions := options.Client().SetTimeout(timeout).ApplyURI(connStr)\n\tif err := clientOptions.Validate(); err != nil {\n\t\treturn false, err\n\t}\n\n\tclient, err := mongo.Connect(ctx, clientOptions)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_ = client.Disconnect(ctx)\n\t}()\n\terr = client.Ping(ctx, readpref.Primary())\n\treturn err == nil, err\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_MongoDB\n}\n"
  },
  {
    "path": "pkg/detectors/mongodb/mongodb_integration_test.go",
    "content": "//go:build detectors && integration\n// +build detectors,integration\n\npackage mongodb\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/brianvoe/gofakeit/v7\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/testcontainers/testcontainers-go\"\n\t\"github.com/testcontainers/testcontainers-go/modules/mongodb\"\n\t\"github.com/testcontainers/testcontainers-go/wait\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestIntegrationMongoDB_FromChunk(t *testing.T) {\n\n\tctx := context.Background()\n\n\tmongoDbUser := gofakeit.Username()\n\tmongoDbPass := gofakeit.Password(true, true, true, false, false, 10)\n\n\tmongoContainer, err := mongodb.RunContainer(\n\t\tctx,\n\t\ttestcontainers.WithImage(\"mongo:7.0.11\"),\n\t\tmongodb.WithUsername(mongoDbUser),\n\t\tmongodb.WithPassword(mongoDbPass),\n\t\ttestcontainers.WithWaitStrategy(\n\t\t\t// mongodb logs \"Waiting for connections\" twice after that it starts accepting connections\n\t\t\twait.ForLog(\"Waiting for connections\").WithOccurrence(2).WithStartupTimeout(10*time.Second),\n\t\t),\n\t)\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer mongoContainer.Terminate(ctx)\n\n\tport, err := mongoContainer.MappedPort(ctx, \"27017\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\thost, err := mongoContainer.Host(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// mongodb+srv://mongotester:Risa0y3t35Si1qT3@cluster0.z8js2ni.mongodb.net/?retryWrites=true&w=majority\n\t// mongodb+srv://mongotester:risa0y3t35Si1qT3@cluster0.z8js2ni.mongodb.net/?retryWrites=true&w=majority\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"mongodb://%s:%s@%s:%s/?retryWrites=true&w=majority\", mongoDbUser, mongoDbPass, host, port.Port())),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MongoDB,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/mongo/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"mongodb://%s:%s@%s:%s/?retryWrites=true&w=majority\", mongoDbUser, \"invalidPassword\", host, port)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MongoDB,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/mongo/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified but for connection timeout\",\n\t\t\ts:    Scanner{timeout: 1 * time.Microsecond},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"mongodb://%s:%s@%s:%s/?retryWrites=true&w=majority\", mongoDbUser, mongoDbPass, host, port.Port())),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MongoDB,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/mongo/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, bad host\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"mongodb://%s:%s@%s:%s/?retryWrites=true&w=majority\", mongoDbUser, mongoDbPass, \"bad.host\", port.Port())),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MongoDB,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/mongo/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"MongoDB.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationErr = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(tt.want, got, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"MongoDB.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestMongoDB_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MONGODB_URI\")\n\tinactiveSecret := testSecrets.MustGetField(\"MONGODB_INACTIVE_URI\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mongodb secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MongoDB,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/mongo/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mongodb secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MongoDB,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/mongo/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified but for connection timeout\",\n\t\t\ts:    Scanner{timeout: 1 * time.Microsecond},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mongodb secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MongoDB,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/mongo/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, bad host\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mongodb secret %s within\", strings.ReplaceAll(secret, \".mongodb.net\", \".mongodb.net.bad\"))),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MongoDB,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/mongo/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"MongoDB.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationErr = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(tt.want, got, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"MongoDB.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mongodb/mongodb_test.go",
    "content": "package mongodb\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\nfunc TestMongoDB_Pattern(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tdata        string\n\t\tshouldMatch bool\n\t\tmatch       string\n\t\tskip        bool\n\t}{\n\t\t// True positives\n\t\t{\n\t\t\tname:        \"long_password\",\n\t\t\tdata:        `mongodb://agenda-live:m21w7PFfRXQwfHZU1Fgx0rTX29ZBQaWMODLeAjsmyslVcMmcmy6CnLyu3byVDtdLYcCokze8lIE4KyAgSCGZxQ==@agenda-live.mongo.cosmos.azure.com:10255/?retryWrites=false&ssl=true&replicaSet=globaldb&maxIdleTimeMS=120000&appName=@agenda-live@`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       `mongodb://agenda-live:m21w7PFfRXQwfHZU1Fgx0rTX29ZBQaWMODLeAjsmyslVcMmcmy6CnLyu3byVDtdLYcCokze8lIE4KyAgSCGZxQ==@agenda-live.mongo.cosmos.azure.com:10255/?appName=%40agenda-live%40&maxIdleTimeMS=120000&replicaSet=globaldb&retryWrites=false&ssl=true`,\n\t\t},\n\t\t{\n\t\t\tname:        \"long_password2\",\n\t\t\tdata:        `mongodb://csb0230eada-2354-4c73-b3e4-8a1aaa996894:AiNtEyASbdXR5neJmTStMzKGItX2xvKuyEkcy65rviKD0ggZR19E1iVFIJ5ZAIY1xvvAiS5tOXsmACDbKDJIhQ==@csb0230eada-2354-4c73-b3e4-8a1aaa996894.mongo.cosmos.cloud-hostname.com:10255/csb-db0230eada-2354-4c73-b3e4-8a1aaa996894?ssl=true&replicaSet=globaldb&retrywrites=false&maxIdleTimeMS=120000&appName=@csb0230eada-2354-4c73-b3e4-8a1aaa996894@`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       `mongodb://csb0230eada-2354-4c73-b3e4-8a1aaa996894:AiNtEyASbdXR5neJmTStMzKGItX2xvKuyEkcy65rviKD0ggZR19E1iVFIJ5ZAIY1xvvAiS5tOXsmACDbKDJIhQ==@csb0230eada-2354-4c73-b3e4-8a1aaa996894.mongo.cosmos.cloud-hostname.com:10255/csb-db0230eada-2354-4c73-b3e4-8a1aaa996894?appName=%40csb0230eada-2354-4c73-b3e4-8a1aaa996894%40&maxIdleTimeMS=120000&replicaSet=globaldb&retrywrites=false&ssl=true`,\n\t\t},\n\t\t{\n\t\t\tname:        \"long_password3\",\n\t\t\tdata:        `mongodb://amsdfasfsadfdfdfpshot:6xNRRsdfsdfafd9NodO8vAFFBEHidfdfdfa87QDKXdCMubACDbhfQH1g==@amssdfafdafdadbsnapshot.mongo.cosmos.azure.com:10255/?ssl=true&replicaSet=globaldb&retrywrites=false&maxIdleTimeMS=120000&appName=@amssadfasdfdbsnsdfadfapshot@`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       `mongodb://amsdfasfsadfdfdfpshot:6xNRRsdfsdfafd9NodO8vAFFBEHidfdfdfa87QDKXdCMubACDbhfQH1g==@amssdfafdafdadbsnapshot.mongo.cosmos.azure.com:10255/?appName=%40amssadfasdfdbsnsdfadfapshot%40&maxIdleTimeMS=120000&replicaSet=globaldb&retrywrites=false&ssl=true`,\n\t\t},\n\t\t{\n\t\t\tname:        \"single_host\",\n\t\t\tdata:        `mongodb://myDBReader:D1fficultP%40ssw0rd@mongodb0.example.com`,\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"single_host+port\",\n\t\t\tdata:        `mongodb://myDBReader:D1fficultP%40ssw0rd@mongodb0.example.com:27017`,\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"single_host+port+authdb\",\n\t\t\tdata:        `mongodb://myDBReader:D1fficultP%40ssw0rd@mongodb0.example.com:27017/?authSource=admin`,\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"single_host_ip\",\n\t\t\tdata:        `mongodb://myDBReader:D1fficultP%40ssw0rd@192.168.74.143`,\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"single_host_ip+port\",\n\t\t\tdata:        `mongodb://myDBReader:D1fficultP%40ssw0rd@192.168.74.143:27017`,\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"multiple_hosts_ip\",\n\t\t\tdata:        `mongodb://root:root@192.168.74.143:27018,192.168.74.143:27019`,\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"multiple_hosts_ip+slash\",\n\t\t\tdata:        `mongodb://root:root@192.168.74.143:27018,192.168.74.143:27019/`,\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"multiple_hosts+port+authdb\",\n\t\t\tdata:        `mongodb://myDBReader:D1fficultP%40ssw0rd@mongodb0.example.com:27017,mongodb0.example.com:27017,mongodb0.example.com:27017/?authSource=admin`,\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"multiple_hosts+options\",\n\t\t\tdata:        `mongodb://username:password@mongodb1.example.com:27317,mongodb2.example.com,mongodb2.example.com:270/?connectTimeoutMS=300000&replicaSet=mySet&authSource=aDifferentAuthDB`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       `mongodb://username:password@mongodb1.example.com:27317,mongodb2.example.com,mongodb2.example.com:270/?authSource=aDifferentAuthDB&connectTimeoutMS=300000&replicaSet=mySet`,\n\t\t},\n\t\t{\n\t\t\tname:        \"multiple_hosts2\",\n\t\t\tdata:        `mongodb://prisma:risima@srv1.bu2lt.mongodb.net:27017,srv2.bu2lt.mongodb.net:27017,srv3.bu2lt.mongodb.net:27017/test?retryWrites=true&w=majority`,\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t// TODO: These fail because the Go driver doesn't explicitly support `authMechanism=DEFAULT`[1].\n\t\t// However, this seems like a valid option[2] and I'm going to try to get that behaviour changed.\n\t\t//\n\t\t// [1] https://github.com/mongodb/mongo-go-driver/blob/master/x/mongo/driver/connstring/connstring.go#L450-L451\n\t\t// [2] https://www.mongodb.com/docs/drivers/node/current/fundamentals/authentication/mechanisms/\n\t\t{\n\t\t\tname:        \"encoded_options1\",\n\t\t\tdata:        `mongodb://dave:password@localhost:27017/?authMechanism=DEFAULT&amp;authSource=db&amp;ssl=true&quot;`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       \"mongodb://dave:password@localhost:27017/?authMechanism=DEFAULT&authSource=db&ssl=true\",\n\t\t},\n\t\t{\n\t\t\tname:        \"encoded_options2\",\n\t\t\tdata:        `mongodb://cefapp:MdTc8Kc8DzlTE1RUl1JVDGS4zw1U1t6145sPWqeStWA50xEUKPfUCGlnk3ACkfqH6qLAwpnm9awpY1m8dg0YlQ==@cefapp.documents.azure.com:10250/?ssl=true&amp;sslverifycertificate=false`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       \"mongodb://cefapp:MdTc8Kc8DzlTE1RUl1JVDGS4zw1U1t6145sPWqeStWA50xEUKPfUCGlnk3ACkfqH6qLAwpnm9awpY1m8dg0YlQ==@cefapp.documents.azure.com:10250/?ssl=true&sslverifycertificate=false\",\n\t\t},\n\t\t// TODO: `%2Ftmp%2Fmongodb-27017.sock` fails with url.Parse.\n\t\t// Then again, TruffleHog will never be able to verify a local socket on a remote machine.\n\t\t{\n\t\t\tname:        \"unix_socket\",\n\t\t\tdata:        `mongodb://u%24ername:pa%24%24w%7B%7Drd@%2Ftmp%2Fmongodb-27017.sock/test`,\n\t\t\tshouldMatch: true,\n\t\t\tskip:        true,\n\t\t},\n\t\t{\n\t\t\tname:        \"dashes\",\n\t\t\tdata:        `mongodb://db-user:db-password@mongodb-instance:27017/db-name`,\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname: \"protocol+srv\",\n\t\t\t// TODO: Figure out how to handle `mongodb+srv`. It performs a DNS lookup, which fails if the host doesn't exist.\n\t\t\t//data:        `mongodb+srv://root:randompassword@cluster0.ab1cd.mongodb.net/mydb?retryWrites=true&w=majority`,\n\t\t\tdata:        `mongodb://root:randompassword@cluster0.ab1cd.mongodb.net/mydb?retryWrites=true&w=majority`,\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"0.0.0.0_host\",\n\t\t\tdata:        `mongodb://username:password@0.0.0.0:27017/?authSource=admin`,\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"localhost_host\",\n\t\t\tdata:        `mongodb://username:password@localhost:27017/?authSource=admin`,\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"127.0.0.1_host\",\n\t\t\tdata:        `mongodb://username:password@127.0.0.1:27017/?authSource=admin`,\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"docker_internal_host\",\n\t\t\tdata:        `mongodb://username:password@host.docker.internal:27018/?authMechanism=PLAIN&tls=true&tlsCertificateKeyFile=/etc/certs/client.pem&tlsCaFile=/etc/certs/rootCA-cert.pem`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       `mongodb://username:password@host.docker.internal:27018/?authMechanism=PLAIN&tls=true&tlsCaFile=%2Fetc%2Fcerts%2FrootCA-cert.pem&tlsCertificateKeyFile=%2Fetc%2Fcerts%2Fclient.pem`,\n\t\t},\n\t\t{\n\t\t\tname:        \"options_authsource_external\",\n\t\t\tdata:        `mongodb://AKIAAAAAAAAAAAA:t9t2mawssecretkey@localhost:27017/?authMechanism=MONGODB-AWS&authsource=$external`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       `mongodb://AKIAAAAAAAAAAAA:t9t2mawssecretkey@localhost:27017/?authMechanism=MONGODB-AWS&authsource=%24external`,\n\t\t},\n\t\t{\n\t\t\tname:        \"generic1\",\n\t\t\tdata:        `mongodb://root:8b6zfr4b@fastgpt-mongo-mongodb.ns-hti44k5d.svc:27017/`,\n\t\t\tshouldMatch: true,\n\t\t},\n\n\t\t// False positives\n\t\t{\n\t\t\tname:        \"no_password\",\n\t\t\tdata:        `mongodb://mongodb0.example.com:27017/?replicaSet=myRepl`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"empty\",\n\t\t\tdata:        `mongodb://username:@mongodb0.example.com:27017/?replicaSet=myRepl`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"invalid_userinfo\",\n\t\t\tdata:        `mongodb+srv://<user>:<password>@myMongoCluster.mongocluster.cosmos.azure.com`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"placeholders_x+single_host\",\n\t\t\tdata:        `mongodb://xxxx:xxxxx@xxxxxxx:3717/zkquant?replicaSet=mgset-3017917`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"placeholders_x+multiple_hosts\",\n\t\t\tdata:        `mongodb://xxxx:xxxxx@xxxxxxx:3717,xxxxxxx:3717/zkquant?replicaSet=mgset-3017917`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tif test.skip {\n\t\t\t\tt.SkipNow()\n\t\t\t}\n\t\t\ts := Scanner{}\n\n\t\t\tresults, err := s.FromData(context.Background(), false, []byte(test.data))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"MongoDB.FromData() error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif test.shouldMatch {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"%s: did not receive a match for '%v' when one was expected\", test.name, test.data)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\texpected := test.data\n\t\t\t\tif test.match != \"\" {\n\t\t\t\t\texpected = test.match\n\t\t\t\t}\n\t\t\t\tresult := string(results[0].Raw)\n\t\t\t\tif result != expected {\n\t\t\t\t\tt.Errorf(\"%s: did not receive expected match.\\n\\texpected: '%s'\\n\\t  actual: '%s'\", test.name, expected, result)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif len(results) > 0 {\n\t\t\t\t\tt.Errorf(\"%s: received a match for '%v' when one wasn't wanted\", test.name, test.data)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/monkeylearn/monkeylearn.go",
    "content": "package monkeylearn\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"monkeylearn\"}) + `\\b([0-9a-f]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"monkeylearn\"}\n}\n\n// FromData will find and optionally verify MonkeyLearn secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_MonkeyLearn,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.monkeylearn.com/v3/classifiers\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Token %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_MonkeyLearn\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"MonkeyLearn is a machine learning platform that allows you to analyze text with custom machine learning models. API keys can be used to access and manage these models.\"\n}\n"
  },
  {
    "path": "pkg/detectors/monkeylearn/monkeylearn_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage monkeylearn\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMonkeyLearn_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MONKEYLEARN\")\n\tinactiveSecret := testSecrets.MustGetField(\"MONKEYLEARN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a monkeylearn secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MonkeyLearn,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a monkeylearn secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MonkeyLearn,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"MonkeyLearn.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"MonkeyLearn.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/monkeylearn/monkeylearn_test.go",
    "content": "package monkeylearn\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"39b75e2230941948df43ecc2a05feb437a2a367d\"\n\tinvalidPattern = \"39b75e2230941948df43ecc2a05feb437a2a367\"\n\tkeyword        = \"monkeylearn\"\n)\n\nfunc TestMonkeyLearn_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword monkeylearn\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/moonclerk/moonclerk.go",
    "content": "package moonclerk\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"moonclerk\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"moonclerk\"}\n}\n\n// FromData will find and optionally verify Moonclerk secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Moonclerk,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.moonclerk.com/forms\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.moonclerk+json;version=1\")\n\t\t\treq.Header.Add(\"Authorization\", \"Token token=\"+resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Moonclerk\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Moonclerk is an online payment system that allows businesses to accept recurring payments. Moonclerk API keys can be used to manage and access payment data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/moonclerk/moonclerk_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage moonclerk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMoonclerk_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MOONCLERCK\")\n\tinactiveSecret := testSecrets.MustGetField(\"MOONCLERCK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a moonclerk secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Moonclerk,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a moonclerk secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Moonclerk,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Moonclerk.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Moonclerk.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/moonclerk/moonclerk_test.go",
    "content": "package moonclerk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"xku7x4a3ow1a0yg3m36pxzx3nugmonep\"\n\tinvalidPattern = \"xku7x4a3ow1a0yg3m36pxzx3nugmone\"\n\tkeyword        = \"moonclerk\"\n)\n\nfunc TestMoonclerk_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword moonclerk\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/moosend/moosend.go",
    "content": "package moosend\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"moosend\"}) + `\\b([0-9Aa-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"moosend\"}\n}\n\n// FromData will find and optionally verify Moosend secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Moosend,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.moosend.com/v3/lists.json?apikey=\"+resMatch+\"&format=json\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\t\t\t\tif strings.Contains(body, \"CreatedOn\") {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Moosend\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Moosend is an email marketing service provider. Moosend API keys can be used to access and manage email marketing campaigns, subscriber lists, and other related data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/moosend/moosend_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage moosend\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMoosend_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MOOSEND_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"MOOSEND_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a moosend secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Moosend,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a moosend secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Moosend,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Moosend.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Moosend.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/moosend/moosend_test.go",
    "content": "package moosend\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"444b05A5-3c4f-cd9c-26df-21eac3f8907a\"\n\tinvalidPattern = \"444b05A5-3c4f-cd9c-26df-21eac3f8907\"\n\tkeyword        = \"moosend\"\n)\n\nfunc TestMoosend_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword moosend\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/moralis/moralis.go",
    "content": "package moralis\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"moralis\"}) + `\\b([0-9a-zA-Z]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"moralis\"}\n}\n\n// FromData will find and optionally verify Moralis secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Moralis,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://deep-index.moralis.io/api/v2/0xd8da6bf26964af9d7eed9e03e53415d37aa96045/nft?chain=eth&format=decimal\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"X-API-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Moralis\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Moralis is a platform for building and deploying decentralized applications (dApps). Moralis API keys can be used to interact with blockchain data and services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/moralis/moralis_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage moralis\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMoralis_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MORALIS\")\n\tinactiveSecret := testSecrets.MustGetField(\"MORALIS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a moralis secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Moralis,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a moralis secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Moralis,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Moralis.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Moralis.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/moralis/moralis_test.go",
    "content": "package moralis\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"uJ2RJISEAWJhMoHWazNYaB8LDSsZwhirg9OvNtZG7eBsep7ySAsHeDCD1Fy64pxU\"\n\tinvalidPattern = \"uJ2RJISEAWJhMoHWazNYaB8LDSsZwhirg9OvNtZG7eBsep7ySAsHeDCD1Fy64px\"\n\tkeyword        = \"moralis\"\n)\n\nfunc TestMoralis_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword moralis\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mrticktock/mrticktock.go",
    "content": "package mrticktock\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\temailPat = regexp.MustCompile(common.EmailPattern)\n\tpwordPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mrticktock\"}) + `\\b([a-zA-Z0-9!=@#$%()_^]{1,50})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mrticktock\"}\n}\n\n// FromData will find and optionally verify Mrticktock secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tpasswordMatches := pwordPat.FindAllStringSubmatch(dataStr, -1)\n\n\tuniqueEmailMatches := make(map[string]struct{})\n\tfor _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor emailMatch := range uniqueEmailMatches {\n\t\tfor _, passwordMatch := range passwordMatches {\n\t\t\tresPassword := strings.TrimSpace(passwordMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Mrticktock,\n\t\t\t\tRaw:          []byte(emailMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tpayload := strings.NewReader(fmt.Sprintf(`email=%s&password=%s`, emailMatch, resPassword))\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://mrticktock.com/app/api/is_timer_active\", payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tbody := string(bodyBytes)\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && strings.Contains(body, `\"errors\":[]`) {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Mrticktock\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mrticktock is a service that tracks time-based activities. These credentials can be used to access and manage timers and associated data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mrticktock/mrticktock_test.go",
    "content": "package mrticktock\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidEmailPattern      = \"example@email.com\"\n\tinvalidEmailPattern    = \"example.@email.com\"\n\tvalidPasswordPattern   = \"eZu6BQuoDbeUH3upVw9tF1u\"\n\tinvalidPasswordPattern = \"?Zu6BQuoDbeUH3upVw9tF1u\"\n\tkeyword                = \"mrticktock\"\n)\n\nfunc TestMrticktock_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mrticktock\",\n\t\t\tinput: fmt.Sprintf(\"%s %s %s\", validEmailPattern, keyword, validPasswordPattern),\n\t\t\twant:  []string{validEmailPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPasswordPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' url = '%s'\", keyword, invalidEmailPattern, invalidPasswordPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mrticktock/mrticktok_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mrticktock\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMrticktock_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\temail := testSecrets.MustGetField(\"SCANNERS_EMAIL\")\n\tpword := testSecrets.MustGetField(\"SCANNERS_PASS\")\n\tinactiveSecret := testSecrets.MustGetField(\"SCANNERS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mrticktock secret %s within %s\", email, pword)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mrticktock,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mrticktock secret %s within %s but not valid\", email, inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mrticktock,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Mrticktock.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Mrticktock.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/multi_part_credential_provider.go",
    "content": "package detectors\n\nvar _ MultiPartCredentialProvider = (*DefaultMultiPartCredentialProvider)(nil)\nvar _ MultiPartCredentialProvider = (*CustomMultiPartCredentialProvider)(nil)\n\ntype DefaultMultiPartCredentialProvider struct{}\n\nconst defaultMaxCredentialSpan = 1024\n\n// MaxCredentialSpan returns the default maximum credential span of 1024 for the\n// DefaultMultiPartCredentialProvider.\nfunc (d DefaultMultiPartCredentialProvider) MaxCredentialSpan() int64 {\n\treturn defaultMaxCredentialSpan\n}\n\ntype CustomMultiPartCredentialProvider struct{ maxCredentialSpan int64 }\n\n// NewCustomMultiPartCredentialProvider creates a new instance of CustomMultiPartCredentialProvider\n// with the specified maximum credential span.\nfunc NewCustomMultiPartCredentialProvider(maxCredentialSpan int64) *CustomMultiPartCredentialProvider {\n\treturn &CustomMultiPartCredentialProvider{maxCredentialSpan: maxCredentialSpan}\n}\n\n// MaxCredentialSpan returns the custom maximum credential span specified during the\n// creation of the CustomMultiPartCredentialProvider.\nfunc (d CustomMultiPartCredentialProvider) MaxCredentialSpan() int64 {\n\treturn d.maxCredentialSpan\n}\n"
  },
  {
    "path": "pkg/detectors/multi_part_credential_provider_test.go",
    "content": "package detectors\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestMultiPartCredentialProviders(t *testing.T) {\n\ttestCases := []struct {\n\t\tname         string\n\t\tprovider     MultiPartCredentialProvider\n\t\texpectedSpan int64\n\t}{\n\t\t{\n\t\t\tname:         \"DefaultMultiPartCredentialProvider\",\n\t\t\tprovider:     DefaultMultiPartCredentialProvider{},\n\t\t\texpectedSpan: defaultMaxCredentialSpan,\n\t\t},\n\t\t{\n\t\t\tname:         \"CustomMultiPartCredentialProvider\",\n\t\t\tprovider:     NewCustomMultiPartCredentialProvider(2048),\n\t\t\texpectedSpan: 2048,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tspan := tc.provider.MaxCredentialSpan()\n\t\t\tassert.Equal(t, tc.expectedSpan, span)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mux/mux.go",
    "content": "package mux\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"mux\"}) + `\\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"mux\"}) + `([ \\r\\n]{0,1}[0-9A-Za-z\\/\\+]{75}[ \\r\\n]{1})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"mux\"}\n}\n\n// FromData will find and optionally verify Mux secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, secretMatch := range secretMatches {\n\t\t\tresSecretMatch := strings.TrimSpace(secretMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Mux,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resSecretMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.mux.com/video/v1/assets\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\treq.SetBasicAuth(resMatch, resSecretMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif s1.Verified {\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\"key\":    resMatch,\n\t\t\t\t\t\t\"secret\": resSecretMatch,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Mux\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Mux is a video streaming service that provides APIs for video hosting, live streaming, and video analytics. Mux API keys can be used to access and manage video assets and streaming data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/mux/mux_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage mux\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMux_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tkey := testSecrets.MustGetField(\"MUX_KEY\")\n\tinactiveKey := testSecrets.MustGetField(\"MUX_KEY_INACTIVE\")\n\tsecret := testSecrets.MustGetField(\"MUX\")\n\tinactiveSecret := testSecrets.MustGetField(\"MUX_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mux key %s with mux secret %s within\", key, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mux,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a mux key %s with mux secret %s within but not valid\", inactiveKey, inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Mux,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Mux.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no rawv2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Mux.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/mux/mux_test.go",
    "content": "package mux\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern      = \"87c64847-eda7-a832-6273-8b8b300b29ac\"\n\tinvalidKeyPattern    = \"G7c64847-eda7-a832-6273-8b8b300b29aG\"\n\tvalidSecretPattern   = \"B8O9w0a7rh0jPxAkfZBhDqb9yppJ4WiiT6yfRR6Fd1eMKL0cAAnv3ShEQU+quVs9xJZgTv0/blh\"\n\tinvalidSecretPattern = \"B8O9w0a7rh0jPxAkfZBhDqb9yppJ4WiiT6.fRR6Fd1eMKL0cAAnv3ShEQU+quVs9xJZgTv0/blh\"\n\tkeyword              = \"mux\"\n)\n\nfunc TestMux_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword mux\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s' %s ' %s '\", keyword, validKeyPattern, keyword, validSecretPattern),\n\t\t\twant:  []string{validKeyPattern + validSecretPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s' secret = ' %s '\", keyword, validKeyPattern, validSecretPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' secret = ' %s '\", keyword, invalidKeyPattern, invalidSecretPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/myfreshworks/myfreshworks.go",
    "content": "package myfreshworks\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"freshworks\"}) + `\\b([a-z0-9A-Z-_]{22})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"freshworks\"}) + `\\b([a-zA-Z0-9-_]{2,20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"freshworks\"}\n}\n\n// FromData will find and optionally verify Myfreshworks secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Myfreshworks,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.getClient()\n\t\t\t\tisVerified, verificationErr := verifyMyfreshworks(ctx, client, resMatch, resIdMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\nfunc verifyMyfreshworks(ctx context.Context, client *http.Client, resMatch, resIdMatch string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://\"+resIdMatch+\".myfreshworks.com/crm/sales/api/sales_accounts/filters\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Token token=%s\", resMatch))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\tif res.StatusCode == http.StatusOK {\n\t\tbody, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\treturn json.Valid(body), nil\n\t} else if !(res.StatusCode == http.StatusUnauthorized || res.StatusCode == http.StatusForbidden) {\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n\n\treturn false, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Myfreshworks\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Freshworks is a customer engagement platform offering various services like CRM, IT service management, and more. Freshworks API keys can be used to access and manage these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/myfreshworks/myfreshworks_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage myfreshworks\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMyfreshworks_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MYFRESHWORKS_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"MYFRESHWORKS_INACTIVE\")\n\tdomain := testSecrets.MustGetField(\"MYFRESHWORKS_DOMAIN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a myfreshworks secret %s domain %s here\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Myfreshworks,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a invalid myfreshworks secret %s domain %s here\", inactiveSecret, domain)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Myfreshworks,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a myfreshworks secret %s domain %s here\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Myfreshworks,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a myfreshworks secret %s domain %s here\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Myfreshworks,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Myfreshworks.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Myfreshworks.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/myfreshworks/myfreshworks_test.go",
    "content": "package myfreshworks\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPattern   = \"SEeNW8bM_Rref-UFdiBKio\"\n\tinvalidKeyPattern = \"?EeNW8bM_Rref-UFdiBKi=\"\n\tvalidIdPattern    = \"xuC1amFX9\"\n\tinvalidIdPattern  = \"?uC1amFX=\"\n\tkeyword           = \"freshworks\"\n)\n\nfunc TestMyfreshworks_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword freshworks\",\n\t\t\tinput: fmt.Sprintf(\"%s %s %s\", keyword, validKeyPattern, validIdPattern),\n\t\t\twant:  []string{validKeyPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s' '%s'\", keyword, validKeyPattern, validIdPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key = '%s' url = '%s'\", keyword, invalidKeyPattern, invalidIdPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/myintervals/myintervals.go",
    "content": "package myintervals\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"myintervals\"}) + `\\b([0-9a-z]{11})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"myintervals\"}\n}\n\n// FromData will find and optionally verify Myintervals secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_MyIntervals,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tdata := fmt.Sprintf(\"%s:X\", resMatch)\n\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.myintervals.com/client/\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_MyIntervals\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"MyIntervals is a project management tool that helps in tracking time, managing tasks, and organizing projects. MyIntervals API keys can be used to access and modify project data and user information.\"\n}\n"
  },
  {
    "path": "pkg/detectors/myintervals/myintervals_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage myintervals\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestMyintervals_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"MYINTERVALS\")\n\tinactiveSecret := testSecrets.MustGetField(\"MYINTERVALS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a myintervals secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MyIntervals,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a myintervals secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_MyIntervals,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Myintervals.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Myintervals.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/myintervals/myintervals_test.go",
    "content": "package myintervals\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"rb1nz5xhues\"\n\tinvalidPattern = \"rb1nz5xhue\"\n\tkeyword        = \"myintervals\"\n)\n\nfunc TestMyintervals_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword myintervals\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nasdaqdatalink/nasdaqdatalink.go",
    "content": "package nasdaqdatalink\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"nasdaq\"}) + `\\b([a-zA-Z0-9_-]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"nasdaq\"}\n}\n\n// FromData will find and optionally verify NasdaqDataLink secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_NasdaqDataLink,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := 10 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://data.nasdaq.com/api/v3/datasets/FRED/GDP.csv?api_key=%s&collapse=annual&rows=6&order=asc&column_index=1\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_NasdaqDataLink\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Nasdaq Data Link provides financial, economic and alternative datasets. The API key can be used to access and retrieve this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/nasdaqdatalink/nasdaqdatalink_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage nasdaqdatalink\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNasdaqDataLink_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NASDAQDATALINK\")\n\tinactiveSecret := testSecrets.MustGetField(\"NASDAQDATALINK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nasdaqdatalink secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NasdaqDataLink,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nasdaqdatalink secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NasdaqDataLink,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"NasdaqDataLink.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"NasdaqDataLink.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nasdaqdatalink/nasdaqdatalink_test.go",
    "content": "package nasdaqdatalink\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"ZvwCOm0nMD1mHo-wVTpJ\"\n\tinvalidPattern = \"ZvwCOm0nMD?mHo-wVTpJ\"\n\tkeyword        = \"nasdaqdatalink\"\n)\n\nfunc TestNasdaqDataLink_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword nasdaqdatalink\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nethunt/nethunt.go",
    "content": "package nethunt\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"nethunt\"}) + `\\b([a-z0-9-\\S]{36})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"nethunt\"}) + `\\b([a-zA-Z0-9.-@]{25,30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"nethunt\"}\n}\n\n// FromData will find and optionally verify Nethunt secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\ttokenPatMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tuserPatMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Nethunt,\n\t\t\t\tRaw:          []byte(tokenPatMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://nethunt.com/api/v1/zapier/triggers/readable-folder\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.SetBasicAuth(userPatMatch, tokenPatMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Nethunt\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Nethunt is a CRM tool. Credentials found can be used to access and manage CRM data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/nethunt/nethunt_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage nethunt\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNethunt_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NETHUNT_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"NETHUNT_INACTIVE\")\n\tuser := testSecrets.MustGetField(\"ACCOUNT_USER\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nethunt secret %s within nethunt %s but verified\", secret, user)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Nethunt,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nethunt secret %s within nethunt %s but not valid\", inactiveSecret, user)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Nethunt,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Nethunt.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Nethunt.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nethunt/nethunt_test.go",
    "content": "package nethunt\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey   = \"ai$oy@v8w9-$rcawnt^87rv3-9k0t&srbc8v\"\n\tinvalidKey = \"ai$oy@v8w9 $rcawnt^87rv3-9k0t&srbc8v\"\n\tvalidId    = \"3ND7dk03Hob3.1U46l1=u;Rc45:9nt\"\n\tinvalidId  = \"3ND7dk03Hob3.1U!6l1=u;Rc45:9nt\"\n\tkeyword    = \"nethunt\"\n)\n\nfunc TestNethunt_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword nethunt\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/netlify/v1/netlify_v1.go",
    "content": "package netlify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"netlify\"}) + `\\b([A-Za-z0-9_-]{43,45})\\b`)\n)\n\nconst (\n\trotationGuideUrl = \"https://howtorotate.com/docs/tutorials/netlify/\"\n\tverificationUrl  = \"https://api.netlify.com/api/v1/sites\"\n)\n\nfunc (Scanner) Version() int { return 1 }\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"netlify\"}\n}\n\n// FromData will find and optionally verify Netlify secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Netlify,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\t\ts1.ExtraData = map[string]string{\n\t\t\t\"rotation_guide\": rotationGuideUrl,\n\t\t\t\"version\":        strconv.Itoa(s.Version()),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\n\t\t\tif s1.Verified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\"key\": match}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, verificationUrl, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tres, err := client.Do(req)\n\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Netlify\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Netlify is a cloud platform for web developers that provides hosting and serverless backend services for web applications and static websites. Netlify API keys can be used to manage sites, deploy applications, and access various services offered by the platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/netlify/v1/netlify_v1_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage netlify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNetlify_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NETLIFY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"NETLIFY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a netlify secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Netlify,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/netlify/\",\n\t\t\t\t\t\t\"version\":        \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a netlify secret %s within but unverified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Netlify,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/netlify/\",\n\t\t\t\t\t\t\"version\":        \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Netlify.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Netlify.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/netlify/v1/netlify_v1_test.go",
    "content": "package netlify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"dxyTVGLfvZNOkenli_Eb-T4eCZcOnoBq5rn0tMmqEBG\"\n\tinvalidPattern = \"dxy?VGLfvZNOkenli_Eb-T4eCZcOnoBq5rn0tMmqEBG\"\n\tkeyword        = \"netlify\"\n)\n\nfunc TestNetlify_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword netlify\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/netlify/v2/netlify_v2.go",
    "content": "package netlify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"netlify\"}) + `\\b(nfp_[a-zA-Z0-9_]{36})\\b`)\n)\n\nconst (\n\trotationGuideUrl = \"https://howtorotate.com/docs/tutorials/netlify/\"\n\tverificationUrl  = \"https://api.netlify.com/api/v1/sites\"\n)\n\nfunc (Scanner) Version() int { return 2 }\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"netlify\"}\n}\n\n// FromData will find and optionally verify Netlify secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Netlify,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\t\ts1.ExtraData = map[string]string{\n\t\t\t\"rotation_guide\": rotationGuideUrl,\n\t\t\t\"version\":        strconv.Itoa(s.Version()),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\n\t\t\tif s1.Verified {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\"key\": match}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, verificationUrl, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tres, err := client.Do(req)\n\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Netlify\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Netlify is a cloud platform for web developers that provides hosting and serverless backend services for web applications and static websites. Netlify API keys can be used to manage sites, deploy applications, and access various services offered by the platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/netlify/v2/netlify_v2_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage netlify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNetlify_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NETLIFY_V2_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"NETLIFY_V2_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a netlify secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Netlify,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/netlify/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a netlify secret %s within but unverified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Netlify,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/netlify/\",\n\t\t\t\t\t\t\"version\":        \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Netlify.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Netlify.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/netlify/v2/netlify_v2_test.go",
    "content": "package netlify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"nfp_gsTPZr8vTqmit69DY8u8es68N3XvxqW20330\"\n\tinvalidPattern = \"nfp_?sTPZr8vTqmit69DY8u8es68N3XvxqW20330\"\n\tkeyword        = \"netlify\"\n)\n\nfunc TestNetlify_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword netlify\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/netsuite/netsuite.go",
    "content": "package netsuite\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tconsumerKeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"netsuite\", \"consumer\", \"key\"}) + `\\b([a-zA-Z0-9]{64})\\b`)\n\tconsumerSecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"netsuite\", \"consumer\", \"secret\"}) + `\\b([a-zA-Z0-9]{64})\\b`)\n\n\ttokenKeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"netsuite\", \"token\", \"key\"}) + `\\b([a-zA-Z0-9]{64})\\b`)\n\ttokenSecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"netsuite\", \"token\", \"secret\"}) + `\\b([a-zA-Z0-9]{64})\\b`)\n\n\taccountIDPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"netsuite\", \"account\", \"id\"}) + `\\b([a-zA-Z0-9-_]{6,15})\\b`)\n)\n\ntype credentialSet struct {\n\tconsumerKey    string\n\tconsumerSecret string\n\ttokenKey       string\n\ttokenSecret    string\n\taccountID      string\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"netsuite\"}\n}\n\n// FromData will find and optionally verify Netsuite secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// find for credentials\n\tconsumerKeyMatches := trimUniqueMatches(consumerKeyPat.FindAllStringSubmatch(dataStr, -1))\n\tconsumerSecretMatches := trimUniqueMatches(consumerSecretPat.FindAllStringSubmatch(dataStr, -1))\n\ttokenKeyMatches := trimUniqueMatches(tokenKeyPat.FindAllStringSubmatch(dataStr, -1))\n\ttokenSecretMatches := trimUniqueMatches(tokenSecretPat.FindAllStringSubmatch(dataStr, -1))\n\taccountIDMatches := trimUniqueMatches(accountIDPat.FindAllStringSubmatch(dataStr, -1))\n\n\tfor consumerKey := range consumerKeyMatches {\n\t\tfor consumerSecret := range consumerSecretMatches {\n\t\t\tfor tokenKey := range tokenKeyMatches {\n\t\t\t\tfor tokenSecret := range tokenSecretMatches {\n\t\t\t\t\tfor accountID := range accountIDMatches {\n\t\t\t\t\t\tcs := credentialSet{\n\t\t\t\t\t\t\tconsumerKey:    consumerKey,\n\t\t\t\t\t\t\tconsumerSecret: consumerSecret,\n\t\t\t\t\t\t\ttokenKey:       tokenKey,\n\t\t\t\t\t\t\ttokenSecret:    tokenSecret,\n\t\t\t\t\t\t\taccountID:      accountID,\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif !isUniqueKeys(cs) {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\t\t\tDetectorType: detectorspb.DetectorType_Netsuite,\n\t\t\t\t\t\t\tRaw:          []byte(consumerKey),\n\t\t\t\t\t\t\tRawV2:        []byte(consumerKey + consumerSecret),\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif verify {\n\t\t\t\t\t\t\tclient := s.client\n\t\t\t\t\t\t\tif client == nil {\n\t\t\t\t\t\t\t\tclient = defaultClient\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tisVerified, err := verifyCredentials(ctx,\n\t\t\t\t\t\t\t\tclient,\n\t\t\t\t\t\t\t\tcs)\n\t\t\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\t\t\ts1.SetVerificationError(err, consumerKey)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresults = append(results, s1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Netsuite\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Netsuite is a cloud business management suite. These credentials can be used to access and modify data within Netsuite.\"\n}\n\nfunc verifyCredentials(ctx context.Context, client *http.Client, cs credentialSet) (bool, error) {\n\t// for url, filter or replace underscore in accountID if needed and lower case the accountID\n\turlAccountId := strings.ToLower(strings.Replace(cs.accountID, \"_\", \"-\", -1))\n\n\tbaseUrl := \"https://\" + urlAccountId + \".suitetalk.api.netsuite.com\"\n\n\tconst path = \"/services/rest/record/v1/metadata-catalog/check\"\n\n\t// nonce generate\n\tnonce, err := netsuiteNonce(11)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tsignature := makeSignature(http.MethodGet, baseUrl, path, map[string]string{\n\t\t\"consumer_key\":     cs.consumerKey,\n\t\t\"consumer_secret\":  cs.consumerSecret,\n\t\t\"token_id\":         cs.tokenKey,\n\t\t\"token_secret\":     cs.tokenSecret,\n\t\t\"signature_method\": \"HMAC-SHA256\",\n\t\t\"timestamp\":        strconv.FormatInt(time.Now().Unix(), 10),\n\t\t\"nonce\":            nonce,\n\t\t\"version\":          \"1.0\",\n\t\t\"realm\":            cs.accountID,\n\t})\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, baseUrl+path, nil)\n\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Set required headers\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", signature)\n\n\t// Make the request\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"no such host\") {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\t// 401 indicates unverified credentials\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc generateHMACSHA256(message, secret string) string {\n\t// Create a new HMAC by defining the hash type and the key (as byte array)\n\th := hmac.New(sha256.New, []byte(secret))\n\n\t// Write the message to it\n\th.Write([]byte(message))\n\n\t// Get the resulting HMAC as a byte slice\n\thash := h.Sum(nil)\n\n\t// Encode the byte slice to a hexadecimal string\n\treturn base64.StdEncoding.EncodeToString(hash)\n}\n\nfunc addOauthParam(builder *strings.Builder, key, value string) {\n\tbuilder.WriteString(key)\n\tbuilder.WriteString(\"=\\\"\")\n\tbuilder.WriteString(value)\n\tbuilder.WriteString(\"\\\",\")\n}\n\nfunc makeSignature(method, baseUrl, path string, params map[string]string) string {\n\tvar paramsStringBuilder strings.Builder\n\tparamsStringBuilder.WriteString(\"oauth_consumer_key=\" + url.QueryEscape(params[\"consumer_key\"]))\n\tparamsStringBuilder.WriteString(\"&oauth_nonce=\" + url.QueryEscape(params[\"nonce\"]))\n\tparamsStringBuilder.WriteString(\"&oauth_signature_method=\" + url.QueryEscape(params[\"signature_method\"]))\n\tparamsStringBuilder.WriteString(\"&oauth_timestamp=\" + url.QueryEscape(params[\"timestamp\"]))\n\tparamsStringBuilder.WriteString(\"&oauth_token=\" + url.QueryEscape(params[\"token_id\"]))\n\tparamsStringBuilder.WriteString(\"&oauth_version=\" + url.QueryEscape(params[\"version\"]))\n\n\tvar signatureBaseBuilder strings.Builder\n\n\tsignatureBaseBuilder.WriteString(method)\n\tsignatureBaseBuilder.WriteString(\"&\")\n\tsignatureBaseBuilder.WriteString(url.QueryEscape(baseUrl + path))\n\tsignatureBaseBuilder.WriteString(\"&\")\n\tsignatureBaseBuilder.WriteString(url.QueryEscape(paramsStringBuilder.String()))\n\n\tkey := url.QueryEscape(params[\"consumer_secret\"]) + \"&\" + url.QueryEscape(params[\"token_secret\"])\n\n\tsignature := generateHMACSHA256(signatureBaseBuilder.String(), key)\n\n\tvar authHeaderBuilder strings.Builder\n\tauthHeaderBuilder.WriteString(\"OAuth\")\n\tauthHeaderBuilder.WriteString(\" \")\n\n\taddOauthParam(&authHeaderBuilder, \"realm\", params[\"realm\"])\n\taddOauthParam(&authHeaderBuilder, \"oauth_consumer_key\", params[\"consumer_key\"])\n\taddOauthParam(&authHeaderBuilder, \"oauth_token\", params[\"token_id\"])\n\taddOauthParam(&authHeaderBuilder, \"oauth_signature_method\", params[\"signature_method\"])\n\taddOauthParam(&authHeaderBuilder, \"oauth_timestamp\", params[\"timestamp\"])\n\taddOauthParam(&authHeaderBuilder, \"oauth_nonce\", params[\"nonce\"])\n\taddOauthParam(&authHeaderBuilder, \"oauth_version\", params[\"version\"])\n\taddOauthParam(&authHeaderBuilder, \"oauth_signature\", url.QueryEscape(signature))\n\n\t// remove trailing comma\n\tauthHeader := authHeaderBuilder.String()\n\tauthHeader = authHeader[:len(authHeader)-1]\n\treturn authHeader\n}\n\nfunc trimUniqueMatches(matches [][]string) (result map[string]struct{}) {\n\tresult = make(map[string]struct{})\n\tfor _, match := range matches {\n\t\tif len(match) > 0 {\n\t\t\ttrimmedString := strings.TrimSpace(match[1])\n\t\t\tresult[trimmedString] = struct{}{}\n\t\t}\n\t}\n\treturn result\n}\n\n// Check if a credential set is unique, this is used to avoid duplicates.\nfunc isUniqueKeys(cs credentialSet) bool {\n\tseen := make(map[string]struct{})\n\tcredentials := []string{cs.consumerKey, cs.consumerSecret, cs.tokenKey, cs.tokenSecret, cs.accountID}\n\n\tfor _, cred := range credentials {\n\t\tif _, exists := seen[cred]; exists {\n\t\t\treturn false\n\t\t}\n\t\tseen[cred] = struct{}{}\n\t}\n\treturn true\n}\n\n/*\nTo generate a nonce, we need to generate a random string of characters.\n*/\n\nvar (\n\tcharset = []byte(\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\")\n\n\t// maxUnbiasedUint32 is the largest multiple of len(charset) that fits in a uint32.\n\t// It's used to ensure unbiased sampling when selecting characters from the charset.\n\tmaxUnbiasedUint32 = uint32((1<<32 - 1) - ((1<<32 - 1) % uint64(len(charset))))\n)\n\nfunc netsuiteNonce(n int) (string, error) {\n\tb := make([]byte, n)\n\tbuf := make([]byte, 4)\n\tfor i := 0; i < n; {\n\t\tif _, err := rand.Read(buf); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tnum := binary.BigEndian.Uint32(buf)\n\t\tif num < maxUnbiasedUint32 {\n\t\t\tb[i] = charset[num%uint32(len(charset))]\n\t\t\ti++\n\t\t}\n\t}\n\treturn string(b), nil\n}\n"
  },
  {
    "path": "pkg/detectors/netsuite/netsuite_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage netsuite\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n)\n\nfunc TestNetsuite_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tconsumerKey := testSecrets.MustGetField(\"NETSUITE_CONSUMER_KEY\")\n\tconsumerSecret := testSecrets.MustGetField(\"NETSUITE_CONSUMER_SECRET\")\n\ttokenKey := testSecrets.MustGetField(\"NETSUITE_TOKEN_KEY\")\n\ttokenSecret := testSecrets.MustGetField(\"NETSUITE_TOKEN_SECRET\")\n\taccountID := testSecrets.MustGetField(\"NETSUITE_ACCOUNT_ID\")\n\n\tinactiveConsumerSecret := testSecrets.MustGetField(\"NETSUITE_CONSUMER_SECRET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname               string\n\t\ts                  Scanner\n\t\targs               args\n\t\twantCount          int\n\t\twantErr            bool\n\t\tShouldHaveVerified bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(`netsuite credentials\n\t\t\t\t\t consumer key %s \n\t\t\t\t\t consumer secret %s, \n\t\t\t\t\t token key %s, \n\t\t\t\t\t token secret %s, \n\t\t\t\t\t account id %s`,\n\t\t\t\t\tconsumerKey,\n\t\t\t\t\tconsumerSecret,\n\t\t\t\t\ttokenKey,\n\t\t\t\t\ttokenSecret,\n\t\t\t\t\taccountID)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\tShouldHaveVerified: true,\n\t\t\twantCount:          1,\n\t\t\twantErr:            false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(\"You can find a netsuite consumer key %s but not valid with secret %s, token key %s, token secret %s, account id %s\",\n\t\t\t\t\tconsumerKey,\n\t\t\t\t\tinactiveConsumerSecret,\n\t\t\t\t\ttokenKey,\n\t\t\t\t\ttokenSecret,\n\t\t\t\t\taccountID)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\tShouldHaveVerified: false,\n\t\t\twantCount:          21,\n\t\t\twantErr:            false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\tShouldHaveVerified: false,\n\t\t\twantCount:          0,\n\t\t\twantErr:            false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Netsuite.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif tt.ShouldHaveVerified {\n\t\t\t\tvar verifiedResults []detectors.Result\n\t\t\t\t// filter verified results\n\t\t\t\tfor i := range got {\n\t\t\t\t\tif got[i].Verified {\n\t\t\t\t\t\tverifiedResults = append(verifiedResults, got[i])\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif len(verifiedResults) != tt.wantCount {\n\t\t\t\t\tt.Errorf(\"Netsuite.FromData() got = %v, want %v\", len(verifiedResults), tt.wantCount)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif len(got) != tt.wantCount {\n\t\t\t\t\tt.Errorf(\"Netsuite.FromData() got = %v, want %v\", len(got), tt.wantCount)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/netsuite/netsuite_test.go",
    "content": "package netsuite\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidConsumerKey      = \"3WaMEd0KQtHSU7b24HEd79RZzSpMOfMdMUpIaXjq83DbNHVosCVrEVDxKiEQzT15\"\n\tinvalidConsumerKey    = \"3Wa?Ed0KQtHSU7b24HEd79RZzSpMOfMdMUpIaXjq83DbNHVosCVrEVDxKiEQzT15\"\n\tvalidConsumerSecret   = \"5BZ70LfNshsJkDya1XaD8bMqtPWlOa2o1yKCk0H2DxnjtoaJKIcAw75GdI6zRaRD\"\n\tinvalidConsumerSecret = \"5BZ70LfNshsJkDya?XaD8bMqtPWlOa2o1yKCk0H2DxnjtoaJKIcAw75GdI6zRaRD\"\n\tvalidTokenKey         = \"KeYcG56ViFDleXPFJuEQ5CAGSJn7o2WDa5iGvLIvVBqZj5rMkaWFmzkp4bveJa74\"\n\tinvalidTokenKey       = \"KeYcG56ViFDleXPFJuEQ5CAGSJn7o2WD?5iGvLIvVBqZj5rMkaWFmzkp4bveJa74\"\n\tvalidTokenSecret      = \"GGQUdyYOGDfDImJWCz4Kufk2GevaIDuVv83kIa9zCRuXIDLB4oh2eVDVPmsaSai2\"\n\tinvalidTokenSecret    = \"GGQUdyYOGDfDImJWCz4Kufk2Ge?aIDuVv83kIa9zCRuXIDLB4oh2eVDVPmsaSai2\"\n\tvalidAccountID        = \"x1L2_BXo\"\n\tinvalidAccountID      = \"x1L2?BXo\"\n\tkeyword               = \"netsuite\"\n\tinputFormat           = `%s id - '%s'\nconsumer - '%s' consumer - '%s'\ntoken - '%s' token - '%s'`\n\toutputPair1 = validConsumerKey + validConsumerSecret\n\toutputPair2 = validConsumerSecret + validConsumerKey\n)\n\nfunc TestNetsuite_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword netsuite\",\n\t\t\tinput: fmt.Sprintf(inputFormat, keyword, validAccountID, validConsumerKey, validConsumerSecret, validTokenKey, validTokenSecret),\n\t\t\twant:  []string{outputPair1, outputPair2, outputPair1, outputPair2},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(inputFormat, keyword, invalidAccountID, invalidConsumerKey, invalidConsumerSecret, invalidTokenKey, invalidTokenSecret),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/neutrinoapi/neutrinoapi.go",
    "content": "package neutrinoapi\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"neutrinoapi\"}) + `\\b([a-zA-Z0-9]{48})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"neutrinoapi\"}) + `\\b([a-zA-Z0-9]{6,24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"neutrinoapi\"}\n}\n\n// FromData will find and optionally verify NeutrinoApi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_NeutrinoApi,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tbody := &bytes.Buffer{}\n\t\t\t\twriter := multipart.NewWriter(body)\n\t\t\t\tfw, err := writer.CreateFormField(\"url\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\t_, err = io.Copy(fw, strings.NewReader(\"https://google.com\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\twriter.Close()\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", fmt.Sprintf(\"https://neutrinoapi.net/url-info?user-id=%s&api-key=%s\", resIdMatch, resMatch), bytes.NewReader(body.Bytes()))\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", writer.FormDataContentType())\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_NeutrinoApi\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Neutrino API provides a variety of services including data tools, security tools, and more. Neutrino API keys can be used to access these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/neutrinoapi/neutrinoapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage neutrinoapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNeutrinoApi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NEUTRINOAPI\")\n\tid := testSecrets.MustGetField(\"NEUTRINOAPI_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"NEUTRINOAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a neutrinoapi secret %s within neutrinoapi %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NeutrinoApi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NeutrinoApi,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a neutrinoapi secret %s within neutrinoapi %s but not valid\", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NeutrinoApi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NeutrinoApi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"NeutrinoApi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"NeutrinoApi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/neutrinoapi/neutrinoapi_test.go",
    "content": "package neutrinoapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey   = \"mHvIoyAsS593j9zKRHEGO81aEMPKgQxmzfWnuDSkWGZZ6qof\"\n    invalidKey = \"mHvIoyAsS593j9zKRH?GO81aEMPKgQxmzfWnuDSkWGZZ6qof\"\n    validId    = \"yWT99sOl6\"\n    invalidId  = \"yWT?9sOl6\"\n    keyword    = \"neutrinoapi\"\n)\n\nfunc TestNeutrinoApi_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword neutrinoapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/newrelicpersonalapikey/newrelicpersonalapikey.go",
    "content": "package newrelicpersonalapikey\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"newrelic\"}) + `\\b([A-Za-z0-9_\\.]{4}-[A-Za-z0-9_\\.]{42})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"newrelic\"}\n}\n\n// FromData will find and optionally verify NewRelicPersonalApiKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_NewRelicPersonalApiKey,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.newrelic.com/v2/users.json\", nil)\n\t\t\treqEU, errEU := http.NewRequestWithContext(ctx, \"GET\", \"https://api.eu.newrelic.com/v2/users.json\", nil)\n\t\t\tif err != nil || errEU != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-Api-Key\", resMatch)\n\t\t\treqEU.Header.Add(\"X-Api-Key\", resMatch)\n\n\t\t\tres, err := client.Do(req)\n\t\t\tresEU, errEU := client.Do(reqEU)\n\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t} else if errEU == nil {\n\t\t\t\tdefer resEU.Body.Close()\n\t\t\t\tif resEU.StatusCode >= 200 && resEU.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_NewRelicPersonalApiKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"New Relic is a software analytics and performance monitoring company. New Relic Personal API keys can be used to access and manage your New Relic account and data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/newrelicpersonalapikey/newrelicpersonalapikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage newrelicpersonalapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNewRelicPersonalApiKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NEWRELICPERSONALAPIKEY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"NEWRELICPERSONALAPIKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a newrelicpersonalapikey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NewRelicPersonalApiKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a newrelicpersonalapikey secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NewRelicPersonalApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"NewRelicPersonalApiKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"NewRelicPersonalApiKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/newrelicpersonalapikey/newrelicpersonalapikey_test.go",
    "content": "package newrelicpersonalapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"ABGA-4OmA485kfT9jonFpx22nzcpJWZjceM2El878Hz6G6_\"\n\tinvalidPattern = \"ABGA-4OmA485kfT9jonFpx?2nzcpJWZjceM2El878Hz6G6_\"\n\tkeyword        = \"newrelicpersonalapikey\"\n)\n\nfunc TestNewRelicPersonalApiKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword newrelicpersonalapikey\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/newsapi/newsapi.go",
    "content": "package newsapi\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"newsapi\"}) + `\\b([a-z0-9]{32})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"newsapi\"}\n}\n\n// FromData will find and optionally verify Newsapi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Newsapi,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://newsapi.org/v2/everything?q=Apple&from=2021-09-29&sortBy=popularity&apiKey=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Newsapi\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"NewsAPI provides access to news articles from various sources. NewsAPI keys can be used to query and retrieve news data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/newsapi/newsapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage newsapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNewsapi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NEWSAPI_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"NEWSAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a newsapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Newsapi,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a newsapi secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Newsapi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Newsapi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Newsapi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/newsapi/newsapi_test.go",
    "content": "package newsapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"kxbj4b6u2iazvhc8n6b3oz0j5x6nrpyy\"\n\tinvalidPattern = \"kxbj4b6u2iazvhc8?6b3oz0j5x6nrpyy\"\n\tkeyword        = \"newsapi\"\n)\n\nfunc TestNewsapi_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword newsapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/newscatcher/newscatcher.go",
    "content": "package newscatcher\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"newscatcher\"}) + `\\b([0-9A-Za-z_]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"newscatcher\"}\n}\n\n// FromData will find and optionally verify Newscatcher secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Newscatcher,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.newscatcherapi.com/v2/search?q=Tesla\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"x-api-key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Newscatcher\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Newscatcher is an API service used to search and retrieve news articles from various sources. Newscatcher API keys can be used to access and query this service.\"\n}\n"
  },
  {
    "path": "pkg/detectors/newscatcher/newscatcher_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage newscatcher\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNewscatcher_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NEWSCATCHER\")\n\tinactiveSecret := testSecrets.MustGetField(\"NEWSCATCHER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a newscatcher secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Newscatcher,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a newscatcher secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Newscatcher,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Newscatcher.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Newscatcher.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/newscatcher/newscatcher_test.go",
    "content": "package newscatcher\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"vW_6rXfYJcjXRU8Z92B9RR6WRpVUN5ebSX07I8_QQDi\"\n\tinvalidPattern = \"vW_6rXfYJcjX?U8Z92B9RR6WRpVUN5ebSX07I8_QQDi\"\n\tkeyword        = \"newscatcher\"\n)\n\nfunc TestNewscatcher_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword newscatcher\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nexmoapikey/nexmoapikey.go",
    "content": "package nexmoapikey\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"nexmo\"}) + `\\b([A-Za-z0-9_-]{8})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"nexmo\"}) + `\\b([A-Za-z0-9_-]{16})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"nexmo\"}\n}\n\n// FromData will find and optionally verify NexmoApiKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretPat := secretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, secretMatch := range secretPat {\n\t\t\tresSecret := strings.TrimSpace(secretMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_NexmoApiKey,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resSecret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://rest.nexmo.com/account/get-balance?api_key=\"+resMatch+\"&api_secret=\"+resSecret+\"\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_NexmoApiKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Nexmo provides APIs for SMS, voice, phone verifications, and more. Nexmo API keys can be used to access and manage these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/nexmoapikey/nexmoapikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage nexmoapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNexmoApiKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NEXMOAPIKEY_TOKEN\")\n\tkey := testSecrets.MustGetField(\"NEXMOAPIKEY_KEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"NEXMOAPIKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nexmoapikey secret %s within https://rest.nexmo.com/account/get-balance?api_key=%s\", secret, key)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NexmoApiKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nexmoapikey secret %s within %s but not valid\", inactiveSecret, key)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NexmoApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"NexmoApiKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"NexmoApiKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nexmoapikey/nexmoapikey_test.go",
    "content": "package nexmoapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey      = \"KULOcaja\"\n\tinvalidKey    = \"KULO?aja\"\n\tvalidSecret   = \"gngKGCWbmK0COTtS\"\n\tinvalidSecret = \"gngKGCWb?K0COTtS\"\n\tkeyword       = \"nexmoapikey\"\n)\n\nfunc TestNexmoApiKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword nexmoapikey\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validSecret),\n\t\t\twant:  []string{validKey + validSecret},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidSecret),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nftport/nftport.go",
    "content": "package nftport\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"nftport\"}) + `\\b([a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"nftport\"}\n}\n\n// FromData will find and optionally verify Nftport secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Nftport,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.nftport.xyz/me/contracts\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Nftport\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Nftport is a service that allows users to interact with NFTs. Nftport API keys can be used to access and manage NFT data and transactions.\"\n}\n"
  },
  {
    "path": "pkg/detectors/nftport/nftport_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage nftport\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNftport_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NFTPORT_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"NFTPORT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nftport secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Nftport,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nftport secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Nftport,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Nftport.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Nftport.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nftport/nftport_test.go",
    "content": "package nftport\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"oy00aqk2-2khu-af0v-crgm-n9duc7hntu4d\"\n\tinvalidPattern = \"oy00aqk2?2khu-af0v-crgm-n9duc7hntu4d\"\n\tkeyword        = \"nftport\"\n)\n\nfunc TestNftport_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword nftport\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ngc/ngc.go",
    "content": "package ngc\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\t// keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ngc\"}) + `\\b([[:alnum:]]{26}:[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12})\\b`)\n\tkeyPat1 = regexp.MustCompile(`\\b([[:alnum:]]{84})\\b`)\n\tkeyPat2 = regexp.MustCompile(`\\b([[:alnum:]]{26}:[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ngc\"}\n}\n\n// FromData will find and optionally verify NGC secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat1.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tdecode, _ := base64.StdEncoding.DecodeString(resMatch)\n\n\t\tcontainsKey := keyPat2.MatchString(string(decode))\n\t\tif containsKey {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_NGC,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tkey := \"Basic \" + string(base64.StdEncoding.EncodeToString([]byte(\"$oauthtoken:\"+resMatch)))\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://authn.nvidia.com/token?service=ngc\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header = http.Header{\n\t\t\t\t\t\"accept\":        {\"*/*\"},\n\t\t\t\t\t\"Authorization\": {key},\n\t\t\t\t}\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_NGC\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Nvidia's API for AI related things\"\n}\n"
  },
  {
    "path": "pkg/detectors/ngc/ngc_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ngc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNGC_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"hackthisai\", \"th-ngc-valid\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NGC\")\n\tinactiveSecret := testSecrets.MustGetField(\"NGC_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ngc secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NGC,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ngc secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NGC,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"NGC.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"NGC.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ngc/ngc_test.go",
    "content": "package ngc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKeyPat1   = \"WGtMOGtDWWFaZEpSbWpKTktWQTQ2VWNKYk06VWxhRjFvMEotS2FEUS0za1hkLVBtekktblJLM0VoZEJPMXAw\"\n\tinvalidKeyPat1 = \"WGtMOGtDWWFaZE?SbWpKTktWQTQ2VWNKYk06VWxhRjFvMEotS2FEUS0za1hkLVBtekktblJLM0VoZEJPMXAw\"\n\tvalidKeyPat2   = \"XkL8kCYaZdJRmjJNKVA46UcJbM:UlaF1o0J-KaDQ-3kXd-PmzI-nRK3EhdBO1p0\"\n\tinvalidKeyPat2 = \"XkL8kCYaZdJRmjJNKVA46UcJbM:UlaF?o0J-KaDQ-3kXd-PmzI-nRK3EhdBO1p0\"\n\tkeyword        = \"ngc\"\n)\n\nfunc TestNGC_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword ngc\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKeyPat1, keyword, validKeyPat2),\n\t\t\twant:  []string{validKeyPat1},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKeyPat1, keyword, invalidKeyPat2),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ngrok/ngrok.go",
    "content": "package ngrok\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nvar _ detectors.Detector = (*Scanner)(nil)\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Ngrok\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Ngrok is a service that provides secure introspectable tunnels to localhost. Ngrok keys can be used to manage and control these tunnels.\"\n}\n\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ngrok\"}\n}\n\nconst (\n\tngrokVerificationURL      = \"https://api.ngrok.com/agent_ingresses\"\n\ttunnelCredentialErrorCode = \"ERR_NGROK_206\"\n)\n\nvar (\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ngrok\"}) + `\\b(2[a-zA-Z0-9]{26}_\\d[a-zA-Z0-9]{20})\\b`)\n)\n\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tm := match[1]\n\t\tif detectors.StringShannonEntropy(m) < 3 {\n\t\t\tcontinue\n\t\t}\n\t\tuniqueMatches[m] = struct{}{}\n\t}\n\n\tfor token := range uniqueMatches {\n\t\tr := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Ngrok,\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tif verify {\n\t\t\tif s.client == nil {\n\t\t\t\ts.client = common.SaneHttpClient()\n\t\t\t}\n\t\t\tisVerified, vErr := verifyMatch(ctx, s.client, token)\n\t\t\tr.Verified = isVerified\n\t\t\tr.SetVerificationError(vErr, token)\n\t\t\tif isVerified {\n\t\t\t\tr.AnalysisInfo = map[string]string{\"key\": token}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, r)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, ngrokVerificationURL, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treq.Header.Set(\"ngrok-version\", \"2\")\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tcase http.StatusForbidden:\n\t\treturn false, nil\n\tcase http.StatusBadRequest:\n\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\t// Check if the error code is \"ERR_NGROK_206\" which indicates that\n\t\t// the credential is a valid tunnel Authtoken rather than an API key.\n\t\tif strings.Contains(string(bodyBytes), tunnelCredentialErrorCode) {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\treturn false, fmt.Errorf(\"ngrok: unexpected status code: %d\", res.StatusCode)\n}\n"
  },
  {
    "path": "pkg/detectors/ngrok/ngrok_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ngrok\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNgrok_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecretAPIKey := testSecrets.MustGetField(\"NGROK\")\n\tsecretAuthtoken := testSecrets.MustGetField(\"NGROK_AUTHTOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"NGROK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, API key, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ngrok secret API key %s within\", secretAPIKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ngrok,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, tunnel authtoken, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ngrok secret tunnel authtoken %s within\", secretAuthtoken)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ngrok,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ngrok secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ngrok,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Ngrok.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Ngrok.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ngrok/ngrok_test.go",
    "content": "package ngrok\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"2WIRSIHOQyHSVklZnoz2k6bTYdH_0E7z0Ta9QEyR1fZvQ0KU9\"\n\tinvalidPattern = \"2WIRSIHOQyHSVklZnoz2k6bT?dH_0E7z0Ta9QEyR1fZvQ0KU9\"\n\tkeyword        = \"ngrok\"\n)\n\nfunc TestNgrok_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword ngrok\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{\"2WIRSIHOQyHSVklZnoz2k6bTYdH_0E7z0Ta9QEyR1fZvQ0KU9\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{\"2WIRSIHOQyHSVklZnoz2k6bTYdH_0E7z0Ta9QEyR1fZvQ0KU9\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nicereply/nicereply.go",
    "content": "package nicereply\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"nicereply\"}) + `\\b([0-9a-f]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"nicereply\"}\n}\n\n// FromData will find and optionally verify Nicereply secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Nicereply,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tdata := fmt.Sprintf(\":%s\", resMatch)\n\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.nicereply.com/v1/users/stats\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Nicereply\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Nicereply is a customer satisfaction survey tool. Nicereply API keys can be used to access and manage survey data and user statistics.\"\n}\n"
  },
  {
    "path": "pkg/detectors/nicereply/nicereply_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage nicereply\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNicereply_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NICEREPLY\")\n\tinactiveSecret := testSecrets.MustGetField(\"NICEREPLY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nicereply secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Nicereply,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nicereply secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Nicereply,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Nicereply.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Nicereply.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nicereply/nicereply_test.go",
    "content": "package nicereply\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"0fdd4fe4c152b466d5b3b79a74c9d620c08c1e89\"\n\tinvalidPattern = \"0fdd4fe4c152?466d5b3b79a74c9d620c08c1e89\"\n\tkeyword        = \"nicereply\"\n)\n\nfunc TestNicereply_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword nicereply\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nightfall/nightfall.go",
    "content": "package nightfall\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(NF\\-[a-zA-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"nightfall\"}\n}\n\n// FromData will find and optionally verify Nightfall secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Nightfall,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\"fileSizeBytes\":256}`)\n\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.nightfall.ai/v3/upload\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Nightfall\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Nightfall is a data security platform providing sensitive data detection and protection. The detected key can be used to interact with Nightfall's API.\"\n}\n"
  },
  {
    "path": "pkg/detectors/nightfall/nightfall_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage nightfall\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNightfall_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NIGHTFALL\")\n\tinactiveSecret := testSecrets.MustGetField(\"NIGHTFALL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nightfall secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Nightfall,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nightfall secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Nightfall,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Nightfall.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Nightfall.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nightfall/nightfall_test.go",
    "content": "package nightfall\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"NF-ELwgMbVC9WYslSoewcZUMnj9oTNhgjVq\"\n\tinvalidPattern = \"NF-ELwgMbVC9WYslS?ewcZUMnj9oTNhgjVq\"\n\tkeyword        = \"nightfall\"\n)\n\nfunc TestNightfall_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword nightfall\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nimble/nimble.go",
    "content": "package nimble\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"nimble\"}) + `\\b([a-zA-Z0-9]{30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"nimble\"}\n}\n\n// FromData will find and optionally verify Nimble secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Nimble,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.nimble.com/api/v1/myself\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Nimble\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Nimble is a customer relationship management (CRM) tool designed to help businesses manage their customer interactions and data. Nimble API keys can be used to access and modify customer records and other CRM functionalities.\"\n}\n"
  },
  {
    "path": "pkg/detectors/nimble/nimble_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage nimble\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNimble_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NIMBLE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"NIMBLE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nimble secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Nimble,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nimble secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Nimble,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Nimble.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Nimble.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nimble/nimble_test.go",
    "content": "package nimble\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"UtXh3oKr1U1DAxeOuqoaIuGB1DKnyK\"\n\tinvalidPattern = \"UtXh3oKr1U1DAxe?uqoaIuGB1DKnyK\"\n\tkeyword        = \"nimble\"\n)\n\nfunc TestNimble_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword nimble\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/noticeable/noticeable.go",
    "content": "package noticeable\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"noticeable\"}) + `\\b([0-9a-zA-Z]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"noticeable\"}\n}\n\n// FromData will find and optionally verify Noticeable secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Noticeable,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\"query\": \"YOUR_GRAPHQL_PAYLOAD\"}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.noticeable.io/graphql\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Apikey %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Noticeable\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Noticeable is a service used for creating and managing changelogs and release notes. Noticeable API keys can be used to access and modify this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/noticeable/noticeable_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage noticeable\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNoticeable_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NOTICEABLE\")\n\tinactiveSecret := testSecrets.MustGetField(\"NOTICEABLE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a noticeable secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Noticeable,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a noticeable secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Noticeable,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Noticeable.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Noticeable.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/noticeable/noticeable_test.go",
    "content": "package noticeable\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"Db0FUP2r8lKJE9sLt8BP\"\n\tinvalidPattern = \"Db0FUP2r8l?JE9sLt8BP\"\n\tkeyword        = \"noticeable\"\n)\n\nfunc TestNoticeable_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword noticeable\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/notion/notion.go",
    "content": "package notion\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(secret_[A-Za-z0-9]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"notion\"}\n}\n\n// FromData will find and optionally verify Notion secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Notion,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.notion.com/v1/users\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Notion-Version\", \"2022-06-28\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 || res.StatusCode == 403 {\n\t\t\t\t\t// if >= 200 and < 300, the secret is valid and has privileges for the /v1/users endpoint\n\t\t\t\t\t// If 403, the secret is valid, but does not have privileges for the /v1/users endpoint,\n\t\t\t\t\t// Notion returns 401 for all non-valid keys, thus 403 indicates it has fine-tuned permissions,\n\t\t\t\t\t// /v1/search, /v1/databases/*, etc. may work.\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\"key\": resMatch}\n\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Notion\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Notion is a productivity tool that provides an all-in-one workspace for note-taking, project management, and collaboration. Notion API keys can be used to access and modify data within a user's Notion workspace.\"\n}\n"
  },
  {
    "path": "pkg/detectors/notion/notion_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage notion\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNotion_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NOTION_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"NOTION_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a notion secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Notion,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a notion secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Notion,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Notion.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Notion.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/notion/notion_test.go",
    "content": "package notion\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"secret_oPp4m4V04lEWqgLGyhCN7b8H9JLANmyE1AYl2aG3aTN\"\n\tinvalidPattern = \"secret_oPp4m4V04lEWqgLGyh?N7b8H9JLANmyE1AYl2aG3aTN\"\n\tkeyword        = \"notion\"\n)\n\nfunc TestNotion_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword notion\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nozbeteams/nozbeteams.go",
    "content": "package nozbeteams\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"nozbe\", \"nozbeteams\"}) + `\\b([0-9A-Za-z]{16}_[0-9A-Za-z\\-_]{64}[ \\r\\n]{1})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"nozbe\", \"nozbeteams\"}\n}\n\n// FromData will find and optionally verify NozbeTeams secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_NozbeTeams,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api4.nozbe.com/v1/api/users\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_NozbeTeams\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"NozbeTeams is a collaborative task and project management tool. NozbeTeams API keys can be used to access and manage tasks and projects within a NozbeTeams account.\"\n}\n"
  },
  {
    "path": "pkg/detectors/nozbeteams/nozbeteams_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage nozbeteams\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNozbeTeams_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NOZBETEAMS\")\n\tinactiveSecret := testSecrets.MustGetField(\"NOZBETEAMS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nozbeteams secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NozbeTeams,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nozbeteams secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NozbeTeams,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"NozbeTeams.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"NozbeTeams.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nozbeteams/nozbeteams_test.go",
    "content": "package nozbeteams\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"A0aYC0WgVlyOIXfk_i17lPGhgawd1Pv-Em50D8eSbBDJQp92J8fPvGTTjMXwxRW1iZP8SFFkvL4c77uw6\"\n\tinvalidPattern = \"A0aYC0WgVlyOIXfk_i17lPGhgawd1Pv-Em50D8eSb?DJQp92J8fPvGTTjMXwxRW1iZP8SFFkvL4c77uw6\"\n\tkeyword        = \"nozbeteams\"\n)\n\nfunc TestNozbeTeams_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword nozbeteams\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s\\r'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s\\r' | '%s\\r'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s\\r'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s\\r'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/npmtoken/npmtoken.go",
    "content": "package npmtoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interfaces at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nfunc (s Scanner) Version() int { return 1 }\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"npm\"}) + `\\b([0-9Aa-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"npm\"}\n}\n\n// FromData will find and optionally verify NpmToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_NpmToken,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\t\ts1.ExtraData = map[string]string{\n\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/npm/\",\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://registry.npmjs.org/-/whoami\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\"key\": resMatch,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_NpmToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"NPM tokens are used to authenticate and publish packages to the npm registry.\"\n}\n"
  },
  {
    "path": "pkg/detectors/npmtoken/npmtoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage npmtoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNpmToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NPMTOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"NPMTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a npmtoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NpmToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a npmtoken secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NpmToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"NpmToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"NpmToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/npmtoken/npmtoken_test.go",
    "content": "package npmtoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"3aAcac6c-9847-23d9-ce65-917590b81cf0\"\n\tinvalidPattern = \"3aAcac6c?9847-23d9-ce65-917590b81cf0\"\n\tkeyword        = \"npmtoken\"\n)\n\nfunc TestNpmToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword npmtoken\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/npmtokenv2/npmtokenv2.go",
    "content": "package npmtokenv2\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interfaces at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nfunc (s Scanner) Version() int { return 2 }\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`(npm_[0-9a-zA-Z]{36})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"npm_\"}\n}\n\n// FromData will find and optionally verify NpmTokenV2 secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := match[1]\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_NpmToken,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\t\ts1.ExtraData = map[string]string{\n\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/npm/\",\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://registry.npmjs.org/-/whoami\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\"key\": resMatch,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_NpmToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"NPM tokens are used to authenticate and publish packages to the NPM registry.\"\n}\n"
  },
  {
    "path": "pkg/detectors/npmtokenv2/npmtokenv2_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage npmtokenv2\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNpmToken_New_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NPMTOKEN_NEW\")\n\tinactiveSecret := testSecrets.MustGetField(\"NPMTOKEN_NEW_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a npmtoken_new secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NpmToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a npmtoken_new secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NpmToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"NpmToken_New.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"NpmToken_New.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/npmtokenv2/npmtokenv2_test.go",
    "content": "package npmtokenv2\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"npm_hK0FJXBYCkejhEMY4Kp6bOOZn1DlfBOmtbJY\"\n\tinvalidPattern = \"npm_hK0FJXBYCkejhEMY?Kp6bOOZn1DlfBOmtbJY\"\n\tkeyword        = \"npmtokenv2\"\n)\n\nfunc TestNpmToken_New_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword npmtokenv2\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nugetapikey/nugetapikey.go",
    "content": "package nugetapikey\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"nuget\"}) + `\\b([a-z0-9]{46})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"nuget\"}\n}\n\n// FromData will find and optionally verify Nugetapikey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_NuGetApiKey,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"PUT\", \"https://www.nuget.org/api/v2/package\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Length\", \"0\")\n\t\t\treq.Header.Add(\"X-NuGet-ApiKey\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\t// we can either match on response code \"400\" or \"Bad Request\" response\n\t\t\t\tif res.StatusCode == 400 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_NuGetApiKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"NuGet is a package manager for .NET. NuGet API keys can be used to publish and manage packages in the NuGet repository.\"\n}\n"
  },
  {
    "path": "pkg/detectors/nugetapikey/nugetapikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage nugetapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNugetapikey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NUGETAPIKEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"NUGETAPIKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nugetapikey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NuGetApiKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nugetapikey secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NuGetApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Nugetapikey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Nugetapikey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nugetapikey/nugetapikey_test.go",
    "content": "package nugetapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"hysqax25w3gqgvz7k1exe07xqosc38yu2oyudjuiaqcyxo\"\n\tinvalidPattern = \"hysqa?25w3gqgvz7k1exe07xqosc38yu2oyudjuiaqcyxo\"\n\tkeyword        = \"nugetapikey\"\n)\n\nfunc TestNugetapikey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword nugetapikey\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/numverify/numverify.go",
    "content": "package numverify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"numverify\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"numverify\"}\n}\n\n// FromData will find and optionally verify Numverify secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Numverify,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://apilayer.net/api/validate?access_key=%s&number=14158586273\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `country_code`) || strings.Contains(bodyString, `\"info\":\"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption.\"`)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Numverify\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Numverify is a service used to validate phone numbers and gather information about them. Numverify API keys can be used to access this service and validate phone numbers.\"\n}\n"
  },
  {
    "path": "pkg/detectors/numverify/numverify_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage numverify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNumverify_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NUMVERIFY\")\n\tinactiveSecret := testSecrets.MustGetField(\"NUMVERIFY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a numverify secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Numverify,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a numverify secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Numverify,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Numverify.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Numverify.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/numverify/numverify_test.go",
    "content": "package numverify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"aeo8h39fhjoa5txhrx633ad7t5ozankv\"\n\tinvalidPattern = \"aeo8h39fhjoa5txh?x633ad7t5ozankv\"\n\tkeyword        = \"numverify\"\n)\n\nfunc TestNumverify_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword numverify\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nutritionix/nutritionix.go",
    "content": "package nutritionix\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"nutritionix\"}) + `\\b([a-z0-9]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"nutritionix\"}) + `\\b([a-z0-9]{8})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"nutritionix\"}\n}\n\n// FromData will find and optionally verify Nutritionix secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Nutritionix,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tpayload := strings.NewReader(`{\"query\":\"for breakfast i ate 2 eggs, bacon, and french toast\",\"timezone\":\"US/Eastern\"}`)\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://trackapi.nutritionix.com/v2/natural/nutrients\", payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\treq.Header.Add(\"x-app-id\", resIdMatch)\n\t\t\t\treq.Header.Add(\"x-app-key\", resMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Nutritionix\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Nutritionix provides a food tracking API. The API keys can be used to access and log nutritional data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/nutritionix/nutritionix_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage nutritionix\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNutritionix_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NUTRITIONIX\")\n\tid := testSecrets.MustGetField(\"NUTRITIONIX_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"NUTRITIONIX_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nutritionix secret %s within nutritionix %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Nutritionix,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nutritionix secret %s within nutritionix %s but not valid\", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Nutritionix,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Nutritionix.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Nutritionix.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nutritionix/nutritionix_test.go",
    "content": "package nutritionix\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey   = \"4wp0s2ug2fvj2qwvi19t1u84juyuj025\"\n    invalidKey = \"4wp0s2ug2fvj2qwv?19t1u84juyuj025\"\n    validId    = \"qqp3hmop\"\n    invalidId  = \"qqp3?mop\"\n    keyword    = \"nutritionix\"\n)\n\nfunc TestNutritionix_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword nutritionix\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nvapi/nvapi.go",
    "content": "package nvapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(nvapi-[a-zA-Z0-9_-]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"nvapi-\"}\n}\n\n// FromData will find and optionally verify Nvapi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_NVAPI,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\tdata := url.Values{}\n\tdata.Set(\"credentials\", token)\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://api.ngc.nvidia.com/v3/keys/get-caller-info\", strings.NewReader(data.Encode()))\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\t// If the endpoint returns useful information, we can return it as a map.\n\t\treturn true, nil, nil\n\tcase http.StatusUnauthorized:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_NVAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"NVAPI keys are used to authenticate API requests to NVIDIA's NGC API. They allow access to NVIDIA's NGC API to manage user data and perform actions on behalf of users.\"\n}\n"
  },
  {
    "path": "pkg/detectors/nvapi/nvapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage nvapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNvapi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NVAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"NVAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nvapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NVAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nvapi secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NVAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nvapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NVAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nvapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_NVAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Nvapi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Nvapi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nvapi/nvapi_test.go",
    "content": "package nvapi\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestNvapi_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: \"nvapi_token = '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'\",\n\t\t\twant:  []string{\"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\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nylas/nylas.go",
    "content": "package nylas\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"nylas\"}) + `\\b([0-9A-Za-z]{30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"nylas\"}\n}\n\n// FromData will find and optionally verify Nylas secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Nylas,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.nylas.com/account\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.nylas+json; version=3\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Nylas\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Nylas is a platform providing email, calendar, and contacts APIs. Nylas API keys can be used to access and manage these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/nylas/nylas_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage nylas\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestNylas_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"NYLAS\")\n\tinactiveSecret := testSecrets.MustGetField(\"NYLAS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nylas secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Nylas,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a nylas secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Nylas,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Nylas.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Nylas.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/nylas/nylas_test.go",
    "content": "package nylas\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"cX68QYZ3qjutMFfWoV1haklENwd9ts\"\n\tinvalidPattern = \"cX68QYZ3qjutMFf?oV1haklENwd9ts\"\n\tkeyword        = \"nylas\"\n)\n\nfunc TestNylas_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword nylas\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/oanda/oanda.go",
    "content": "package oanda\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"oanda\"}) + `\\b([a-zA-Z0-9]{24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"oanda\"}\n}\n\n// FromData will find and optionally verify Oanda secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Oanda,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://web-services.oanda.com/rates/api/v2/currencies.json\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Oanda\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Oanda is a forex trading platform. Oanda API keys can be used to access and manage trading accounts, execute trades, and retrieve market data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/oanda/oanda_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage oanda\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOanda_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"OANDA\")\n\tinactiveSecret := testSecrets.MustGetField(\"OANDA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a oanda secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Oanda,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a oanda secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Oanda,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Oanda.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Oanda.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/oanda/oanda_test.go",
    "content": "package oanda\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"W4fZrpEAmoSctNxqLvmXFoXE\"\n\tinvalidPattern = \"W4fZrpEAmoSc?NxqLvmXFoXE\"\n\tkeyword        = \"oanda\"\n)\n\nfunc TestOanda_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword oanda\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/okta/okta.go",
    "content": "package okta\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\tdomainPat     = regexp.MustCompile(`\\b[a-z0-9-]{1,40}\\.okta(?:preview|-emea){0,1}\\.com\\b`)\n\ttokenPat      = regexp.MustCompile(`\\b00[a-zA-Z0-9_-]{40}\\b`)\n\t// TODO: Oauth client secrets\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\".okta\"}\n}\n\n// FromData will find and optionally verify Okta secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueTokens, uniqueDomains = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, matches := range tokenPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokens[matches[0]] = struct{}{}\n\t}\n\n\tfor _, matches := range domainPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueDomains[matches[0]] = struct{}{}\n\t}\n\n\tfor token := range uniqueTokens {\n\t\tfor domain := range uniqueDomains {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Okta,\n\t\t\t\tRaw:          []byte(token),\n\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s\", domain, token)),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, verificationErr := verifyOktaToken(ctx, client, domain, token)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc verifyOktaToken(ctx context.Context, client *http.Client, domain, token string) (bool, error) {\n\t// curl -v -X GET \\\n\t// -H \"Accept: application/json\" \\\n\t// -H \"Content-Type: application/json\" \\\n\t// -H \"Authorization: SSWS token\" \\\n\t// \"https://subdomain.okta.com/api/v1/users/me\"\n\n\turl := fmt.Sprintf(\"https://%s/api/v1/users/me\", domain)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Set(\"Accept\", \"application/json\")\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"SSWS %s\", token))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer resp.Body.Close()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tbody, _ := io.ReadAll(resp.Body)\n\t\treturn strings.Contains(string(body), \"\\\"activated\\\":\"), nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Okta\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Okta is an identity and access management service. Okta tokens can be used to authenticate and access various resources and APIs within an organization.\"\n}\n"
  },
  {
    "path": "pkg/detectors/okta/okta_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage okta\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOkta_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"OKTA\")\n\tsecretInactive := testSecrets.MustGetField(\"OKTA_INACTIVE\")\n\tdomain := testSecrets.MustGetField(\"OKTA_DOMAIN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found token, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a okta secret %s within oktaDomain %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Okta,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a okta secret %s within oktaDomain %s\", secretInactive, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Okta,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found verifiable secret, verification failed due to unexpected API response\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a okta secret %s within oktaDomain %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Okta,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Okta.FromData) error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Okta.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/okta/okta_test.go",
    "content": "package okta\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidDomain   = \"a254u0kjk-y64jh762weozo374t.okta.com\"\n\tinvalidDomain = \"a254u0kjk-y64jh762?eozo374t.okta.com\"\n\tvalidToken    = \"00JIBRGk12UbkjQaaw4L7VYy9EE8zAEEkIeqGNcSQm\"\n\tinvalidToken  = \"00JIBRGk12UbkjQaaw4L7?Yy9EE8zAEEkIeqGNcSQm\"\n\tkeyword       = \"okta\"\n)\n\nfunc TestOkta_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword okta\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validDomain, keyword, validToken),\n\t\t\twant:  []string{validDomain + \":\" + validToken},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidDomain, keyword, invalidToken),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/omnisend/omnisend.go",
    "content": "package omnisend\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"omnisend\"}) + `\\b([a-z0-9A-Z-]{75})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"omnisend\"}\n}\n\n// FromData will find and optionally verify Omnisend secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Omnisend,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.omnisend.com/v3/contacts?limit=100&offset=0\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-API-KEY\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Omnisend\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Omnisend is a marketing automation platform focused on e-commerce. Omnisend API keys can be used to access and manage marketing campaigns, contacts, and other resources within the Omnisend platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/omnisend/omnisend_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage omnisend\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOmnisend_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"OMNISEND_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"OMNISEND_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a omnisend secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Omnisend,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a omnisend secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Omnisend,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Omnisend.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Omnisend.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/omnisend/omnisend_test.go",
    "content": "package omnisend\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"XoKBwjiMvYw7bhB0kX8c4O02YYsy5rrwDFszYLlJsTT08jZqFH4byZsqdHrlHrid-m5zeIaQlGZ\"\n\tinvalidPattern = \"XoKBwjiMvYw7bhB0kX8c4O02YYsy5rrwDFszY?lJsTT08jZqFH4byZsqdHrlHrid-m5zeIaQlGZ\"\n\tkeyword        = \"omnisend\"\n)\n\nfunc TestOmnisend_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword omnisend\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/onedesk/onedesk.go",
    "content": "package onedesk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\temailPat = regexp.MustCompile(common.EmailPattern)\n\tpwordPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"onedesk\"}) + `\\b([a-zA-Z0-9!=@#$%^]{8,64})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"onedesk\"}\n}\n\n// FromData will find and optionally verify Onedesk secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tpwordMatches := pwordPat.FindAllStringSubmatch(dataStr, -1)\n\n\tuniqueEmailMatches := make(map[string]struct{})\n\tfor _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor emailMatch := range uniqueEmailMatches {\n\t\tfor _, pwordMatch := range pwordMatches {\n\t\t\tresPword := strings.TrimSpace(pwordMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Onedesk,\n\t\t\t\tRaw:          []byte(emailMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tpayload := strings.NewReader(fmt.Sprintf(`{\"email\": \"%s\", \"password\": \"%s\"}`, emailMatch, resPword))\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://app.onedesk.com/rest/2.0/login/loginUser\", payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tbody := string(bodyBytes)\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && strings.Contains(body, `\"code\":\"SUCCESS\"`) {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Onedesk\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Onedesk is a customer service and project management software. Onedesk credentials can be used to access and manage customer service tickets and project tasks.\"\n}\n"
  },
  {
    "path": "pkg/detectors/onedesk/onedesk_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage onedesk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOnedesk_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\temail := testSecrets.MustGetField(\"SCANNERS_EMAIL\")\n\tpword := testSecrets.MustGetField(\"SCANNERS_PASS\")\n\tinactivePword := testSecrets.MustGetField(\"SCANNERS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a onedesk email %s within onedesk password %s\", email, pword)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Onedesk,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a onedesk secret %s within onedesk password %s but not valid\", email, inactivePword)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Onedesk,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Onedesk.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Onedesk.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/onedesk/onedesk_test.go",
    "content": "package onedesk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"abc123!@#^()def456GHijk$% / testuser1005@example.com\"\n\tinvalidPattern = \"abcde/testing@go\"\n)\n\nfunc TestOneDesk_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: fmt.Sprintf(\"onedesk: %s\", validPattern),\n\t\t\twant:  []string{\"testuser1005@example.com\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"onedesk: %s\", invalidPattern),\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 && test.want != nil {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"expected %d results, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/onelogin/onelogin.go",
    "content": "package onelogin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\toauthClientIDPat     = regexp.MustCompile(`(?i)id[a-zA-Z0-9_' \"=]{0,20}([a-z0-9]{64})`)\n\toauthClientSecretPat = regexp.MustCompile(`(?i)secret[a-zA-Z0-9_' \"=]{0,20}([a-z0-9]{64})`)\n\n\t// TODO: Legacy API tokens\n\n\tapiDomains = []string{\"api.us.onelogin.com\", \"api.eu.onelogin.com\"}\n\n\tclient = http.Client{Timeout: time.Second * 5}\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"onelogin\"}\n}\n\n// FromData will find and optionally verify Onelogin secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tfor _, clientID := range oauthClientIDPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tfor _, clientSecret := range oauthClientSecretPat.FindAllStringSubmatch(dataStr, -1) {\n\n\t\t\tresult := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_OneLogin,\n\t\t\t\tRaw:          []byte(clientID[1]),\n\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s\", clientID[1], clientSecret[1])),\n\t\t\t\tRedacted:     clientID[1],\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tfor _, domain := range apiDomains {\n\t\t\t\t\ttokenURL := fmt.Sprintf(\"https://%s/auth/oauth2/v2/token\", domain)\n\t\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", tokenURL, strings.NewReader(`{\"grant_type\":\"client_credentials\"}`))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"client_id:%s, client_secret:%s\", clientID[1], clientSecret[1]))\n\t\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json; charset=utf-8\")\n\t\t\t\t\tres, err := client.Do(req)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tres.Body.Close() // The request body is unused.\n\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\t\tresult.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, result)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_OneLogin\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"OneLogin is an identity and access management provider. OneLogin OAuth client IDs and secrets can be used to authenticate and authorize API requests.\"\n}\n"
  },
  {
    "path": "pkg/detectors/onelogin/onelogin_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage onelogin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOnelogin_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ONELOGIN\")\n\tsecretInactive := testSecrets.MustGetField(\"ONELOGIN_INACTIVE\")\n\tid := testSecrets.MustGetField(\"ONELOGIN_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a onelogin secret=%s within onelogin id %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OneLogin,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     id,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a onelogin secret=%s within onelogin id %s\", secretInactive, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OneLogin,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     id,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Onelogin.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Onelogin.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/onelogin/onelogin_test.go",
    "content": "package onelogin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidOauthClientID       = \"rz5dx87tuykcvonk5577picx71wkllmq14tzexx8a8oaeee6yar815drbn3s4umx\"\n\tinvalidOauthClientID     = \"rz5dx87tuy?cvonk5577picx71wkllmq14tzexx8a8oaeee6yar815drbn3s4umx\"\n\tvalidOauthClientSecret   = \"51exgkt8tf875584xr5awedi6y48txm0vzdwjzxxgp3119o5rpyubn07gm6a5u2l\"\n\tinvalidOauthClientSecret = \"51exgkt?tf875584xr5awedi6y48txm0vzdwjzxxgp3119o5rpyubn07gm6a5u2l\"\n\tkeyword                  = \"onelogin\"\n)\n\nfunc TestOnelogin_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword onelogin\",\n\t\t\tinput: fmt.Sprintf(\"%s token - 'idr_%s'\\n%s token - 'secret_%s'\\n\", keyword, validOauthClientID, keyword, validOauthClientSecret),\n\t\t\twant:  []string{validOauthClientID + \":\" + validOauthClientSecret},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - 'idr_%s'\\n%s token - 'secret_%s'\\n\", keyword, invalidOauthClientID, keyword, invalidOauthClientSecret),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/onepagecrm/onepagecrm.go",
    "content": "package onepagecrm\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"onepagecrm\"}) + `\\b([a-zA-Z0-9=]{44})`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"onepagecrm\"}) + `\\b([a-z0-9]{24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"onepagecrm\"}\n}\n\n// FromData will find and optionally verify OnepageCRM secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\ttokenPatMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tuserPatMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_OnepageCRM,\n\t\t\t\tRaw:          []byte(tokenPatMatch),\n\t\t\t}\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.onepagecrm.com/api/v3/contacts.json?per_page=20&page=1\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.SetBasicAuth(userPatMatch, tokenPatMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_OnepageCRM\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"OnepageCRM is a simple CRM system designed for small businesses. The API keys can be used to access and manage contacts, deals, and other CRM data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/onepagecrm/onepagecrm_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage onepagecrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOnepageCRM_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ONEPAGECRM\")\n\tuser := testSecrets.MustGetField(\"ONEPAGECRM_USER\")\n\tinactiveSecret := testSecrets.MustGetField(\"ONEPAGECRM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a onepagecrm secret %s within onepagecrm %s within\", secret, user)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OnepageCRM,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a onepagecrm secret %s within onepagecrm %s but not valid\", inactiveSecret, user)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OnepageCRM,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"OnepageCRM.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"OnepageCRM.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/onepagecrm/onepagecrm_test.go",
    "content": "package onepagecrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey   = \"R1kugPxUfw9oSeg=f7tofxYrLnG2PIEVuOSJhUNNahEe\"\n    invalidKey = \"R1kugPxUfw9oSeg=f7tofx?rLnG2PIEVuOSJhUNNahEe\"\n    validId    = \"tg1fnd78xz8ye9jl4zf6sarf\"\n    invalidId  = \"tg1fnd78xz8y?9jl4zf6sarf\"\n    keyword    = \"onepagecrm\"\n)\n\nfunc TestOnepageCRM_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword onepagecrm\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/onesignal/onesignal.go",
    "content": "package onesignal\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"onesignal\"}) + common.UUIDPattern)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"onesignal\"}\n}\n\n// FromData will find and optionally verify Onesignal secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Onesignal,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tdata := fmt.Sprintf(\"%s:\", resMatch)\n\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://onesignal.com/api/v1/apps\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.onesignal+json; version=3\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Onesignal\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"OneSignal is a customer engagement platform that enables businesses to send push notifications, in-app messages, SMS, and emails. OneSignal API keys can be used to access and send messages through these channels.\"\n}\n"
  },
  {
    "path": "pkg/detectors/onesignal/onesignal_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage onesignal\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOnesignal_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ONESIGNAL\")\n\tinactiveSecret := testSecrets.MustGetField(\"ONESIGNAL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a onesignal secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Onesignal,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a onesignal secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Onesignal,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Onesignal.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Onesignal.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/onesignal/onesignal_test.go",
    "content": "package onesignal\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"e8b13e2b-79a9-e75d-b8e3-b2200edbc52a\"\n\tinvalidPattern = \"e8b13e2b?79a9-e75d-b8e3-b2200edbc52a\"\n)\n\nfunc TestOnesignal_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: fmt.Sprintf(\"onesignal: %s\", validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"onesignal: %s\", invalidPattern),\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 && test.want != nil {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"expected %d results, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/onfleet/onfleet.go",
    "content": "package onfleet\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"onfleet\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"onfleet\"}\n}\n\n// FromData will find and optionally verify Onfleet secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Onfleet,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://onfleet.com/api/v2/organization\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdata := fmt.Sprintf(\"%s:\", token)\n\tencoded := b64.StdEncoding.EncodeToString([]byte(data))\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", encoded))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t// If the endpoint returns useful information, we can return it as a map.\n\t\treturn true, nil, nil\n\t} else if res.StatusCode == 401 {\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\t} else {\n\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\treturn false, nil, err\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Onfleet\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Onfleet is a delivery management software that allows businesses to manage and analyze their delivery operations. Onfleet API keys can be used to access and manage the delivery data and operations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/onfleet/onfleet_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage onfleet\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOnfleet_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ONFLEET\")\n\tinactiveSecret := testSecrets.MustGetField(\"ONFLEET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a onfleet secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Onfleet,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a onfleet secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Onfleet,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a onfleet secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Onfleet,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a onfleet secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Onfleet,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Onfleet.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Onfleet.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/onfleet/onfleet_test.go",
    "content": "package onfleet\n\nimport (\n\t\"context\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"testing\"\n)\n\nfunc TestOnfleet_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: \"onfleet_token = 'fhyki1ktfg7upk93qmzm424mn8p3tbm1'\",\n\t\t\twant:  []string{\"fhyki1ktfg7upk93qmzm424mn8p3tbm1\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/oopspam/oopspam.go",
    "content": "package oopspam\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"oopspam\"}) + `\\b([a-zA-Z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"oopspam\"}\n}\n\n// FromData will find and optionally verify OOPSpam secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_OOPSpam,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\"checkForLength\":true,\"content\":\"Dear Agent, We are a manufacturing company which specializes in supplying Aluminum Rod with Zinc Alloy Rod to customers worldwide, based in Japan, Asia. We have been unable to follow up payments effectively for transactions with debtor customers in your country due to our distant locations, thus our reason for requesting for your services representation.\",\"senderIP\":\"185.234.219.246\",\"allowedCountries\":[\"it\",\"us\"],\"allowedLanguages\":[\"en\"]}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.oopspam.com/v1/spamdetection\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"X-Api-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_OOPSpam\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"OOPSpam is a spam detection service. OOPSpam API keys can be used to verify and detect spam content.\"\n}\n"
  },
  {
    "path": "pkg/detectors/oopspam/oopspam_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage oopspam\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOOPSpam_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"OOPSPAM\")\n\tinactiveSecret := testSecrets.MustGetField(\"OOPSPAM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a oopspam secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OOPSpam,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a oopspam secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OOPSpam,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"OOPSpam.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"OOPSpam.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/oopspam/oopspam_test.go",
    "content": "package oopspam\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"a9PxCUoI7bCKQogm47qpznV3hu49kO0Qduc4025t\"\n\tinvalidPattern = \"a9PxCUoI7bCKQogm47qp?nV3hu49kO0Qduc4025t\"\n\tkeyword        = \"oopspam\"\n)\n\nfunc TestOOPSpam_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword oopspam\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/openai/openai.go",
    "content": "package openai\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// The magic string T3BlbkFJ is the base64-encoded string: OpenAI\n\t// Matches: legacy keys (sk-{alnum}T3BlbkFJ...), project keys (sk-proj-...),\n\t//          service account keys (sk-svcacct-... or sk-service-...)\n\t// Does NOT match: admin keys (sk-admin-...)\n\tkeyPat = regexp.MustCompile(`\\b(sk-(?:(?:proj|svcacct|service)-[A-Za-z0-9_-]+|[a-zA-Z0-9]+)T3BlbkFJ[A-Za-z0-9_-]+)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"T3BlbkFJ\"}\n}\n\n// FromData will find and optionally verify OpenAI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor token := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_OpenAI,\n\t\t\tRedacted:     token[:3] + \"...\" + token[min(len(token)-1, 47):],\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tverified, extraData, verificationErr := verifyToken(ctx, client, token)\n\t\t\ts1.Verified = verified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\ts1.AnalysisInfo = map[string]string{\"key\": token}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyToken(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.openai.com/v1/me\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json; charset=utf-8\")\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase 200:\n\t\tvar resData response\n\t\tif err = json.NewDecoder(res.Body).Decode(&resData); err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\n\t\textraData := map[string]string{\n\t\t\t\"id\":          resData.ID,\n\t\t\t\"total_orgs\":  fmt.Sprintf(\"%d\", len(resData.Orgs.Data)),\n\t\t\t\"mfa_enabled\": strconv.FormatBool(resData.MfaFlagEnabled),\n\t\t\t\"created_at\":  time.Unix(int64(resData.Created), 0).Format(time.RFC3339),\n\t\t}\n\n\t\tif len(resData.Orgs.Data) > 0 {\n\t\t\textraData[\"description\"] = resData.Orgs.Data[0].Description\n\t\t\textraData[\"is_personal\"] = strconv.FormatBool(resData.Orgs.Data[0].Personal)\n\t\t\textraData[\"is_default\"] = strconv.FormatBool(resData.Orgs.Data[0].IsDefault)\n\t\t}\n\t\treturn true, extraData, nil\n\tcase 401:\n\t\t// Invalid\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_OpenAI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"OpenAI provides various AI models and services. The API keys can be used to access and interact with these models and services.\"\n}\n\ntype response struct {\n\tObject                   string `json:\"object\"`\n\tID                       string `json:\"id\"`\n\tEmail                    any    `json:\"email\"`\n\tName                     any    `json:\"name\"`\n\tPicture                  any    `json:\"picture\"`\n\tCreated                  int    `json:\"created\"`\n\tPhoneNumber              any    `json:\"phone_number\"`\n\tMfaFlagEnabled           bool   `json:\"mfa_flag_enabled\"`\n\tOrgs                     orgs   `json:\"orgs\"`\n\tHasProjectsArchive       bool   `json:\"has_projects_archive\"`\n\tHasPaygProjectSpendLimit bool   `json:\"has_payg_project_spend_limit\"`\n\tAmr                      []any  `json:\"amr\"`\n}\ntype settings struct {\n\tThreadsUIVisibility      string `json:\"threads_ui_visibility\"`\n\tUsageDashboardVisibility string `json:\"usage_dashboard_visibility\"`\n}\ntype projects struct {\n\tObject string `json:\"object\"`\n\tData   []any  `json:\"data\"`\n}\ntype data struct {\n\tObject      string   `json:\"object\"`\n\tID          string   `json:\"id\"`\n\tCreated     int      `json:\"created\"`\n\tTitle       string   `json:\"title\"`\n\tName        string   `json:\"name\"`\n\tDescription string   `json:\"description\"`\n\tPersonal    bool     `json:\"personal\"`\n\tSettings    settings `json:\"settings\"`\n\tParentOrgID any      `json:\"parent_org_id\"`\n\tIsDefault   bool     `json:\"is_default\"`\n\tRole        string   `json:\"role\"`\n\tProjects    projects `json:\"projects\"`\n}\ntype orgs struct {\n\tObject string `json:\"object\"`\n\tData   []data `json:\"data\"`\n}\n"
  },
  {
    "path": "pkg/detectors/openai/openai_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage openai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOpenAI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*10)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\toaiUnverified := testSecrets.MustGetField(\"OPENAI_UNVERIFIED\")\n\toaiVerified := testSecrets.MustGetField(\"OPENAI_VERIFIED\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Found, unverified OpenAI token sk-\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find an OpenAI secret %s within\", oaiUnverified)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OpenAI,\n\t\t\t\t\tRedacted:     \"sk-...gOPc\",\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Found, verified OpenAI token sk-\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find an OpenAI secret %s within\", oaiVerified)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OpenAI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     \"sk-...gOPb\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"OpenAI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].ExtraData = nil\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"OpenAI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/openai/openai_test.go",
    "content": "package openai\n\nimport (\n\t\"context\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"testing\"\n)\n\nfunc TestOpenAI_DoesNotMatchAdminKeys(t *testing.T) {\n\td := Scanner{}\n\tadminKey := `OPENAI_ADMIN_KEY = \"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\"`\n\n\tresults, err := d.FromData(context.Background(), false, []byte(adminKey))\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\tif len(results) != 0 {\n\t\tt.Errorf(\"openai detector should not match admin keys, but got %d results\", len(results))\n\t}\n}\n\nfunc TestOpenAI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"user API key\",\n\t\t\tinput: \"openai.api-key: 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\",\n\t\t\twant:  []string{\"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\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"project API key\",\n\t\t\tinput: `OPENAI_API_KEY = \"sk-proj-mpjtr05CFsJqs4TAeKlCT3BlbkFJsh1KtN0SUjTPeJiagE8K\"`,\n\t\t\twant:  []string{\"sk-proj-mpjtr05CFsJqs4TAeKlCT3BlbkFJsh1KtN0SUjTPeJiagE8K\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"service account API key\",\n\t\t\tinput: `OPENAI_API_KEY = \"sk-service-account-name-Ofbtr05CFsJqs4TAeKlCT3BlbkFJsh1KtN0SUjTPeJiaglyC\"`,\n\t\t\twant:  []string{\"sk-service-account-name-Ofbtr05CFsJqs4TAeKlCT3BlbkFJsh1KtN0SUjTPeJiaglyC\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"newer user API key\",\n\t\t\tinput: `\"OPENAI_API_KEY = \"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\"`,\n\t\t\twant:  []string{\"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\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"newer service account API key\",\n\t\t\tinput: `OPENAI_API_KEY = \"sk-svcacct-IUXtc5gIZK-2cBfB-nTgEWbD8mi-fi-gc20oGtq8ve51sET3BlbkFJCg8iQkCVz_nmE_q1dCWlMpemoaoMqHzQ6D-FnWGqlz4C8A\"`,\n\t\t\twant:  []string{\"sk-svcacct-IUXtc5gIZK-2cBfB-nTgEWbD8mi-fi-gc20oGtq8ve51sET3BlbkFJCg8iQkCVz_nmE_q1dCWlMpemoaoMqHzQ6D-FnWGqlz4C8A\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tdetectorMatches := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(detectorMatches) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/openaiadmin/openaiadmin.go",
    "content": "package openaiadmin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Admin keys follow the format: sk-admin-{58 chars}T3BlbkFJ{58 chars}\n\t// Total length: 133 chars (9 char prefix + 124 chars for key)\n\t// where T3BlbkFJ is the base64-encoded string: OpenAI\n\tkeyPat = regexp.MustCompile(`\\b(sk-admin-[A-Za-z0-9_-]{58}T3BlbkFJ[A-Za-z0-9_-]{58})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sk-admin-\"}\n}\n\n// FromData will find and optionally verify Openaiadmin secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor token := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_OpenAIAdmin,\n\t\t\tRedacted:     token[:11] + \"...\" + token[len(token)-4:],\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, token)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, token)\n\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\"key\": token,\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\t// Use the Admin API Keys list endpoint to verify the admin key\n\t// https://platform.openai.com/docs/api-reference/admin-api-keys/list\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.openai.com/v1/organization/admin_api_keys\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\t// Invalid admin key - determinate failure\n\t\treturn false, nil\n\tdefault:\n\t\t// Unexpected response - indeterminate failure\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_OpenAIAdmin\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"OpenAI Admin API keys provide administrative access to OpenAI organization resources. These keys can be used to manage API keys, audit logs, and other organization-level settings.\"\n}\n"
  },
  {
    "path": "pkg/detectors/openaiadmin/openaiadmin_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage openaiadmin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOpenAIAdmin_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*10)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"OPENAI_ADMIN\")\n\tinactiveSecret := testSecrets.MustGetField(\"OPENAI_ADMIN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find an OpenAI admin secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OpenAIAdmin,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find an OpenAI admin secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OpenAIAdmin,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find an OpenAI admin secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OpenAIAdmin,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find an OpenAI admin secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OpenAIAdmin,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"OpenAIAdmin.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"AnalysisInfo\", \"Redacted\")\n\t\t\tignoreUnexported := cmpopts.IgnoreUnexported(detectors.Result{})\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts, ignoreUnexported); diff != \"\" {\n\t\t\t\tt.Errorf(\"OpenAIAdmin.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/openaiadmin/openaiadmin_test.go",
    "content": "package openaiadmin\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestOpenAIAdmin_DoesNotMatchRegularKeys(t *testing.T) {\n\td := Scanner{}\n\n\tregularKeys := []struct {\n\t\tname  string\n\t\tinput string\n\t}{\n\t\t{\n\t\t\tname:  \"legacy key\",\n\t\t\tinput: `OPENAI_API_KEY = \"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\"`,\n\t\t},\n\t\t{\n\t\t\tname:  \"project key\",\n\t\t\tinput: `OPENAI_API_KEY = \"sk-proj-mpjtr05CFsJqs4TAeKlCT3BlbkFJsh1KtN0SUjTPeJiagE8K\"`,\n\t\t},\n\t\t{\n\t\t\tname:  \"service account key\",\n\t\t\tinput: `OPENAI_API_KEY = \"sk-svcacct-IUXtc5gIZK-2cBfB-nTgEWbD8mi-fi-gc20oGtq8ve51sET3BlbkFJCg8iQkCVz_nmE_q1dCWlMpemoaoMqHzQ6D-FnWGqlz4C8A\"`,\n\t\t},\n\t\t{\n\t\t\tname:  \"service account key (old format)\",\n\t\t\tinput: `OPENAI_API_KEY = \"sk-service-account-name-Ofbtr05CFsJqs4TAeKlCT3BlbkFJsh1KtN0SUjTPeJiaglyC\"`,\n\t\t},\n\t}\n\n\tfor _, tc := range regularKeys {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(tc.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\tif len(results) != 0 {\n\t\t\t\tt.Errorf(\"openaiadmin detector should not match %s, but got %d results\", tc.name, len(results))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestOpenAIAdmin_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid admin key - example 1\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Authenticating with OpenAI Admin API\n\t\t\t\t[DEBUG] Admin Key=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\n\t\t\t\t[INFO] Successfully authenticated\n\t\t\t`,\n\t\t\twant: []string{\"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\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid admin key - example 2\",\n\t\t\tinput: `\n\t\t\t\texport OPENAI_ADMIN_KEY=\"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\"\n\t\t\t`,\n\t\t\twant: []string{\"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\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid admin key - example 3\",\n\t\t\tinput: `\n\t\t\t\t{\n\t\t\t\t\t\"openai_admin_api_key\": \"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\"\n\t\t\t\t}\n\t\t\t`,\n\t\t\twant: []string{\"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\"},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple admin keys\",\n\t\t\tinput: `\n\t\t\t\t# Development environment\n\t\t\t\tOPENAI_ADMIN_KEY_DEV=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\n\t\t\t\t# Production environment\n\t\t\t\tOPENAI_ADMIN_KEY_PROD=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\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"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\",\n\t\t\t\t\"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\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"regular OpenAI key should not match\",\n\t\t\tinput: `\n\t\t\t\t# This is a regular OpenAI key, not an admin key\n\t\t\t\tOPENAI_API_KEY=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\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"project key should not match\",\n\t\t\tinput: `\n\t\t\t\tOPENAI_API_KEY = \"sk-proj-mpjtr05CFsJqs4TAeKlCT3BlbkFJsh1KtN0SUjTPeJiagE8K\"\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid - missing T3BlbkFJ signature\",\n\t\t\tinput: `\n\t\t\t\t# This looks like an admin key but is missing the OpenAI signature\n\t\t\t\tFAKE_KEY=sk-admin-JWARXiHjpLXSh6W_0pFGb3sW7yr0cKheXXtWGMY0Q8kbBNqsxLskJy0LCOABCDEFGHgTJWgjMvdi6YlPvdXRqmSlZ4dLK-nFxUG2d9Tgaz5Q6weGVNBaLuUmMV4A\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid - wrong length (too short before signature)\",\n\t\t\tinput: `\n\t\t\t\t# Admin key with incorrect length\n\t\t\t\tBAD_KEY=sk-admin-shortT3BlbkFJgTJWgjMvdi6YlPvdXRqmSlZ4dLK-nFxUG2d9Tgaz5Q6weGVNBaLuUmMV4A\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid - wrong length (too short after signature)\",\n\t\t\tinput: `\n\t\t\t\t# Admin key with incorrect length\n\t\t\t\tBAD_KEY=sk-admin-JWARXiHjpLXSh6W_0pFGb3sW7yr0cKheXXtWGMY0Q8kbBNqsxLskJy0LCOT3BlbkFJshort\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"no matches\",\n\t\t\tinput: `\n\t\t\t\tSome random text without any keys\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t// Skip keyword check for tests that don't expect matches\n\t\t\tif len(test.want) > 0 {\n\t\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/opencagedata/opencagedata.go",
    "content": "package opencagedata\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"opencagedata\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"opencagedata\"}\n}\n\n// FromData will find and optionally verify OpenCageData secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_OpenCageData,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.opencagedata.com/geocode/v1/json?q=12.8797,121.7740&key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_OpenCageData\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"OpenCageData provides a geocoding API to convert coordinates to and from places. OpenCageData keys can be used to access the service's geocoding capabilities.\"\n}\n"
  },
  {
    "path": "pkg/detectors/opencagedata/opencagedata_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage opencagedata\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOpenCageData_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"OPENCAGEDATA\")\n\tinactiveSecret := testSecrets.MustGetField(\"OPENCAGEDATA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a opencagedata secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OpenCageData,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a opencagedata secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OpenCageData,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"OpenCageData.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"OpenCageData.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/opencagedata/opencagedata_test.go",
    "content": "package opencagedata\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"dc07kkpxqkraj009u95s7ay7wpgfjt4j\"\n\tinvalidPattern = \"dc07kkpxqkraj009?95s7ay7wpgfjt4j\"\n\tkeyword        = \"opencagedata\"\n)\n\nfunc TestOpenCageData_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword opencagedata\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/openuv/openuv.go",
    "content": "package openuv\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"openuv\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"openuv\"}\n}\n\n// FromData will find and optionally verify Openuv secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Openuv,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.openuv.io/api/v1/uv?lat=-33.34&lng=115.342\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"x-access-token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} \n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Openuv\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Openuv provides UV index data through its API. These API keys can be used to access UV index information.\"\n}\n"
  },
  {
    "path": "pkg/detectors/openuv/openuv_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage openuv\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOpenuv_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"OPENUV\")\n\tinactiveSecret := testSecrets.MustGetField(\"OPENUV_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a openuv secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Openuv,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a openuv secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Openuv,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Openuv.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Openuv.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/openuv/openuv_test.go",
    "content": "package openuv\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"2f1wbkmd2zg23eqkn4i5lpehcev7xlm3\"\n\tinvalidPattern = \"2f1wbkmd2zg23eqk?4i5lpehcev7xlm3\"\n\tkeyword        = \"openuv\"\n)\n\nfunc TestOpenuv_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword openuv\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/openvpn/openvpn.go",
    "content": "package openvpn\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\tclientIDPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"openvpn\"}) + `\\b([A-Za-z0-9-]{3,40}\\.[A-Za-z0-9-]{3,40})\\b`)\n\tclientSecretPat = regexp.MustCompile(`\\b([a-zA-Z0-9_-]{64,})\\b`)\n\tdomainPat       = regexp.MustCompile(`\\b(https?://[A-Za-z0-9-]+\\.api\\.openvpn\\.com)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"openvpn\"}\n}\n\n// FromData will find and optionally verify Openvpn secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tdomainMatches := domainPat.FindAllStringSubmatch(dataStr, -1)\n\tclientIdMatches := clientIDPat.FindAllStringSubmatch(dataStr, -1)\n\tclientSecretMatches := clientSecretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, clientIdMatch := range clientIdMatches {\n\t\tclientIDRes := strings.TrimSpace(clientIdMatch[1])\n\t\tfor _, clientSecretMatch := range clientSecretMatches {\n\t\t\tclientSecretRes := strings.TrimSpace(clientSecretMatch[1])\n\t\t\tfor _, domainMatch := range domainMatches {\n\t\t\t\tdomainRes := strings.TrimSpace(domainMatch[1])\n\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OpenVpn,\n\t\t\t\t\tRaw:          []byte(clientSecretRes),\n\t\t\t\t\tRawV2:        []byte(clientIDRes + clientSecretRes),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tclient := s.client\n\t\t\t\t\tif client == nil {\n\t\t\t\t\t\tclient = defaultClient\n\t\t\t\t\t}\n\n\t\t\t\t\tpayload := strings.NewReader(\"grant_type=client_credentials\")\n\t\t\t\t\t// OpenVPN API is in beta, We'll have to update the API endpoint once\n\t\t\t\t\t// Docs: https://openvpn.net/cloud-docs/developer/creating-api-credentials.html\n\t\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", domainRes+\"/api/beta/oauth/token\", payload)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tdata := fmt.Sprintf(\"%s:%s\", clientIDRes, clientSecretRes)\n\t\t\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\n\t\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\t\t\treq.Header.Add(\"content-type\", \"application/x-www-form-urlencoded\")\n\t\t\t\t\tres, err := client.Do(req)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\t\t\ts1.SetVerificationError(err, clientSecretRes)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts1.SetVerificationError(err, clientSecretRes)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_OpenVpn\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"OpenVPN is a virtual private network (VPN) solution. OpenVPN API credentials can be used to manage and configure VPN connections and settings.\"\n}\n"
  },
  {
    "path": "pkg/detectors/openvpn/openvpn_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage openvpn\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOpenvpn_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tdomain := testSecrets.MustGetField(\"OPENVPN_DOMAIN\")\n\tclientId := testSecrets.MustGetField(\"OPENVPN_CLIENT_ID\")\n\tclientSecret := testSecrets.MustGetField(\"OPENVPN_CLIENT_SECRET\")\n\tinactiveClientSecret := testSecrets.MustGetField(\"OPENVPN_CLIENT_SECRET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a openvpn secret %s openvpn clientId %s and domain %s  within\", clientSecret, clientId, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OpenVpn,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRawV2:        []byte(clientId + clientSecret),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a openvpn secret %s openvpn clientId %s and domain %s within but not valid\", inactiveClientSecret, clientId, domain)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OpenVpn,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(clientId + inactiveClientSecret),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Openvpn.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Openvpn.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/openvpn/openvpn_test.go",
    "content": "package openvpn\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidClientID       = \"H-IyMCPZj4DJhmri60908NY.TqE0u\"\n\tinvalidClientID     = \"H-IyMCPZj4DJhm?i60908NY.TqE0u\"\n\tvalidClientSecret   = \"KoBB7qmuBIvgqr_uC8OR-lNIm_8SPKRqqUvk-4bAFNNgivl8RFEO9pSbKXxHAmq753QFimSaC-V91h8tlipArc_\"\n\tinvalidClientSecret = \"KoBB7qmuBIv?qr_uC8OR-lNIm_8SPKRqqUvk-4bAFNNgivl8RFEO9pSbKXxHAmq753QFimSaC-V91h8tlipArc_\"\n\tinputDomain         = \"pzUwgmXv5iKlMz9voI8gVVmWrtBzi.api\"\n\tvalidDomain         = \"https://pzUwgmXv5iKlMz9voI8gVVmWrtBzi.api.openvpn.com\"\n\tinvalidDomain       = \"https://pzUwgmXv5iKlMz9voI?gVVmWrtBzi.api.openvpn.com\"\n\tkeyword             = \"openvpn\"\n)\n\nfunc TestOpenvpn_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword openvpn\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, validClientID, keyword, validClientSecret, keyword, validDomain),\n\t\t\twant:  []string{validClientID + validClientSecret, inputDomain + validClientSecret},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidClientID, keyword, invalidClientSecret, keyword, invalidDomain),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/openweather/openweather.go",
    "content": "package openweather\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"openweather\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"openweather\"}\n}\n\n// FromData will find and optionally verify OpenWeather secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_OpenWeather,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.openweathermap.org/data/2.5/weather?id=524901&lang=fr&appid=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_OpenWeather\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"OpenWeather provides weather data and forecasting services. OpenWeather API keys can be used to access these weather data services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/openweather/openweather_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage openweather\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOpenWeather_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"OPENWEATHER_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"OPENWEATHER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a openweather secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OpenWeather,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a openweather secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_OpenWeather,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"OpenWeather.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"OpenWeather.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/openweather/openweather_test.go",
    "content": "package openweather\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"a790rfidzfevxz03c2vja3vtu91usebu\"\n\tinvalidPattern = \"a790rfidzfevxz03?2vja3vtu91usebu\"\n\tkeyword        = \"openweather\"\n)\n\nfunc TestOpenWeather_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword openweather\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/opsgenie/opsgenie.go",
    "content": "package opsgenie\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"opsgenie\"}) + `\\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\b`)\n)\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Opsgenie\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"opsgenie\"}\n}\n\n// Description returns a description for the result being detected\nfunc (s Scanner) Description() string {\n\treturn \"Opsgenie is an alerting and incident management platform. Opsgenie API keys can be used to interact with the Opsgenie API to manage alerts and incidents.\"\n}\n\n// FromData will find and optionally verify Opsgenie secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tif strings.Contains(match[0], \"opsgenie.com/alert/detail/\") {\n\t\t\tcontinue\n\t\t}\n\n\t\tk := match[1]\n\t\tif detectors.StringShannonEntropy(k) < 3 {\n\t\t\tcontinue\n\t\t}\n\t\tkeyMatches[k] = struct{}{}\n\t}\n\n\tfor key := range keyMatches {\n\t\tr := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Opsgenie,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, extraData, vErr := verifyMatch(ctx, client, key)\n\t\t\tif isVerified {\n\t\t\t\tr.Verified = isVerified\n\t\t\t\tr.ExtraData = extraData\n\t\t\t\tr.AnalysisInfo = map[string]string{\n\t\t\t\t\t\"key\": key,\n\t\t\t\t}\n\t\t\t}\n\t\t\tr.SetVerificationError(vErr, key)\n\t\t}\n\n\t\tresults = append(results, r)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, key string) (bool, map[string]string, error) {\n\t// https://docs.opsgenie.com/docs/account-api\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.opsgenie.com/v2/account\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"GenieKey %s\", key))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tvar accountRes accountResponse\n\t\tif err := json.NewDecoder(res.Body).Decode(&accountRes); err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\n\t\textraData := map[string]string{\n\t\t\t\"account\": accountRes.Data.Name,\n\t\t\t\"plan\":    accountRes.Data.Plan.Name,\n\t\t}\n\t\treturn true, extraData, nil\n\tcase http.StatusUnauthorized:\n\t\t// Key is not valid\n\t\treturn false, nil, nil\n\tcase http.StatusForbidden:\n\t\t// Key is valid but lacks permissions\n\t\treturn true, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\ntype accountResponse struct {\n\tData struct {\n\t\tName      string `json:\"name\"`\n\t\tUserCount int    `json:\"userCount\"`\n\t\tPlan      struct {\n\t\t\tName string `json:\"name\"`\n\t\t} `json:\"plan\"`\n\t} `json:\"data\"`\n}\n"
  },
  {
    "path": "pkg/detectors/opsgenie/opsgenie_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage opsgenie\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOpsgenie_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"OPSGENIE\")\n\tinactiveSecret := testSecrets.MustGetField(\"OPSGENIE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a opsgenie secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Opsgenie,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"account\": \"secretdetectors\",\n\t\t\t\t\t\t\"plan\":    \"Free\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a opsgenie secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Opsgenie,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Opsgenie.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Opsgenie.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/opsgenie/opsgenie_test.go",
    "content": "package opsgenie\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"1c73ff35-2415-fc58-8712-3d24f00cbe2a\"\n\tinvalidPattern = \"1c73ff35?2415-fc58-8712-3d24f00cbe2a\"\n\tkeyword        = \"opsgenie\"\n)\n\nfunc TestOpsgenie_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword opsgenie\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/optimizely/optimizely.go",
    "content": "package optimizely\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"optimizely\"}) + `\\b([0-9A-Za-z-:]{54})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"optimizely\"}\n}\n\n// FromData will find and optionally verify Optimizely secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Optimizely,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.optimizely.com/v2/projects\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Optimizely\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Optimizely is a platform for A/B testing and feature flagging. Optimizely API keys can be used to manage and access these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/optimizely/optimizely_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage optimizely\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOptimizely_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"OPTIMIZELY\")\n\tinactiveSecret := testSecrets.MustGetField(\"OPTIMIZELY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a optimizely secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Optimizely,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a optimizely secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Optimizely,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Optimizely.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Optimizely.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/optimizely/optimizely_test.go",
    "content": "package optimizely\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"KUoABcy4vAON5IDR1AskOXAjwrlcoz2IiNYKj8:ocjGep7tW9WMHS7\"\n\tinvalidPattern = \"KUoAB?y4vAON5IDR1AskOXAjwrlcoz2IiNYKj8:ocjGep7tW9WMHS7\"\n\tkeyword        = \"optimizely\"\n)\n\nfunc TestOptimizely_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword optimizely\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/overloop/overloop.go",
    "content": "package overloop\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"overloop\"}) + `\\b([a-zA-Z\\_\\-0-9]{50})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"overloop\"}\n}\n\n// FromData will find and optionally verify Overloop secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Overloop,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.overloop.com/public/v1/users\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Set(\"Authorization\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Overloop\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Overloop is a service that provides API keys for accessing its platform. These keys can be used to interact with the Overloop API to manage and retrieve user data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/overloop/overloop_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage overloop\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOverloop_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"OVERLOOP\")\n\tinactiveSecret := testSecrets.MustGetField(\"OVERLOOP_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a overloop secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Overloop,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a overloop secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Overloop,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a overloop secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Overloop,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a overloop secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Overloop,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Overloop.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Overloop.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/overloop/overloop_test.go",
    "content": "package overloop\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"Uf0rlS3D-nwUKNuo1FO2oQpaT43SaLG4jBvJupG--kBPLxhjOV\"\n\tinvalidPattern = \"Uf0rlS3D-nwUKNuo1FO2oQpaT?3SaLG4jBvJupG--kBPLxhjOV\"\n\tkeyword        = \"overloop\"\n)\n\nfunc TestOverloop_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword overloop\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/owlbot/owlbot.go",
    "content": "package owlbot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"owlbot\"}) + `\\b([a-z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"owlbot\"}\n}\n\n// FromData will find and optionally verify Owlbot secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Owlbot,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://owlbot.info/api/v4/dictionary/security\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Token %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Owlbot\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Owlbot is an API service providing dictionary definitions, synonyms, and example sentences. Owlbot API keys can be used to access and retrieve this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/owlbot/owlbot_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage owlbot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestOwlbot_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"OWLBOT\")\n\tinactiveSecret := testSecrets.MustGetField(\"OWLBOT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a owlbot secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Owlbot,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a owlbot secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Owlbot,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Owlbot.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Owlbot.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/owlbot/owlbot_test.go",
    "content": "package owlbot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"ujxs1kktljev8g7oijfynoykedznskiheijttwm3\"\n\tinvalidPattern = \"ujxs1kktljev8g7oijfy?oykedznskiheijttwm3\"\n\tkeyword        = \"owlbot\"\n)\n\nfunc TestOwlbot_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword owlbot\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/packagecloud/packagecloud.go",
    "content": "package packagecloud\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"packagecloud\"}) + `\\b([0-9a-f]{48})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"packagecloud\"}\n}\n\n// FromData will find and optionally verify PackageCloud secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_PackageCloud,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://packagecloud.io/api/v1/repos\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.SetBasicAuth(resMatch, \"\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PackageCloud\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PackageCloud is a cloud-based service for hosting and distributing software packages. PackageCloud API keys can be used to manage and access these packages.\"\n}\n"
  },
  {
    "path": "pkg/detectors/packagecloud/packagecloud_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage packagecloud\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPackageCloud_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PACKAGECLOUD\")\n\tinactiveSecret := testSecrets.MustGetField(\"PACKAGECLOUD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a packagecloud secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PackageCloud,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a packagecloud secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PackageCloud,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PackageCloud.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"PackageCloud.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/packagecloud/packagecloud_test.go",
    "content": "package packagecloud\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"3a4c163d0c145e7346b53f6d9be8e0a18058a5b7f03e2b41\"\n\tinvalidPattern = \"3a4c163d0c145e7346b53f6d?be8e0a18058a5b7f03e2b41\"\n\tkeyword        = \"packagecloud\"\n)\n\nfunc TestPackageCloud_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword packagecloud\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pagarme/pagarme.go",
    "content": "package pagarme\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(ak_live_[a-zA-Z0-9]{30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ak_live_\"}\n}\n\n// FromData will find and optionally verify Pagarme secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Pagarme,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\tdata := `{\"api_key\":\"` + token + `\"}`\n\tpayload := strings.NewReader(data)\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.pagar.me/1/balance\", payload)\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t// If the endpoint returns useful information, we can return it as a map.\n\t\treturn true, nil, nil\n\t} else if res.StatusCode == 401 {\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\t} else {\n\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\treturn false, nil, err\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Pagarme\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Pagarme is a payment service provider. Pagarme API keys can be used to access and manage payment transactions and other related services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pagarme/pagarme_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pagarme\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPagarme_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PAGARME\")\n\tinactiveSecret := testSecrets.MustGetField(\"PAGARME_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pagarme secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pagarme,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pagarme secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pagarme,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pagarme secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pagarme,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pagarme secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pagarme,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Pagarme.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Pagarme.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pagarme/pagarme_test.go",
    "content": "package pagarme\n\nimport (\n\t\"context\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"testing\"\n)\n\nfunc TestPagarme_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: \"pagarme_token = 'ak_live_ubMaiZa2EwqPZXC3wN0ezZtiCl1Hep'\",\n\t\t\twant:  []string{\"ak_live_ubMaiZa2EwqPZXC3wN0ezZtiCl1Hep\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pagerdutyapikey/pagerdutyapikey.go",
    "content": "package pagerdutyapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nconst verifyURL = \"https://api.pagerduty.com/users\"\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pagerduty\", \"pager_duty\", \"pd_\", \"pd-\"}) + `\\b(u\\+[a-zA-Z0-9_+-]{18})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pagerduty\", \"pager_duty\", \"pd_\", \"pd-\"}\n}\n\n// FromData will find and optionally verify PagerDutyApiKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_PagerDutyApiKey,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.getClient()\n\t\t\tisVerified, verificationErr := verifyPagerdutyapikey(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\nfunc verifyPagerdutyapikey(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, verifyURL, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Accept\", \"application/vnd.pagerduty+json;version=2\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Token %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PagerDutyApiKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PagerDuty is an incident management platform that provides reliable notifications, automatic escalations, on-call scheduling, and other functionality to help teams detect and fix infrastructure problems quickly. PagerDuty API keys can be used to access and manage these functionalities.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pagerdutyapikey/pagerdutyapikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pagerdutyapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPagerDutyApiKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PAGERDUTYAPIKEY_TOKEN\")\n\tinvalidSecret := testSecrets.MustGetField(\"PAGERDUTYAPIKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pagerdutyapikey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PagerDutyApiKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pagerdutyapikey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PagerDutyApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pagerdutyapikey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PagerDutyApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pagerdutyapikey secret %s within but not valid\", invalidSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PagerDutyApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PagerDutyApiKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Errorf(\"PagerDutyApiKey.FromData() verificationError = %v, wantVerificationErr %v\", got[i].VerificationError(), tt.wantVerificationErr)\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"PagerDutyApiKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pagerdutyapikey/pagerdutyapikey_test.go",
    "content": "package pagerdutyapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"u+C4lGv0tct7TlvSUDoc\"\n\tinvalidPattern = \"u+C4lGv0?ct7TlvSUDoc\"\n\tkeyword        = \"pagerdutyapikey\"\n)\n\nfunc TestPagerDutyApiKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword pagerdutyapikey\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pandadoc/pandadoc.go",
    "content": "package pandadoc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pandadoc\"}) + `\\b([a-zA-Z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pandadoc\"}\n}\n\n// FromData will find and optionally verify Pandadoc secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Pandadoc,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.pandadoc.com/public/v1/documents\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"API-Key %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Pandadoc\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Pandadoc is a document automation software that helps create, manage, and sign digital documents. Pandadoc API keys can be used to access and manage documents through the Pandadoc API.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pandadoc/pandadoc_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pandadoc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPandadoc_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PANDADOC\")\n\tinactiveSecret := testSecrets.MustGetField(\"PANDADOC_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pandadoc secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pandadoc,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pandadoc secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pandadoc,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Pandadoc.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Pandadoc.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pandadoc/pandadoc_test.go",
    "content": "package pandadoc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"gL1LKFp8p0q7ORUybiyovqIuGUsGjx4adxMRcHPh\"\n\tinvalidPattern = \"gL1LKFp8p0q7ORUybiyo?qIuGUsGjx4adxMRcHPh\"\n\tkeyword        = \"pandadoc\"\n)\n\nfunc TestPandadoc_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword pandadoc\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pandascore/pandascore.go",
    "content": "package pandascore\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pandascore\"}) + `([ \\r\\n]{0,1}[0-9A-Za-z\\-\\_]{51}[ \\r\\n]{1})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pandascore\"}\n}\n\n// FromData will find and optionally verify PandaScore secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_PandaScore,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.pandascore.co/videogames\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else {\n\t\t\t\t\ts1.Verified = false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PandaScore\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PandaScore is an esports data provider offering a wide range of statistics and live data for various video games. PandaScore API keys can be used to access and retrieve this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pandascore/pandascore_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pandascore\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPandaScore_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*10)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PANDASCORE\")\n\tinactiveSecret := testSecrets.MustGetField(\"PANDASCORE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pandascore secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PandaScore,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pandascore secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PandaScore,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PandaScore.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"PandaScore.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pandascore/pandascore_test.go",
    "content": "package pandascore\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"iahNbqgnxq7tzca9hNBWiVexHeePoQ2nnXwN06wmaFSY5BMGyfq\"\n\tinvalidPattern = \"iahNbqgnxq7tzca9hNBWiV?xHeePoQ2nnXwN06wmaFSY5BMGyfq\"\n\tkeyword        = \"pandascore\"\n)\n\nfunc TestPandaScore_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword pandascore\",\n\t\t\tinput: fmt.Sprintf(\"%s token = ' %s '\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = ' %s ' | ' %s '\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = ' %s '\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = ' %s '\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/paperform/paperform.go",
    "content": "package paperform\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"paperform\"}) + `\\b([a-z0-9A-Z-._]{850,1000})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"paperform\"}\n}\n\n// FromData will find and optionally verify Paperform secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Paperform,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.paperform.co/v1/forms\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Paperform\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"An API for building and creating forms. API keys could be used to read proprietary or not public form data\"\n}\n"
  },
  {
    "path": "pkg/detectors/paperform/paperform_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage paperform\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPaperform_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PAPERFORM\")\n\tinactiveSecret := testSecrets.MustGetField(\"PAPERFORM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a paperform secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Paperform,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a paperform secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Paperform,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Paperform.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Paperform.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/paperform/paperform_test.go",
    "content": "package paperform\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"NvBbE5vYVPhidc-tiZ7E6DvP.z4bAQj29KEZOCv_XEl1pIn.Y1q8JoeGNoSelGj.db1iQpLM0fsype86LD.Vk.p6yweF5A2MlfxXDrEd7nz2SBExnTpp4QRN94pBAeulPLLtzqTz--y_UuF1g6cjE_.kuW_u5He0QkBdLMkyAgSx94N3Csj9LY37XOmUp9IIi9LXpTvZGa8oywp6JuMfhzwg5OCEvdp9mx.UyfQcnnJtYzzP5dItmwfEC-KJIvjvS8LF2NU2w2japQHtnAJqBAn3_EP-FN78wHnDEWANANT0cfor6kDqyKraO0Y-26PdB6xBjm3_VpU.8hnKIyoKdLQ6S.HZwr5rx0Bx76zXTCBv4uEzhtDFcDqVPN8ZG_kE90P..ldReG0jU4w3YA2jbaOgi6i-8llYWGoCiFFBm3Od-zLOEDYL2BlGsUFRUkiEMjytCVDqcIOdfPT7GQcd3wdmort6FFv8SbCu95f2gBCcM.5.ZmMxIOybubMGmiRunYM8-pSaVvXfBQSkM2Eygh15tkKCDHf8X3InAkPh7HQn13mP5y1gFRLsVAUWb-91PeHASP6hluUEdsX3uLQ9OJFenKrk.0zS9Goy08bfttd4h4Jtb2JV8vbJ8-3Wb4AJWqf0eUALMxOChB3sSBKW37s4vDb1NKOnoqOeoYQUBijqRGu9YLKIAimwo7Uvl0CuD7bWNrERweBqNVWjfGhlE8Yvvklm5YhCk5XY02pOa3IjMf_TDKhbTr8bh_20SXevnDk80XKg_3mWbhuieL23kx835AokAg9JpEkgydBBqo8nzQg23R5xJzKRT64kgb5GBlzuM9Oxh7pXsmzlYf\"\n\tinvalidPattern = \"NvBbE5vYVPhidc?tiZ7E6DvP.z4bAQj29KEZOCv_XEl1pIn.Y1q8JoeGNoSelGj.db1iQpLM0fsype86LD.Vk.p6yweF5A2MlfxXDrEd7nz2SBExnTpp4QRN94pBAeulPLLtzqTz--y_UuF1g6cjE_.kuW_u5He0QkBdLMkyAgSx94N3Csj9LY37XOmUp9IIi9LXpTvZGa8oywp6JuMfhzwg5OCEvdp9mx.UyfQcnnJtYzzP5dItmwfEC-KJIvjvS8LF2NU2w2japQHtnAJqBAn3_EP-FN78wHnDEWANANT0cfor6kDqyKraO0Y-26PdB6xBjm3_VpU.8hnKIyoKdLQ6S.HZwr5rx0Bx76zXTCBv4uEzhtDFcDqVPN8ZG_kE90P..ldReG0jU4w3YA2jbaOgi6i-8llYWGoCiFFBm3Od-zLOEDYL2BlGsUFRUkiEMjytCVDqcIOdfPT7GQcd3wdmort6FFv8SbCu95f2gBCcM.5.ZmMxIOybubMGmiRunYM8-pSaVvXfBQSkM2Eygh15tkKCDHf8X3InAkPh7HQn13mP5y1gFRLsVAUWb-91PeHASP6hluUEdsX3uLQ9OJFenKrk.0zS9Goy08bfttd4h4Jtb2JV8vbJ8-3Wb4AJWqf0eUALMxOChB3sSBKW37s4vDb1NKOnoqOeoYQUBijqRGu9YLKIAimwo7Uvl0CuD7bWNrERweBqNVWjfGhlE8Yvvklm5YhCk5XY02pOa3IjMf_TDKhbTr8bh_20SXevnDk80XKg_3mWbhuieL23kx835AokAg9JpEkgydBBqo8nzQg23R5xJzKRT64kgb5GBlzuM9Oxh7pXsmzlYf\"\n\tkeyword        = \"paperform\"\n)\n\nfunc TestPaperform_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword paperform\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/paralleldots/paralleldots.go",
    "content": "package paralleldots\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"paralleldots\"}) + `\\b([0-9A-Za-z]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"paralleldots\"}\n}\n\n// FromData will find and optionally verify Paralleldots secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ParallelDots,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := &bytes.Buffer{}\n\t\t\twriter := multipart.NewWriter(payload)\n\t\t\tfw, err := writer.CreateFormField(\"api_key\")\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t_, err = io.Copy(fw, strings.NewReader(resMatch))\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfw, err = writer.CreateFormField(\"text\")\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t_, err = io.Copy(fw, strings.NewReader(\"sample text\"))\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\twriter.Close()\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://apis.paralleldots.com/v4/intent\", bytes.NewReader(payload.Bytes()))\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", writer.FormDataContentType())\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\t\t\t\tif (res.StatusCode >= 200 && res.StatusCode < 300) && strings.Contains(body, \"intent\") {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ParallelDots\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ParallelDots is an AI service offering various APIs for text analysis. API keys can be used to access these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/paralleldots/paralleldots_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage paralleldots\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestParalleldots_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PARALLELDOTS\")\n\tinactiveSecret := testSecrets.MustGetField(\"PARALLELDOTS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a paralleldots secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ParallelDots,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a paralleldots secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ParallelDots,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Paralleldots.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Paralleldots.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/paralleldots/paralleldots_test.go",
    "content": "package paralleldots\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"Ki82DlwEBI9dqr4k3cZPH6Z5fP39XgEPktFfgZTM4mW\"\n\tinvalidPattern = \"Ki82DlwEBI9dqr4k3cZPH?Z5fP39XgEPktFfgZTM4mW\"\n\tkeyword        = \"paralleldots\"\n)\n\nfunc TestParalleldots_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword paralleldots\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/parsehub/parsehub.go",
    "content": "package parsehub\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"parsehub\"}) + `\\b([0-9a-zA-Z]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"parsehub\"}\n}\n\n// FromData will find and optionally verify Parsehub secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Parsehub,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://www.parsehub.com/api/v2/projects?api_key=%s&limit=1\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Parsehub\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Parsehub is a web scraping service that allows users to extract data from websites. Parsehub API keys can be used to access and manage scraping projects.\"\n}\n"
  },
  {
    "path": "pkg/detectors/parsehub/parsehub_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage parsehub\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestParsehub_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PARSEHUB\")\n\tinactiveSecret := testSecrets.MustGetField(\"PARSEHUB_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a parsehub secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Parsehub,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a parsehub secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Parsehub,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Parsehub.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Parsehub.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/parsehub/parsehub_test.go",
    "content": "package parsehub\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"vZrEclFwOqeA\"\n\tinvalidPattern = \"vZrEcl?wOqeA\"\n\tkeyword        = \"parsehub\"\n)\n\nfunc TestParsehub_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword parsehub\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/parsers/parsers.go",
    "content": "package parsers\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"parsers\"}) + `\\b([0-9a-z]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"parsers\"}\n}\n\n// FromData will find and optionally verify Parsers secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Parsers,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\"sql\": \"CREATE TABLE t();SELECT 1; SELECT 1 FROM ; SELECT 2\"}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.parsers.dev/api/v1/parse/postgresql/\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"x-api-key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Parsers\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Parsers is a service used for parsing SQL queries. Parsers API keys can be used to access and modify this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/parsers/parsers_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage parsers\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestParsers_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PARSERS\")\n\tinactiveSecret := testSecrets.MustGetField(\"PARSERS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a parsers secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Parsers,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a parsers secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Parsers,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Parsers.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Parsers.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/parsers/parsers_test.go",
    "content": "package parsers\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"3ui8k045w9b9n6c2fuqbr44p1fg64cjsvn2dv2uvkyxfmjler9ddsls6uqtizt7q\"\n\tinvalidPattern = \"3ui8k045w9b9n6c2fuqbr44p1fg64cjs?n2dv2uvkyxfmjler9ddsls6uqtizt7q\"\n\tkeyword        = \"parsers\"\n)\n\nfunc TestParsers_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword parsers\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/parseur/parseur.go",
    "content": "package parseur\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"parseur[^il]\"}) + `\\b([a-f0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"parseur\"}\n}\n\n// FromData will find and optionally verify Parseur secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Parseur,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tif s.client == nil {\n\t\t\t\ts.client = defaultClient\n\t\t\t}\n\t\t\tisVerified, verificationErr := verifyResult(ctx, s.client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyResult(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.parseur.com/\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Token %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer res.Body.Close()\n\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\treturn true, nil\n\t}\n\treturn false, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Parseur\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Parseur is a document parsing software used to extract data from emails and other documents. Parseur API keys can be used to access and manage this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/parseur/parseur_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage parseur\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestParseur_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PARSEUR\")\n\tinactiveSecret := testSecrets.MustGetField(\"PARSEUR_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a parseur secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Parseur,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a parseur secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Parseur,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Parseur.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Parseur.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/parseur/parseur_test.go",
    "content": "package parseur\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\nfunc TestParseur_Pattern(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tdata        string\n\t\tshouldMatch bool\n\t\tmatch       string\n\t}{\n\t\t// True positives\n\t\t{\n\t\t\tname:        \"valid\",\n\t\t\tdata:        `const parseurToken = \"6813a07afc6b4ed35518635c6fb70abf4e721962\";`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       \"6813a07afc6b4ed35518635c6fb70abf4e721962\",\n\t\t},\n\t\t// This technically isn't valid but shouldn't be excluded based on the current pattern.\n\t\t{\n\t\t\tname: \"valid\",\n\t\t\tdata: `commit 6813a07afc6b4ed35518635c6fb70abf4e721962\nAuthor: Stéphane Borel <stef@videolan.org>\nDate:   Thu Dec 30 13:59:59 1999 +0000\n\n    * Modifications de quelques erreurs sur le parseur\n\ncommit 2c65bd981d308d264aa0c07083b2bc914905deb3`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       \"2c65bd981d308d264aa0c07083b2bc914905deb3\",\n\t\t},\n\n\t\t// False positives\n\t\t{\n\t\t\tname: `invalid_parseuri_package.json`,\n\t\t\tdata: `{\n  \"dist\": {\n    \"shasum\": \"80204a50d4dbb779bfdc6ebe2778d90e4bce320a\",\n    \"tarball\": \"https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz\"\n  },\n  \"gitHead\": \"792c9a63162a4484eb6b4f95fc611ccf224a24b6\",`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t// https://github.com/airalab/airapkgs/blob/cb3f8021303f79345f65b5328b75117044bde852/pkgs/servers/mesh/meshviewer/yarn.nix#L6066\n\t\t{\n\t\t\tname: `invalid_parseuri_nix`,\n\t\t\tdata: `\n    {\n      name = \"parseuri-0.0.5.tgz\";\n      path = fetchurl {\n        name = \"parseuri-0.0.5.tgz\";\n        url  = \"https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz\";\n        sha1 = \"80204a50d4dbb779bfdc6ebe2778d90e4bce320a\";\n      };\n    }`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\tname: `invalid_parseurl_yarn`,\n\t\t\tdata: `parseurl@~1.3.1:\n  version \"1.3.1\"\n  resolved \"https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56\"`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t// https://github.com/tolerious/django-wechat/blob/18f3f2d5d8377c7dde8700afc5977861c8488b68/django_weixin/Sample.py#L30\n\t\t{\n\t\t\tname: `invalid_parseurl_func`,\n\t\t\tdata: `#sVerifyMsgSig=HttpUtils.ParseUrl(\"msg_signature\")\n   sVerifyMsgSig=\"5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3\"`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\n\t\t\tresults, err := s.FromData(context.Background(), false, []byte(test.data))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Parseur.FromData() error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif test.shouldMatch {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"%s: did not receive a match for '%v' when one was expected\", test.name, test.data)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\texpected := test.data\n\t\t\t\tif test.match != \"\" {\n\t\t\t\t\texpected = test.match\n\t\t\t\t}\n\t\t\t\tresult := results[0]\n\t\t\t\tresultData := string(result.Raw)\n\t\t\t\tif resultData != expected {\n\t\t\t\t\tt.Errorf(\"%s: did not receive expected match.\\n\\texpected: '%s'\\n\\t  actual: '%s'\", test.name, expected, resultData)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif len(results) > 0 {\n\t\t\t\t\tt.Errorf(\"%s: received a match for '%v' when one wasn't wanted\", test.name, test.data)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/partnerstack/partnerstack.go",
    "content": "package partnerstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"partnerstack\"}) + `\\b([0-9A-Za-z]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"partnerstack\"}\n}\n\n// FromData will find and optionally verify Partnerstack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Partnerstack,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.partnerstack.com/api/v2/partnerships\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Partnerstack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PartnerStack is a partner management platform that helps companies manage their partner programs. PartnerStack API keys can be used to access and manage these programs.\"\n}\n"
  },
  {
    "path": "pkg/detectors/partnerstack/partnerstack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage partnerstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPartnerstack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PARTNERSTACK\")\n\tinactiveSecret := testSecrets.MustGetField(\"PARTNERSTACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a partnerstack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Partnerstack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a partnerstack secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Partnerstack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Partnerstack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Partnerstack.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/partnerstack/partnerstack_test.go",
    "content": "package partnerstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"mDrW4HFb6fVh2ii74V41bh6EH52sWirlQuKr3svxfgGueyj32HW7OhIhNLZSgicW\"\n\tinvalidPattern = \"mDrW4HFb6fVh2ii74V41bh6EH52sWirl?uKr3svxfgGueyj32HW7OhIhNLZSgicW\"\n\tkeyword        = \"partnerstack\"\n)\n\nfunc TestPartnerstack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword partnerstack\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pastebin/pastebin.go",
    "content": "package pastebin\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pastebin\"}) + `\\b([a-zA-Z0-9_]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pastebin\"}\n}\n\n// FromData will find and optionally verify Pastebin secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Pastebin,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tbody := &bytes.Buffer{}\n\t\t\twriter := multipart.NewWriter(body)\n\t\t\tfw, err := writer.CreateFormField(\"api_dev_key\")\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t_, err = io.Copy(fw, strings.NewReader(resMatch))\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfw, err = writer.CreateFormField(\"api_paste_code\")\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t_, err = io.Copy(fw, strings.NewReader(\"test\"))\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfw, err = writer.CreateFormField(\"api_option\")\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t_, err = io.Copy(fw, strings.NewReader(\"paste\"))\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\twriter.Close()\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://pastebin.com/api/api_post.php\", bytes.NewReader(body.Bytes()))\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", writer.FormDataContentType())\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Pastebin\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Pastebin is a website where users can store plain text. Pastebin keys can be used to access and manipulate stored data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pastebin/pastebin_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pastebin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPastebin_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PASTEBIN\")\n\tinactiveSecret := testSecrets.MustGetField(\"PASTEBIN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pastebin secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pastebin,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pastebin secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pastebin,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Pastebin.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Pastebin.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pastebin/pastebin_test.go",
    "content": "package pastebin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"92q9vNtJQRsjQDdbiYRRHBUNBYHSymKL\"\n\tinvalidPattern = \"92q9vNtJQRsjQDdb?YRRHBUNBYHSymKL\"\n\tkeyword        = \"pastebin\"\n)\n\nfunc TestPastebin_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword pastebin\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/paydirtapp/paydirtapp.go",
    "content": "package paydirtapp\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"paydirtapp\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"paydirtapp\"}\n}\n\n// FromData will find and optionally verify paydirtapp secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Paydirtapp,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://paydirtapp.com/api/v1/clients?api_key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Paydirtapp\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Paydirtapp is a time tracking and invoicing app. Paydirtapp API keys can be used to access and manage user data and billing information.\"\n}\n"
  },
  {
    "path": "pkg/detectors/paydirtapp/paydirtapp_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage paydirtapp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPaydirtyapp_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PAYDIRTAPP\")\n\tinactiveSecret := testSecrets.MustGetField(\"PAYDIRTAPP_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a paydirtapp secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Paydirtapp,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a paydirtapp secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Paydirtapp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Paydirtyapp.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Paydirtyapp.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/paydirtapp/paydirtapp_test.go",
    "content": "package paydirtapp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"teboslrqf1lirq5gx0ca6544y3tkzsq0\"\n\tinvalidPattern = \"teboslrqf1lirq5g?0ca6544y3tkzsq0\"\n\tkeyword        = \"paydirtapp\"\n)\n\nfunc TestPaydirtyapp_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword paydirtapp\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/paymoapp/paymoapp.go",
    "content": "package paymoapp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"paymoapp\"}) + `\\b([a-zA-Z0-9]{44})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"paymoapp\"}\n}\n\n// FromData will find and optionally verify Paymoapp secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Paymoapp,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.paymoapp.com/api/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Paymoapp\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Paymoapp is a project management and collaboration tool. Paymoapp API keys can be used to access and modify project data and user information.\"\n}\n"
  },
  {
    "path": "pkg/detectors/paymoapp/paymoapp_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage paymoapp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPaymoapp_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PAYMOAPP\")\n\tinactiveSecret := testSecrets.MustGetField(\"PAYMOAPP_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a paymoapp secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Paymoapp,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a paymoapp secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Paymoapp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Paymoapp.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Paymoapp.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/paymoapp/paymoapp_test.go",
    "content": "package paymoapp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"EpKoocVnygMTgD6XB0c4QQ2mYIqXlt1efBuIvuihzKjU\"\n\tinvalidPattern = \"EpKoocVnygMTgD6XB0c4QQ?mYIqXlt1efBuIvuihzKjU\"\n\tkeyword        = \"paymoapp\"\n)\n\nfunc TestPaymoapp_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword paymoapp\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/paymongo/paymongo.go",
    "content": "package paymongo\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"paymongo\"}) + `\\b([a-zA-Z0-9_]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"paymongo\"}\n}\n\n// FromData will find and optionally verify Paymongo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Paymongo,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\"data\":{\"attributes\":{\"type\":\"paymaya\"}}}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.paymongo.com/v1/payment_methods\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.SetBasicAuth(resMatch, \"\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Paymongo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PayMongo is a payment processing platform in the Philippines. PayMongo API keys can be used to access and manage payment methods, transactions, and other related data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/paymongo/paymongo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage paymongo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPaymongo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PAYMONGO\")\n\tinactiveSecret := testSecrets.MustGetField(\"PAYMONGO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a paymongo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Paymongo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a paymongo secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Paymongo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Paymongo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Paymongo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/paymongo/paymongo_test.go",
    "content": "package paymongo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"okrrdIMzwSl350mcZkrtMkzEJ_xuLaJc\"\n\tinvalidPattern = \"okrrdIMzwSl350mc?krtMkzEJ_xuLaJc\"\n\tkeyword        = \"paymongo\"\n)\n\nfunc TestPaymongo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword paymongo\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/paypaloauth/paypaloauth.go",
    "content": "package paypaloauth\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tidPat  = regexp.MustCompile(`\\b([A-Za-z0-9_\\.]{7}-[A-Za-z0-9_\\.]{72}|[A-Za-z0-9_\\.]{5}-[A-Za-z0-9_\\.]{38})\\b`)\n\tkeyPat = regexp.MustCompile(`\\b([A-Za-z0-9_\\.\\-]{44,80})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"paypal\"}\n}\n\n// FromData will find and optionally verify PaypalOauth secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, idMatch := range idmatches {\n\t\tresIDMatch := strings.TrimSpace(idMatch[1])\n\t\tfor _, secretMatch := range matches {\n\t\t\tresSecretMatch := strings.TrimSpace(secretMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_PaypalOauth,\n\t\t\t\tRaw:          []byte(resSecretMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tdata := fmt.Sprintf(\"%s:%s\", resIDMatch, resSecretMatch)\n\t\t\t\tencoded := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\t\tpayload := strings.NewReader(\"grant_type=client_credentials\")\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api-m.sandbox.paypal.com/v1/oauth2/token\", payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\t\treq.Header.Add(\"Accept-Language\", \"en_US\")\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", encoded))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PaypalOauth\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PaypalOauth is used for authenticating with PayPal's API. These credentials can be used to access and perform transactions on behalf of a PayPal account.\"\n}\n"
  },
  {
    "path": "pkg/detectors/paypaloauth/paypaloauth_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage paypaloauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPaypalOauth_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PAYPALOAUTH_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"PAYPALOAUTH_SECRET_INACTIVE\")\n\tid := testSecrets.MustGetField(\"PAYPALOAUTH_CLIENTID\")\n\n\tnewId := testSecrets.MustGetField(\"PAYPALOAUTH_NEW_INACTIVE_CLIENTID\")\n\tnewSecret := testSecrets.MustGetField(\"PAYPALOAUTH_NEW_INACTIVE_SECRET\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a paypaloauth secret %s within %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PaypalOauth,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PaypalOauth,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a paypaloauth secret %s within %s but not valid\", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PaypalOauth,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PaypalOauth,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"new format, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a paypaloauth secret %s within %s but not valid\", newSecret, newId)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PaypalOauth,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PaypalOauth,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PaypalOauth.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"PaypalOauth.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/paypaloauth/paypaloauth_test.go",
    "content": "package paypaloauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidId    = \"Dddpt-5MySAKlcPX07LKjhzbHTbf9m2Xv9OFSw0bTdrZ\"\n\tinvalidId  = \"Dddpt-5MySAKlcPX07LKjh?bHTbf9m2Xv9OFSw0bTdrZ\"\n\tvalidKey   = \"PDMDWZ3BAwfXHoU.P.pJVUPIYlkJ2jVP-ns2icvSlWeb-_0Qa5\"\n\tinvalidKey = \"PDMDWZ3BAwfXHoU.P.pJVUPIY?kJ2jVP-ns2icvSlWeb-_0Qa5\"\n\tkeyword    = \"paypaloauth\"\n)\n\nfunc TestPaypalOauth_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword paypaloauth\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validId, keyword, validKey),\n\t\t\twant:  []string{validId, validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidId, keyword, invalidKey),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/paystack/paystack.go",
    "content": "package paystack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\t// TODO: support live key\n\tkeyPat = regexp.MustCompile(`\\b(sk\\_[a-z]{1,}\\_[A-Za-z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"paystack\"}\n}\n\n// FromData will find and optionally verify Paystack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Paystack,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.paystack.co/customer\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Paystack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Paystack is a payment processing service. Paystack API keys can be used to access and manage payment transactions and customer data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/paystack/paystack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage paystack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPaystack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PAYSTACK_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"PAYSTACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a paystack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Paystack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a paystack secret %s within but unverified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Paystack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Paystack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Paystack.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/paystack/paystack_test.go",
    "content": "package paystack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"sk_xigrvarm_cJHGpWQwCTHajG2A2o8eC8TQaQGZdoMVhgXUA9Lm\"\n\tinvalidPattern = \"sk_xigrvarm_cJHGpWQwCTHajG?A2o8eC8TQaQGZdoMVhgXUA9Lm\"\n\tkeyword        = \"paystack\"\n)\n\nfunc TestPaystack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword paystack\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pdflayer/pdflayer.go",
    "content": "package pdflayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pdflayer\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pdflayer\"}\n}\n\n// FromData will find and optionally verify PdfLayer secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_PdfLayer,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.pdflayer.com/api/convert?access_key=%s&document_url=https://pdflayer.com/downloads/invoice.html\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `Contents`)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PdfLayer\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PdfLayer is a service for converting HTML documents to PDF. PdfLayer API keys can be used to access and utilize the PDF conversion service.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pdflayer/pdflayer_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pdflayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPdfLayer_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PDFLAYER\")\n\tinactiveSecret := testSecrets.MustGetField(\"PDFLAYER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pdflayer secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PdfLayer,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pdflayer secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PdfLayer,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PdfLayer.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"PdfLayer.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pdflayer/pdflayer_test.go",
    "content": "package pdflayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"wv8d7n0j02zxzrsbcs8cyk9yvqch9gvr\"\n\tinvalidPattern = \"wv8d7n0j02zxzrsb?s8cyk9yvqch9gvr\"\n\tkeyword        = \"pdflayer\"\n)\n\nfunc TestPdfLayer_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword pdflayer\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pdfshift/pdfshift.go",
    "content": "package pdfshift\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pdfshift\"}) + `\\b([0-9a-f]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pdfshift\"}\n}\n\n// FromData will find and optionally verify PdfShift secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_PdfShift,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.pdfshift.io/v3/credits/usage\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.SetBasicAuth(\"api\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PdfShift\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PdfShift is a service for converting HTML documents to PDF. PdfShift API keys can be used to access and utilize this conversion service.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pdfshift/pdfshift_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pdfshift\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPdfShift_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PDFSHIFT\")\n\tinactiveSecret := testSecrets.MustGetField(\"PDFSHIFT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pdfshift secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PdfShift,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pdfshift secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PdfShift,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PdfShift.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"PdfShift.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pdfshift/pdfshift_test.go",
    "content": "package pdfshift\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"b03ed226557f474fda3f5a8fdd498f7c\"\n\tinvalidPattern = \"b03e?226557f474fda3f5a8fdd498f7c\"\n\tkeyword        = \"pdfshift\"\n)\n\nfunc TestPdfShift_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword pdfshift\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/peopledatalabs/peopledatalabs.go",
    "content": "package peopledatalabs\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"peopledatalabs\"}) + `\\b([a-z0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"peopledatalabs\"}\n}\n\n// FromData will find and optionally verify PeopleDataLabs secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_PeopleDataLabs,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.peopledatalabs.com/v5/person/enrich?min_likelihood=6&profile=https://linkedin.com/in/williamhgates\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-Api-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PeopleDataLabs\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PeopleDataLabs provides access to person and company data through their API. The API keys can be used to enrich profiles with additional data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/peopledatalabs/peopledatalabs_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage peopledatalabs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPeopleDataLabs_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PEOPLEDATALABS\")\n\tinactiveSecret := testSecrets.MustGetField(\"PEOPLEDATALABS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a peopledatalabs secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PeopleDataLabs,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a peopledatalabs secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PeopleDataLabs,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PeopleDataLabs.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"PeopleDataLabs.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/peopledatalabs/peopledatalabs_test.go",
    "content": "package peopledatalabs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"tp8k005gi460y93zclyrifwjnij6492iiwignkeuccqjkrs1rqcw0lsuvjm39ij9\"\n\tinvalidPattern = \"tp8k005g?460y93zclyrifwjnij6492iiwignkeuccqjkrs1rqcw0lsuvjm39ij9\"\n\tkeyword        = \"peopledatalabs\"\n)\n\nfunc TestPeopleDataLabs_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword peopledatalabs\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pepipost/pepipost.go",
    "content": "package pepipost\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pepipost\", \"netcore\"}) + `\\b([a-zA-Z-0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pepipost\", \"netcore\"}\n}\n\n// FromData will find and optionally verify Pepipost secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Pepipost,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.pepipost.com/v5.1/domain/getDomains?domain=pepisandbox.com\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"api_key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Pepipost\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Pepipost is an email delivery service. The API keys can be used to send and track emails through Pepipost's infrastructure.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pepipost/pepipost_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pepipost\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPepipost_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PEPIPOST_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"PEPIPOST_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pepipost secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pepipost,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pepipost secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pepipost,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Pepipost.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Pepipost.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pepipost/pepipost_test.go",
    "content": "package pepipost\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"zajmEjyCrhFdm1gQXoZD6r8WKLubC-B2\"\n\tinvalidPattern = \"zajmEjyCrhFdm1gQ?oZD6r8WKLubC-B2\"\n\tkeyword        = \"pepipost\"\n)\n\nfunc TestPepipost_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword pepipost\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/percy/percy.go",
    "content": "package percy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"percy\"}) + `\\bPERCY_TOKEN=([0-9Aa-f]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"percy\"}\n}\n\n// FromData will find and optionally verify Percy secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Percy,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://percy.io/api/v1/projects\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.percy+json; version=3\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Token %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Percy\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Percy is a visual testing and review platform. Percy tokens can be used to access and manage visual testing projects.\"\n}\n"
  },
  {
    "path": "pkg/detectors/percy/percy_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage percy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPercy_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PERCY_VERIFIED\")\n\tinactiveSecret := testSecrets.MustGetField(\"PERCY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a percy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Percy,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a percy secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Percy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Percy.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Percy.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/percy/percy_test.go",
    "content": "package percy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"84f2cfA002913e5afbe0a43d71e49ac9389Ab4f4f827bceAed69ec34f844ed22\"\n\tinvalidPattern = \"84f2cfA002913?5afbe0a43d71e49ac9389Ab4f4f827bceAed69ec34f844ed22\"\n\tkeyword        = \"percy\"\n)\n\nfunc TestPercy_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword percy\",\n\t\t\tinput: fmt.Sprintf(\"%s token = 'PERCY_TOKEN=%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = 'PERCY_TOKEN=%s' | 'PERCY_TOKEN=%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = 'PERCY_TOKEN=%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = 'PERCY_TOKEN=%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/photoroom/photoroom.go",
    "content": "package photoroom\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"photoroom\"}) + `\\b(?:sandbox_)?(sk_pr_[a-zA-Z0-9]{1,64}_[a-fA-F0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"photoroom\"}\n}\n\n// FromData will find and optionally verify Photoroom secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Photoroom,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, key string) (bool, error) {\n\t// API Reference: https://www.photoroom.com/api/docs/reference/611c03dbcec67-account-details\n\turl := \"https://image-api.photoroom.com/v1/account\"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Set(\"x-api-key\", key)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusForbidden:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Photoroom\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Photoroom is an AI photo editing platform that provides a suite of tools and services for creating and enhancing images. Photoroom API keys can be used to access these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/photoroom/photoroom_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage photoroom\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPhotoroom_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PHOTOROOM\")\n\tinactiveSecret := testSecrets.MustGetField(\"PHOTOROOM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a photoroom secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Photoroom,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a photoroom secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Photoroom,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a photoroom secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Photoroom,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a photoroom secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Photoroom,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Photoroom.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Photoroom.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/photoroom/photoroom_test.go",
    "content": "package photoroom\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestPhotoroom_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the photoroom API\n\t\t\t\t[DEBUG] Using Key=sk_pr_mykey_fa02df705055df9e4d159fe3bad0cf3bb8cd1dc2\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"sk_pr_mykey_fa02df705055df9e4d159fe3bad0cf3bb8cd1dc2\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid sandbox pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the photoroom API\n\t\t\t\t[DEBUG] Using Key=sandbox_sk_pr_mykey_fa02df705055df9e4d159fe3bad0cf3bb8cd1dc2\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"sk_pr_mykey_fa02df705055df9e4d159fe3bad0cf3bb8cd1dc2\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - xml\",\n\t\t\tinput: `\n\t\t\t\t<com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t\t\t<scope>GLOBAL</scope>\n\t\t\t\t\t<id>{photoroom}</id>\n\t\t\t\t\t<secret>{photoroom AQAAABAAA sk_pr_mykey_fa02df705055df9e4d159fe3bad0cf3bb8cd1dc2}</secret>\n\t\t\t\t\t<description>configuration for production</description>\n\t\t\t\t\t<creationDate>2023-05-18T14:32:10Z</creationDate>\n\t\t\t\t\t<owner>jenkins-admin</owner>\n\t\t\t\t</com.cloudbees.plugins.credentials.impl.StringCredentialsImpl>\n\t\t\t`,\n\t\t\twant: []string{\"sk_pr_mykey_fa02df705055df9e4d159fe3bad0cf3bb8cd1dc2\"},\n\t\t},\n\t\t{\n\t\t\tname: \"finds all matches\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the photoroom API\n\t\t\t\t[DEBUG] Using Key=sk_pr_mykey_fa02df705055df9e4d159fe3bad0cf3bb8cd1dc2\n\t\t\t\t[ERROR] Response received 401 UnAuthorized\n\t\t\t\t[DEBUG] Using photoroom Key=sk_pr_mykey2_509b8c07454aca248a0a381b46e07f467b84c77d\n\t\t\t\t[INFO] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"sk_pr_mykey_fa02df705055df9e4d159fe3bad0cf3bb8cd1dc2\", \"sk_pr_mykey2_509b8c07454aca248a0a381b46e07f467b84c77d\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t\t[INFO] Sending request to the photoroom API\n\t\t\t\t[DEBUG] Using Key=sk_pr_my_key_fa02df705055df9e4d159fe3bad0cf3bb8cd1dc2\n\t\t\t\t[ERROR] Response received: 401 UnAuthorized\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"test %q failed: expected keywords %v to be found in the input\", test.name, d.Keywords())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"mismatch in result count: expected %d, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/phraseaccesstoken/phraseaccesstoken.go",
    "content": "package phraseaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Phrase access tokens are typically 64-character hexadecimal strings\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"phrase\", \"accessToken\", \"access_token\"}) + `\\b([a-z0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"phrase\"}\n}\n\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar tokens = make(map[string]struct{})\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\ttokens[match[1]] = struct{}{}\n\t}\n\n\tfor token := range tokens {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_PhraseAccessToken,\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, token)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, token)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.phrase.com/v2/projects\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Phrase uses Authorization header with \"token\" prefix\n\treq.Header.Add(\"Authorization\", \"token \"+token)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PhraseAccessToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Phrase is a translation management platform for software projects. Phrase API keys can be used to access translation projects, locales, and manage translations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/phraseaccesstoken/phraseaccesstoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage phraseaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPhrase_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PHRASE_OAUTH_ACCESS_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"PHRASE_OAUTH_ACCESS_TOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a phrase secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PhraseAccessToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRaw:          []byte(secret),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a phrase secret %s within but not valid\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PhraseAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(inactiveSecret),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a phrase secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PhraseAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(secret),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a phrase secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PhraseAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(secret),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Phrase.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Validate that we got results when expected\n\t\t\tif len(got) != len(tt.want) {\n\t\t\t\tt.Errorf(\"Phrase.FromData() got %d results, want %d\", len(got), len(tt.want))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Check individual results\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Use IgnoreUnexported to handle the unexported primarySecret field\n\t\t\t// Also ignore verificationError as it's handled separately above\n\t\t\tignoreOpts := cmpopts.IgnoreUnexported(detectors.Result{})\n\t\t\tif diff := cmp.Diff(tt.want, got, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Phrase.FromData() %s diff: (-want +got)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/phraseaccesstoken/phraseaccesstoken_test.go",
    "content": "package phraseaccesstoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestPhrase_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern - with keyword phrase\",\n\t\t\tinput: `\n\t\t\t[INFO] Initializing authentication\n\t\t\t[DEBUG] phrase token = 1a2b3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890\n\t\t\t[Info] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"1a2b3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - ignore duplicate\",\n\t\t\tinput: `\n\t\t\t[INFO] Processing authentication tokens\n\t\t\t[DEBUG] phrase token = '1a2b3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890'\n\t\t\t[WARN] Duplicate token found: phrase token = '1a2b3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890'\n\t\t\t[Info] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"1a2b3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - key out of prefix range\",\n\t\t\tinput: `\n\t\t\t[INFO] Starting system initialization\n\t\t\t[DEBUG] phrase keyword is not close to the real key in the data\n\t\t\t[DEBUG] Configuration loaded successfully\n\t\t\t[DEBUG] Secret key = '1a2b3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890'\n\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t[INFO] Loading configuration\n\t\t\t[DEBUG] phrase = 7cf4135a4e7f7ac228d36f210f151917a86f5dbd6\n\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"finds all valid matches\",\n\t\t\tinput: `\n\t\t\t[INFO] Multi-token authentication\n\t\t\t[DEBUG] phrase token1 = '1a2b3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890'\n\t\t\t[DEBUG] phrase token2 = 'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890'\n\t\t\t[Info] Response received: 200 OK\n\t\t\t`,\n\t\t\twant: []string{\"1a2b3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890\", \"abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern - too short\",\n\t\t\tinput: `\n\t\t\t[INFO] Processing short token\n\t\t\t[DEBUG] phrase = '1a2b3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef12345678'\n\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern - too long\",\n\t\t\tinput: `\n\t\t\t[INFO] Processing long token\n\t\t\t[DEBUG] phrase = '1a2b3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef123456789012'\n\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern - contains uppercase\",\n\t\t\tinput: `\n\t\t\t[INFO] Processing token with uppercase\n\t\t\t[DEBUG] phrase = '1A2B3C4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890'\n\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern - contains special characters\",\n\t\t\tinput: `\n\t\t\t[INFO] Processing token with special chars\n\t\t\t[DEBUG] phrase = '1a2b3c4d-e6f7890abcdef1234567890abcdef1234567890abcdef1234567890'\n\t\t\t[ERROR] Response received: 400 BadRequest\n\t\t\t`,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pinata/pinata.go",
    "content": "package pinata\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pinata\"}) + `\\b([0-9a-z]{64})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"pinata\"}) + `\\b([0-9a-z]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pinata\"}\n}\n\n// FromData will find and optionally verify Pinata secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Pinata,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\ttimeout := 10 * time.Second\n\t\t\t\tclient.Timeout = timeout\n\t\t\t\tpayload := strings.NewReader(`{\"pinataMetadata\": {\"name\": \"ItemStatus\",\"keyvalues\": {\"ItemID\": \"Item001\",\"CheckpointID\": \"Checkpoint002\",\"Source\": \"CompanyA\",\"WeightInKilos\": 5.25}},\"pinataContent\": {\"itemName\": \"exampleItemName\",\"inspectedBy\": \"Inspector001\",\"dataValues\": []}}`)\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.pinata.cloud/pinning/pinJSONToIPFS\", payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\treq.Header.Add(\"pinata_api_key\", resIdMatch)\n\t\t\t\treq.Header.Add(\"pinata_secret_api_key\", resMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Pinata\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Pinata is a service for managing files on IPFS. Pinata API keys can be used to pin and manage files on the IPFS network.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pinata/pinata_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pinata\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPinata_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PINATA\")\n\tkey := testSecrets.MustGetField(\"PINATA_KEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"PINATA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pinata secret %s within pinata %s\", secret, key)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pinata,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pinata secret %s within pinata %s but not valid\", inactiveSecret, key)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pinata,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Pinata.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Pinata.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pinata/pinata_test.go",
    "content": "package pinata\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey   = \"xw4xxzwjzerq6rf3zvd8zwnlh0yq62g4f7l97xxlg4u1043zrx4ndtptkoqdn49e\"\n    invalidKey = \"xw4xxzwjzerq6r?3zvd8zwnlh0yq62g4f7l97xxlg4u1043zrx4ndtptkoqdn49e\"\n    validId    = \"lnurl7e15mgdgv1hyalo\"\n    invalidId  = \"lnurl7e15m?dgv1hyalo\"\n    keyword    = \"pinata\"\n)\n\nfunc TestPinata_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword pinata\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pipedream/pipedream.go",
    "content": "package pipedream\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pipedream\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pipedream\"}\n}\n\n// FromData will find and optionally verify Pipedream secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Pipedream,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := 15 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.pipedream.com/v1/users/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Pipedream\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Pipedream is an integration platform for developers to build and run workflows that integrate apps, data, and APIs. Pipedream API keys can be used to access and modify these workflows and data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pipedream/pipedream_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pipedream\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPipedream_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PIPEDREAM\")\n\tinactiveSecret := testSecrets.MustGetField(\"PIPEDREAM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pipedream secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pipedream,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pipedream secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pipedream,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Pipedream.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Pipedream.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pipedream/pipedream_test.go",
    "content": "package pipedream\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"dh6zkbdzrjlsz5gy4gj2zg9stqttbd65\"\n\tinvalidPattern = \"dh6zkbdzrjlsz5gy?gj2zg9stqttbd65\"\n\tkeyword        = \"pipedream\"\n)\n\nfunc TestPipedream_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword pipedream\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pipedrive/pipedrive.go",
    "content": "package pipedrive\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pipedrive\"}) + `\\b([a-zA-Z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pipedrive\"}\n}\n\n// FromData will find and optionally verify Pipedrive secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Pipedrive,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://hooman.pipedrive.com/api/v1/users?api_token=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Pipedrive\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Pipedrive is a sales-focused customer relationship management (CRM) tool. Pipedrive API tokens can be used to access and manipulate CRM data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pipedrive/pipedrive_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pipedrive\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPipedrive_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PIPEDRIVE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"PIPEDRIVE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pipedrive secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pipedrive,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pipedrive secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pipedrive,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Pipedrive.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Pipedrive.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pipedrive/pipedrive_test.go",
    "content": "package pipedrive\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"ZVt2kcilMoT5gvZDppcXkF5JPzyinJrkSwroZ9dB\"\n\tinvalidPattern = \"ZVt2?cilMoT5gvZDppcXkF5JPzyinJrkSwroZ9dB\"\n\tkeyword        = \"pipedrive\"\n)\n\nfunc TestPipedrive_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword pipedrive\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pivotaltracker/pivotaltracker.go",
    "content": "package pivotaltracker\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\t// Get token at https://www.pivotaltracker.com/profile\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pivotal\"}) + `([a-z0-9]{32})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pivotal\"}\n}\n\n// FromData will find and optionally verify PivotalTracker secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\n\t\t// First match is entire regex, second is the first group.\n\n\t\ttoken := match[1]\n\n\t\tresult := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_PivotalTracker,\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := common.SaneHttpClient()\n\t\t\t// https://www.pivotaltracker.com/help/api/rest/v5#top\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.pivotaltracker.com/services/v5/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json; charset=utf-8\")\n\t\t\treq.Header.Add(\"X-TrackerToken\", token)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tres.Body.Close() // The request body is unused.\n\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\tresult.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, result)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PivotalTracker\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PivotalTracker is a project management tool. PivotalTracker tokens can be used to access and manage projects and data within PivotalTracker.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pivotaltracker/pivotaltracker_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pivotaltracker\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPivotalTracker_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PIVOTALTRACKER\")\n\tsecretInactive := testSecrets.MustGetField(\"PIVOTALTRACKER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pivotal secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PivotalTracker,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pivotal secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PivotalTracker,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PivotalTracker.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"PivotalTracker.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pivotaltracker/pivotaltracker_test.go",
    "content": "package pivotaltracker\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"hzwlc7d249nw76xwx372n09spjhjgidv\"\n\tinvalidPattern = \"hzwlc7d249nw76?wx372n09spjhjgidv\"\n\tkeyword        = \"pivotaltracker\"\n)\n\nfunc TestPivotalTracker_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword pivotaltracker\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pixabay/pixabay.go",
    "content": "package pixabay\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pixabay\"}) + `\\b([a-z0-9-]{34})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pixabay\"}\n}\n\n// FromData will find and optionally verify Pixabay secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Pixabay,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://pixabay.com/api/?key=%s&q=yellow+flowers&image_type=photo\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Pixabay\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Pixabay is a website for sharing photos, illustrations, vector graphics, and film footage under a proprietary license. Pixabay API keys can be used to access and retrieve this media content programmatically.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pixabay/pixabay_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pixabay\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPixabay_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PIXABAY\")\n\tinactiveSecret := testSecrets.MustGetField(\"PIXABAY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pixabay secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pixabay,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pixabay secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pixabay,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Pixabay.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Pixabay.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pixabay/pixabay_test.go",
    "content": "package pixabay\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"yz4gjwgew94zn73y9ds7hlwob6ytl70827\"\n\tinvalidPattern = \"yz4gjwgew94zn73y9?s7hlwob6ytl70827\"\n\tkeyword        = \"pixabay\"\n)\n\nfunc TestPixabay_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword pixabay\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/plaidkey/plaidkey.go",
    "content": "package plaidkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"plaid\"}) + `\\b([a-f0-9]{30})\\b`)\n\tidPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"plaid\"}) + `\\b([a-f0-9]{24})\\b`)\n\ttokenPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"plaid\"}) + `\\b(access-(sandbox|production)-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"plaid\"}\n}\n\n// FromData will find and optionally verify PlaidKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// find all the matching keys and ids in the data and make a unique maps for both.\n\tuniqueSecrets, uniqueIds, uniqueTokens := make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, foundKey := range secretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tkey := foundKey[1]\n\t\tif detectors.StringShannonEntropy(key) < 3 {\n\t\t\tcontinue\n\t\t}\n\n\t\tuniqueSecrets[key] = struct{}{}\n\t}\n\n\tfor _, foundId := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tid := foundId[1]\n\t\tif detectors.StringShannonEntropy(id) < 3 {\n\t\t\tcontinue\n\t\t}\n\n\t\tuniqueIds[id] = struct{}{}\n\t}\n\n\tfor _, foundToken := range tokenPat.FindAllStringSubmatch(dataStr, -1) {\n\t\ttoken := foundToken[1]\n\t\tif detectors.StringShannonEntropy(token) < 3 {\n\t\t\tcontinue\n\t\t}\n\n\t\tuniqueTokens[token] = struct{}{}\n\t}\n\n\tfor secret := range uniqueSecrets {\n\t\tfor id := range uniqueIds {\n\t\t\tfor token := range uniqueTokens {\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PlaidKey,\n\t\t\t\t\tRaw:          []byte(secret),\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(`%s:%s:%s`, secret, id, token)),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tenvironment := \"sandbox\"\n\t\t\t\t\tif strings.Contains(token, \"production\") {\n\t\t\t\t\t\tenvironment = \"production\"\n\t\t\t\t\t}\n\t\t\t\t\tisVerified, _, verificationErr := verifyMatch(ctx, client, id, secret, token, environment)\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\ts1.ExtraData = map[string]string{\"environment\": fmt.Sprintf(\"https://%s.plaid.com\", environment)}\n\t\t\t\t\ts1.SetVerificationError(verificationErr, id, secret)\n\t\t\t\t\tif s1.Verified {\n\t\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\t\"secret\": secret,\n\t\t\t\t\t\t\t\"id\":     id,\n\t\t\t\t\t\t\t\"token\":  token,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, id string, secret string, token string, env string) (bool, map[string]string, error) {\n\tpayload := strings.NewReader(`{\"client_id\":\"` + id + `\",\"secret\":\"` + secret + `\",\"access_token\":\"` + token + `\"}`)\n\turl := \"https://\" + env + \".plaid.com/item/get\"\n\treq, err := http.NewRequestWithContext(ctx, \"POST\", url, payload)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer res.Body.Close()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil, nil\n\tcase http.StatusBadRequest:\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PlaidKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Plaid is a financial services company that provides a way to connect applications to users' bank accounts. Plaid API keys can be used to access and manage financial data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/plaidkey/plaidkey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage plaidkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPlaidKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PLAIDKEY_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"PLAIDKEY_SECRET_INACTIVE\")\n\tid := testSecrets.MustGetField(\"PLAIDKEY_CLIENTID\")\n\ttoken := testSecrets.MustGetField(\"PLAIDKEY_ACCESS_TOKEN\")\n\tenv := \"sandbox\"\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a plaid secret %s within plaid %s and plaid token %s\", secret, id, token)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PlaidKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s:%s\", secret, id, token)),\n\t\t\t\t\tAnalysisInfo: map[string]string{\n\t\t\t\t\t\t\"secret\": secret,\n\t\t\t\t\t\t\"id\":     id,\n\t\t\t\t\t\t\"token\":  token,\n\t\t\t\t\t},\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"environment\": fmt.Sprintf(\"https://%s.plaid.com\", env),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a plaid secret %s within plaid %s and plaid token %s\", inactiveSecret, id, token)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PlaidKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s:%s\", inactiveSecret, id, token)),\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"environment\": fmt.Sprintf(\"https://%s.plaid.com\", env),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PlaidKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw v2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"AnalysisInfo\", \"ExtraData\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"PlaidKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/plaidkey/plaidkey_test.go",
    "content": "package plaidkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidSecret   = \"6e611cb89c263457b5e028d66c16c4\"\n\tinvalidSecret = \"3vl81ihtozf9im7dqz7ldp6kxbsd8y\"\n\tvalidId       = \"60e3ee4019a2660010f8bc54\"\n\tinvalidId     = \"ic1Ah5b49ycvmz2vgvlgxtb0\"\n\tvalidToken    = \"access-sandbox-833d862e-ffa8-43a7-ae28-72f56f1acb32\"\n\tinvalidToken  = \"access-sandbox-g33z362e-fha8-43a7-au28-7kf56z1acl32\"\n\tkeyword       = \"plaid\"\n)\n\nfunc TestPlaidKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword plaid\",\n\t\t\tinput: fmt.Sprintf(\"%s secret - '%s'\\n%s client id - '%s'\\n%s token - '%s'\", keyword, validSecret, keyword, validId, keyword, validToken),\n\t\t\twant:  []string{fmt.Sprintf(\"%s:%s:%s\", validSecret, validId, validToken)},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern - with keyword plaid\",\n\t\t\tinput: fmt.Sprintf(\"%s secret - '%s'\\n%s client id - '%s'\\n%s token - '%s'\", keyword, invalidSecret, keyword, invalidId, keyword, invalidToken),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/planetscale/planetscale.go",
    "content": "package planetscale\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\tusernamePat   = regexp.MustCompile(`\\b[a-z0-9]{12}\\b`)\n\tpasswordPat   = regexp.MustCompile(`\\bpscale_tkn_[A-Za-z0-9_]{43}\\b`)\n)\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\n// Keywords are used for efficiently pre-filtering chunks.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pscale_tkn_\"}\n}\n\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tusernameMatches := usernamePat.FindAllString(dataStr, -1)\n\tpasswordMatches := passwordPat.FindAllString(dataStr, -1)\n\n\tfor _, username := range usernameMatches {\n\n\t\tfor _, password := range passwordMatches {\n\t\t\tcredentials := fmt.Sprintf(\"%s:%s\", username, password)\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_PlanetScale,\n\t\t\t\tRaw:          []byte(credentials),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\t// Construct HTTP request\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.planetscale.com/v1/organizations\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Set(\"Authorization\", credentials)\n\t\t\t\treq.Header.Set(\"accept\", \"application/json\")\n\n\t\t\t\t// Send HTTP request\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\t\"id\":    username,\n\t\t\t\t\t\t\t\"token\": password,\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t\t// The secret is determinately not verified\n\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = fmt.Errorf(\"unexpected status code %d\", res.StatusCode)\n\t\t\t\t\t\ts1.SetVerificationError(err, password)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ts1.SetVerificationError(err, password)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PlanetScale\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PlanetScale is a database platform. PlanetScale tokens can be used to access and manage database instances.\"\n}\n"
  },
  {
    "path": "pkg/detectors/planetscale/planetscale_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage planetscale\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPlanetscale_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PLANET_SCALE_TOKEN\")\n\tsecretID := testSecrets.MustGetField(\"PLANET_SCALE_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"PLANET_SCALE_TOKEN_INACTIVE\")\n\tinactiveSecretID := testSecrets.MustGetField(\"PLANET_SCALE_ID_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a planetscale secret %s within with id %s\", secret, secretID)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PlanetScale,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a planetscale secret %s within with id %s but not valid\", inactiveSecret, inactiveSecretID)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PlanetScale,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a planetscale secret %s within with id %s\", secret, secretID)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PlanetScale,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a planetscale secret %s within with id %s\", secret, secretID)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PlanetScale,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Planetscale.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"AnalysisInfo\", \"ExtraData\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Planetscale.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/planetscale/planetscale_test.go",
    "content": "package planetscale\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidUsername   = \"fo57eya1lvvh\"\n\tinvalidUsername = \"fo57ey?1lvvh\"\n\tvalidPassword   = \"pscale_tkn_toLyJg1LfgY8vDQr9vhfF2HcT410U3q1alxt7012kcV\"\n\tinvalidPassword = \"pscale_tkn_toLyJg1LfgY8vDQr?vhfF2HcT410U3q1alxt7012kcV\"\n\tkeyword         = \"planetscale\"\n)\n\nfunc TestPlanetscale_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword planetscale\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validUsername, keyword, validPassword),\n\t\t\twant:  []string{validUsername + \":\" + validPassword},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidUsername, keyword, invalidPassword),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/planetscaledb/planetscaledb.go",
    "content": "package planetscaledb\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"strings\"\n\n\t\"github.com/go-sql-driver/mysql\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tusernamePat = regexp.MustCompile(`\\b[a-z0-9]{20}\\b`)\n\tpasswordPat = regexp.MustCompile(`\\bpscale_pw_[A-Za-z0-9_]{43}\\b`)\n\thostPat     = regexp.MustCompile(`\\b(aws|gcp)\\.connect\\.psdb\\.cloud\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pscale_pw_\"}\n}\n\n// FromData will find and optionally verify Planetscaledb secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tusernameMatches := usernamePat.FindAllStringSubmatch(dataStr, -1)\n\tpasswordMatches := passwordPat.FindAllStringSubmatch(dataStr, -1)\n\thostMatches := hostPat.FindAllString(dataStr, -1)\n\n\tfor _, username := range usernameMatches {\n\t\tfor _, password := range passwordMatches {\n\t\t\tfor _, host := range hostMatches {\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PlanetScaleDb,\n\t\t\t\t\tRaw:          []byte(strings.Join([]string{host, username[0], password[0]}, \"\\t\")),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tcfg := mysql.Config{\n\t\t\t\t\t\tUser:                 username[0],\n\t\t\t\t\t\tPasswd:               password[0],\n\t\t\t\t\t\tNet:                  \"tcp\",\n\t\t\t\t\t\tAddr:                 host,\n\t\t\t\t\t\tTLSConfig:            \"true\", // assuming SSL is required\n\t\t\t\t\t\tAllowNativePasswords: true,\n\t\t\t\t\t}\n\t\t\t\t\tdb, err := sql.Open(\"mysql\", cfg.FormatDSN())\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\ts1.SetVerificationError(err, password[0])\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = db.PingContext(ctx)\n\t\t\t\t\t\tif err == nil {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.SetVerificationError(err, password[0])\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdb.Close()\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PlanetScaleDb\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PlanetScaleDB is a serverless database platform built on Vitess. Credentials found here can be used to connect to the database and perform operations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/planetscaledb/planetscaledb_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage planetscaledb\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPlanetscaledb_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tusername := testSecrets.MustGetField(\"PLANET_SCALEDB_USERNAME\")\n\thost := testSecrets.MustGetField(\"PLANET_SCALEDB_HOST\")\n\tpassword := testSecrets.MustGetField(\"PLANET_SCALEDB_PASSWORD\")\n\tinactivePassword := testSecrets.MustGetField(\"PLANET_SCALEDB_PASSWORD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a planetscaledb secret %s %s %s\", username, password, host)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PlanetScaleDb,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:  context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(\"You can find a planetscaledb secret %s %s %s\", username, inactivePassword, host)),\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PlanetScaleDb,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Planetscaledb.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Planetscaledb.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/planetscaledb/planetscaledb_test.go",
    "content": "package planetscaledb\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidUsername   = \"82mygyuh2y23aw1k8lzv\"\n\tinvalidUsername = \"8?mygyuh2y23aw1k8lzv\"\n\tvalidPassword   = \"pscale_pw_iAhKQKjU8nUrHagaygAubhM6x0LTaBz6kOyqx9AIS6V\"\n\tinvalidPassword = \"pscale_pw_iAhKQKjU8nUrHaga?gAubhM6x0LTaBz6kOyqx9AIS6V\"\n\tvalidHost       = \"gcp.connect.psdb.cloud\"\n\tinvalidHost     = \"gcp?connect.psdb.cloud\"\n\tkeyword         = \"planetscaledb\"\n)\n\nfunc TestPlanetscaledb_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword planetscaledb\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, validUsername, keyword, validPassword, keyword, validHost),\n\t\t\twant:  []string{validHost + \"\\t\" + validUsername + \"\\t\" + validPassword},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidUsername, keyword, invalidPassword, keyword, invalidHost),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/planviewleankit/planviewleankit.go",
    "content": "package planviewleankit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat       = regexp.MustCompile(detectors.PrefixRegex([]string{\"planviewleankit\", \"planview\"}) + `\\b([0-9a-f]{128})\\b`)\n\tsubDomainPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"planviewleankit\", \"planview\"}) + `(?:subdomain).\\b([a-zA-Z][a-zA-Z0-9.-]{1,23}[a-zA-Z0-9])\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"planviewleankit\", \"planview\"}\n}\n\n// FromData will find and optionally verify PlanviewLeanKit secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsubdomainMatches := subDomainPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, subdomainMatch := range subdomainMatches {\n\t\tresSubdomainMatch := strings.TrimSpace(subdomainMatch[1])\n\n\t\tfor _, match := range matches {\n\t\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_PlanviewLeanKit,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://%s.leankit.com/io/account\", resSubdomainMatch), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PlanviewLeanKit\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Planview LeanKit is a visual project delivery tool that enables teams to apply Lean management principles to their work. The detected credential can be used to access and manage LeanKit accounts.\"\n}\n"
  },
  {
    "path": "pkg/detectors/planviewleankit/planviewleankit_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage planviewleankit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPlanviewLeanKit_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PLANVIEWLEANKIT\")\n\tinactiveSecret := testSecrets.MustGetField(\"PLANVIEWLEANKIT_INACTIVE\")\n\tsubdomain := testSecrets.MustGetField(\"PLANVIEWLEANKIT_SUBDOMAIN\")\n\n\t// log.Println(secret)\n\t// log.Println(inactiveSecret)\n\t// log.Println(subdomain)\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a planviewleankit subdomain %s with planviewleankit secret %s within\", subdomain, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PlanviewLeanKit,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a planviewleankit subdomain %s with planviewleankit secret %s within but not valid\", subdomain, inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PlanviewLeanKit,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PlanviewLeanKit.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"PlanviewLeanKit.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/planviewleankit/planviewleankit_test.go",
    "content": "package planviewleankit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey         = \"b1bb66781f857e6edf539042389e77043396b123c85197676719ef79a420277bd5dc2f6c299bb01895c604f7038c27af9035937823c7c0e8e25c2efe7f6ddd4f\"\n\tinvalidKey       = \"B1bb66781f857e6edf539042389e77043396b123c85197676719ef79a420277bd5dc2f6c299bb01895c604f7038c27af9035937823c7c0e8e25c2efe7f6ddd4F\"\n\tvalidSubDomain   = \"subdomain.pYwPPnHSc\"\n\tinvalidSubDomain = \"?ubdomain.pYwPPnHS?\"\n\tkeyword          = \"planviewleankit\"\n)\n\nfunc TestPlanviewLeanKit_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword planviewleankit\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validSubDomain),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidSubDomain),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/planyo/planyo.go",
    "content": "package planyo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"planyo\"}) + `\\b([0-9a-z]{62})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"planyo\"}\n}\n\n// FromData will find and optionally verify Planyo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Planyo,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://www.planyo.com/rest/?method=get_site_info&api_key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.planyo+json; version=3\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\t\t\t\tvalidResponse := strings.Contains(body, \"data\") || strings.Contains(body, \"Your Planyo site has expired\")\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && validResponse {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Planyo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Planyo is an online reservation system that offers various API methods to interact with its services. Planyo API keys can be used to access and modify reservation data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/planyo/planyo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage planyo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPlanyo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PLANYO\")\n\tinactiveSecret := testSecrets.MustGetField(\"PLANYO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a planyo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Planyo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a planyo secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Planyo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Planyo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Planyo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/planyo/planyo_test.go",
    "content": "package planyo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"big57xmqvemmsopa2s7s3805oo70dzxk7subgcao8zerjg89ze8walz5si63x7\"\n\tinvalidPattern = \"big57xmqvemmsopa2s7s3805oo70dzx?7subgcao8zerjg89ze8walz5si63x7\"\n\tkeyword        = \"planyo\"\n)\n\nfunc TestPlanyo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword planyo\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/plivo/plivo.go",
    "content": "package plivo\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"plivo\"}) + `\\b([A-Z]{20})\\b`)\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"plivo\"}) + `\\b([A-Za-z0-9_-]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"plivo\"}\n}\n\n// FromData will find and optionally verify Plivo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, idMatch := range idMatches {\n\t\t\tid := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Plivo,\n\t\t\t\tRedacted:     id,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + id),\n\t\t\t}\n\t\t\tstringResMatch := fmt.Sprintf(\"%s:%s\", id, resMatch)\n\t\t\tdecodeSecret := b64.StdEncoding.EncodeToString([]byte(stringResMatch))\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.plivo.com/v1/Account/\"+id+\"/Number/\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", decodeSecret))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Plivo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Plivo is a cloud-based communications platform that provides API services for voice and messaging. Plivo credentials can be used to access and manage these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/plivo/plivo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage plivo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPlivo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PLIVO_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"PLIVO_INACTIVE\")\n\tid := testSecrets.MustGetField(\"PLIVO_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a plivo secret %s within https://api.plivo.com/v1/Account/%s/Number/\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Plivo,\n\t\t\t\t\tRedacted:     id,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRawV2:        []byte(secret + id),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a plivo secret %s within https://api.plivo.com/v1/Account/%s/Number/ but not valid\", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Plivo,\n\t\t\t\t\tRedacted:     id,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(inactiveSecret + id),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Plivo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Plivo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/plivo/plivo_test.go",
    "content": "package plivo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidId    = \"YGIQXPGZSVVGVGOREMAE\"\n\tinvalidId  = \"YGIQXPGZS?VGVGOREMAE\"\n\tvalidKey   = \"qFj32Da8vf-g_-8qLu9P_k8XPyAHGrvKrzGTQIN4\"\n\tinvalidKey = \"qFj32Da8vf-g?-8qLu9P_k8XPyAHGrvKrzGTQIN4\"\n\tkeyword    = \"plivo\"\n)\n\nfunc TestPlivo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword plivo\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validId, keyword, validKey),\n\t\t\twant:  []string{validKey + validId},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidId, keyword, invalidKey),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/podio/podio.go",
    "content": "package podio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"podio\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"podio\"}\n}\n\n// FromData will find and optionally verify Podio secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Podio,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.podio.com/user\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Podio\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Podio is an intranet communication platform. API keys can be used to read sensitive data\"\n}\n"
  },
  {
    "path": "pkg/detectors/podio/podio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage podio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPodio_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PODIO\")\n\tinactiveSecret := testSecrets.MustGetField(\"PODIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a podio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Podio,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a podio secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Podio,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Podio.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Podio.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/podio/podio_test.go",
    "content": "package podio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"d3ij9dt7n9pu9enq0seldrpwcbl6p1ky\"\n\tinvalidPattern = \"d3ij9dt7n9pu9enq?seldrpwcbl6p1ky\"\n\tkeyword        = \"podio\"\n)\n\nfunc TestPodio_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword podio\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pollsapi/pollsapi.go",
    "content": "package pollsapi\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pollsapi\"}) + `\\b([A-Z0-9]{28})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pollsapi\"}\n}\n\n// FromData will find and optionally verify PollsAPI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_PollsAPI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.pollsapi.com/v1/get/polls?offset=0&limit=10\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"api-key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PollsAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PollsAPI is a service used for creating and managing polls. PollsAPI keys can be used to access and modify poll data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pollsapi/pollsapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pollsapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPollsAPI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"POLLSAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"POLLSAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pollsapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PollsAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pollsapi secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PollsAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PollsAPI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"PollsAPI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pollsapi/pollsapi_test.go",
    "content": "package pollsapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"WHWPPKLOD23IRAUKKY24V9WWV6IC\"\n\tinvalidPattern = \"WHWPPKLOD23IRA?KKY24V9WWV6IC\"\n\tkeyword        = \"pollsapi\"\n)\n\nfunc TestPollsAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword pollsapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/poloniex/poloniex.go",
    "content": "package poloniex\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha512\"\n\t\"encoding/hex\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"poloniex\"}) + `\\b([0-9A-Z]{8}-[0-9A-Z]{8}-[0-9A-Z]{8}-[0-9A-Z]{8})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"poloniex\"}) + `\\b([0-9a-f]{128})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"poloniex\"}\n}\n\n// FromData will find and optionally verify Poloniex secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, secretMatch := range secretMatches {\n\t\t\tresSecretMatch := strings.TrimSpace(secretMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Poloniex,\n\t\t\t\tRaw:          []byte(resSecretMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resSecretMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\n\t\t\t\ttimestamp := strconv.FormatInt(time.Now().Unix()*1000, 10)\n\n\t\t\t\tpayload := url.Values{}\n\t\t\t\tpayload.Add(\"command\", \"returnBalances\")\n\t\t\t\tpayload.Add(\"nonce\", timestamp)\n\n\t\t\t\tsignature := getPoloniexSignature(resSecretMatch, payload.Encode())\n\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://poloniex.com/tradingApi\", strings.NewReader(payload.Encode()))\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\t\treq.Header.Add(\"Key\", resMatch)\n\t\t\t\treq.Header.Add(\"Sign\", signature)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc getPoloniexSignature(secret string, payload string) string {\n\tmac := hmac.New(sha512.New, []byte(secret))\n\tmac.Write([]byte(payload))\n\tmacsum := mac.Sum(nil)\n\treturn hex.EncodeToString(macsum)\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Poloniex\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Poloniex is a cryptocurrency exchange that allows users to trade various digital assets. Poloniex API keys can be used to access and manage account data and perform trading operations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/poloniex/poloniex_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage poloniex\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPoloniex_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tkey := testSecrets.MustGetField(\"POLONIEX_KEY\")\n\tinactiveKey := testSecrets.MustGetField(\"POLONIEX_KEY_INACTIVE\")\n\tsecret := testSecrets.MustGetField(\"POLONIEX\")\n\tinactiveSecret := testSecrets.MustGetField(\"POLONIEX_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a poloniex key %s with poloniex secret %s within\", key, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Poloniex,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a poloniex key %s with poloniex secret %s within but not valid\", inactiveKey, inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Poloniex,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Poloniex.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Poloniex.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/poloniex/poloniex_test.go",
    "content": "package poloniex\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey      = \"2ELRAUEF-NB06CX0R-ZGXL88HF-BX8B77S6\"\n\tinvalidKey    = \"2ELRAUEF?NB06CX0R-ZGXL88HF-BX8B77S6\"\n\tvalidSecret   = \"d5dc2f6c299bb01895c604f7038c27af9035937823c7c0e8e25c2efe7f6ddd4fb1bb66781f857e6edf539042389e77043396b123c85197676719ef79a420277b\"\n\tinvalidSecret = \"D5dc2f6c299bb01895c604f7038c27af9035937823c7c0e8e25c2efe7f6ddd4fb1bb66781f857e6edf539042389e77043396b123c85197676719ef79a420277B\"\n\tkeyword       = \"poloniex\"\n)\n\nfunc TestPoloniex_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword poloniex\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validSecret),\n\t\t\twant:  []string{validKey + validSecret},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidSecret),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/polygon/polygon.go",
    "content": "package polygon\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"polygon\"}) + `\\b([a-z0-9A-Z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"polygon\"}\n}\n\n// FromData will find and optionally verify Polygon secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Polygon,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.polygon.io/v2/reference/locales?apiKey=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Polygon\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Polygon is a protocol and a framework for building and connecting Ethereum-compatible blockchain networks. Polygon API keys can be used to interact with its services and access blockchain data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/polygon/polygon_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage polygon\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPolygon_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"POLYGON_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"POLYGON_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a polygon secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Polygon,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a polygon secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Polygon,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Polygon.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Polygon.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/polygon/polygon_test.go",
    "content": "package polygon\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"vfINal1N4C0YH6fME3dxmRGaeuO1WjnP\"\n\tinvalidPattern = \"vfINal1N4C0YH6fM?3dxmRGaeuO1WjnP\"\n\tkeyword        = \"polygon\"\n)\n\nfunc TestPolygon_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword polygon\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/portainer/portainer.go",
    "content": "package portainer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithLocalAddresses\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tendpointPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"portainer\"}) + `\\b(https?:\\/\\/\\S+(:[0-9]{4,5})?)\\b`)\n\ttokenPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"portainer\"}) + `\\b(eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\\.[0-9A-Za-z]{50,310}\\.[0-9A-Z-a-z\\-_]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"portainer\"}\n}\n\n// FromData will find and optionally verify Portainer secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := tokenPat.FindAllStringSubmatch(dataStr, -1)\n\tendpointMatches := endpointPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, endpointMatch := range endpointMatches {\n\t\t\tresEndpointMatch := strings.TrimSpace(endpointMatch[1])\n\n\t\t\tu, err := detectors.ParseURLAndStripPathAndParams(resEndpointMatch)\n\t\t\tif err != nil {\n\t\t\t\t// if the URL is invalid just move onto the next one\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tu.Path = \"/api/endpoints\"\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Portainer,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resEndpointMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", u.String(), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else if res.StatusCode == 401 || res.StatusCode == 403 {\n\t\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(endpointMatches) > 0 {\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Portainer\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Portainer is a management UI for Docker, Docker Swarm, Kubernetes, and Azure ACI. Portainer API tokens can be used to access and control these environments.\"\n}\n"
  },
  {
    "path": "pkg/detectors/portainer/portainer_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage portainer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPortainer_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PORTAINER\")\n\tendpoint := testSecrets.MustGetField(\"PORTAINER_ENDPOINT\")\n\tinactiveSecret := testSecrets.MustGetField(\"PORTAINER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a portainer secret %s for portainer url %s\", secret, endpoint)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Portainer,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRawV2:        []byte(secret + endpoint),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a portainer secret %s for portainer %s within but not valid\", inactiveSecret, endpoint)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Portainer,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(inactiveSecret + endpoint),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Portainer.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Portainer.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/portainer/portainer_test.go",
    "content": "package portainer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidEndpoint   = \"http://>xC'w//b7U@CtF|>|Fqw'2Z\"\n\tinvalidEndpoint = \"?ttp://>xC'w//b7U@CtF|>|Fqw'2?\"\n\tvalidToken      = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.EHHJE6Aht7Exhje8rUMLScr2bxcoTvWBl9bjMYZhCMYMLPD3EpUZL9SNd839DcI95lYtMfclPffpFrrJ0BbgryxnrfUSeeSKHu.W9Ur5_DLIBpXO3mfh404_7Kt9o8XZRnLTLyam2fdhB_\"\n\tinvalidToken    = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.EHHJE6Aht7Exhje8rUM?Scr2bxcoTvWBl9bjMYZhCMYMLPD3EpUZL9SNd839DcI95lYtMfclPffpFrrJ0BbgryxnrfUSeeSKHu.W9Ur5_DLIBpXO3mfh404_7Kt9o8XZRnLTLyam2fdhB_\"\n\tkeyword         = \"portainer\"\n)\n\nfunc TestPortainer_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword portainer\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s;'\\n%s token - '%s'\\n\", keyword, validEndpoint, keyword, validToken),\n\t\t\twant:  []string{validToken + validEndpoint},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s;'\\n%s token - '%s'\\n\", keyword, invalidEndpoint, keyword, invalidToken),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/portainertoken/portainertoken.go",
    "content": "package portainertoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithLocalAddresses\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat      = regexp.MustCompile(detectors.PrefixRegex([]string{\"portainertoken\"}) + `\\b(ptr_[A-Za-z0-9\\/_\\-+=]{20,60})`)\n\tendpointPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"portainer\"}) + `\\b(https?:\\/\\/\\S+(:[0-9]{4,5})?)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"portainertoken\"}\n}\n\n// FromData will find and optionally verify Portainertoken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tendpointMatches := endpointPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, endpointMatch := range endpointMatches {\n\t\t\tresEndpointMatch := strings.TrimSpace(endpointMatch[1])\n\n\t\t\tu, err := detectors.ParseURLAndStripPathAndParams(resEndpointMatch)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Printf(\"\\nINVALID URL\\n\")\n\t\t\t\t// if the URL is invalid just move onto the next one\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tu.Path = \"/api/stacks\"\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_PortainerToken,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resEndpointMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", u.String(), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\treq.Header.Add(\"X-API-Key\", resMatch)\n\n\t\t\t\tres, err := client.Do(req)\n\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(endpointMatches) > 0 {\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PortainerToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Portainer is a management UI for Docker environments. Portainer tokens can be used to authenticate and interact with the Portainer API.\"\n}\n"
  },
  {
    "path": "pkg/detectors/portainertoken/portainertoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage portainertoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPortainertoken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PORTAINERTOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"PORTAINERTOKEN_INACTIVE\")\n\tendpoint := testSecrets.MustGetField(\"PORTAINER_ENDPOINT\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a portainertoken secret %s within for portainer url %s\", secret, endpoint)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PortainerToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRawV2:        []byte(secret + endpoint),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a portainertoken secret %s within but not valid for portainer url %s\", inactiveSecret, endpoint)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PortainerToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(inactiveSecret + endpoint),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Portainertoken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Portainertoken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/portainertoken/portainertoken_test.go",
    "content": "package portainertoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey        = \"ptr_9zmQMKyeMqEB_pei887xQBZGhGWm1jXCIT0gI\"\n\tinvalidKey      = \"ptr_9zmQMKyeMqEB_pei?87xQBZGhGWm1jXCIT0gI\"\n\tvalidEndpoint   = \"http://api.SAaiuc8123.com:12345\"\n\tinvalidEndpoint = \"?ttp://api.SAaiuc8123.com:12345\"\n\tkeyword         = \"portainertoken\"\n)\n\nfunc TestPortainertoken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword portainertoken\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\nportainer token - '%s'\\n\", keyword, validKey, validEndpoint),\n\t\t\twant:  []string{validKey + validEndpoint},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\nportainer token - '%s'\\n\", keyword, invalidKey, invalidEndpoint),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/positionstack/positionstack.go",
    "content": "package positionstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"positionstack\"}) + `\\b([a-zA-Z0-9_]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"positionstack\"}\n}\n\n// FromData will find and optionally verify PositionStack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_PositionStack,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.positionstack.com/v1/forward?access_key=%s&query=Philippines\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PositionStack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PositionStack is a geocoding API service providing forward and reverse geocoding. PositionStack API keys can be used to access geocoding services to convert addresses to coordinates and vice versa.\"\n}\n"
  },
  {
    "path": "pkg/detectors/positionstack/positionstack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage positionstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPositionStack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"POSITIONSTACK\")\n\tinactiveSecret := testSecrets.MustGetField(\"POSITIONSTACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a positionstack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PositionStack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a positionstack secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PositionStack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PositionStack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"PositionStack.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/positionstack/positionstack_test.go",
    "content": "package positionstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"iUgwG0nYZt0TY1x5bfyWMJj02PhW7EGX\"\n\tinvalidPattern = \"iUgwG0nYZt0TY1x5?fyWMJj02PhW7EGX\"\n\tkeyword        = \"positionstack\"\n)\n\nfunc TestPositionStack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword positionstack\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/postageapp/postageapp.go",
    "content": "package postageapp\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"postageapp\"}) + `\\b([0-9A-Za-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"postageapp\"}\n}\n\n// FromData will find and optionally verify PostageApp secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_PostageApp,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.postageapp.com/v.1.0/get_account_info.json?api_key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Transfer-Encoding\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PostageApp\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PostageApp is a service for sending emails via their API. The API keys can be used to send emails and access account information.\"\n}\n"
  },
  {
    "path": "pkg/detectors/postageapp/postageapp_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage postageapp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPostageApp_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"POSTAGEAPP\")\n\tinactiveSecret := testSecrets.MustGetField(\"POSTAGEAPP_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a postageapp secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PostageApp,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a postageapp secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PostageApp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PostageApp.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"PostageApp.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/postageapp/postageapp_test.go",
    "content": "package postageapp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"VJhmldVkyWB2bmLumsZzOtJKfxliCAXP\"\n\tinvalidPattern = \"VJh?ldVkyWB2bmLumsZzOtJKfxliCAXP\"\n\tkeyword        = \"postageapp\"\n)\n\nfunc TestPostageApp_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword postageapp\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/postbacks/postbacks.go",
    "content": "package postbacks\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"postbacks\"}) + `\\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"postbacks\"}\n}\n\n// FromData will find and optionally verify Postbacks secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Postbacks,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := 10 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\tpayload := strings.NewReader(`{\"url\": \"https://foo.com/bar\",\"send_at\": 1579049877,\"body_string\": \"{ \\\"email_id\\\":\\\"123456789\\\" }\"}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.postbacks.io/v1/requestPostback\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Postbacks-Authorization\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Postbacks\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Postbacks are used to send data to a specified URL based on certain events or triggers. Postback URLs are often used in affiliate marketing, ad tracking, and analytics.\"\n}\n"
  },
  {
    "path": "pkg/detectors/postbacks/postbacks_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage postbacks\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPostbacks_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"POSTBACKS\")\n\tinactiveSecret := testSecrets.MustGetField(\"POSTBACKS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a postbacks secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postbacks,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a postbacks secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postbacks,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Postbacks.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Postbacks.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/postbacks/postbacks_test.go",
    "content": "package postbacks\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"64176f2e-e1da-3e29-2a08-19fa8bcb1838\"\n\tinvalidPattern = \"64176f2e?e1da-3e29-2a08-19fa8bcb1838\"\n\tkeyword        = \"postbacks\"\n)\n\nfunc TestPostbacks_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword postbacks\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/postgres/postgres.go",
    "content": "package postgres\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/lib/pq\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nconst (\n\tdefaultPort = \"5432\"\n\n\tpgConnectTimeout = \"connect_timeout\"\n\tpgDbname         = \"dbname\"\n\tpgHost           = \"host\"\n\tpgPassword       = \"password\"\n\tpgPort           = \"port\"\n\tpgRequiressl     = \"requiressl\"\n\tpgSslmode        = \"sslmode\"\n\tpgSslmodeAllow   = \"allow\"\n\tpgSslmodeDisable = \"disable\"\n\tpgSslmodePrefer  = \"prefer\"\n\tpgSslmodeRequire = \"require\"\n\tpgUser           = \"user\"\n\tpgDbType         = \"db_type\"\n)\n\n// This detector currently only finds Postgres connection string URIs\n// (https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS) When it finds one, it uses\n// pq.ParseURI to normalize this into space-separated key-value pair Postgres connection string, and then uses a regular\n// expression to transform this connection string into a parameters map. This parameters map is manipulated prior to\n// verification, which operates by transforming the map back into a space-separated kvp connection string. This is kind\n// of clunky overall, but it has the benefit of preserving the connection string as a map when it needs to be modified,\n// which is much nicer than having to patch a space-separated string of kvps.\n\n// Multi-host connection string URIs are currently not supported because pq.ParseURI doesn't parse them correctly. If we\n// happen to run into a case where this matters we can address it then.\nvar (\n\t_                  detectors.Detector = (*Scanner)(nil)\n\turiPattern                            = regexp.MustCompile(`\\b(?i)(postgres(?:ql)?)://\\S+\\b`)\n\tconnStrPartPattern                    = regexp.MustCompile(`([[:alpha:]]+)='(.+?)' ?`)\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tdetectLoopback bool // Automated tests run against localhost, but we want to ignore those results in the wild\n\tignorePatterns []*regexp.Regexp\n}\n\nfunc New(opts ...func(*Scanner)) *Scanner {\n\tscanner := &Scanner{\n\t\tignorePatterns: []*regexp.Regexp{},\n\t}\n\tfor _, opt := range opts {\n\t\topt(scanner)\n\t}\n\n\treturn scanner\n}\n\nfunc WithIgnorePattern(ignoreStrings []string) func(*Scanner) {\n\treturn func(s *Scanner) {\n\t\tvar ignorePatterns []*regexp.Regexp\n\t\tfor _, ignoreString := range ignoreStrings {\n\t\t\tignorePattern, err := regexp.Compile(ignoreString)\n\t\t\tif err != nil {\n\t\t\t\tpanic(fmt.Sprintf(\"%s is not a valid regex, error received: %v\", ignoreString, err))\n\t\t\t}\n\t\t\tignorePatterns = append(ignorePatterns, ignorePattern)\n\t\t}\n\n\t\ts.ignorePatterns = ignorePatterns\n\t}\n}\n\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)\n\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"postgres\"}\n}\n\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) ([]detectors.Result, error) {\n\tvar results []detectors.Result\n\tcandidateParamSets := findUriMatches(data, s.ignorePatterns)\n\n\tfor _, params := range candidateParamSets {\n\t\tif common.IsDone(ctx) {\n\t\t\tbreak\n\t\t}\n\t\tuser, ok := params[pgUser]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tpassword, ok := params[pgPassword]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\thost, ok := params[pgHost]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tif !s.detectLoopback {\n\t\t\tif host == \"localhost\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif ip := net.ParseIP(host); ip != nil && ip.IsLoopback() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tport, ok := params[pgPort]\n\t\tif !ok {\n\t\t\tport = defaultPort\n\t\t\tparams[pgPort] = port\n\t\t}\n\n\t\tconst defaultDBType = \"postgresql\"\n\t\tdbType, ok := params[pgDbType]\n\t\tif !ok {\n\t\t\tdbType = defaultDBType\n\t\t}\n\t\traw := []byte(fmt.Sprintf(\"%s://%s:%s@%s:%s\", dbType, user, password, host, port))\n\n\t\tresult := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Postgres,\n\t\t\tRaw:          raw,\n\t\t\tRawV2:        raw,\n\t\t}\n\n\t\t// We don't need to normalize the (deprecated) requiressl option into the (up-to-date) sslmode option - pq can\n\t\t// do it for us - but we will do it anyway here so that when we later capture sslmode into ExtraData we will\n\t\t// capture it post-normalization. (The detector's behavior is undefined for candidate secrets that have both\n\t\t// requiressl and sslmode set.)\n\t\tif requiressl := params[pgRequiressl]; requiressl == \"0\" {\n\t\t\tparams[pgSslmode] = pgSslmodePrefer\n\t\t} else if requiressl == \"1\" {\n\t\t\tparams[pgSslmode] = pgSslmodeRequire\n\t\t}\n\n\t\tif verify {\n\t\t\t// pq appears to ignore the context deadline, so we copy any timeout that's been set into the connection\n\t\t\t// parameters themselves.\n\t\t\tif timeout, ok := getDeadlineInSeconds(ctx); ok && timeout > 0 {\n\t\t\t\tparams[pgConnectTimeout] = strconv.Itoa(timeout)\n\t\t\t} else if ok && timeout <= 0 {\n\t\t\t\t// Deadline in the context has already exceeded.\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyPostgres(params)\n\t\t\tresult.Verified = isVerified\n\t\t\tresult.SetVerificationError(verificationErr, password)\n\t\t\tresult.AnalysisInfo = map[string]string{\n\t\t\t\t\"connection_string\": string(raw),\n\t\t\t}\n\t\t}\n\n\t\t// We gather SSL information into ExtraData in case it's useful for later reporting.\n\t\tsslmode := params[pgSslmode]\n\t\tif sslmode == \"\" {\n\t\t\tsslmode = \"<unset>\"\n\t\t}\n\t\tresult.ExtraData = map[string]string{\n\t\t\tpgSslmode: sslmode,\n\t\t}\n\n\t\tresults = append(results, result)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {\n\treturn false, \"\"\n}\n\nfunc findUriMatches(data []byte, ignorePatterns []*regexp.Regexp) []map[string]string {\n\tvar matches []map[string]string\n\tfor _, uri := range uriPattern.FindAll(data, -1) {\n\t\tif shouldIgnore(uri, ignorePatterns) {\n\t\t\tcontinue\n\t\t}\n\t\t// Capture the database type (e.g., \"postgres\" or \"postgresql\")\n\t\tdbTypeMatch := uriPattern.FindSubmatch(uri)\n\t\tif len(dbTypeMatch) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\tdbType := string(dbTypeMatch[1])\n\n\t\tconnStr, err := pq.ParseURL(string(uri))\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tparts := connStrPartPattern.FindAllStringSubmatch(connStr, -1)\n\t\tparams := make(map[string]string, len(parts))\n\t\tfor _, part := range parts {\n\t\t\tparams[part[1]] = part[2]\n\t\t}\n\n\t\tparams[pgDbType] = dbType\n\t\tmatches = append(matches, params)\n\t}\n\treturn matches\n}\n\nfunc shouldIgnore(uri []byte, ignorePatterns []*regexp.Regexp) bool {\n\tfor _, ignore := range ignorePatterns {\n\t\tif ignore.Match(uri) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// getDeadlineInSeconds gets the deadline from the context in seconds. If there\n// is no deadline, false is returned. If the deadline is already exceeded, a\n// negative or 0 value will be returned.\nfunc getDeadlineInSeconds(ctx context.Context) (int, bool) {\n\tdeadline, ok := ctx.Deadline()\n\tif !ok {\n\t\t// Context does not have a deadline.\n\t\treturn 0, false\n\t}\n\n\tduration := time.Until(deadline)\n\treturn int(duration.Seconds()), true\n}\n\nfunc isErrorDatabaseNotFound(err error, dbName string) bool {\n\tif dbName == \"\" {\n\t\tdbName = \"postgres\"\n\t}\n\tmissingDbErrorText := fmt.Sprintf(\"database \\\"%s\\\" does not exist\", dbName)\n\n\treturn strings.Contains(err.Error(), missingDbErrorText)\n}\n\nfunc verifyPostgres(params map[string]string) (bool, error) {\n\tif sslmode := params[pgSslmode]; sslmode == pgSslmodeAllow || sslmode == pgSslmodePrefer {\n\t\t// pq doesn't support 'allow' or 'prefer'. If we find either of them, we'll just ignore it. This will trigger\n\t\t// the same logic that is run if no sslmode is set at all (which mimics 'prefer', which is the default).\n\t\tdelete(params, pgSslmode)\n\n\t\t// We still want to save the original sslmode in ExtraData, so we'll re-add it before returning.\n\t\tdefer func() {\n\t\t\tparams[pgSslmode] = sslmode\n\t\t}()\n\t}\n\n\t// db_type is not a valid configuration parameter, so we remove it before connecting.\n\tdbType := params[pgDbType]\n\tdelete(params, pgDbType)\n\n\t// we re-add it before returning to preserve in ExtraData\n\tdefer func() {\n\t\tparams[pgDbType] = dbType\n\t}()\n\n\tvar connStr string\n\tfor key, value := range params {\n\t\tconnStr += fmt.Sprintf(\"%s='%s'\", key, value)\n\t}\n\n\tdb, err := sql.Open(\"postgres\", connStr)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer db.Close()\n\n\terr = db.Ping()\n\tswitch {\n\tcase err == nil:\n\t\treturn true, nil\n\tcase strings.Contains(err.Error(), \"password authentication failed\"):\n\t\treturn false, nil\n\tcase errors.Is(err, pq.ErrSSLNotSupported) && params[pgSslmode] == \"\":\n\t\t// If the sslmode is unset, then either it was unset in the candidate secret, or we've intentionally unset it\n\t\t// because it was specified as 'allow' or 'prefer', neither of which pq supports. In all of these cases, non-SSL\n\t\t// connections are acceptable, so now we try a connection without SSL.\n\t\tparams[pgSslmode] = pgSslmodeDisable\n\t\tdefer delete(params, pgSslmode) // We want to return with the original params map intact (for ExtraData)\n\t\treturn verifyPostgres(params)\n\tcase isErrorDatabaseNotFound(err, params[pgDbname]):\n\t\treturn true, nil // If we know this, we were able to authenticate\n\tdefault:\n\t\treturn false, err\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Postgres\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Postgres connection string containing credentials\"\n}\n"
  },
  {
    "path": "pkg/detectors/postgres/postgres_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage postgres\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/lib/pq\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nvar postgresDockerHash string\n\nconst (\n\tpostgresUser = \"postgres\"\n\tpostgresPass = \"23201da=b56ca236f3dc6736c0f9afad\"\n\tpostgresHost = \"localhost\"\n\tpostgresPort = \"5434\" // Do not use 5433, as local dev environments can use it for other things\n\n\tinactivePass = \"inactive\"\n\tinactiveHost = \"192.0.2.0\"\n)\n\nfunc TestPostgres_FromChunk(t *testing.T) {\n\tctx := context.Background()\n\tif err := startPostgres(); err != nil {\n\t\tif exitErr, ok := err.(*exec.ExitError); ok {\n\t\t\tt.Fatalf(\"could not start local postgres: %v w/stderr:\\n%s\", err, string(exitErr.Stderr))\n\t\t} else {\n\t\t\tt.Fatalf(\"could not start local postgres: %v\", err)\n\t\t}\n\t}\n\tdefer stopPostgres()\n\n\t// The detector is written to connect to the database 'postgres' if no explicit database is found in the candidate\n\t// secret (because pq uses 'postgres' as a default if no database is specified). If the target cluster doesn't\n\t// actually have a database with this name, but our credentials are good, then Postgres will give us a \"missing\n\t// database\" error message instead of an authentication failure.\n\t//\n\t// Unfortunately, directly validating this in the automated tests is awkward because the docker image's POSTGRES_DB\n\t// environment variable doesn't appear to work: The database created is always named 'postgres', no matter what\n\t// POSTGRES_DB is set to. This means that we can't replicate a cluster that has no database named 'postgres', so we\n\t// can't directly test what happens if we see one. To work around this, all the automated tests try to connect to\n\t// the nonexistent database 'postgres2'. In this way, we test the logic of attempting to connect to a non-existent\n\t// database, even though the test cases are the inverse of what we'd see in the wild.\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\n\t\t// For tests that require a timeout context, in which case the above ctx will be ignored and new ctx at the\n\t\t// time of test execution will be created\n\t\trequiresTimeoutContext bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found connection URI with ssl mode unset, verified\",\n\t\t\ts:    Scanner{detectLoopback: true},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(`postgresql://%s:%s@%s:%s/postgres2`, postgresUser, postgresPass, postgresHost, postgresPort)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postgres,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRaw:          []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@localhost:5434\"),\n\t\t\t\t\tRawV2:        []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@localhost:5434\"),\n\t\t\t\t\tExtraData:    map[string]string{\"sslmode\": \"<unset>\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found connection URI with ssl mode 'prefer', verified\",\n\t\t\ts:    Scanner{detectLoopback: true},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(`postgresql://%s:%s@%s:%s/postgres2?sslmode=prefer`, postgresUser, postgresPass, postgresHost, postgresPort)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postgres,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRaw:          []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@localhost:5434\"),\n\t\t\t\t\tRawV2:        []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@localhost:5434\"),\n\t\t\t\t\tExtraData:    map[string]string{\"sslmode\": \"prefer\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found connection URI with ssl mode 'allow', verified\",\n\t\t\ts:    Scanner{detectLoopback: true},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(`postgresql://%s:%s@%s:%s/postgres2?sslmode=allow`, postgresUser, postgresPass, postgresHost, postgresPort)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postgres,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRaw:          []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@localhost:5434\"),\n\t\t\t\t\tRawV2:        []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@localhost:5434\"),\n\t\t\t\t\tExtraData:    map[string]string{\"sslmode\": \"allow\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found connection URI with requiressl=0, verified\",\n\t\t\ts:    Scanner{detectLoopback: true},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(`postgresql://%s:%s@%s:%s/postgres2?requiressl=0`, postgresUser, postgresPass, postgresHost, postgresPort)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postgres,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRaw:          []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@localhost:5434\"),\n\t\t\t\t\tRawV2:        []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@localhost:5434\"),\n\t\t\t\t\tExtraData:    map[string]string{\"sslmode\": \"prefer\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found connection URI without database, verified\",\n\t\t\ts:    Scanner{detectLoopback: true},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(`postgresql://%s:%s@%s:%s/`, postgresUser, postgresPass, postgresHost, postgresPort)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postgres,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRaw:          []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@localhost:5434\"),\n\t\t\t\t\tRawV2:        []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@localhost:5434\"),\n\t\t\t\t\tExtraData:    map[string]string{\"sslmode\": \"<unset>\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found connection URI, unverified\",\n\t\t\ts:    Scanner{detectLoopback: true},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(`postgresql://%s:%s@%s:%s/postgres2`, postgresUser, inactivePass, postgresHost, postgresPort)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postgres,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(\"postgresql://postgres:inactive@localhost:5434\"),\n\t\t\t\t\tRawV2:        []byte(\"postgresql://postgres:inactive@localhost:5434\"),\n\t\t\t\t\tExtraData:    map[string]string{\"sslmode\": \"<unset>\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ignored localhost\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(`postgresql://%s:%s@%s:%s/postgres2`, postgresUser, postgresPass, \"localhost\", postgresPort)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ignored 127.0.0.1\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(`postgresql://%s:%s@%s:%s/postgres2`, postgresUser, postgresPass, \"127.0.0.1\", postgresPort)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found connection URI, unverified due to error - inactive host\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:                    ctx,\n\t\t\t\tdata:                   []byte(fmt.Sprintf(`postgresql://%s:%s@%s:%s/postgres2`, postgresUser, postgresPass, inactiveHost, postgresPort)),\n\t\t\t\tverify:                 true,\n\t\t\t\trequiresTimeoutContext: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postgres,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@192.0.2.0:5434\"),\n\t\t\t\t\tRawV2:        []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@192.0.2.0:5434\"),\n\t\t\t\t\tExtraData:    map[string]string{\"sslmode\": \"<unset>\"},\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(errors.New(\"i/o timeout\"))\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found connection URI, unverified due to error - wrong port\",\n\t\t\ts:    Scanner{detectLoopback: true},\n\t\t\targs: args{\n\t\t\t\tctx:                    ctx,\n\t\t\t\tdata:                   []byte(fmt.Sprintf(`postgresql://%s:%s@%s/postgres2`, postgresUser, postgresPass, postgresHost)),\n\t\t\t\tverify:                 true,\n\t\t\t\trequiresTimeoutContext: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postgres,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@localhost:5432\"),\n\t\t\t\t\tRawV2:        []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@localhost:5432\"),\n\t\t\t\t\tExtraData:    map[string]string{\"sslmode\": \"<unset>\"},\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(errors.New(\"connection refused\"))\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found connection URI, unverified due to error - ssl not supported (using sslmode)\",\n\t\t\ts:    Scanner{detectLoopback: true},\n\t\t\targs: args{\n\t\t\t\tctx:                    ctx,\n\t\t\t\tdata:                   []byte(fmt.Sprintf(`postgresql://%s:%s@%s:%s/postgres2?sslmode=require`, postgresUser, postgresPass, postgresHost, postgresPort)),\n\t\t\t\tverify:                 true,\n\t\t\t\trequiresTimeoutContext: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postgres,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@localhost:5434\"),\n\t\t\t\t\tRawV2:        []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@localhost:5434\"),\n\t\t\t\t\tExtraData:    map[string]string{\"sslmode\": \"require\"},\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(pq.ErrSSLNotSupported)\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found connection URI, unverified due to error - ssl not supported (using requiressl)\",\n\t\t\ts:    Scanner{detectLoopback: true},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(`postgresql://%s:%s@%s:%s/postgres2?requiressl=1`, postgresUser, postgresPass, postgresHost, postgresPort)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postgres,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@localhost:5434\"),\n\t\t\t\t\tRawV2:        []byte(\"postgresql://postgres:23201da=b56ca236f3dc6736c0f9afad@localhost:5434\"),\n\t\t\t\t\tExtraData:    map[string]string{\"sslmode\": \"require\"},\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(pq.ErrSSLNotSupported)\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tctx := tt.args.ctx\n\t\t\tvar cancel context.CancelFunc\n\t\t\tif tt.args.requiresTimeoutContext {\n\t\t\t\tctx, cancel = context.WithTimeout(context.Background(), 3*time.Second)\n\t\t\t\tdefer cancel()\n\t\t\t}\n\t\t\tgot, err := tt.s.FromData(ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"postgres.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgotErr := \"\"\n\t\t\t\tif got[i].VerificationError() != nil {\n\t\t\t\t\tgotErr = got[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\twantErr := \"\"\n\t\t\t\tif tt.want[i].VerificationError() != nil {\n\t\t\t\t\twantErr = tt.want[i].VerificationError().Error()\n\t\t\t\t}\n\t\t\t\tif gotErr != wantErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.want[i].VerificationError(), got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"verificationError\", \"AnalysisInfo\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Postgres.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc dockerLogLine(hash string, needle string) chan struct{} {\n\tch := make(chan struct{}, 1)\n\tgo func() {\n\t\tfor {\n\t\t\tout, err := exec.Command(\"docker\", \"logs\", hash).CombinedOutput()\n\t\t\tif err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tif strings.Contains(string(out), needle) {\n\t\t\t\tch <- struct{}{}\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t}\n\t}()\n\treturn ch\n}\n\nfunc startPostgres() error {\n\tcmd := exec.Command(\n\t\t\"docker\", \"run\", \"--rm\", \"-p\", postgresPort+\":\"+defaultPort,\n\t\t\"-e\", \"POSTGRES_PASSWORD=\"+postgresPass,\n\t\t\"-e\", \"POSTGRES_USER=\"+postgresUser,\n\t\t\"-d\", \"postgres\",\n\t)\n\tfmt.Println(cmd.String())\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn err\n\t}\n\tpostgresDockerHash = string(bytes.TrimSpace(out))\n\tselect {\n\tcase <-dockerLogLine(postgresDockerHash, \"PostgreSQL init process complete; ready for start up.\"):\n\t\treturn nil\n\tcase <-time.After(30 * time.Second):\n\t\tstopPostgres()\n\t\treturn errors.New(\"timeout waiting for postgres database to be ready\")\n\t}\n}\n\nfunc stopPostgres() {\n\texec.Command(\"docker\", \"kill\", postgresDockerHash).Run()\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/postgres/postgres_test.go",
    "content": "package postgres\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidUriPattern           = \"postgres://sN19x:d7N8bs@1.2.3.4:5432\"\n\tinvalidUriPattern         = \"?ostgres://sN19x:d7N8bs@1.2.3.4:5432\"\n\tvalidConnStrPartPattern   = \"gVmMTdkwLwmZljcIOXhEmuZ='.jD#=-;|9tD!r^6('\"\n\tinvalidConnStrPartPattern = \"gVmMTdkwLwmZljcIOXhEmu?='.jD#=-;|9tD!r^6('\"\n\tkeyword                   = \"postgres\"\n)\n\nfunc TestPostgres_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword postgres\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validUriPattern, keyword, validConnStrPartPattern),\n\t\t\twant:  []string{validUriPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidUriPattern, keyword, invalidConnStrPartPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPostgres_FromDataWithIgnorePattern(t *testing.T) {\n\ts := New(\n\t\tWithIgnorePattern([]string{\n\t\t\t`1\\.2\\.3\\.4`,\n\t\t}))\n\tgot, err := s.FromData(context.Background(), false, []byte(validUriPattern))\n\tif err != nil {\n\t\tt.Errorf(\"FromData() error = %v\", err)\n\t\treturn\n\t}\n\tif len(got) != 0 {\n\t\tt.Errorf(\"expected no results, but got %d\", len(got))\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/posthog/posthog.go",
    "content": "package posthog\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(phx_[a-zA-Z0-9_]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"phx_\"}\n}\n\n// FromData will find and optionally verify AppPosthog secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_PosthogApp,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.posthog.com/api/event/?personal_api_key=\"+resMatch, nil)\n\t\t\treqEU, errEU := http.NewRequestWithContext(ctx, \"GET\", \"https://eu.posthog.com/api/event/?personal_api_key=\"+resMatch, nil)\n\n\t\t\tif err != nil || errEU != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treqEU.Header.Add(\"Content-Type\", \"application/json\")\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\"key\": resMatch,\n\t\t\t\t\t}\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// Try EU Endpoint only if other one fails.\n\t\t\t\t\tres, err := client.Do(reqEU)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\t\t\"key\": resMatch,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PosthogApp\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PostHog is an open-source product analytics platform. The phx_ keys are used to authenticate and track events in PostHog.\"\n}\n"
  },
  {
    "path": "pkg/detectors/posthog/posthog_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage posthog\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestAppPosthog_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"APPPOSTHOG_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"APPPOSTHOG_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a appposthog secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PosthogApp,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a appposthog secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PosthogApp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"AppPosthog.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"AppPosthog.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/posthog/posthog_test.go",
    "content": "package posthog\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"phx_C1rP9fnAtEFJvb0IYCFdeQhar2WdwUFBYHJym1F_Zqr\"\n\tinvalidPattern = \"phx_C1rP9fnAtEFJvb0IYCF?eQhar2WdwUFBYHJym1F_Zqr\"\n\tkeyword        = \"posthog\"\n)\n\nfunc TestAppPosthog_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword posthog\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/postman/postman.go",
    "content": "package postman\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nconst verifyURL = \"https://api.getpostman.com/collections\"\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(PMAK-[a-zA-Z-0-9]{59})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"PMAK-\"}\n}\n\n// FromData will find and optionally verify Postman secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Postman,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.getClient()\n\t\t\tisVerified, verificationErr := verifyPostman(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\"key\": resMatch,\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\nfunc verifyPostman(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, verifyURL, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"x-api-key\", token)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Postman\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Postman is a collaboration platform for API development. Postman API keys can be used to access and modify collections, environments, and other resources.\"\n}\n"
  },
  {
    "path": "pkg/detectors/postman/postman_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage postman\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPostman_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"POSTMAN_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"POSTMAN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a postman secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postman,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a postman secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postman,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a postman secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postman,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a postman secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postman,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Postman.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Errorf(\"Postman.FromData() error = %v, wantErr %v\", got[i].VerificationError(), tt.wantVerificationErr)\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"AnalysisInfo\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Postman.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/postman/postman_test.go",
    "content": "package postman\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"PMAK-aMUVdak2ohpOeeJQVlb1G9EWB09YVQfbvH6YH8HpPRkXHPBg5qYvp9YGOhI\"\n\tinvalidPattern = \"PMAK-aMUVdak2ohpOeeJQVlb1G9EWB09?VQfbvH6YH8HpPRkXHPBg5qYvp9YGOhI\"\n\tkeyword        = \"postman\"\n)\n\nfunc TestPostman_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword postman\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/postmark/postmark.go",
    "content": "package postmark\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"postmark\"}) + `\\b([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"postmark\"}\n}\n\n// FromData will find and optionally verify Postmark secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Postmark,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tvalid, extraData, err := verifyKey(ctx, client, resMatch)\n\t\t\ts1.Verified = valid\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(err)\n\n\t\t\tif valid {\n\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\"key\": resMatch,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\n// verifyKey verifies a Postmark key by making requests to the Postmark API.\n// It tries both server and account API key verification.\nfunc verifyKey(ctx context.Context, client *http.Client, key string) (bool, map[string]string, error) {\n\terrs := make([]error, 0, 2)\n\n\t// Try verifying as server API key first\n\tvalid, err := verifyServerAPIKey(ctx, client, key)\n\tif valid {\n\t\t// If valid as server API key, return immediately\n\t\treturn true, map[string]string{\"type\": \"server\"}, nil\n\t}\n\tif err != nil {\n\t\terrs = append(errs, err)\n\t}\n\n\t// Try verifying as account API key next\n\tvalid, err = verifyAccountAPIKey(ctx, client, key)\n\tif valid {\n\t\t// If valid as account API key, return immediately\n\t\treturn true, map[string]string{\"type\": \"account\"}, nil\n\t}\n\tif err != nil {\n\t\terrs = append(errs, err)\n\t}\n\tif len(errs) > 0 {\n\t\t// If there were errors during verification, return them\n\t\treturn false, nil, errors.Join(errs...)\n\t}\n\treturn false, nil, nil\n}\n\n// verifyServerAPIKey verifies a Postmark server API key by making a request to the Postmark API.\nfunc verifyServerAPIKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treturn verifyKeyWithOptions(\n\t\tctx,\n\t\tclient,\n\t\tkey,\n\t\t\"/deliverystats\",\n\t\t\"X-Postmark-Server-Token\",\n\t)\n}\n\n// verifyAccountAPIKey verifies a Postmark account API key by making a request to the Postmark API.\nfunc verifyAccountAPIKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treturn verifyKeyWithOptions(\n\t\tctx,\n\t\tclient,\n\t\tkey,\n\t\t\"/domains?count=10&offset=0\",\n\t\t\"X-Postmark-Account-Token\",\n\t)\n}\n\n// verifyKeyWithOptions is a generic function to verify a Postmark key with given endpoint and header.\nfunc verifyKeyWithOptions(ctx context.Context, client *http.Client, key, endpoint, authHeader string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.postmarkapp.com\"+endpoint, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Accept\", \"application/json\")\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(authHeader, key)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Postmark\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Postmark is an email delivery service. Postmark server tokens can be used to access and manage email delivery and statistics.\"\n}\n"
  },
  {
    "path": "pkg/detectors/postmark/postmark_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage postmark\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPostmark_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecretServerApi := testSecrets.MustGetField(\"POSTMARK_TOKEN\")\n\tsecretAccountApi := testSecrets.MustGetField(\"POSTMARK_ACCOUNT_API_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"POSTMARK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found server api key, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a postmark secret %s within\", secretServerApi)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postmark,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData:    map[string]string{\"type\": \"server\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found account api key, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a postmark secret %s within\", secretAccountApi)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postmark,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData:    map[string]string{\"type\": \"account\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a postmark secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Postmark,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Postmark.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Postmark.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/postmark/postmark_test.go",
    "content": "package postmark\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"uigi9zkt-7q3u-v1v9-5x2s-91p78wlmsuxv\"\n\tinvalidPattern = \"uigi9zkt?7q3u-v1v9-5x2s-91p78wlmsuxv\"\n\tkeyword        = \"postmark\"\n)\n\nfunc TestPostmark_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword postmark\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/powrbot/powrbot.go",
    "content": "package powrbot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"powrbot\"}) + `\\b([a-z0-9A-Z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"powrbot\"}\n}\n\n// FromData will find and optionally verify Powrbot secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Powrbot,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://powrbot.com/api/v1/search/single/?company=Apple\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"secret-key %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Powrbot\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Powrbot is a service that provides company information. Powrbot API keys can be used to access and retrieve data from their database.\"\n}\n"
  },
  {
    "path": "pkg/detectors/powrbot/powrbot_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage powrbot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPowrbot_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"POWRBOT_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"POWRBOT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a powrbot secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Powrbot,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a powrbot secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Powrbot,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Powrbot.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Powrbot.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/powrbot/powrbot_test.go",
    "content": "package powrbot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"BuvR1k59n67R96aInATbIUbchmLcpLVN10oyqKuB\"\n\tinvalidPattern = \"BuvR1k59n67R96a?nATbIUbchmLcpLVN10oyqKuB\"\n\tkeyword        = \"powrbot\"\n)\n\nfunc TestPowrbot_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword powrbot\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/prefect/prefect.go",
    "content": "package prefect\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(pnu_[a-zA-Z0-9]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pnu_\"}\n}\n\n// FromData will find and optionally verify Prefect secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Prefect,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.prefect.cloud/auth/login\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Prefect\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Prefect is a workflow orchestration tool. Prefect API keys can be used to access and manage workflows and tasks.\"\n}\n"
  },
  {
    "path": "pkg/detectors/prefect/prefect_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage prefect\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPrefect_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PREFECT\")\n\tinactiveSecret := testSecrets.MustGetField(\"PREFECT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a prefect secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Prefect,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a prefect secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Prefect,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Prefect.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Prefect.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/prefect/prefect_test.go",
    "content": "package prefect\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"pnu_v7mBGSR1R1WT0He5KS2w83RZgInZO8Qwalvr\"\n\tinvalidPattern = \"pnu_v7mBGSR1R1WT0He5?S2w83RZgInZO8Qwalvr\"\n\tkeyword        = \"prefect\"\n)\n\nfunc TestPrefect_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword prefect\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/privacy/privacy.go",
    "content": "package privacy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"privacy\"}) + `\\b([a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"privacy\"}\n}\n\n// FromData will find and optionally verify Privacy secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Privacy,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.privacy.com/v1/card?page=1&page_size=50\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Set(\"Authorization\", \"api-key \"+resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Privacy\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Privacy provides virtual cards for secure online payments. Privacy API keys can be used to manage and create these virtual cards.\"\n}\n"
  },
  {
    "path": "pkg/detectors/privacy/privacy_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage privacy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPrivacy_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PRIVACY\")\n\tinactiveSecret := testSecrets.MustGetField(\"PRIVACY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a privacy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Privacy,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a privacy secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Privacy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a privacy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Privacy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a privacy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Privacy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Privacy.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Privacy.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/privacy/privacy_test.go",
    "content": "package privacy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"sccqe60k-nsak-h1m1-vzq8-vr7u4gxihiwl\"\n\tinvalidPattern = \"sccqe60k?nsak-h1m1-vzq8-vr7u4gxihiwl\"\n\tkeyword        = \"privacy\"\n)\n\nfunc TestPrivacy_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword privacy\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/privatekey/cracker.go",
    "content": "package privatekey\n\nimport (\n\t\"bytes\"\n\t\"crypto/x509\"\n\t_ \"embed\"\n\t\"errors\"\n\n\t\"golang.org/x/crypto/ssh\"\n)\n\n//go:embed \"list.txt\"\nvar rawCrackList []byte\nvar passphrases [][]byte\n\nfunc init() {\n\tpassphrases = bytes.Split(rawCrackList, []byte(\"\\n\"))\n}\n\nvar (\n\tErrUncrackable = errors.New(\"unable to crack encryption\")\n)\n\nfunc Crack(in []byte) (any, string, error) {\n\tfor _, passphrase := range passphrases {\n\t\tparsed, err := ssh.ParseRawPrivateKeyWithPassphrase(in, passphrase)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, x509.IncorrectPasswordError) {\n\t\t\t\tcontinue\n\t\t\t} else {\n\t\t\t\treturn nil, \"\", err\n\t\t\t}\n\t\t}\n\t\treturn parsed, string(passphrase), nil\n\t}\n\treturn nil, \"\", ErrUncrackable\n}\n"
  },
  {
    "path": "pkg/detectors/privatekey/cracker_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage privatekey\n\nimport (\n\t_ \"embed\"\n\t\"testing\"\n\n\t\"golang.org/x/crypto/ssh\"\n)\n\nvar (\n\ttestEncryptedKeyCorrectPassword   = []byte(\"123456\")\n\ttestEncryptedKeyIncorrectPassword = []byte(\"incorrect\")\n\ttestEncryptedKey                  = []byte(Normalize(`-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAjNIZuun\nxgLkM8KuzfmQuRAAAAEAAAAAEAAAGXAAAAB3NzaC1yc2EAAAADAQABAAABgQDe3Al0EMPz\nutVNk5DixaYrGMK56RqUoqGBinke6SWVWmqom1lBcJWzor6HlnMRPPr7YCEsJKL4IpuVwu\ninRa5kdtNTyM7yyQTSR2xXCS0fUItNuq8pUktsH8VUggpMeew8hJv7rFA7tnIg3UXCl6iF\nOLZKbDA5aa24idpcD8b1I9/RzTOB1fu0of5xd9vgODzGw5JvHQSJ0FaA42aNBMGwrDhDB3\nsgnRNdWf6NNIh8KpXXMKJADf3klsyn6He8L2bPMp8a4wwys2YB35p5zQ0JURovsdewlOxH\nNT7eP19eVf4dCreibxUmRUaob5DEoHEk8WrxjKWIYUuLeD6AfcW6oXyRU2Yy8Vrt6SqFl5\nWAi47VMFTkDZYS/eCvG53q9UBHpCj7Qvb0vSkCZXBvBIhlw193F3PX4WvO1IXsMwvQ1D1X\nlmomsItbqM0cJyKw6LU18QWiBHvE7BqcphaoL5E08W2ATTSRIMCp6rt4rptM7KyGK8rc6W\nUYrCnWt6KlCA8AAAWQXk+lVx6bH5itIKKYmQr6cR/5xtZ2GHAxnYtvlW3xnGhU0MHv+lJ2\nuoWlT2RXE5pdMUQj7rNWAMqkwifSKZs9wBfYeo1TaFDmC3nW7yHSN3XTuO78mPIW5JyvmE\nRj5qjsUn7fNmzECoAxnVERhwnF3KqUBEPzIAc6/7v/na9NTiiGaJPco9lvCoPWbVLN08WG\nSuyU+0x5zc3ebzuPcYqu5/c5nmiGxhALrIhjIS0OV1mtAAFhvdMjMIHOijOzSKVCC7rRk5\nkG9EMLNvOn/DUVSRHamw5gs2V3V+Zq2g5nYWfgq8aDSTB8XlIzOj1cz3HwfN6pfSNQ/3Qe\nwOQfWfTWdO+JSL8aoBN5Wg8tDbgmvmbFrINsJfFfSm0wZgcHhC7Ul4U3v4c8PoNdK9HXwi\nTKKzJ9nxLYb+vDh50cnkseu2gt0KwVpjIorxEqeK755mKPao3JmOMr6uFTQsb+g+ZNgPwl\nnRHA4Igx+zADFj3twldnKIiRpBQ5J4acur3uQ+saanBTXgul1TiFiUGT2cnz+IiCsdPovg\nTAMt868W5LmzpfH4Cy54JtaRC4/UuMnkTGbWgutVDnWj2stOAzsQ1YmhH5igUmc94mUL+W\n8vQDCKpeI8n+quDS9zxTvy4L4H5Iz7OZlh0h6N13BDvCYXKcNF/ugkfxZbu8mZsZQQzXNR\nwOrEtKoHc4AnXYNzsuHEoEyLyJxGfFRDSTLbyN9wFOS/c0k9Gjte+kQRZjBVGORE5sN6X3\nakUnTF76RhbEc+LamrwM1h5340bwosRbR8I+UrsQdFfJBEj1ZSyMRJlMkFUNi6blt7bhyx\nea+Pm2A614nlYUBjw2KKzzn8N/0H2NpJjIptvDsbrx3BS/rKwOeJwavRrGnIlEzuAag4vx\nZb2TPVta45uz7fQP5IBl83b0BJKI5Zv/fniUeLI78W/UsZqb64YQbfRyBzFtI1T/SsCi0B\ne0EyKMzbxtSceT1Mb8eJiVIq04Xpwez9fIUt5rSedZD8KPq8P6s0cGsR7Qmw6eXZ/dBR/a\ns5vPhfIUmQawmnwAVuWNRdQQ79jUBSn5M+ZRVVTgEG+vFyvxr/bZqOo1JCoq5BmQhLWGRJ\nDk9TolbeFIVFrkuXkcu99a079ux7XSkON64oPzHrcsEzjPA1GPqs9CGBSO16wq/nI3zg+E\nkcOCaurc9yHJJPwduem0+8WLX3WoGNfQRKurtQze2ppy8KarEtDhDd96sKkhYaqOg3GOX8\nYx827L4vuWSJSIqKuO2kH6kOCMUNO16piv0z/8u3CJxOGh9+4FZIop81fiFTKLhV3/gwLm\nfzFY++KIZrLfZcUjzd80NNEja69F452Eb9HrI5BurN/PznDEi9bzM598Y7beyl4/kd4R2e\nS7SW9/LOrGw5UgxtiU+kV8nPz1PdgxO4sRlnntSBEwkQBzMkLOpq2h2BuJ2TlMP/TWuwLQ\nsDkv1Yk1pD0roGmtMzbujnURGxqRJ8gUmuIot4hpfyRSssvnRQQZ3lQCQCwHiE+HJxXWf5\nc58zOMjW7o21tI8e13uUnbRoQVJM9XYqk1usPXIkYPYL9uOw3AW/Zn+cnDrsXvTK9ZxgGD\n/90b1BNwVqMlUK+QggHNwl5qD8eoXK5cDvav66te+E+V7FYFQ06w3tytRVz8SjoaiChN02\nmuIjvl6G7Hoj1hObM2t/ZheN1EShS11z868hhS6Mx7GvIdtkXuvdiBYMiBLOshJQxB8Mzx\niug9W+Di3upLf0UMC1TqADGphsIHRU7RbmHQ8Rwp7dogswmDfpRSapPt9p0D+6Ad5VBzi3\nf3BPXj76UBLMEJCrZR1P28vnAA7AyNHaLvMPlWDMG5v3V/UV+ugyFcoBAOyjiQgYST8F3e\nHx7UPVlTK8dyvk1Z+Yw0nrfNClI=\n-----END OPENSSH PRIVATE KEY-----`))\n)\n\nfunc Test_crack(t *testing.T) {\n\ttests := []struct {\n\t\tname             string\n\t\tin               []byte\n\t\twantedPassphrase string\n\t\twantErr          bool\n\t}{\n\t\t{\n\t\t\tname:             \"crackable\",\n\t\t\twantErr:          false,\n\t\t\tin:               testEncryptedKey,\n\t\t\twantedPassphrase: string(testEncryptedKeyCorrectPassword),\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t_, passphrase, err := Crack(tt.in)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"crack() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif passphrase != string(tt.wantedPassphrase) {\n\t\t\t\tt.Errorf(\"crack() passphrase = %v, want %v\", passphrase, string(testEncryptedKeyCorrectPassword))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkParseWrongPassword(b *testing.B) {\n\tfor n := 0; n < b.N; n++ {\n\t\tssh.ParseRawPrivateKeyWithPassphrase(testEncryptedKey, testEncryptedKeyIncorrectPassword)\n\t}\n}\n\nfunc BenchmarkParseRightPassword(b *testing.B) {\n\tfor n := 0; n < b.N; n++ {\n\t\tssh.ParseRawPrivateKeyWithPassphrase(testEncryptedKey, testEncryptedKeyCorrectPassword)\n\t}\n}\n\nfunc BenchmarkCracker(b *testing.B) {\n\tfor n := 0; n < b.N; n++ {\n\t\tCrack(testEncryptedKey)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/privatekey/fingerprint.go",
    "content": "package privatekey\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/ed25519\"\n\t\"crypto/rsa\"\n\t\"crypto/sha1\"\n\t\"crypto/sha256\"\n\t\"crypto/x509\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"golang.org/x/crypto/ssh\"\n)\n\nvar (\n\tErrNotSupported = errors.New(\"key type not supported\")\n\tErrEncryptedKey = errors.New(\"key is encrypted\")\n)\n\nfunc FingerprintPEMKey(parsedKey any) (string, error) {\n\tvar pubKey any\n\tswitch privateKey := parsedKey.(type) {\n\tcase *rsa.PrivateKey:\n\t\tpubKey = &privateKey.PublicKey\n\tcase *ecdsa.PrivateKey:\n\t\tpubKey = &privateKey.PublicKey\n\tcase *ed25519.PrivateKey:\n\t\tpubKey = privateKey.Public()\n\t// No fingerprinting support for DSA\n\t// case *dsa.PrivateKey:\n\t// \tpubKey = privateKey.PublicKey\n\tdefault:\n\t\treturn \"\", ErrNotSupported\n\t}\n\n\treturn fingerprintPublicKey(pubKey)\n}\n\nfunc fingerprintPublicKey(pubKey any) (string, error) {\n\tpublickeyBytes, err := x509.MarshalPKIXPublicKey(pubKey)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tpublicKeyFingerprint := sha1.Sum(publickeyBytes)\n\tpublicKeyFingerprintInHex := hex.EncodeToString(publicKeyFingerprint[:])\n\treturn publicKeyFingerprintInHex, nil\n}\n\nfunc fingerprintSSHPublicKey(pubKey ssh.PublicKey) string {\n\tpublicKeyFingerprint := sha256.Sum256(pubKey.Marshal())\n\treturn fmt.Sprintf(\"SHA256:%s\", base64.RawStdEncoding.EncodeToString(publicKeyFingerprint[:]))\n}\n"
  },
  {
    "path": "pkg/detectors/privatekey/list.txt",
    "content": "123456\n12345\n123456789\npassword\niloveyou\nprincess\n1234567\n12345678\nabc123\nnicole\ndaniel\nbabygirl\nmonkey\nlovely\njessica\n654321\nmichael\nashley\nqwerty\n111111\niloveu\n000000\nmichelle\ntigger\nsunshine\nchocolate\npassword1\nsoccer\nanthony\nfriends\nbutterfly\npurple\nangel\njordan\nliverpool\njustin\nloveme\nfuckyou\n123123\nfootball\nsecret\nandrea\ncarlos\njennifer\njoshua\nbubbles\n1234567890\nsuperman\nhannah\namanda\nloveyou\npretty\nbasketball\nandrew\nangels\ntweety\nflower\nplayboy\nhello\nelizabeth\nhottie\ntinkerbell\ncharlie\nsamantha\nbarbie\nchelsea\nlovers\nteamo\njasmine\nbrandon\n666666\nshadow\nmelissa\neminem\nmatthew\nrobert\ndanielle\nforever\nfamily\njonathan\n987654321\ncomputer\nwhatever\ndragon\nvanessa\ncookie\nnaruto\nsummer\nsweety\nspongebob\njoseph\njunior\nsoftball\ntaylor\nyellow\ndaniela\nlauren\nmickey\nprincesa\nalexandra\nalexis\njesus\nestrella\nmiguel\nwilliam\nthomas\nbeautiful\nmylove\nangela\npoohbear\npatrick\niloveme\nsakura\nadrian\nalexander\ndestiny\nchristian\n121212\nsayang\namerica\ndancer\nmonica\nrichard\n112233\nprincess1\n555555\ndiamond\ncarolina\nsteven\nrangers\nlouise\norange\n789456\n999999\nshorty\n11111\nnathan\nsnoopy\ngabriel\nhunter\ncherry\nkiller\nsandra\nalejandro\nbuster\ngeorge\nbrittany\nalejandra\npatricia\nrachel\ntequiero\n7777777\ncheese\n159753\narsenal\ndolphin\nantonio\nheather\ndavid\nginger\nstephanie\npeanut\nblink182\nsweetie\n222222\nbeauty\n987654\nvictoria\nhoney\n00000\nfernando\npokemon\nmaggie\ncorazon\nchicken\npepper\ncristina\nrainbow\nkisses\nmanuel\nmyspace\nrebelde\nangel1\nricardo\nbabygurl\nheaven\n55555\nbaseball\nmartin\ngreenday\nnovember\nalyssa\nmadison\nmother\n123321\n123abc\nmahalkita\nbatman\nseptember\ndecember\nmorgan\nmariposa\nmaria\ngabriela\niloveyou2\nbailey\njeremy\npamela\nkimberly\ngemini\nshannon\npictures\nasshole\nsophie\njessie\nhellokitty\nclaudia\nbabygirl1\nangelica\naustin\nmahalko\nvictor\nhorses\ntiffany\nmariana\neduardo\nandres\ncourtney\nbooboo\nkissme\nharley\nronaldo\niloveyou1\nprecious\noctober\ninuyasha\npeaches\nveronica\nchris\n888888\nadriana\ncutie\njames\nbanana\nprince\nfriend\njesus1\ncrystal\nceltic"
  },
  {
    "path": "pkg/detectors/privatekey/normalize.go",
    "content": "package privatekey\n\nimport (\n\t\"strings\"\n)\n\nfunc Normalize(in string) string {\n\tin = strings.ReplaceAll(in, `\"`, \"\")\n\tin = strings.ReplaceAll(in, `'`, \"\")\n\tin = strings.ReplaceAll(in, \"\\t\", \"\")\n\tin = strings.ReplaceAll(in, `\\t`, \"\")\n\tin = strings.ReplaceAll(in, `\\\\t`, \"\")\n\tin = strings.ReplaceAll(in, `\\n`, \"\\n\")\n\tin = strings.ReplaceAll(in, `\\\\r\\\\n`, \"\\n\")\n\tin = strings.ReplaceAll(in, `\\r\\n`, \"\\n\")\n\tin = strings.ReplaceAll(in, \"\\r\\n\", \"\\n\")\n\tin = strings.ReplaceAll(in, `\\\\r`, \"\\n\")\n\tin = strings.ReplaceAll(in, \"\\r\", \"\\n\")\n\tin = strings.ReplaceAll(in, `\\r`, \"\\n\")\n\tin = strings.ReplaceAll(in, `\\\\n`, \"\\n\")\n\tin = strings.ReplaceAll(in, `\\n\\n`, \"\\n\")\n\tin = strings.ReplaceAll(in, \"\\n\\n\", \"\\n\")\n\tin = strings.ReplaceAll(in, `\\\\`, \"\\n\")\n\n\tcleaned := strings.Builder{}\n\tparts := strings.Split(in, \"\\n\")\n\tfor _, line := range parts {\n\t\tcleaned.WriteString(strings.TrimSpace(line) + \"\\n\")\n\t}\n\treturn cleaned.String()\n}\n"
  },
  {
    "path": "pkg/detectors/privatekey/privatekey.go",
    "content": "package privatekey\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"golang.org/x/crypto/ssh\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nvar (\n\tfalsePositiveGHUsernames = map[detectors.FalsePositive]struct{}{\n\t\t// This hack is because it's probably one of the most widely distributed github keys\n\t\t// and a frequent annoyance.\n\t\t// It is active at the time of this commit, but the developer is unresponsive.\n\t\tdetectors.FalsePositive(\"aaron1234567890123\"): {},\n\t}\n)\n\ntype Scanner struct {\n\tIncludeExpired bool\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)\nvar _ detectors.MaxSecretSizeProvider = (*Scanner)(nil)\n\nvar (\n\t// TODO: add base64 encoded key support\n\tclient = common.RetryableHTTPClient()\n\tkeyPat = regexp.MustCompile(`(?i)-----\\s*?BEGIN[ A-Z0-9_-]*?PRIVATE KEY\\s*?-----[\\s\\S]*?----\\s*?END[ A-Z0-9_-]*? PRIVATE KEY\\s*?-----`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"private key\"}\n}\n\nconst maxPrivateKeySize = 4096\n\n// ProvideMaxSecretSize returns the maximum size of a secret that this detector can find.\nfunc (s Scanner) MaxSecretSize() int64 { return maxPrivateKeySize }\n\n// FromData will find and optionally verify Privatekey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllString(dataStr, -1)\n\tfor _, match := range matches {\n\t\ttoken := Normalize(match)\n\t\tif len(token) < 64 {\n\t\t\tcontinue\n\t\t}\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_PrivateKey,\n\t\t\tRaw:          []byte(token),\n\t\t\tRedacted:     token[0:64],\n\t\t\tExtraData:    make(map[string]string),\n\t\t}\n\n\t\t// set not normalized match as primary secret value so it is used to calculate line of code\n\t\ts1.SetPrimarySecretValue(match)\n\n\t\tvar passphrase string\n\t\tparsedKey, err := ssh.ParseRawPrivateKey([]byte(token))\n\t\tif err != nil && strings.Contains(err.Error(), \"private key is passphrase protected\") {\n\t\t\ts1.ExtraData[\"encrypted\"] = \"true\"\n\t\t\tparsedKey, passphrase, err = Crack([]byte(token))\n\t\t\tif err != nil {\n\t\t\t\ts1.SetVerificationError(err, token)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif passphrase != \"\" {\n\t\t\t\ts1.ExtraData[\"cracked_encryption_passphrase\"] = \"true\"\n\t\t\t}\n\t\t} else if err != nil {\n\t\t\t// couldn't parse key, probably invalid\n\t\t\tcontinue\n\t\t}\n\n\t\tfingerprint, err := FingerprintPEMKey(parsedKey)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif verify {\n\t\t\tvar (\n\t\t\t\twg                 sync.WaitGroup\n\t\t\t\textraData          = newExtraData()\n\t\t\t\tverificationErrors = NewVerificationErrors(3)\n\t\t\t)\n\n\t\t\t// Look up certificate information.\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tdata, err := lookupFingerprintCertificateUrls(ctx, fingerprint, s.IncludeExpired)\n\t\t\t\tif err == nil {\n\t\t\t\t\tif data != nil {\n\t\t\t\t\t\textraData.Add(\"certificate_urls\", strings.Join(data.CertificateURLs, \", \"))\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tverificationErrors.Add(err)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t// Test SSH key against github.com\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tusername, err := VerifyGitHubUser(ctx, parsedKey)\n\t\t\t\tif err != nil && !errors.Is(err, errPermissionDenied) {\n\t\t\t\t\tverificationErrors.Add(err)\n\t\t\t\t}\n\t\t\t\tif username != nil {\n\t\t\t\t\tisFalsePositive, _ := detectors.IsKnownFalsePositive(*username, falsePositiveGHUsernames, false)\n\t\t\t\t\tif !isFalsePositive {\n\t\t\t\t\t\textraData.Add(\"github_user\", *username)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t// Test SSH key against gitlab.com\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tuser, err := VerifyGitLabUser(ctx, parsedKey)\n\t\t\t\tif err != nil && !errors.Is(err, errPermissionDenied) {\n\t\t\t\t\tverificationErrors.Add(err)\n\t\t\t\t}\n\t\t\t\tif user != nil {\n\t\t\t\t\textraData.Add(\"gitlab_user\", *user)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\twg.Wait()\n\t\t\tif len(extraData.data) > 0 {\n\t\t\t\ts1.Verified = true\n\t\t\t\tfor k, v := range extraData.data {\n\t\t\t\t\ts1.ExtraData[k] = v\n\t\t\t\t}\n\n\t\t\t\t// enabled th\n\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\"token\": token,\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.ExtraData = nil\n\t\t\t}\n\t\t\tif len(verificationErrors.Errors) > 0 {\n\t\t\t\ts1.SetVerificationError(fmt.Errorf(\"verification failures: %s\", strings.Join(verificationErrors.Errors, \", \")), token)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {\n\treturn false, \"\"\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Private keys are used for securely connecting and authenticating to various systems and services. Exposure of private keys can lead to unauthorized access and data breaches.\"\n}\n\ntype result struct {\n\tCertificateURLs []string\n\tGitHubUsername  string\n}\n\nfunc lookupFingerprintCertificateUrls(\n\tctx context.Context,\n\tpublicKeyFingerprintInHex string,\n\tincludeExpired bool,\n) (*result, error) {\n\tresults, err := LookupFingerprint(\n\t\tctx,\n\t\tpublicKeyFingerprintInHex,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar data *result\n\n\tseen := map[string]struct{}{}\n\tfor _, r := range results.CertificateResults {\n\t\tif _, ok := seen[r.CertificateFingerprint]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tif !includeExpired && time.Since(r.ExpirationTimestamp) > 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif data == nil {\n\t\t\tdata = &result{}\n\t\t}\n\t\tdata.CertificateURLs = append(data.CertificateURLs, fmt.Sprintf(\"https://crt.sh/?q=%s\", r.CertificateFingerprint))\n\t\tseen[r.CertificateFingerprint] = struct{}{}\n\t}\n\n\treturn data, nil\n}\n\nfunc LookupFingerprint(ctx context.Context, publicKeyFingerprintInHex string) (*DriftwoodResult, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://keychecker.trufflesecurity.com/fingerprint/%s\", publicKeyFingerprintInHex), nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer res.Body.Close()\n\n\tresults := DriftwoodResult{}\n\terr = json.NewDecoder(res.Body).Decode(&results)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &results, nil\n}\n\ntype DriftwoodResult struct {\n\tCertificateResults []struct {\n\t\tDomains                []string  `json:\",omitempty\"`\n\t\tCertificateFingerprint string    `json:\"CertificateFingerprint\"`\n\t\tExpirationTimestamp    time.Time `json:\"ExpirationTimestamp\"`\n\t\tIssuerName             string    `json:\",omitempty\"` // CA information\n\t\tSubjectName            string    `json:\",omitempty\"` // Certificate subject\n\t\tIssuerOrganization     []string  `json:\",omitempty\"` // CA organization(s)\n\t\tSubjectOrganization    []string  `json:\",omitempty\"` // Subject organization(s)\n\t\tKeyUsages              []string  `json:\",omitempty\"` // e.g., [\"DigitalSignature\", \"KeyEncipherment\"]\n\t\tExtendedKeyUsages      []string  `json:\",omitempty\"` // e.g., [\"ServerAuth\", \"ClientAuth\"]\n\t\tSubjectKeyID           string    `json:\",omitempty\"` // hex encoded\n\t\tAuthorityKeyID         string    `json:\",omitempty\"` // hex encoded\n\t\tSerialNumber           string    `json:\",omitempty\"` // hex encoded\n\t} `json:\"CertificateResults\"`\n\tGitHubSSHResults []struct {\n\t\tUsername string `json:\"Username\"`\n\t} `json:\"GitHubSSHResults\"`\n}\n\ntype extraData struct {\n\tmutex sync.Mutex\n\tdata  map[string]string\n}\n\nfunc newExtraData() *extraData {\n\treturn &extraData{\n\t\tdata: make(map[string]string),\n\t}\n}\n\nfunc (e *extraData) Add(key string, value string) {\n\te.mutex.Lock()\n\te.data[key] = value\n\te.mutex.Unlock()\n}\n\ntype VerificationErrors struct {\n\tmutex  sync.Mutex\n\tErrors []string\n}\n\nfunc NewVerificationErrors(capacity int) *VerificationErrors {\n\treturn &VerificationErrors{\n\t\tErrors: make([]string, 0, capacity),\n\t}\n}\n\nfunc (e *VerificationErrors) Add(err error) {\n\te.mutex.Lock()\n\te.Errors = append(e.Errors, err.Error())\n\te.mutex.Unlock()\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PrivateKey\n}\n"
  },
  {
    "path": "pkg/detectors/privatekey/privatekey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage privatekey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPrivatekey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecretTLS := testSecrets.MustGetField(\"PRIVATEKEY_TLS\")\n\tsecretGitHub := testSecrets.MustGetField(\"PRIVATEKEY_GITHUB\")\n\tsecretGitHubEncrypted := testSecrets.MustGetField(\"PRIVATEKEY_GITHUB_ENCRYPTED\")\n\tsecretInactive := testSecrets.MustGetField(\"PRIVATEKEY_UNVERIFIED\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find privatekey secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PrivateKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"-----BEGIN PRIVATE KEY-----\\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYw\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PrivateKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"-----BEGIN PRIVATE KEY-----\\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgw\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found TLS private key, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(secretTLS),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PrivateKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     \"-----BEGIN PRIVATE KEY-----\\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgw\",\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"certificate_urls\": \"https://crt.sh/?q=1e20c40deb44a8539dd3ac3e8c53b72750cb19f9, https://crt.sh/?q=0e9de31fb2ee16465a4d5d93b227d54f870326d1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found GitHub SSH private key, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(secretGitHub),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PrivateKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     \"-----BEGIN OPENSSH PRIVATE KEY-----\\nb3BlbnNzaC1rZXktdjEAAAAABG5v\",\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"github_user\": \"sirdetectsalot\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found encrypted GitHub SSH private key, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(secretGitHubEncrypted),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PrivateKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     \"-----BEGIN OPENSSH PRIVATE KEY-----\\nb3BlbnNzaC1rZXktdjEAAAAACmFl\",\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"github_user\":                   \"sirdetectsalot\",\n\t\t\t\t\t\t\"encrypted\":                     \"true\",\n\t\t\t\t\t\t\"cracked_encryption_passphrase\": \"true\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{IncludeExpired: true}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PrivatekeyCI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif os.Getenv(\"FORCE_PASS_DIFF\") == \"true\" {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"PrivatekeyCI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_lookupFingerprint(t *testing.T) {\n\ttests := []struct {\n\t\tname                      string\n\t\tpublicKeyFingerprintInHex string\n\t\twantFingerprints          bool\n\t\twantErr                   bool\n\t\tincludeExpired            bool\n\t}{\n\t\t{\n\t\t\tname:                      \"got some\",\n\t\t\tpublicKeyFingerprintInHex: \"4c5da06caa1c81df9c8e1abe43bac385de1bda76\",\n\t\t\twantFingerprints:          true,\n\t\t\twantErr:                   false,\n\t\t\tincludeExpired:            true,\n\t\t},\n\t\t{\n\t\t\tname:                      \"got some\",\n\t\t\tpublicKeyFingerprintInHex: \"none\",\n\t\t\twantFingerprints:          false,\n\t\t\twantErr:                   false,\n\t\t\tincludeExpired:            true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgotFingerprints, err := lookupFingerprintCertificateUrls(context.TODO(), tt.publicKeyFingerprintInHex, tt.includeExpired)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"lookupFingerprint() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(gotFingerprints != nil && len(gotFingerprints.CertificateURLs) > 0, tt.wantFingerprints) {\n\t\t\t\tt.Errorf(\"lookupFingerprint() = %v, want %v\", gotFingerprints, tt.wantFingerprints)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/privatekey/privatekey_test.go",
    "content": "package privatekey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `-----BEGIN RSA PRIVATE KEY-----\nMIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8Qu\nKUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEm\no3qGy0t6z09AIJtH+5OeRV1be+N4cDYJKffGzDa88vQENZiRm0GRq6a+HPGQMd2k\nTQIhAKMSvzIBnni7ot/OSie2TmJLY4SwTQAevXysE2RbFDYdAiEBCUEaRQnMnbp7\n9mxDXDf6AU0cN/RPBjb9qSHDcWZHGzUCIG2Es59z8ugGrDY+pxLQnwfotadxd+Uy\nv/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs\n/5OiPgoTdSy7bcF9IGpSE8ZgGKzgYQVZeN97YE00\n-----END RSA PRIVATE KEY-----\n`\n\tinvalidPattern = `-----BEGIN?RSA?PRIVATE?KEY-----\nMIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8Qu\nKUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEm\no3qGy0t6z09AIJtH+5OeRV1be+N4cDYJKffGzDa88vQENZiRm0GRq6a+HPGQMd2k\nTQIhAKMSvzIBnni7ot/OSie2TmJLY4SwTQAevXysE2RbFDYdAiEBCUEaRQnMnbp7\n9mxDXDf6AU0cN/RPBjb9qSHDcWZHGzUCIG2Es59z8ugGrDY+pxLQnwfotadxd+Uy\nv/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs\n/5OiPgoTdSy7bcF9IGpSE8ZgGKzgYQVZeN97YE00\n-----END RSA PRIVATE KEY-----`\n\tkeyword = \"privatekey\"\n)\n\nfunc TestPrivatekey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword privatekey\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/privatekey/ssh_integration.go",
    "content": "package privatekey\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\n\t\"golang.org/x/crypto/ssh\"\n)\n\n// https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints\nvar githubFingerprints = map[string]string{\n\t\"SHA256:uNiVztksCsDhcc0u9e8BujQXVUpKZIDTMczCvj3tD2s\": \"RSA\",\n\t\"SHA256:br9IjFspm1vxR3iA35FWE+4VTyz1hYVLIE2t1/CeyWQ\": \"DSA - deprecated\",\n\t\"SHA256:p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM\": \"ECDSA\",\n\t\"SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU\": \"ED25519\",\n}\n\n// https://docs.gitlab.com/ee/user/gitlab_com/index.html#ssh-host-keys-fingerprints\nvar gitlabFingerprints = map[string]string{\n\t\"SHA256:HbW3g8zUjNSksFbqTiUWPWg2Bq1x8xdGUrliXFzSnUw\": \"ECDSA\",\n\t\"SHA256:eUXGGm1YGsMAS7vkcx6JOJdOGHPem5gQp4taiCfCLB8\": \"ED25519\",\n\t\"SHA256:ROQFvPThGrW4RuWLoL9tq9I9zJ42fK4XywyRtbOz/EQ\": \"RSA\",\n}\n\nfunc firstResponseFromSSH(ctx context.Context, parsedKey any, username, hostport string) (string, error) {\n\tsigner, err := ssh.NewSignerFromKey(parsedKey)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// Verify the server fingerprint to ensure that there is no MITM replay attack\n\tconfig := &ssh.ClientConfig{\n\t\tUser: username,\n\t\tAuth: []ssh.AuthMethod{\n\t\t\tssh.PublicKeys(signer),\n\t\t},\n\t\tHostKeyCallback: func(hostname string, _ net.Addr, key ssh.PublicKey) error {\n\t\t\tswitch hostname {\n\t\t\tcase \"github.com:22\":\n\t\t\t\tfingerprint := fingerprintSSHPublicKey(key)\n\t\t\t\tif _, ok := githubFingerprints[fingerprint]; !ok {\n\t\t\t\t\treturn fmt.Errorf(\"unknown host fingerprint for github.com, got %s\", fingerprint)\n\t\t\t\t}\n\t\t\tcase \"gitlab.com:22\":\n\t\t\t\tfingerprint := fingerprintSSHPublicKey(key)\n\t\t\t\tif _, ok := gitlabFingerprints[fingerprint]; !ok {\n\t\t\t\t\treturn fmt.Errorf(\"unknown host fingerprint for gitlab.com, got %s\", fingerprint)\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\treturn errors.New(\"unknown host in fingerprint db\")\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tclient, err := sshDialWithContext(ctx, \"tcp\", hostport, config)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"unable to authenticate\") {\n\t\t\treturn \"\", errPermissionDenied\n\t\t}\n\t\treturn \"\", err\n\t}\n\tdefer client.Close()\n\n\tsession, err := client.NewSession()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer session.Close()\n\n\tvar output bytes.Buffer\n\tsession.Stderr = &output\n\n\terr = session.Shell()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\t_ = session.Wait()\n\n\treturn output.String(), err\n}\n\nfunc sshDialWithContext(ctx context.Context, network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {\n\td := net.Dialer{}\n\tconn, err := d.DialContext(ctx, network, addr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error dialing %s: %w\", addr, err)\n\t}\n\n\tncc, chans, reqs, err := ssh.NewClientConn(conn, addr, config)\n\tif err != nil {\n\t\tconn.Close()\n\t\treturn nil, fmt.Errorf(\"error creating SSH connection to %s: %w\", addr, err)\n\t}\n\n\tclient := ssh.NewClient(ncc, chans, reqs)\n\treturn client, nil\n}\n\nvar errPermissionDenied = errors.New(\"permission denied\")\n\nfunc VerifyGitHubUser(ctx context.Context, parsedKey any) (*string, error) {\n\toutput, err := firstResponseFromSSH(ctx, parsedKey, \"git\", \"github.com:22\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif strings.Contains(output, \"Permission denied\") {\n\t\treturn nil, errPermissionDenied\n\t}\n\n\tif strings.Contains(output, \"successfully authenticated\") {\n\t\tusername := strings.TrimSuffix(strings.Split(output, \" \")[1], \"!\")\n\t\treturn &username, nil\n\t}\n\n\treturn nil, nil\n}\n\nfunc VerifyGitLabUser(ctx context.Context, parsedKey any) (*string, error) {\n\toutput, err := firstResponseFromSSH(ctx, parsedKey, \"git\", \"gitlab.com:22\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif strings.Contains(output, \"Permission denied\") {\n\t\treturn nil, errPermissionDenied\n\t}\n\n\tif strings.Contains(output, \"Welcome to GitLab\") {\n\t\tsplit := strings.Split(output, \" \")\n\t\tusername := strings.TrimPrefix(strings.TrimSuffix(split[len(split)-1], \"!\"), \"@\")\n\t\treturn &username, nil\n\t}\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "pkg/detectors/privatekey/ssh_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage privatekey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"golang.org/x/crypto/ssh\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\nfunc TestFirstResponseFromSSH(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecretGitHub := testSecrets.MustGetField(\"PRIVATEKEY_GITHUB\")\n\n\tparsedKey, err := ssh.ParseRawPrivateKey([]byte(Normalize(secretGitHub)))\n\tif err != nil {\n\t\tt.Fatalf(\"could not parse test secret: %s\", err)\n\t}\n\n\toutput, err := firstResponseFromSSH(ctx, parsedKey, \"git\", \"github.com:22\")\n\tif err != nil {\n\t\tt.Fail()\n\t}\n\n\tif len(output) == 0 {\n\t\tt.Fail()\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/prodpad/prodpad.go",
    "content": "package prodpad\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"prodpad\"}) + `\\b([a-f0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"prodpad\"}\n}\n\n// FromData will find and optionally verify Prodpad secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Prodpad,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.prodpad.com/v1/tags\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Prodpad\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Prodpad is a product management tool that helps teams plan and manage product development. Prodpad API keys can be used to access and modify product data and configurations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/prodpad/prodpad_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage prodpad\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestProdpad_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PRODPAD\")\n\tinactiveSecret := testSecrets.MustGetField(\"PRODPAD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a prodpad secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Prodpad,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a prodpad secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Prodpad,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Prodpad.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Prodpad.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/prodpad/prodpad_test.go",
    "content": "package prodpad\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"96d3a2234e30b05b6c0ec74df1943b95ec708d09b0c45779874be404ce004335\"\n\tinvalidPattern = \"96d3a2234?30b05b6c0ec74df1943b95ec708d09b0c45779874be404ce004335\"\n\tkeyword        = \"prodpad\"\n)\n\nfunc TestProdpad_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword prodpad\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/prospectcrm/prospectcrm.go",
    "content": "package prospectcrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"prospect\"}) + `\\b([a-z0-9-]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"prospectcrm\"}\n}\n\n// FromData will find and optionally verify ProspectCRM secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ProspectCRM,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api-batch-v1.prospect365.com/Contacts\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ProspectCRM\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ProspectCRM is a customer relationship management system. The API keys can be used to access and manage customer data within ProspectCRM.\"\n}\n"
  },
  {
    "path": "pkg/detectors/prospectcrm/prospectcrm_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage prospectcrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestProspectCRM_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PROSPECTCRM\")\n\tinactiveSecret := testSecrets.MustGetField(\"PROSPECTCRM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a prospectcrm secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ProspectCRM,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a prospectcrm secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ProspectCRM,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ProspectCRM.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ProspectCRM.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/prospectcrm/prospectcrm_test.go",
    "content": "package prospectcrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"yawgnnmzs6hqcpdakzpshc79enh55iz0\"\n\tinvalidPattern = \"yawgnnmzs6hqcpda?zpshc79enh55iz0\"\n\tkeyword        = \"prospectcrm\"\n)\n\nfunc TestProspectCRM_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword prospectcrm\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/protocolsio/protocolsio.go",
    "content": "package protocolsio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"protocols\"}) + `\\b([a-z0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"protocols\"}\n}\n\n// FromData will find and optionally verify ProtocolsIO secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ProtocolsIO,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.protocols.io/api/v3/session/profile\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ProtocolsIO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Protocols.io is a platform for developing and sharing reproducible research protocols. The API keys can be used to access and manage protocols and data on the platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/protocolsio/protocolsio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage protocolsio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestProtocolsIO_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PROTOCOLSIO\")\n\tinactiveSecret := testSecrets.MustGetField(\"PROTOCOLSIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a protocolsio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ProtocolsIO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a protocolsio secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ProtocolsIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ProtocolsIO.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ProtocolsIO.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/protocolsio/protocolsio_test.go",
    "content": "package protocolsio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"4aphfkag41i9kow1la6gm7sj9zsj5i8xznwlg5j9eq0e1xl3dzr44b19llppaiut\"\n\tinvalidPattern = \"4aphfkag41i9kow1la6gm7sj9?sj5i8xznwlg5j9eq0e1xl3dzr44b19llppaiut\"\n\tkeyword        = \"protocolsio\"\n)\n\nfunc TestProtocolsIO_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword protocolsio\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/proxycrawl/proxycrawl.go",
    "content": "package proxycrawl\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"proxycrawl\"}) + `\\b([a-zA-Z0-9_]{22})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"proxycrawl\"}\n}\n\n// FromData will find and optionally verify ProxyCrawl secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ProxyCrawl,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := 10 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.proxycrawl.com/leads?token=%s&domain=slack.com\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ProxyCrawl\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ProxyCrawl is a service that provides web scraping and crawling capabilities. ProxyCrawl tokens can be used to access and control these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/proxycrawl/proxycrawl_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage proxycrawl\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestProxyCrawl_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PROXYCRAWL\")\n\tinactiveSecret := testSecrets.MustGetField(\"PROXYCRAWL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a proxycrawl secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ProxyCrawl,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a proxycrawl secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ProxyCrawl,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ProxyCrawl.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ProxyCrawl.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/proxycrawl/proxycrawl_test.go",
    "content": "package proxycrawl\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"wjXKCFzOWEPRqhTmmbWtVQ\"\n\tinvalidPattern = \"wjXKCFzOWEP?qhTmmbWtVQ\"\n\tkeyword        = \"proxycrawl\"\n)\n\nfunc TestProxyCrawl_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword proxycrawl\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pubnubpublishkey/pubnubpublishkey.go",
    "content": "package pubnubpublishkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tpubPat = regexp.MustCompile(`\\b(pub-c-[0-9a-z]{8}-[0-9a-z]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})\\b`)\n\tsubPat = regexp.MustCompile(`\\b(sub-c-[0-9a-z]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sub-c-\"}\n}\n\n// FromData will find and optionally verify PubNubPublishKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := pubPat.FindAllStringSubmatch(dataStr, -1)\n\tsubMatches := subPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, subMatch := range subMatches {\n\t\t\tressubMatch := strings.TrimSpace(subMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_PubNubPublishKey,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + \"/\" + ressubMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.getClient()\n\n\t\t\t\tisVerified, verificationErr := verifyPubNub(ctx, client, resMatch, ressubMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\nfunc verifyPubNub(ctx context.Context, client *http.Client, resMatch, ressubMatch string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://ps.pndsn.com/signal/\"+resMatch+\"/\"+ressubMatch+\"/0/ch1/0/%22typing_on%22?uuid=user-123\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer res.Body.Close()\n\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\treturn true, nil\n\t} else if !(res.StatusCode == 400 || res.StatusCode == 403) {\n\t\t// 403 is suggested by the API docs (https://www.pubnub.com/docs/sdks/rest-api/send-signal-to-channel)\n\t\t// 400 is what actually seems to be coming back for invalid credentials\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n\n\treturn false, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PubNubPublishKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PubNub is a real-time communication platform that provides APIs for sending and receiving messages. Publish keys are used to send messages to channels, while subscribe keys are used to receive messages from channels.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pubnubpublishkey/pubnubpublishkey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pubnubpublishkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPubNubPublishKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tpub := testSecrets.MustGetField(\"PUBNUBPUBLISHKEY_TOKEN\")\n\tinactivePub := testSecrets.MustGetField(\"PUBNUBPUBLISHKEY_INACTIVE\")\n\tsub := testSecrets.MustGetField(\"PUBNUBSUBSCRIPTIONKEY_TOKEN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pubnub secret %s within pubnub %s\", pub, sub)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PubNubPublishKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pubnub secret %s within pubnub %s but not valid\", inactivePub, sub)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PubNubPublishKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified but for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pubnub secret %s within pubnub %s\", pub, sub)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PubNubPublishKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, valid but unexpected api response\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pubnub secret %s within pubnub %s\", pub, sub)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PubNubPublishKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PubNubPublishKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\" wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\topts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, opts); diff != \"\" {\n\t\t\t\tt.Errorf(\"PubNubPublishKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pubnubpublishkey/pubnubpublishkey_test.go",
    "content": "package pubnubpublishkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPub   = \"pub-c-3886bd4t-dwor-vuqm-ond2-4wz4supiwwa2\"\n\tinvalidPub = \"p?b-c-3886bd4t-dwor-vuqm-ond2-4wz4supiwwa2\"\n\tvalidSub   = \"sub-c-adky624g-bux6-lz1j-8yrx-8fvodaul9hu5\"\n\tinvalidSub = \"sub-c-adky624g-bux6-l?1j-8yrx-8fvodaul9hu5\"\n\tkeyword    = \"pubnubpublishkey\"\n)\n\nfunc TestPubNubPublishKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword pubnubpublishkey\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validPub, keyword, validSub),\n\t\t\twant:  []string{validPub + \"/\" + validSub},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidPub, keyword, invalidSub),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pubnubsubscriptionkey/pubnubsubscriptionkey.go",
    "content": "package pubnubsubscriptionkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(sub-c-[0-9a-z]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sub-c-\"}\n}\n\n// FromData will find and optionally verify PubNubSubscriptionKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_PubNubSubscriptionKey,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyKey(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PubNubSubscriptionKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PubNub is a real-time communication platform. A PubNub Subscription Key allows access to the PubNub API for subscribing to channels and receiving messages.\"\n}\n\nfunc verifyKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://ps.pndsn.com/v2/objects/\"+key+\"/uuids\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusForbidden:\n\t\tbodyBytes, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tif strings.Contains(string(bodyBytes), \"Objects not enabled for this subscriber key.\") {\n\t\t\treturn true, nil\n\t\t}\n\n\t\treturn false, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pubnubsubscriptionkey/pubnubsubscriptionkey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pubnubsubscriptionkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPubNubSubscriptionKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PUBNUBSUBSCRIPTIONKEY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"PUBNUBSUBSCRIPTIONKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pubnubsubscriptionkey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PubNubSubscriptionKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pubnubsubscriptionkey secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PubNubSubscriptionKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PubNubSubscriptionKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"PubNubSubscriptionKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pubnubsubscriptionkey/pubnubsubscriptionkey_test.go",
    "content": "package pubnubsubscriptionkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"sub-c-sy4te40h-w1oc-zpyq-zlrw-w6ehv0y0y78c\"\n\tinvalidPattern = \"sub-c-sy4te40h-w1oc-z?yq-zlrw-w6ehv0y0y78c\"\n\tkeyword        = \"pubnubsubscriptionkey\"\n)\n\nfunc TestPubNubSubscriptionKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword pubnubsubscriptionkey\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pulumi/pulumi.go",
    "content": "package pulumi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(pul-[a-z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pul-\"}\n}\n\n// FromData will find and optionally verify Pulumi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Pulumi,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.pulumi.com/api/user/stacks\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.pulumi+8\")\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"token %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Pulumi\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Pulumi is an Infrastructure as Code platform. Pulumi API keys can be used to manage cloud infrastructure and services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pulumi/pulumi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pulumi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPulumi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PULUMI\")\n\tinactiveSecret := testSecrets.MustGetField(\"PULUMI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pulumi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pulumi,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pulumi secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Pulumi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Pulumi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Pulumi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pulumi/pulumi_test.go",
    "content": "package pulumi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"pul-xm8r0t347qgoqhrp4c5pq0jkkabeue0x7u35l2ac\"\n\tinvalidPattern = \"pul-xm8r0t347qgoqhrp4c?pq0jkkabeue0x7u35l2ac\"\n\tkeyword        = \"pulumi\"\n)\n\nfunc TestPulumi_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword pulumi\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/purestake/purestake.go",
    "content": "package purestake\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"purestake\"}) + `\\b([a-zA-Z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"purestake\"}\n}\n\n// FromData will find and optionally verify PureStake secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_PureStake,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://testnet-algorand.api.purestake.io/idx2/health\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"x-api-key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PureStake\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PureStake is a blockchain infrastructure provider. PureStake API keys can be used to interact with the Algorand blockchain network.\"\n}\n"
  },
  {
    "path": "pkg/detectors/purestake/purestake_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage purestake\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPureStake_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PURESTAKE\")\n\tinactiveSecret := testSecrets.MustGetField(\"PURESTAKE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a purestake secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PureStake,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a purestake secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PureStake,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PureStake.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"PureStake.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/purestake/purestake_test.go",
    "content": "package purestake\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"uR05tbMNjbrrHCVDGinuNn54QqYiSiSZn5BN3AGW\"\n\tinvalidPattern = \"uR05tbM?jbrrHCVDGinuNn54QqYiSiSZn5BN3AGW\"\n\tkeyword        = \"purestake\"\n)\n\nfunc TestPureStake_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword purestake\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pushbulletapikey/pushbulletapikey.go",
    "content": "package pushbulletapikey\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pushbullet\"}) + `\\b([A-Za-z0-9_\\.]{34})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pushbullet\"}\n}\n\n// FromData will find and optionally verify PushBulletApiKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_PushBulletApiKey,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.pushbullet.com/v2/users/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Access-Token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PushBulletApiKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Pushbullet is a service that allows you to send notifications between your devices. Pushbullet API keys can be used to interact with the Pushbullet API to send and receive notifications.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pushbulletapikey/pushbulletapikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pushbulletapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPushBulletApiKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PUSHBULLETAPIKEY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"PUSHBULLETAPIKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pushbulletapikey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PushBulletApiKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pushbulletapikey secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PushBulletApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PushBulletApiKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"PushBulletApiKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pushbulletapikey/pushbulletapikey_test.go",
    "content": "package pushbulletapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"S2QPKsMCE3hpR1ac2HYl5EvzdvbrugJCpP\"\n\tinvalidPattern = \"S2QPKsMCE3hpR1ac2?Yl5EvzdvbrugJCpP\"\n\tkeyword        = \"pushbulletapikey\"\n)\n\nfunc TestPushBulletApiKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword pushbulletapikey\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pusherchannelkey/pusherchannelkey.go",
    "content": "package pusherchannelkey\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/md5\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tappIdPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pusher\"}) + `\\b([0-9]{7})\\b`)\n\tkeyPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"key\"}) + `\\b([a-z0-9]{20})\\b`)\n\t// this is currently incorrect, should be a callback from the API\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pusher\"}) + `\\b([a-z0-9]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pusher\"}\n}\n\nconst (\n\tauth_version = \"1.0\"\n)\n\n// FromData will find and optionally verify PusherChannelKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueKeyMatches, uniqueAppMatches, uniqueSecretMatches := make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, keyMatch := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeyMatches[keyMatch[1]] = struct{}{}\n\t}\n\n\tfor _, appMatch := range appIdPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueAppMatches[appMatch[1]] = struct{}{}\n\t}\n\n\tfor _, secretMatch := range secretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueSecretMatches[secretMatch[1]] = struct{}{}\n\t}\n\n\tfor app := range uniqueAppMatches {\n\t\tfor key := range uniqueKeyMatches {\n\t\t\tfor secret := range uniqueSecretMatches {\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PusherChannelKey,\n\t\t\t\t\tRaw:          []byte(app),\n\t\t\t\t\tRawV2:        []byte(app + key),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tisVerified, verificationErr := verifyPusherChannelKey(ctx, client, app, key, secret)\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\nfunc hmacBytes(toSign, secret []byte) []byte {\n\t_authSignature := hmac.New(sha256.New, secret)\n\t_authSignature.Write(toSign)\n\treturn _authSignature.Sum(nil)\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PusherChannelKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Pusher is a service for adding real-time functionality to web and mobile apps. Pusher Channel keys can be used to authenticate and send messages to channels.\"\n}\n\nfunc verifyPusherChannelKey(ctx context.Context, client *http.Client, app, key, secret string) (bool, error) {\n\tmethod := \"POST\"\n\tpath := \"/apps/\" + app + \"/events\"\n\n\tstringPayload := `{\"channels\":[\"my-channel\"],\"data\":\"{\\\"message\\\":\\\"hello world\\\"}\",\"name\":\"my_event\"}`\n\tpayload := strings.NewReader(stringPayload)\n\t_bodyMD5 := md5.New()\n\t_bodyMD5.Write([]byte(stringPayload))\n\thash := hex.EncodeToString(_bodyMD5.Sum(nil))\n\n\ttimestamp := strconv.FormatInt(time.Now().Unix(), 10)\n\tparams := url.Values{\n\t\t\"auth_key\":       {key},\n\t\t\"auth_timestamp\": {timestamp},\n\t\t\"auth_version\":   {auth_version},\n\t\t\"body_md5\":       {hash},\n\t}\n\n\tusecd, _ := url.QueryUnescape(params.Encode())\n\n\tstringToSign := strings.Join([]string{method, path, usecd}, \"\\n\")\n\tsignature := hex.EncodeToString(hmacBytes([]byte(stringToSign), []byte(secret)))\n\n\tmd5Str := \"https://api-ap1.pusher.com/apps/\" + app + \"/events?auth_key=\" + key + \"&auth_signature=\" + signature + \"&auth_timestamp=\" + timestamp + \"&auth_version=1.0&body_md5=\" + hash\n\n\treq, err := http.NewRequestWithContext(ctx, method, md5Str, payload)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pusherchannelkey/pusherchannelkey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pusherchannelkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPusherChannelKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tappId := testSecrets.MustGetField(\"PUSHER_APPID\")\n\tkey := testSecrets.MustGetField(\"PUSHER_KEY\")\n\tsecret := testSecrets.MustGetField(\"PUSHER_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"PUSHER_SECRET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pusherSecret secret %s within pusherId %s within pusherKey %s\", secret, appId, key)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PusherChannelKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRawV2:        []byte(appId + key),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PusherChannelKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(appId + key),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pusherSecret secret %s within pusherId %s but not pusherKey %s valid\", inactiveSecret, appId, key)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PusherChannelKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(appId + key),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PusherChannelKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(appId + key),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"PusherChannelKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"PusherChannelKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pusherchannelkey/pusherchannelkey_test.go",
    "content": "package pusherchannelkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidAppId    = \"4870172\"\n\tinvalidAppId  = \"487?172\"\n\tvalidKey      = \"8mcc284l3i4sph5a3mgf\"\n\tinvalidKey    = \"8mcc28?l3i4sph5a3mgf\"\n\tvalidSecret   = \"pg1podgq3qabvh4yd1np\"\n\tinvalidSecret = \"pg1podgq3q?bvh4yd1np\"\n\tvalidInput    = `pusher - '%s'\n                            key - '%s'\n\t\t\t\t\t\t    pusher - '%s'`\n)\n\nfunc TestPusherChannelKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: fmt.Sprintf(validInput, validAppId, validKey, validSecret),\n\t\t\twant:  []string{validAppId + validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(validInput, invalidAppId, invalidKey, invalidSecret),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pypi/pypi.go",
    "content": "package pypi\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(\"(pypi-AgEIcHlwaS5vcmcCJ[a-zA-Z0-9-_]{150,157})\")\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"pypi-AgEIcHlwaS5vcmcCJ\"}\n}\n\n// FromData will find and optionally verify Pypi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_PyPI,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\t// Create a buffer to hold the multipart form data\n\tvar body bytes.Buffer\n\twriter := multipart.NewWriter(&body)\n\n\t// Add the form fields like in the curl request\n\t_ = writer.WriteField(\":action\", \"file_upload\")\n\t_ = writer.WriteField(\"name\", \"dummy-package\")\n\t_ = writer.WriteField(\"version\", \"0.0.1\")\n\t_ = writer.WriteField(\"content\", \"dummy-content\")\n\n\t// Close the writer to finalize the form\n\twriter.Close()\n\n\t// Create a new POST request to the PyPI legacy upload URL\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://upload.pypi.org/legacy/\", &body)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\t// Add the Authorization header with the PyPI API token\n\treq.Header.Add(\"Authorization\", \"token \"+token)\n\t// Set the Content-Type to the multipart form boundary\n\treq.Header.Set(\"Content-Type\", writer.FormDataContentType())\n\n\t// Execute the HTTP request\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\t// Check for expected status codes for verification\n\tif res.StatusCode == http.StatusBadRequest {\n\t\tverified, err := common.ResponseContainsSubstring(res.Body, \"Include at least one message digest.\")\n\t\tif err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\t\tif verified {\n\t\t\treturn true, nil, nil\n\t\t}\n\t} else if res.StatusCode == http.StatusForbidden {\n\t\t// If we get a 403 status, the key is invalid\n\t\treturn false, nil, nil\n\t}\n\n\t// For all other status codes, return an error\n\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_PyPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"PyPI is a repository of software for the Python programming language. The credential allows for managing Pypi packages.\"\n}\n"
  },
  {
    "path": "pkg/detectors/pypi/pypi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage pypi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestPypi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"PYPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"PYPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pypi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PyPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pypi secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PyPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pypi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PyPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a pypi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_PyPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Pypi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Pypi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/pypi/pypi_test.go",
    "content": "package pypi\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestPypi_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: \"pypi_token = 'pypi-AgEIcHlwaS5vcmcCJDQyM2M0Yjg4LWUyNDnnnnhhMy1hNigyLWI2ZWUyMTMwYzI2MgACKlszLCJhOWQwMWE0MS01Nzk4LTQyOWYtOTk4MS1lYzE5NTJhM2E3YzgiXQAABiBeGtDnnnnnV32VpiyeU-YUDKplSv0E5ngmwsnHaV2jGg'\",\n\t\t\twant:  []string{\"pypi-AgEIcHlwaS5vcmcCJDQyM2M0Yjg4LWUyNDnnnnhhMy1hNigyLWI2ZWUyMTMwYzI2MgACKlszLCJhOWQwMWE0MS01Nzk4LTQyOWYtOTk4MS1lYzE5NTJhM2E3YzgiXQAABiBeGtDnnnnnV32VpiyeU-YUDKplSv0E5ngmwsnHaV2jGg\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/qase/qase.go",
    "content": "package qase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"qase\"}) + `\\b([0-9a-zA-Z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"qase\"}\n}\n\n// FromData will find and optionally verify Qase secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Qase,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyQase(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Qase\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Qase is a test management platform that allows users to manage test cases, plans, and runs. Qase API tokens can be used to access and modify test data and settings.\"\n}\n\nfunc verifyQase(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.qase.io/v1/user\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Token\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/qase/qase_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage qase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestQase_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"QASE\")\n\tinactiveSecret := testSecrets.MustGetField(\"QASE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a qase secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Qase,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a qase secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Qase,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Qase.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Qase.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/qase/qase_test.go",
    "content": "package qase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"X8FDrZHoBELPU4UpeeSIMyQPEi7JyQFsF6oQZi6X\"\n\tinvalidPattern = \"X8FDrZHoBELPU4UpeeSI?yQPEi7JyQFsF6oQZi6X\"\n\tkeyword        = \"qase\"\n)\n\nfunc TestQase_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword qase\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/qualaroo/qualaroo.go",
    "content": "package qualaroo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"qualaroo\"}) + `\\b([a-z0-9A-Z=]{64})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"qualaroo\"}\n}\n\n// FromData will find and optionally verify Qualaroo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Qualaroo,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyQualaroo(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Qualaroo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Qualaroo is a customer survey tool that helps in collecting feedback from users. Qualaroo API keys can be used to access and manage survey data.\"\n}\n\nfunc verifyQualaroo(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.qualaroo.com/api/v1/nudges\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", key))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/qualaroo/qualaroo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage qualaroo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestQualaroo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"QUALAROO_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"QUALAROO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a qualaroo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Qualaroo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a qualaroo secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Qualaroo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Qualaroo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Qualaroo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/qualaroo/qualaroo_test.go",
    "content": "package qualaroo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"w82SrLORVOsr4VMrPhDG4fih0rYGw86vxGBZch99TVvTfiSBSlpWF6f42BVtf0jI\"\n\tinvalidPattern = \"w82SrLORVOsr4VMrPhDG4fih0rYGw86v?GBZch99TVvTfiSBSlpWF6f42BVtf0jI\"\n\tkeyword        = \"qualaroo\"\n)\n\nfunc TestQualaroo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword qualaroo\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/qubole/qubole.go",
    "content": "package qubole\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"qubole\"}) + `\\b([0-9a-z]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"qubole\"}\n}\n\n// FromData will find and optionally verify Qubole secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Qubole,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyQubole(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Qubole\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Qubole is a cloud-based data platform that provides data processing, management, and analytics services. Qubole API keys can be used to access and manage data workflows and resources.\"\n}\n\nfunc verifyQubole(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://us.qubole.com/api/v1.2/account\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Accept\", \"application/json\")\n\treq.Header.Add(\"X-AUTH-TOKEN\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/qubole/qubole_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage qubole\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestQubole_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"QUBOLE\")\n\tinactiveSecret := testSecrets.MustGetField(\"QUBOLE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a qubole secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Qubole,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a qubole secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Qubole,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Qubole.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Qubole.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/qubole/qubole_test.go",
    "content": "package qubole\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"8p9tm1c0ejc70n8vpef7nq7agfzq7kks73lwxpo29z25ggxszg22ecl9toewbdob\"\n\tinvalidPattern = \"8p9tm1c0ejc?0n8vpef7nq7agfzq7kks73lwxpo29z25ggxszg22ecl9toewbdob\"\n\tkeyword        = \"qubole\"\n)\n\nfunc TestQubole_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword qubole\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rabbitmq/rabbitmq.go",
    "content": "package rabbitmq\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\tamqp \"github.com/rabbitmq/amqp091-go\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tkeyPat = regexp.MustCompile(`\\b(?:amqps?):\\/\\/[\\S]{3,50}:([\\S]{3,50})@[-.%\\w\\/:]+\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"amqp\"}\n}\n\n// FromData will find and optionally verify RabbitMQ secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueMatches = make(map[string]string)\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[matches[0]] = matches[1]\n\t}\n\n\tfor urlMatch, password := range uniqueMatches {\n\t\t// Skip common test hosts.\n\t\tif strings.Contains(urlMatch, \"127.0.0.1\") ||\n\t\t\tstrings.Contains(urlMatch, \"localhost\") ||\n\t\t\tstrings.Contains(urlMatch, \"contoso.com\") ||\n\t\t\tstrings.Contains(urlMatch, \"example.com\") {\n\t\t\tcontinue\n\t\t}\n\t\t// Skip findings where the password only has \"*\" characters, this is a redacted password\n\t\tif strings.Trim(password, \"*\") == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tparsedURL, err := url.Parse(urlMatch)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := parsedURL.User.Password(); !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tr := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_RabbitMQ,\n\t\t\tRaw:          []byte(urlMatch),\n\t\t\tRedacted:     strings.TrimSpace(strings.Replace(parsedURL.String(), password, \"********\", -1)),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := s.verify(urlMatch)\n\t\t\tr.Verified = isVerified\n\t\t\tif verificationErr != nil {\n\t\t\t\tr.SetVerificationError(verificationErr, urlMatch)\n\t\t\t}\n\t\t}\n\n\t\tif !r.Verified {\n\t\t\t// Skip unverified findings where the password starts with a `$` - it's almost certainly a variable.\n\t\t\tif strings.HasPrefix(password, \"$\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, r)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) verify(url string) (bool, error) {\n\t// Add a timeout.\n\t// https://github.com/rabbitmq/amqp091-go/blob/dc67c21576c230f589636319f05b7262915313e6/examples_test.go#L22\n\tconn, err := amqp.DialConfig(url, amqp.Config{\n\t\tDial: func(network, addr string) (net.Conn, error) {\n\t\t\treturn net.DialTimeout(network, addr, 10*time.Second)\n\t\t},\n\t})\n\tdefer func() {\n\t\tif conn != nil {\n\t\t\t_ = conn.Close()\n\t\t}\n\t}()\n\tif err == nil {\n\t\treturn true, nil\n\t}\n\t// Check if this is a determinate authentication failure\n\terrStr := strings.ToLower(err.Error())\n\n\tif (strings.Contains(errStr, \"403\") &&\n\t\tstrings.Contains(errStr, \"access_refused\")) ||\n\t\tstrings.Contains(errStr, \"username or password not allowed\") {\n\t\t// make secret as rotated\n\t\treturn false, nil\n\t}\n\treturn false, err\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_RabbitMQ\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"RabbitMQ is an open-source message broker software that originally implemented the Advanced Message Queuing Protocol (AMQP). RabbitMQ credentials can be used to access and manage message queues.\"\n}\n"
  },
  {
    "path": "pkg/detectors/rabbitmq/rabbitmq_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage rabbitmq\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRabbitMQ_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"RABBITMQ\")\n\tinactiveSecret := testSecrets.MustGetField(\"RABBITMQ_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rabbitmq secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RabbitMQ,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     \"amqp://rabbit123:*******************@127.0.0.1\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rabbitmq secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RabbitMQ,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"amqp://rabbituser:*******************@127.0.0.1\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"RabbitMQ.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"RabbitMQ.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rabbitmq/rabbitmq_test.go",
    "content": "package rabbitmq\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"amqps://user123:pass456@rabbitmq-host.pattern.com:5672/vhost789\"\n\tinvalidPattern = \"amqps://user123:pass456@?abbitmq-host.pattern.com:5672/vhost789\"\n\tkeyword        = \"rabbitmq\"\n)\n\nfunc TestRabbitMQ_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword rabbitmq\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/railwayapp/railwayapp.go",
    "content": "package railwayapp\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// graphQLResponse will handle the response from railway API\ntype graphQLResponse struct {\n\tData   interface{}   `json:\"data\"`\n\tErrors []interface{} `json:\"errors\"`\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\tapiToken = regexp.MustCompile(detectors.PrefixRegex([]string{\"railway\"}) +\n\t\t`\\b([a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12})\\b`)\n)\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"railway\"}\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Railway is a deployment platform designed to streamline the software development life-cycle, starting with instant deployments and effortless scale, extending to CI/CD integrations and built-in observability.\"\n}\n\n// FromData will find and optionally verify SaladCloud API Key secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// uniqueMatches will hold unique match values and ensure we only process unique matches found in the data string\n\tvar uniqueMatches = make(map[string]bool)\n\n\tfor _, match := range apiToken.FindAllStringSubmatch(dataStr, -1) {\n\t\tif len(match) != 2 {\n\t\t\tcontinue\n\t\t}\n\n\t\tuniqueMatches[strings.TrimSpace(match[1])] = true\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_RailwayApp,\n\t\t\tRaw:          []byte(match),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/railwayapp/\",\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.getClient()\n\t\t\tisVerified, verificationErr := verifyRailwayApp(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_RailwayApp\n}\n\n/*\nverifyRailwayApp verifies if the passed matched api key for railwayapp is active or not.\ndocs: https://docs.railway.app/reference/public-api\n*/\nfunc verifyRailwayApp(ctx context.Context, client *http.Client, match string) (bool, error) {\n\tjsonPayload, err := getJSONPayload()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://backboard.railway.app/graphql/v2\", bytes.NewBuffer(jsonPayload))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// set the required headers\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+match)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer resp.Body.Close()\n\n\t/*\n\t\tGraphQL queries return response with 200 OK status code even for errors\n\t\tSources:\n\t\thttps://www.apollographql.com/docs/react/data/error-handling/#graphql-errors\n\t\thttps://github.com/rmosolgo/graphql-ruby/issues/1130\n\t\thttps://inigo.io/blog/graphql_error_handling\n\t*/\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn false, fmt.Errorf(\"railway app verification failed with status code %d\", resp.StatusCode)\n\t}\n\n\t// if rate limit is reached, return verification as false with no error\n\tif resp.Header.Get(\"x-ratelimit-remaining\") == \"0\" {\n\t\treturn false, nil\n\t}\n\n\t// read the response body\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// parse the response body into a structured format\n\tvar graphqlResponse graphQLResponse\n\tif err = json.Unmarshal(body, &graphqlResponse); err != nil {\n\t\treturn false, err\n\t}\n\n\t// if any errors exist in response, return verification as false\n\tif len(graphqlResponse.Errors) > 0 {\n\t\treturn false, nil\n\t}\n\n\treturn true, nil\n}\n\n// getJSONPayload return the graphQL query as a JSON\nfunc getJSONPayload() ([]byte, error) {\n\tpayload := map[string]string{\n\t\t\"query\": `query me {me {email}}`,\n\t}\n\n\t// convert the payload to JSON\n\tjsonPayload, err := json.Marshal(payload)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error marshalling JSON: %w\", err)\n\t}\n\n\treturn jsonPayload, nil\n}\n"
  },
  {
    "path": "pkg/detectors/railwayapp/railwayapp_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage railwayapp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRailwayApp_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"RAILWAYAPP\")\n\tinactiveSecret := testSecrets.MustGetField(\"RAILWAYAPP_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a railwayapp secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RailwayApp,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/railwayapp/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a railwayapp secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RailwayApp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/railwayapp/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a railwayapp secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RailwayApp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/railwayapp/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a railwayapp secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RailwayApp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/railwayapp/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"RailwayApp.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"RailwayApp.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/railwayapp/railwayapp_test.go",
    "content": "package railwayapp\n\nimport (\n\t\"context\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"testing\"\n)\n\nfunc TestRailwayApp_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern - with keyword railwayapp\",\n\t\t\tinput: \"railwayapp token = 'a52a85d1-33c4-4808-8fa0-c375f3c6013a'\",\n\t\t\twant:  []string{\"a52a85d1-33c4-4808-8fa0-c375f3c6013a\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"typical pattern - with keyword railway\",\n\t\t\tinput: \"railway = 'a52a85d1-33c4-4808-8fa0-c375f3c6013a'\",\n\t\t\twant:  []string{\"a52a85d1-33c4-4808-8fa0-c375f3c6013a\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"typical pattern - ignore duplicate\",\n\t\t\tinput: \"railwayapp token = 'a52a85d1-33c4-4808-8fa0-c375f3c6013a | a52a85d1-33c4-4808-8fa0-c375f3c6013a'\",\n\t\t\twant:  []string{\"a52a85d1-33c4-4808-8fa0-c375f3c6013a\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: \"railway = 'a52e95g1-33c4-4808-8fa0-b375f3c6013a'\",\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ramp/ramp.go",
    "content": "package ramp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\tkeyPat    = regexp.MustCompile(`\\b(ramp_id_[[:alnum:]]{40})\\b`)\n\tsecretPat = regexp.MustCompile(`\\b(ramp_sec_[[:alnum:]]{48})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ramp_\"}\n}\n\n// FromData will find and optionally verify Ramp secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, secretMatch := range secretMatches {\n\n\t\t\tresSecret := strings.TrimSpace(secretMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Ramp,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + \":\" + resSecret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tbody := url.Values{\n\t\t\t\t\t\"grant_type\": {\"client_credentials\"},\n\t\t\t\t\t\"scope\":      {\"user:read\"},\n\t\t\t\t}\n\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.ramp.com/developer/v1/token\", strings.NewReader(body.Encode()))\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.SetBasicAuth(resMatch, resSecret)\n\t\t\t\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Ramp\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Ramp provides financial services including expense management and corporate cards. Ramp credentials can be used to access and manage these financial services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ramp/ramp_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ramp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRamp_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tid := testSecrets.MustGetField(\"RAMP\")\n\tsecret := testSecrets.MustGetField(\"RAMP_SECRET\")\n\tinactiveId := testSecrets.MustGetField(\"RAMP_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ramp client id %s and a ramp secret %s within\", id, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ramp,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ramp id %s and ramp secret %s within but not valid\", inactiveId, secret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ramp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ramp id %s and ramp secret %s within\", id, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ramp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ramp id %s and secret %s within\", id, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ramp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Ramp.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Ramp.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ramp/ramp_test.go",
    "content": "package ramp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey      = \"ramp_id_tz8O3QzUBiPcAejdGSIvfcvOxbk1wVfnUEIsieV3\"\n\tinvalidKey    = \"ramp_id_tz8O3QzUBiPcAejd?SIvfcvOxbk1wVfnUEIsieV3\"\n\tvalidSecret   = \"ramp_sec_maFm2aZYzbZOVNbEy4RiuvREnhZ2Xe19E45hl4A3en20WJyX\"\n\tinvalidSecret = \"ramp_sec_maFm2aZYzbZOVNbEy4R?uvREnhZ2Xe19E45hl4A3en20WJyX\"\n\tkeyword       = \"ramp\"\n)\n\nfunc TestRamp_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword ramp\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validSecret),\n\t\t\twant:  []string{validKey + \":\" + validSecret},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidSecret),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rapidapi/rapidapi.go",
    "content": "package rapidapi\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"rapidapi\"}) + `\\b([A-Za-z0-9_-]{50})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"rapidapi\"}\n}\n\n// FromData will find and optionally verify RapidApi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_RapidApi,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://covid-19-tracking.p.rapidapi.com/v1\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"x-rapidapi-key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_RapidApi\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"RapidAPI is a platform for connecting to thousands of APIs. RapidAPI keys can be used to access and interact with these APIs.\"\n}\n"
  },
  {
    "path": "pkg/detectors/rapidapi/rapidapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage rapidapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRapidApi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"RAPIDAPI_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"RAPIDAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rapidapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RapidApi,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rapidapi secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RapidApi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"RapidApi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"RapidApi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rapidapi/rapidapi_test.go",
    "content": "package rapidapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"HG_Wv9C_fvjMHOUokbSvUCEw3scqZB1Q4LBgw5u8Nb-Khx8HBi\"\n\tinvalidPattern = \"HG_Wv9C_fvjMHOUokbSvUCEw3?cqZB1Q4LBgw5u8Nb-Khx8HBi\"\n\tkeyword        = \"rapidapi\"\n)\n\nfunc TestRapidApi_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword rapidapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/raven/raven.go",
    "content": "package raven\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"raven\"}) + `\\b([A-Z0-9-]{16})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"raven\"}\n}\n\n// FromData will find and optionally verify Raven secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Raven,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.raventools.com/api?key=%s&method=profile_info&format=json\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\tif json.Valid(bodyBytes) {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Raven\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Raven credentials can be used to access and manage your Raven account and services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/raven/raven_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage raven\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRaven_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"RAVEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"RAVEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a raven secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Raven,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a raven secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Raven,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Raven.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Raven.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/raven/raven_test.go",
    "content": "package raven\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"JJ4ZIC9EJNWVIS86\"\n\tinvalidPattern = \"?J4ZIC9EJNWVIS86\"\n\tkeyword        = \"raven\"\n)\n\nfunc TestRaven_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword raven\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rawg/rawg.go",
    "content": "package rawg\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"rawg\"}) + `\\b([0-9Aa-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"rawg\"}\n}\n\n// FromData will find and optionally verify Rawg secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Rawg,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.rawg.io/api/platforms?key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Rawg\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"RAWG is a video game database and discovery service. RAWG API keys can be used to access game data and related services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/rawg/rawg_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage rawg\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRawg_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"RAWG\")\n\tinactiveSecret := testSecrets.MustGetField(\"RAWG_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rawg secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Rawg,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rawg secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Rawg,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Rawg.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Rawg.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rawg/rawg_test.go",
    "content": "package rawg\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"43zgwcor66n9phsxAxxfi7j81fpaecwy\"\n\tinvalidPattern = \"43zgwcor66n9phsx?xxfi7j81fpaecwy\"\n\tkeyword        = \"rawg\"\n)\n\nfunc TestRawg_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword rawg\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/razorpay/razorpay.go",
    "content": "package razorpay\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\n// The (`) character adds secondary encoding to parsed strings by Golang which also allows for escape sequences\nvar (\n\tclient = common.SaneHttpClient()\n\n\tkeyPat    = regexp.MustCompile(`(?i)\\brzp_live_[A-Za-z0-9]{14}\\b`)\n\tsecretPat = regexp.MustCompile(`\\b[A-Za-z0-9]{24}\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"rzp_live_\"}\n}\n\n// FromData will find and optionally verify RazorPay secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatches := keyPat.FindAllString(dataStr, -1)\n\n\tfor _, key := range keyMatches {\n\t\tsecMatches := secretPat.FindAllString(dataStr, -1)\n\n\t\tfor _, secret := range secMatches {\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_RazorPay,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tRawV2:        []byte(key + secret),\n\t\t\t\tRedacted:     key,\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.razorpay.com/v1/items?count=1\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.SetBasicAuth(key, secret)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif json.Valid(bodyBytes) {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_RazorPay\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"RazorPay is a payment gateway service that allows businesses to accept, process, and disburse payments. RazorPay keys can be used to access and manage payment transactions.\"\n}\n"
  },
  {
    "path": "pkg/detectors/razorpay/razorpay_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage razorpay\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRazorPay_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecretInactive := testSecrets.MustGetField(\"RAZORPAY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"25:RAZORPAY_KEY='rzp_live_SnDTaP1ncfliDt'\\n\\\"rzp_secret\\\" : \\\" %s\\\", \", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RazorPay,\n\t\t\t\t\tRedacted:     \"rzp_live_SnDTaP1ncfliDt\",\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"RazorPay.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif os.Getenv(\"FORCE_PASS_DIFF\") == \"true\" {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"RazorPay.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/razorpay/razorpay_test.go",
    "content": "package razorpay\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey      = \"rzp_live_oE9IEkoNYnmwkG\"\n\tinvalidKey    = \"rzp_live_oE?IEkoNYnmwkG\"\n\tvalidSecret   = \"u5FJFQ0KuTe4hD8iwza076e2\"\n\tinvalidSecret = \"u5FJFQ0KuTe4?D8iwza076e2\"\n\tkeyword       = \"razorpay\"\n)\n\nfunc TestRazorPay_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword razorpay\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validSecret),\n\t\t\twant:  []string{validKey + validSecret},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidSecret),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/reachmail/reachmail.go",
    "content": "package reachmail\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"reachmail\"}) + `\\b([a-zA-Z0-9-_]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"reachmail\"}\n}\n\n// FromData will find and optionally verify Reachmail secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Reachmail,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://services.reachmail.net/administration/users/current\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Reachmail\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Reachmail is an email marketing service. Reachmail API keys can be used to access and manage email marketing campaigns and user data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/reachmail/reachmail_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage reachmail\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestReachmail_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"REACHMAIL\")\n\tinactiveSecret := testSecrets.MustGetField(\"REACHMAIL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a reachmail secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Reachmail,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a reachmail secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Reachmail,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Reachmail.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Reachmail.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/reachmail/reachmail_test.go",
    "content": "package reachmail\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"qFcJR3KhMRWcm1y2O7C1_8pr-VQJgQ8vBvVKmctf8JRr5CFfU6VF9Np2ZMZatF7k\"\n\tinvalidPattern = \"qFcJR3KhMRWcm1y2O7C1_8pr-VQJgQ8v?vVKmctf8JRr5CFfU6VF9Np2ZMZatF7k\"\n\tkeyword        = \"reachmail\"\n)\n\nfunc TestReachmail_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword reachmail\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/readme/readme.go",
    "content": "package readme\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`(rdme_[a-z0-9]{70})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"rdme_\"}\n}\n\n// FromData will find and optionally verify ReadMe secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ReadMe,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://dash.readme.com/api/v1\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.SetBasicAuth(resMatch, \"\")\n\t\t\treq.Header.Add(\"accept\", \"application/json\")\n\t\t\tres, err := http.DefaultClient.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ReadMe\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ReadMe is a documentation platform that helps you build out your API and product documentation. ReadMe API keys can be used to access and modify your documentation data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/readme/readme_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage readme\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestReadMe_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"README\")\n\tinactiveSecret := testSecrets.MustGetField(\"README_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a readme secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ReadMe,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a readme secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ReadMe,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ReadMe.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ReadMe.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/readme/readme_test.go",
    "content": "package readme\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"rdme_xybq9sovqyexf5wtc437abdsgvw6dfvagdstx3leyr4t2cam7xk0ukv14on7ghun67curt\"\n\tinvalidPattern = \"rdme_xybq9sovqyexf5wtc437abds?vw6dfvagdstx3leyr4t2cam7xk0ukv14on7ghun67curt\"\n\tkeyword        = \"readme\"\n)\n\nfunc TestReadMe_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword readme\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/reallysimplesystems/reallysimplesystems.go",
    "content": "package reallysimplesystems\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(ey[a-zA-Z0-9-._]{153}.ey[a-zA-Z0-9-._]{916,1000})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"reallysimplesystems\"}\n}\n\n// FromData will find and optionally verify ReallySimpleSystems secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ReallySimpleSystems,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://apiv4.reallysimplesystems.com/accounts\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\tif json.Valid(bodyBytes) {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ReallySimpleSystems\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ReallySimpleSystems is a CRM system. The detected credentials can be used to access and manage customer relationship data and workflows within the ReallySimpleSystems platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/reallysimplesystems/reallysimplesystems_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage reallysimplesystems\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestReallySimpleSystems_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"REALLYSIMPLESYSTEMS\")\n\tinactiveSecret := testSecrets.MustGetField(\"REALLYSIMPLESYSTEMS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a reallysimplesystems secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ReallySimpleSystems,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a reallysimplesystems secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ReallySimpleSystems,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ReallySimpleSystems.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ReallySimpleSystems.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/reallysimplesystems/reallysimplesystems_test.go",
    "content": "package reallysimplesystems\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"eyt_8-ytglklqvyjGF6dgbXsCSMRLdryUWQCSQ1n1uSph1Ppl.jMeMx0EkxVLcmbKPTi7kPYKzNa04V96eiJlV.XxtKFDKTJCkcThvKR2x0ctVUL.FrbHVXEx55VZZPD0FKW4onNDO05Ab_9cbnrLTr4d--.eyX.hZw.Q5g-pvyWS5jjDUyiE60WZSgQq4uM8PUVw1-OJegMnGGLEKq48G5X7zgQAcYvUIRQy4Ku.gKQkNFIL4Nlj3ob7Bi4VUDs_DjvtS_Jxho-Hn_hgIhfYQVw19wQCcc8m7v2jCXz2MTi74vpzW7aY2C123i5uFr1KdVQLX.Xlf5ZtuLAsYZ5MsG1G6VYO_t7l6nl8D-Y3fml7uSGPXkeQT7AW1HKOTrBdGj0L_OMMYBaZ4-R9tnUjM7Jf1LExVn2NIQnNGR7Jyl69WigxQGLaUbNvE9DQkVtV4ar0FORi8mrwAQdb5UxZkOxW4me_F189Y-rs0h9q4vrjmM3Fu1Uj_UCrV4IP6AYr4b9yXzgAlcybQSNUISltLwYC.07LKjRy.dLvF-A2b5mIjyKbR3rOUmiXfeAveBRakkEOKSNOSTDVM839O9a-8tqAggj8eDdPunO8Pg6L_Af_eJowZNhtRluVuFDTHuUW.lAG1bqm.ZJQak800cspbDbB8InwOb0V1UvoO.c.ZOAWM3fQVoidNeDbKbWKR3JdE3iWCGsqz89Zdd6LkxwHAd5TRLn.ZVg67d1.g2S3kadXrTZ0qN6o_7apQ38PbGKydnCTLMfdgN6uIPqAtQntsF6YNvNfFHnXgsfhTkVUDJc5g6oQB_jDeGa8rkCmH6_DO6Z1qV04G.fj.QLJIgMhcdGL8Y3NMRv87spZxNWmX1glZ1AYQlWbbqwzAC1v-p0ImXQSuq6G8aIt8e4w7FnOmOn_Q0NwD85JRgdy.W4f.5hX-zLhj1RDqvC1-Ebd2OLDdne0xqMMNe4kGnzQ6dpDtbZXPcim443CyFxaFD_DK5-Iupc032AKV_Sm9eDSlRuAmAQ7LhvypNWi_6e9NuVUEQ5P6HfBe1eXkZ3y5HkZpWbJqJW2D\"\n\tinvalidPattern = \"ayt_8-ytglklqvyjGF6dgbXsCSMRLdryUWQCSQ1n1uSph1Ppl.jMeMx0EkxVLcmbKPTi7kPYKzNa04V96eiJlV.XxtKFDKTJCkcThvKR2x0ctVUL.FrbHVXEx55VZZPD0FKW4onNDO05Ab_9cbnrLTr4d--.eyX.hZw.Q5g-pvyWS5jjDUyiE60WZSgQq4uM8PUVw1-OJegMnGGLEKq48G5X7zgQAcYvUIRQy4Ku.gKQkNFIL4Nlj3ob7Bi4VUDs_DjvtS_Jxho-Hn_hgIhfYQVw19wQCcc8m7v2jCXz2MTi74vpzW7aY2C123i5uFr1KdVQLX.Xlf5ZtuLAsYZ5MsG1G6VYO_t7l6nl8D-Y3fml7uSGPXkeQT7AW1HKOTrBdGj0L_OMMYBaZ4-R9tnUjM7Jf1LExVn2NIQnNGR7Jyl69WigxQGLaUbNvE9DQkVtV4ar0FORi8mrwAQdb5UxZkOxW4me_F189Y-rs0h9q4vrjmM3Fu1Uj_UCrV4IP6AYr4b9yXzgAlcybQSNUISltLwYC.07LKjRy.dLvF-A2b5mIjyKbR3rOUmiXfeAveBRakkEOKSNOSTDVM839O9a-8tqAggj8eDdPunO8Pg6L_Af_eJowZNhtRluVuFDTHuUW.lAG1bqm.ZJQak800cspbDbB8InwOb0V1UvoO.c.ZOAWM3fQVoidNeDbKbWKR3JdE3iWCGsqz89Zdd6LkxwHAd5TRLn.ZVg67d1.g2S3kadXrTZ0qN6o_7apQ38PbGKydnCTLMfdgN6uIPqAtQntsF6YNvNfFHnXgsfhTkVUDJc5g6oQB_jDeGa8rkCmH6_DO6Z1qV04G.fj.QLJIgMhcdGL8Y3NMRv87spZxNWmX1glZ1AYQlWbbqwzAC1v-p0ImXQSuq6G8aIt8e4w7FnOmOn_Q0NwD85JRgdy.W4f.5hX-zLhj1RDqvC1-Ebd2OLDdne0xqMMNe4kGnzQ6dpDtbZXPcim443CyFxaFD_DK5-Iupc032AKV_Sm9eDSlRuAmAQ7LhvypNWi_6e9NuVUEQ5P6HfBe1eXkZ3y5HkZpWbJqJW2D\"\n\tkeyword        = \"reallysimplesystems\"\n)\n\nfunc TestReallySimpleSystems_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword reallysimplesystems\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rebrandly/rebrandly.go",
    "content": "package rebrandly\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"rebrandly\"}) + `\\b([a-zA-Z0-9_]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"rebrandly\"}\n}\n\n// FromData will find and optionally verify Rebrandly secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Rebrandly,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.rebrandly.com/v1/account\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"apikey\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Rebrandly\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Rebrandly is a link management platform that allows users to brand, track, and share short URLs. Rebrandly API keys can be used to manage and analyze links programmatically.\"\n}\n"
  },
  {
    "path": "pkg/detectors/rebrandly/rebrandly_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage rebrandly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRebrandly_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"REBRANDLY\")\n\tinactiveSecret := testSecrets.MustGetField(\"REBRANDLY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rebrandly secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Rebrandly,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rebrandly secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Rebrandly,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Rebrandly.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Rebrandly.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rebrandly/rebrandly_test.go",
    "content": "package rebrandly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"_dd9gKTi7gDhQYowXu5efwPLKkAAQ4qP\"\n\tinvalidPattern = \"_dd9gKTi7gDhQYow?u5efwPLKkAAQ4qP\"\n\tkeyword        = \"rebrandly\"\n)\n\nfunc TestRebrandly_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword rebrandly\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rechargepayments/rechargepayments.go",
    "content": "package rechargepayments\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tverifyURL = \"https://api.rechargeapps.com/token_information\"\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\ttokenPats = map[string]*regexp.Regexp{\n\t\t\"Newer API Keys\":        regexp.MustCompile(`\\bsk(_test)?_(1|2|3|5|10)x[123]_[0-9a-fA-F]{64}\\b`),\n\t\t\"Old API key (SHA-224)\": regexp.MustCompile(`\\b[0-9a-fA-F]{56}\\b`),\n\t\t\"Old API key (MD-5)\":    regexp.MustCompile(`\\b[0-9a-fA-F]{32}\\b`),\n\t}\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sk_1x1\", \"sk_1x3\", \"sk_2x1\", \"sk_2x2\", \"sk_3x3\", \"sk_5x3\", \"sk_10x3\", \"sk_test_1x1\", \"sk_test_1x3\", \"sk_test_2x1\", \"sk_test_2x2\", \"sk_test_3x3\", \"sk_test_5x3\", \"sk_test_10x3\", \"X-Recharge-Access-Token\"}\n}\n\n// FromData will find and optionally verify Recharge Payment secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tfor _, tokenPat := range tokenPats {\n\t\ttokens := tokenPat.FindAllString(dataStr, -1)\n\n\t\tfor _, token := range tokens {\n\t\t\tresult := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_RechargePayments,\n\t\t\t\tRaw:          []byte(token),\n\t\t\t}\n\t\t\tif verify {\n\t\t\t\tclient := common.SaneHttpClient()\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", verifyURL, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\treq.Header.Add(\"X-Recharge-Access-Token\", token)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tres.Body.Close() // The request body is unused.\n\n\t\t\t\t\tif res.StatusCode == http.StatusOK {\n\t\t\t\t\t\tresult.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, result)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_RechargePayments\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Recharge is a subscription payment solution. Recharge API keys can be used to access and manage subscription data and transactions.\"\n}\n"
  },
  {
    "path": "pkg/detectors/rechargepayments/rechargepayments_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage rechargepayments\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRechargePayments_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"RECHARGEPAYMENTS\")\n\tinactiveSecret := testSecrets.MustGetField(\"RECHARGEPAYMENTS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rechargepayments secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RechargePayments,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rechargepayments secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RechargePayments,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"RechargePayments.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"RechargePayments.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rechargepayments/rechargepayments_test.go",
    "content": "package rechargepayments\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidApiKey      = \"sk_test_3x3_2fbA8B8f75b3f74D3caEF6ec023c3AAbbe48Edcc2393dc343688Dfa8bbcb141f\"\n\tinvalidApiKey    = \"sk_test_3x3_?fbA8B8f75b3f74D3caEF6ec023c3AAbbe48Edcc2393dc343688Dfa8bbcb141f\"\n\tvalidOldApiSha   = \"09ECafFABafCeDec29b51B425E1Ed5BC70c6C88c8CeddDe0ba7ab0Af\"\n\tinvalidOldApiSha = \"0?ECafFABafCeDec29b51B425E1Ed5BC70c6C88c8CeddDe0ba7ab0Af\"\n\tvalidOldApiMd5   = \"FCd1AAaD6EF3D4bCFdaB9c831cBc0d57\"\n\tinvalidOldApiMd5 = \"F?d1AAaD6EF3D4bCFdaB9c831cBc0d57\"\n\tkeyword          = \"rechargepayments\"\n)\n\nfunc TestRechargePayments_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword rechargepayments\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, validApiKey, keyword, validOldApiSha, keyword, validOldApiMd5),\n\t\t\twant:  []string{validApiKey, validOldApiSha, validOldApiMd5},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidApiKey, keyword, invalidOldApiSha, keyword, invalidOldApiMd5),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/redis/redis.go",
    "content": "package redis\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/go-redis/redis\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tkeyPat        = regexp.MustCompile(`\\bredi[s]{1,2}://[\\S]{3,50}:([\\S]{3,50})@[-.%\\w\\/:]+\\b`)\n\tazureRedisPat = regexp.MustCompile(`\\b([\\w\\d.-]{1,100}\\.redis\\.cache\\.windows\\.net:6380),password=([^,]{44}),ssl=True,abortConnect=False\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"redis\"}\n}\n\n// FromData will find and optionally verify URI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tazureMatches := azureRedisPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range azureMatches {\n\t\thost := match[1]\n\t\tpassword := match[2]\n\t\turlMatch := fmt.Sprintf(\"rediss://:%s@%s\", password, host)\n\n\t\t// Skip findings where the password only has \"*\" characters, this is a redacted password\n\t\tif strings.Trim(password, \"*\") == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tparsedURL, err := url.Parse(urlMatch)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := parsedURL.User.Password(); !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tredact := strings.TrimSpace(strings.Replace(urlMatch, password, \"*******\", -1))\n\n\t\ts := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Redis,\n\t\t\tRaw:          []byte(urlMatch),\n\t\t\tRedacted:     redact,\n\t\t}\n\n\t\tif verify {\n\t\t\ts.Verified = verifyRedis(ctx, parsedURL)\n\t\t}\n\n\t\tif !s.Verified {\n\t\t\t// Skip unverified findings where the password starts with a `$` - it's almost certainly a variable.\n\t\t\tif strings.HasPrefix(password, \"$\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s)\n\t}\n\n\tfor _, match := range matches {\n\t\turlMatch := match[0]\n\t\tpassword := match[1]\n\n\t\t// Skip findings where the password only has \"*\" characters, this is a redacted password\n\t\tif strings.Trim(password, \"*\") == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tparsedURL, err := url.Parse(urlMatch)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := parsedURL.User.Password(); !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tredact := strings.TrimSpace(strings.Replace(urlMatch, password, \"*******\", -1))\n\n\t\ts := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Redis,\n\t\t\tRaw:          []byte(urlMatch),\n\t\t\tRedacted:     redact,\n\t\t}\n\n\t\tif verify {\n\t\t\ts.Verified = verifyRedis(ctx, parsedURL)\n\t\t}\n\n\t\tif !s.Verified {\n\t\t\t// Skip unverified findings where the password starts with a `$` - it's almost certainly a variable.\n\t\t\tif strings.HasPrefix(password, \"$\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyRedis(ctx context.Context, u *url.URL) bool {\n\topt, err := redis.ParseURL(u.String())\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tclient := redis.NewClient(opt)\n\n\tstatus, err := client.Ping().Result()\n\tif err == nil && status == \"PONG\" {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Redis\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Redis is an in-memory data structure store, used as a database, cache, and message broker. Redis credentials can be used to access and manipulate stored data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/redis/redis_integration_test.go",
    "content": "//go:build detectors && integration\n// +build detectors,integration\n\npackage redis\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/brianvoe/gofakeit/v7\"\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/testcontainers/testcontainers-go\"\n\t\"github.com/testcontainers/testcontainers-go/wait\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc setupACLConfigFile(redisUser, redisPass string) (*os.File, error) {\n\n\taclString := fmt.Sprintf(`\n\t\tuser default on >%s ~* +@all\n\t\tuser %s on >%s ~* +@all\n\t`, redisPass, redisUser, redisPass)\n\n\taclFile, err := ioutil.TempFile(\"\", \"redis_users.acl\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif _, err := aclFile.Write([]byte(aclString)); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn aclFile, nil\n}\n\nfunc TestRedisIntegration_FromChunk(t *testing.T) {\n\tredisUser := gofakeit.Username()\n\tredisPass := gofakeit.Password(true, true, true, false, false, 10)\n\n\tctx := context.Background()\n\n\taclFile, err := setupACLConfigFile(redisUser, redisPass)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer aclFile.Close()\n\n\tredisContainerRequest := testcontainers.ContainerRequest{\n\t\tImage:        \"redis:7-alpine\",\n\t\tExposedPorts: []string{\"6379/tcp\"},\n\t\tMounts: testcontainers.ContainerMounts{\n\t\t\ttestcontainers.BindMount(aclFile.Name(), \"/usr/local/etc/redis/users.acl\"),\n\t\t},\n\t\tCmd:        []string{\"redis-server\", \"--aclfile\", \"/usr/local/etc/redis/users.acl\"},\n\t\tWaitingFor: wait.ForLog(\"Ready to accept connections\").WithStartupTimeout(10 * time.Second),\n\t}\n\n\tredisC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{\n\t\tContainerRequest: redisContainerRequest,\n\t\tStarted:          true,\n\t})\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tport, err := redisC.MappedPort(ctx, \"6379\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\thost, err := redisC.Host(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdefer redisC.Terminate(ctx)\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"bad scheme\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"file://user:pass@foo.com:123/wh/at/ever\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"unverified Redis\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"redis://%s:%s@%s:%s\", redisUser, \"wrongpass\", host, port.Port())),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Redis,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     fmt.Sprintf(\"redis://%s:*******@%s:%s\", redisUser, host, port.Port()),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"verified Redis\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"redis://%s:%s@%s:%s\", redisUser, redisPass, host, port.Port())),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Redis,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     fmt.Sprintf(\"redis://%s:*******@%s:%s\", redisUser, host, port.Port()),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"URI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"URI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestURI_FromChunk(t *testing.T) {\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"bad scheme\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"file://user:pass@foo.com:123/wh/at/ever\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"unverified Redis\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"redis://user:invalid@redis.com\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Redis,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"redis://user:*******@redis.com\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"URI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"URI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/redis/redis_test.go",
    "content": "package redis\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey          = \"redis://1SHPg6BjyS:$YYbMDot25P@PvfdX9VshFjW\"\n\tinvalidKey        = \"redis://1SHPg6BjySX$YYbMDot25P@PvfdX9VshFjW\"\n\tvalidDomain       = \"QZc67koZ6rUV.redis.cache.windows.net:6380\"\n\tinvalidDomain     = \"QZc67koZ6rUV.redis.cache.windows.net:6381\"\n\tpassword          = \"Xcc3S9d7And6aMdfOcUc0acHJh3CiDh3l9DsapNwGwyS\"\n\tvalidAzureRedis   = validDomain + \",password=\" + password + \",ssl=True,abortConnect=False\"\n\tinvalidAzureRedis = invalidDomain + \",password=\" + password + \",ssl=False,abortConnect=True\"\n\tkeyword           = \"redis\"\n)\n\nfunc TestRedisIntegration_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword redis\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validAzureRedis),\n\t\t\twant:  []string{\"rediss://:\" + password + \"@\" + validDomain},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidAzureRedis),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/refiner/refiner.go",
    "content": "package refiner\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"refiner\"}) + `\\b([0-9Aa-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"refiner\"}\n}\n\n// FromData will find and optionally verify Refiner secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Refiner,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(\"id=Your-User-Id&event=Your%20event%20name\")\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.refiner.io/v1/identify-user\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Refiner\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Refiner is a user feedback platform. Refiner keys can be used to authenticate and identify users within the Refiner platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/refiner/refiner_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage refiner\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRefiner_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"REFINER\")\n\tinactiveSecret := testSecrets.MustGetField(\"REFINER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a refiner secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Refiner,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a refiner secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Refiner,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Refiner.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Refiner.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/refiner/refiner_test.go",
    "content": "package refiner\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"2341fb6f-71d4-dbbc-1317-98c28ceb5837\"\n\tinvalidPattern = \"2341fb6f?71d4-dbbc-1317-98c28ceb5837\"\n\tkeyword        = \"refiner\"\n)\n\nfunc TestRefiner_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword refiner\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rentman/rentman.go",
    "content": "package rentman\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"rentman\"}) + `\\b(ey[a-zA-Z0-9]{34}.ey[a-zA-Z0-9._-]{250,300})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"rentman\"}\n}\n\n// FromData will find and optionally verify Rentman secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Rentman,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.rentman.net/files\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Rentman\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Rentman is a resource management software for AV & Event production. Rentman API keys can be used to access and manage resources and data within the Rentman platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/rentman/rentman_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage rentman\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRentman_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"RENTMAN\")\n\tinactiveSecret := testSecrets.MustGetField(\"RENTMAN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rentman secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Rentman,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rentman secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Rentman,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Rentman.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Rentman.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rentman/rentman_test.go",
    "content": "package rentman\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"eytuZDhcszM3EZO7iPeRfJfZMoRMLXe7qoNV.eyA3uoaQ3ttxNcElupuqjIGN2K-bLTq_RVtB6w6ySxFCOttLniQBf84yvwaBBjw4W1veuikC2uWzoW5Gtw-CpEC-.35zBGkn7t7G19FduNmXTy1r_8bXME-VD5xIflYzioiZvyRLfy685qs8TQDIJV7zLi98cPLrLTE_yXwmppu9o88_OWPzh9oAqmVGqvX5Z_1RXqd6e5MsaTFx22tqKw14njwD.qvyujp.NR6QOQmyREUt2XnUtqdupyGX\"\n\tinvalidPattern = \"aytuZDhcszM3EZO7iPeRfJfZMoRMLXe7qoNV.eyA3uoaQ3ttxNcElupuqjIGN2K-bLTq_RVtB6w6ySxFCOttLniQBf84yvwaBBjw4W1veuikC2uWzoW5Gtw-CpEC-.35zBGkn7t7G19FduNmXTy1r_8bXME-VD5xIflYzioiZvyRLfy685qs8TQDIJV7zLi98cPLrLTE_yXwmppu9o88_OWPzh9oAqmVGqvX5Z_1RXqd6e5MsaTFx22tqKw14njwD.qvyujp.NR6QOQmyREUt2XnUtqdupyG?\"\n\tkeyword        = \"rentman\"\n)\n\nfunc TestRentman_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword rentman\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/repairshopr/repairshopr.go",
    "content": "package repairshopr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"repairshopr\"}) + `\\b([a-zA-Z0-9-]{51})\\b`)\n\tdomainPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"repairshopr\"}) + `\\b([a-zA-Z0-9_.!+$#^*]{3,32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"repairshopr\"}\n}\n\n// FromData will find and optionally verify Sugester secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tdomainMatches := domainPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, domainmatch := range domainMatches {\n\t\t\tresDomainMatch := strings.TrimSpace(domainmatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Repairshopr,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://\"+resDomainMatch+\".repairshopr.com/api/v1/appointment_types\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Accept\", \"application/vnd.sugester+json; version=3\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Repairshopr\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"RepairShopr is a CRM and ticketing system designed for repair shops. The API keys allow access to various functionalities such as managing appointments, customers, and invoices.\"\n}\n"
  },
  {
    "path": "pkg/detectors/repairshopr/repairshopr_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage repairshopr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRepairshopr_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"REPAIRSHOPR\")\n\tdomain := testSecrets.MustGetField(\"REPAIRSHOPR_DOMAIN\")\n\tinactiveSecret := testSecrets.MustGetField(\"REPAIRSHOPR_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a repairshopr secret %s within repairshoprdomain %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Repairshopr,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a repairshopr secret %s within repairshoprdomain %s but  not valid\", inactiveSecret, domain)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Repairshopr,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Repairshopr.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Repairshopr.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/repairshopr/repairshopr_test.go",
    "content": "package repairshopr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey      = \"sMpDWS14xuMP2E1FMCufXKajIsodvYiuGT9lWll8gYMAYFDRgOD\"\n\tinvalidKey    = \"?MpDWS14xuMP2E1FMCufXKajIsodvYiuGT9lWll8gYMAYFDRgOD\"\n\tvalidDomain   = \"uI8mmO1KgWwz\"\n\tinvalidDomain = \"?I8mmO1KgWw?\"\n\tkeyword       = \"repairshopr\"\n)\n\nfunc TestRepairshopr_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword repairshopr\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validDomain),\n\t\t\twant:  []string{validKey, validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidDomain),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/replicate/replicate.go",
    "content": "package replicate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\tkeyPat        = regexp.MustCompile(`\\b(r8_[0-9A-Za-z-_]{37})\\b`)\n)\n\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"replicate\", \"r8_\"}\n}\n\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Replicate,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.replicate.com/v1/predictions\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Token %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Replicate\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Replicate is a platform for running machine learning models in the cloud. Replicate API keys can be used to manage and run these models.\"\n}\n"
  },
  {
    "path": "pkg/detectors/replicate/replicate_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage replicate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestReplicate_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"REPLICATE\")\n\tinactiveSecret := testSecrets.MustGetField(\"REPLICATE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a replicate secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Replicate,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a replicate secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Replicate,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Replicate.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Replicate.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/replicate/replicate_test.go",
    "content": "package replicate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"r8_7DTl1j7Nsy6dpc3GGwkd8qsWFl33hy4ICtNzH\"\n\tinvalidPattern = \"r8_7DTl1j7Nsy6dpc3GG?kd8qsWFl33hy4ICtNzH\"\n\tkeyword        = \"replicate\"\n)\n\nfunc TestReplicate_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword replicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/replyio/replyio.go",
    "content": "package replyio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"replyio\"}) + `\\b([0-9A-Za-z]{24})\\b`)\n)\n\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"replyio\"}\n}\n\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ReplyIO,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.reply.io/v1/people\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-Api-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ReplyIO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Reply.io is a sales engagement platform used for automating communication with prospects. The API key can be used to access and manage communication data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/replyio/replyio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage replyio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestReplyio_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"REPLYIO\")\n\tinactiveSecret := testSecrets.MustGetField(\"REPLYIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a replyio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ReplyIO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a replyio secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ReplyIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Replyio.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Replyio.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/replyio/replyio_test.go",
    "content": "package replyio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"Y8rVcIMii3FFhyQmzEDfttno\"\n\tinvalidPattern = \"Y8rVcIMii3FF?yQmzEDfttno\"\n\tkeyword        = \"replyio\"\n)\n\nfunc TestReplyio_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword replyio\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/requestfinance/requestfinance.go",
    "content": "package requestfinance\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\tkeyPat        = regexp.MustCompile(detectors.PrefixRegex([]string{\"requestfinance\"}) + `\\b([0-9A-Z]{7}-[0-9A-Z]{7}-[0-9A-Z]{7}-[0-9A-Z]{7})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"requestfinance\"}\n}\n\n// FromData will find and optionally verify Requestfinance secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_RequestFinance,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.request.finance/invoices\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_RequestFinance\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Request Finance is a platform for managing and automating invoices and payments. Request Finance credentials can be used to access and manage these financial operations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/requestfinance/requestfinance_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage requestfinance\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRequestfinance_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"REQUESTFINANCE\")\n\tinactiveSecret := testSecrets.MustGetField(\"REQUESTFINANCE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a requestfinance secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RequestFinance,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a requestfinance secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RequestFinance,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Requestfinance.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Requestfinance.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/requestfinance/requestfinance_test.go",
    "content": "package requestfinance\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"IZAT6SA-AJUQES1-BZWZYWJ-VDIXPVG\"\n\tinvalidPattern = \"IZAT6SA?AJUQES1-BZWZYWJ-VDIXPVG\"\n\tkeyword        = \"requestfinance\"\n)\n\nfunc TestRequestfinance_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword requestfinance\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/restpackhtmltopdfapi/restpackhtmltopdfapi.go",
    "content": "package restpackhtmltopdfapi\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"restpack\"}) + `\\b([0-9A-Za-z]{48})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"restpack\"}\n}\n\n// FromData will find and optionally verify RestpackHtmlToPdfAPI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_RestpackHtmlToPdfAPI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://restpack.io/api/html2pdf/usage\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"X-Access-Token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_RestpackHtmlToPdfAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Restpack is a service that provides APIs for converting HTML to PDF. Restpack API keys can be used to access and utilize this service.\"\n}\n"
  },
  {
    "path": "pkg/detectors/restpackhtmltopdfapi/restpackhtmltopdfapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage restpackhtmltopdfapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRestpackHtmlToPdfAPI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"RESTPACKHTMLTOPDFAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"RESTPACKHTMLTOPDFAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a restpackhtmltopdfapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RestpackHtmlToPdfAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a restpackhtmltopdfapi secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RestpackHtmlToPdfAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"RestpackHtmlToPdfAPI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"RestpackHtmlToPdfAPI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/restpackhtmltopdfapi/restpackhtmltopdfapi_test.go",
    "content": "package restpackhtmltopdfapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"vVIKB7ypMMXOlpRx2ad1g1t8JmvDBsvQ02LMc1nuNOQYAMEF\"\n\tinvalidPattern = \"vVIKB7ypMMXOlpRx2ad1g1t8?mvDBsvQ02LMc1nuNOQYAMEF\"\n\tkeyword        = \"restpackhtmltopdfapi\"\n)\n\nfunc TestRestpackHtmlToPdfAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword restpackhtmltopdfapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/restpackscreenshotapi/restpackscreenshotapi.go",
    "content": "package restpackscreenshotapi\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"restpack\"}) + `\\b([0-9A-Za-z]{48})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"restpack\"}\n}\n\n// FromData will find and optionally verify RestpackScreenshotAPI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_RestpackScreenshotAPI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://restpack.io/api/screenshot/usage\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"X-Access-Token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_RestpackScreenshotAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Restpack Screenshot API is a service used to capture screenshots of web pages. The API key can be used to authenticate requests to the service.\"\n}\n"
  },
  {
    "path": "pkg/detectors/restpackscreenshotapi/restpackscreenshotapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage restpackscreenshotapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRestpackScreenshotAPI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"RESTPACKSCREENSHOTAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"RESTPACKSCREENSHOTAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a restpackscreenshotapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RestpackScreenshotAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a restpackscreenshotapi secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RestpackScreenshotAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"RestpackScreenshotAPI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"RestpackScreenshotAPI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/restpackscreenshotapi/restpackscreenshotapi_test.go",
    "content": "package restpackscreenshotapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"O6xSfj5GgiV3imFS5IU13903DwIfCSnawlBybGQ9sMKg9Ugk\"\n\tinvalidPattern = \"O6xSfj5GgiV3imFS5IU13903?wIfCSnawlBybGQ9sMKg9Ugk\"\n\tkeyword        = \"restpackscreenshotapi\"\n)\n\nfunc TestRestpackScreenshotAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword restpackscreenshotapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rev/rev.go",
    "content": "package rev\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tuserKeyPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"rev\"}) + `\\b([0-9a-zA-Z\\/\\+]{27}\\=[ \\r\\n]{1})`)\n\tclientKeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"rev\"}) + `\\b([0-9a-zA-Z\\-]{27}[ \\r\\n]{1})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"rev\"}\n}\n\n// FromData will find and optionally verify Rev secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuserMatches := userKeyPat.FindAllStringSubmatch(dataStr, -1)\n\tclientMatches := clientKeyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, userMatch := range userMatches {\n\t\tresUserMatch := strings.TrimSpace(userMatch[1])\n\n\t\tfor _, clientMatch := range clientMatches {\n\t\t\tresClientMatch := strings.TrimSpace(clientMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Rev,\n\t\t\t\tRaw:          []byte(resUserMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.rev.com/api/v1/orders\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Rev %s:%s\", resClientMatch, resUserMatch))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Rev\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Rev is a transcription service. Rev API keys can be used to access and modify transcription orders and data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/rev/rev_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage rev\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRev_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"REV\")\n\tinactiveSecret := testSecrets.MustGetField(\"REV_INACTIVE\")\n\tclientSecret := testSecrets.MustGetField(\"REV_CLIENT\")\n\tinactiveClientSecret := testSecrets.MustGetField(\"REV_CLIENT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rev secret %s with rev client secret %s within\", secret, clientSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Rev,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rev secret %s with rev client secret %s within but not valid\", inactiveSecret, inactiveClientSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Rev,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Rev.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Rev.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rev/rev_test.go",
    "content": "package rev\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidUserKey     = \"T5LJHzWwZzdpmNtxKDaDcDUlckU=\"\n\tinvalidUserKey   = \"T5LJHzWwZzdpmN?xKDaDcDUlckU=\"\n\tvalidClientKey   = \"utD8J485zVIeRCbHyU8yYfw55m2\"\n\tinvalidClientKey = \"utD8J485zVIeRC?HyU8yYfw55m2\"\n\tkeyword          = \"rev\"\n)\n\nfunc TestRev_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword rev\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s '\\n%s token - '%s '\\n\", keyword, validUserKey, keyword, validClientKey),\n\t\t\twant:  []string{validUserKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s '\\n%s token - '%s '\\n\", keyword, invalidUserKey, keyword, invalidClientKey),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/revampcrm/revampcrm.go",
    "content": "package revampcrm\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"revamp\"}) + `\\b([a-zA-Z0-9]{40}\\b)`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"revamp\"}) + `\\b([a-zA-Z0-9.-@]{25,30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"revampcrm\"}\n}\n\n// FromData will find and optionally verify RevampCRM secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\ttokenPatMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tuserPatMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_RevampCRM,\n\t\t\t\tRaw:          []byte(tokenPatMatch),\n\t\t\t}\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.revampcrm.com/api/1.0/User/WhoAmI\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.SetBasicAuth(userPatMatch, tokenPatMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_RevampCRM\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"RevampCRM is a customer relationship management service. The credentials can be used to access and manage customer data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/revampcrm/revampcrm_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage revampcrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRevampCRM_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"REVAMPCRM\")\n\tuser := testSecrets.MustGetField(\"ACCOUNT_USER\")\n\tinactiveSecret := testSecrets.MustGetField(\"REVAMPCRM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a revampcrm secret %s within revampcrm %s\", secret, user)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RevampCRM,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a revampcrm secret %s within revampcrm %s but not valid\", inactiveSecret, user)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RevampCRM,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"RevampCRM.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"RevampCRM.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/revampcrm/revampcrm_test.go",
    "content": "package revampcrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey   = \"JFjlJg7KqFGp5RmsJVq7WDmwObgHW6JEtMOitGuP\"\n    invalidKey = \"JFjlJg7KqFGp5RmsJVq7?DmwObgHW6JEtMOitGuP\"\n    validId    = \"JzNRi@oQyzxG>LG7f2oe7R.Xf\"\n    invalidId  = \"JzNRi@oQyzxG!LG7f2oe7R.Xf\"\n    keyword    = \"revampcrm\"\n)\n\nfunc TestRevampCRM_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword revampcrm\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ringcentral/ringcentral.go",
    "content": "package ringcentral\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ringcentral\"}) + `\\b([0-9A-Za-z_-]{22})\\b`)\n\t// TODO: this domain pattern is too restrictive\n\turiPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ringcentral\"}) + `\\b(https://www.[0-9A-Za-z_-]{1,}.com)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ringcentral\"}\n}\n\n// FromData will find and optionally verify Ringcentral secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\turiMatches := uriPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, uriMatch := range uriMatches {\n\t\t\tresURI := strings.TrimSpace(uriMatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_RingCentral,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://platform.devtest.ringcentral.com/restapi/oauth/authorize?response_type=code&redirect_uri=%s&client_id=%s\", resURI, resMatch), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_RingCentral\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"RingCentral is a cloud-based communication system that offers messaging, video conferencing, and phone services. RingCentral API keys can be used to access and manage these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ringcentral/ringcentral_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ringcentral\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRingcentral_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"RINGCENTRAL\")\n\turi := testSecrets.MustGetField(\"REDIRECTURI\")\n\tinactiveSecret := testSecrets.MustGetField(\"RINGCENTRAL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ringcentral secret %s within ringcentral uri %s\", secret, uri)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RingCentral,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ringcentral secret %s within ringcentral uri %s but not valid\", inactiveSecret, uri)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RingCentral,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Ringcentral.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Ringcentral.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ringcentral/ringcentral_test.go",
    "content": "package ringcentral\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey   = \"OTXwnia5kOs-SlF3kZEFJK\"\n    invalidKey = \"OTXwnia5kOs?SlF3kZEFJK\"\n    validUri   = \"https://wwwSG7$com\"\n    invalidUri = \"https://?wwSG7$com\"\n    keyword    = \"ringcentral\"\n)\n\nfunc TestRingcentral_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword ringcentral\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validUri),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidUri),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ritekit/ritekit.go",
    "content": "package ritekit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ritekit\"}) + `\\b([0-9a-f]{44})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ritekit\"}\n}\n\n// FromData will find and optionally verify RiteKit secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_RiteKit,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.ritekit.com/v1/stats/multiple-hashtags?tags=hello&client_id=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_RiteKit\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"RiteKit is a social media toolkit that provides various APIs for managing social media content and analytics. RiteKit API keys can be used to access and manage social media data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ritekit/ritekit_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ritekit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRiteKit_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"RITEKIT\")\n\tinactiveSecret := testSecrets.MustGetField(\"RITEKIT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ritekit secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RiteKit,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ritekit secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RiteKit,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"RiteKit.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"RiteKit.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ritekit/ritekit_test.go",
    "content": "package ritekit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"6c2428ff83181c3dcf94e97cb0b15b23599a3cb22857\"\n\tinvalidPattern = \"6c2428ff83181c3dcf94e9?cb0b15b23599a3cb22857\"\n\tkeyword        = \"ritekit\"\n)\n\nfunc TestRiteKit_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword ritekit\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/roaring/roaring.go",
    "content": "package roaring\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tclientPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"roaring\"}) + `\\b([0-9A-Za-z_-]{28})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"roaring\"}) + `\\b([0-9A-Za-z_-]{28})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"roaring\"}\n}\n\n// FromData will find and optionally verify Roaring secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tclientMatches := clientPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, clientMatch := range clientMatches {\n\t\tresClient := strings.TrimSpace(clientMatch[1])\n\n\t\tfor _, secretMatch := range secretMatches {\n\t\t\tresSecret := strings.TrimSpace(secretMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Roaring,\n\t\t\t\tRaw:          []byte(resClient),\n\t\t\t\tRawV2:        []byte(resClient + resSecret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tdata := fmt.Sprintf(\"%s:%s\", resClient, resSecret)\n\t\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\t\tpayload := strings.NewReader(\"grant_type=client_credentials\")\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.roaring.io/token\", payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Roaring\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Roaring credentials can be used to access the Roaring API, which provides services for high-performance, compressed bitmaps.\"\n}\n"
  },
  {
    "path": "pkg/detectors/roaring/roaring_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage roaring\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRoaring_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tclient := testSecrets.MustGetField(\"ROARING_CLIENT\")\n\tsecret := testSecrets.MustGetField(\"ROARING_SECRET\")\n\tinactiveClient := testSecrets.MustGetField(\"ROARING_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a roaring secret %s within roaring client %s\", secret, client)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Roaring,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a roaring client %s within roaring secret %s but not valid\", inactiveClient, secret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Roaring,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Roaring.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Roaring.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/roaring/roaring_test.go",
    "content": "package roaring\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidClient   = \"-3o_mYkUEBbUKtKjGCg5tgnkzU4T\"\n\tinvalidClient = \"-3o_mYkUEBbU?tKjGCg5tgnkzU4T\"\n\tvalidSecret   = \"XqTQqnAkyH6We_TEK1CRjhwRV51h\"\n\tinvalidSecret = \"Xq?QqnAkyH6We_TEK1CRjhwRV51h\"\n\tkeyword       = \"roaring\"\n)\n\nfunc TestRoaring_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword roaring\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validClient, keyword, validSecret),\n\t\t\twant:  []string{validSecret + validSecret},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidClient, keyword, invalidSecret),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/robinhoodcrypto/robinhoodcrypto.go",
    "content": "package robinhoodcrypto\n\nimport (\n\t\"context\"\n\t\"crypto/ed25519\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\t// Reference: https://docs.robinhood.com/crypto/trading/#section/Authentication\n\tkeyPat = regexp.MustCompile(`\\b(rh-api-[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`)\n\n\t// Matches base64 strings. Taken from https://stackoverflow.com/a/475217.\n\tprivKeyBase64Pat = regexp.MustCompile(`(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=)`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"rh-api-\"}\n}\n\n// FromData will find and optionally verify RobinhoodCrypto secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tapiKeyMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tapiKeyMatches[match[1]] = struct{}{}\n\t}\n\n\tbase64PrivateKeyMatches := make(map[string]struct{})\n\tfor _, match := range privKeyBase64Pat.FindAllString(dataStr, -1) {\n\t\tbase64PrivateKeyMatches[match] = struct{}{}\n\t}\n\n\tfor apiKey := range apiKeyMatches {\n\t\tfor base64PrivateKey := range base64PrivateKeyMatches {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_RobinhoodCrypto,\n\t\t\t\tRaw:          []byte(apiKey),\n\t\t\t\tRawV2:        []byte(apiKey + base64PrivateKey),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, apiKey, base64PrivateKey)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.ExtraData = extraData\n\t\t\t\ts1.SetVerificationError(verificationErr, apiKey, base64PrivateKey)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, apiKey, base64PrivateKey string) (\n\tbool, map[string]string, error,\n) {\n\t// Decode the base64 private key.\n\tprivateBytes, err := base64.StdEncoding.DecodeString(base64PrivateKey)\n\tif err != nil {\n\t\treturn false, nil, fmt.Errorf(\"failed to decode base64 private key: %w\", err)\n\t}\n\n\t// Sanity check the private key length.\n\tif len(privateBytes) < 32 {\n\t\treturn false, nil, fmt.Errorf(\"private key is too short, expected at least 32 bytes, got %d\", len(privateBytes))\n\t}\n\n\t// Create the private key from the seed.\n\tprivateKey := ed25519.NewKeyFromSeed(privateBytes[:32])\n\n\t// Draft the message to be signed.\n\t// Reference: https://docs.robinhood.com/crypto/trading/#section/Authentication/Headers-and-Signature\n\tvar (\n\t\ttimestamp = fmt.Sprint(time.Now().UTC().Unix())\n\t\tpath      = \"/api/v1/crypto/trading/accounts/\"\n\t\tmethod    = http.MethodGet\n\t\tbody      = \"\"\n\t)\n\n\tmessage := apiKey + timestamp + path + method + body\n\tsignature := ed25519.Sign(privateKey, []byte(message))\n\n\treq, err := http.NewRequestWithContext(ctx, method, \"https://trading.robinhood.com/\"+path, strings.NewReader(body))\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\t// Set the required headers.\n\theaders := map[string]string{\n\t\t\"x-api-key\":   apiKey,\n\t\t\"x-signature\": base64.StdEncoding.EncodeToString(signature),\n\t\t\"x-timestamp\": timestamp,\n\t}\n\tfor key, value := range headers {\n\t\treq.Header.Add(key, value)\n\t}\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\t// StatusOK: The secret is verified.\n\tcase http.StatusOK:\n\t\t// Include the additional information returned by the endpoint.\n\t\tif len(res.Header) > 0 && res.Header.Get(\"Content-Type\") == \"application/json\" {\n\t\t\tresponse := struct {\n\t\t\t\tAccountNumber       string `json:\"account_number\"`\n\t\t\t\tStatus              string `json:\"status\"`\n\t\t\t\tBuyingPower         string `json:\"buying_power\"`\n\t\t\t\tBuyingPowerCurrency string `json:\"buying_power_currency\"`\n\t\t\t}{}\n\n\t\t\tif err = json.NewDecoder(res.Body).Decode(&response); err != nil {\n\t\t\t\treturn true, nil, fmt.Errorf(\"failed to obtain additional information: %w\", err)\n\t\t\t}\n\n\t\t\treturn true, map[string]string{\"Robinhood Crypto Account Number\": response.AccountNumber}, nil\n\t\t}\n\n\t\t// The secret is verified, but there is no additional information.\n\t\treturn true, nil, nil\n\n\t// StatusForbidden: The secret is valid, but the credentials do not have access to the endpoint.\n\tcase http.StatusForbidden:\n\t\treturn true, map[string]string{\"Explanation\": \"Valid credentials without access to Get Crypto Trading Account Details API\"}, nil\n\n\t// StatusUnauthorized:\n\t// Two scenarios can happen,\n\t// \t\t1. The secret is verified, but is currently inactive.\n\t// \t\t2. The secret is determinately not verified.\n\tcase http.StatusUnauthorized:\n\t\t// Check if the secret is verified but currently inactive.\n\t\t// We want to handle this case because an inactive secret can be activated in the future, at which point it\n\t\t// becomes a security risk.\n\t\tif len(res.Header) > 0 && res.Header.Get(\"Content-Type\") == \"text/plain\" {\n\t\t\tbody, err := io.ReadAll(res.Body)\n\t\t\tif err != nil {\n\t\t\t\t// The secret is considered verified but inactive only if the body suggests so. Since the body is not\n\t\t\t\t// readable, we cannot determine if the secret is verified but inactive.\n\t\t\t\treturn false, nil, fmt.Errorf(\"failed to read response body: %w\", err)\n\t\t\t}\n\n\t\t\tif strings.TrimSpace(string(body)) == \"API credential is not active.\" {\n\t\t\t\treturn true, map[string]string{\"Explanation\": \"Valid credentials in inactive state\"}, nil\n\t\t\t}\n\t\t}\n\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_RobinhoodCrypto\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Robinhood Crypto API keys can be used to access and trade cryptocurrencies on the Robinhood platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/robinhoodcrypto/robinhoodcrypto_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage robinhoodcrypto\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRobinhoodcrypto_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\t// Valid and active credentials.\n\tapiKey := testSecrets.MustGetField(\"ROBINHOODCRYPTO_APIKEY\")\n\tprivateKey := testSecrets.MustGetField(\"ROBINHOODCRYPTO_PRIVATEKEY\")\n\n\t// Valid but inactive credentials.\n\tinactiveApiKey := testSecrets.MustGetField(\"ROBINHOODCRYPTO_APIKEY_INACTIVE\")\n\tinactivePrivateKey := testSecrets.MustGetField(\"ROBINHOODCRYPTO_PRIVATEKEY_INACTIVE\")\n\n\t// Invalid credentials.\n\tdeletedApiKey := testSecrets.MustGetField(\"ROBINHOODCRYPTO_APIKEY_DELETED\")\n\tdeletedPrivateKey := testSecrets.MustGetField(\"ROBINHOODCRYPTO_PRIVATEKEY_DELETED\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(\n\t\t\t\t\t\"You can find a robinhoodcrypto api key %s and a private key %s within\", apiKey, privateKey,\n\t\t\t\t)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RobinhoodCrypto,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified, but inactive\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(\n\t\t\t\t\t\"You can find a robinhoodcrypto api key %s and a private key %s within\", inactiveApiKey,\n\t\t\t\t\tinactivePrivateKey,\n\t\t\t\t)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RobinhoodCrypto,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(\n\t\t\t\t\t\"You can find a robinhoodcrypto api key %s and a private key %s within\", deletedApiKey,\n\t\t\t\t\tdeletedPrivateKey,\n\t\t\t\t)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RobinhoodCrypto,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(\n\t\t\t\t\t\"You can find a robinhoodcrypto api key %s and a private key %s within\", apiKey, privateKey,\n\t\t\t\t)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RobinhoodCrypto,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(\n\t\t\t\t\t\"You can find a robinhoodcrypto api key %s and a private key %s within\", apiKey, privateKey,\n\t\t\t\t)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RobinhoodCrypto,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(\n\t\t\ttt.name, func(t *testing.T) {\n\t\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\t\tt.Errorf(\"Robinhoodcrypto.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tfor i := range got {\n\t\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t\t}\n\t\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\t\tt.Fatalf(\n\t\t\t\t\t\t\t\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr,\n\t\t\t\t\t\t\tgot[i].VerificationError(),\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"ExtraData\", \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"Robinhoodcrypto.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t\t}\n\t\t\t},\n\t\t)\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(\n\t\t\tname, func(b *testing.B) {\n\t\t\t\tb.ResetTimer()\n\t\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tb.Fatal(err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/robinhoodcrypto/robinhoodcrypto_test.go",
    "content": "package robinhoodcrypto\n\nimport (\n\t\"context\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"testing\"\n)\n\nfunc TestRobinhoodCrypto_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"typical pattern\",\n\t\t\tinput: `\n\t\t\t\tapi_key = \"rh-api-e3bb245e-a45c-4729-8a9b-10201756f8cc\"\n\t\t\t\tprivate_key_base64 = \"aVhXn8ghC9YqSz5RyFuKc6SsDC6SuPIqSW3IXH76ZlMCjOxkazBQjQFucJLk3uNorpBt6TbYpo/D1lHA7s4+hQ==\"\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"rh-api-e3bb245e-a45c-4729-8a9b-10201756f8cc\" +\n\t\t\t\t\t\"aVhXn8ghC9YqSz5RyFuKc6SsDC6SuPIqSW3IXH76ZlMCjOxkazBQjQFucJLk3uNorpBt6TbYpo/D1lHA7s4+hQ==\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(\n\t\t\ttest.name, func(t *testing.T) {\n\t\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif len(results) != len(test.want) {\n\t\t\t\t\tif len(results) == 0 {\n\t\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\t\tfor _, r := range results {\n\t\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\t\tfor _, v := range test.want {\n\t\t\t\t\texpected[v] = struct{}{}\n\t\t\t\t}\n\n\t\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t\t}\n\t\t\t},\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rocketreach/rocketreach.go",
    "content": "package rocketreach\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"rocketreach\"}) + `\\b([a-z0-9-]{39})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"rocketreach\"}\n}\n\n// FromData will find and optionally verify RocketReach secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_RocketReach,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.rocketreach.co/v2/api/account\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Api-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_RocketReach\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"RocketReach is a tool used to find email addresses, phone numbers, and social media profiles for professionals. RocketReach API keys can be used to access and retrieve this contact information.\"\n}\n"
  },
  {
    "path": "pkg/detectors/rocketreach/rocketreach_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage rocketreach\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRocketReach_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ROCKETREACH\")\n\tinactiveSecret := testSecrets.MustGetField(\"ROCKETREACH_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rocketreach secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RocketReach,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rocketreach secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RocketReach,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"RocketReach.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"RocketReach.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rocketreach/rocketreach_test.go",
    "content": "package rocketreach\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"c897n-ee7bm86g-9vh17ixulnqwz4qmxk2bjf77\"\n\tinvalidPattern = \"c89?n-ee7bm86g-9vh17ixulnqwz4qmxk2bjf77\"\n\tkeyword        = \"rocketreach\"\n)\n\nfunc TestRocketReach_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword rocketreach\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rootly/rootly.go",
    "content": "package rootly\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\n\t\"fmt\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\tregexp \"github.com/wasilibs/go-re2\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(rootly_[a-f0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"rootly_\"}\n}\n\n// FromData will find and optionally verify Rootly secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Rootly,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\n\t// this endpoint returns 200 if results exist and 404 if incidents do not exist or that token is not authorized\n\t// considering both 200 and 404 as positive results i.e. the token is valid\n\t// /user/me endpoint does not verify Team and Global API Keys returning 422 error. (There are 3 types of API keys in Rootly, Global, Team and Personal)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.rootly.com/v1/incidents\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Authorization\", \"Bearer \"+token)\n\tres, err := client.Do(req)\n\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK, http.StatusNotFound:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Rootly\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Rootly is an incident management platform. Rootly API keys can be used to access and manage incident data and other services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/rootly/rootly_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage rootly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRootly_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ROOTLY\")\n\tinactiveSecret := testSecrets.MustGetField(\"ROOTLY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rootly secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Rootly,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rootly secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Rootly,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Rootly.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Rootly.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rootly/rootly_test.go",
    "content": "package rootly\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestRootly_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: \"rootly_7f1e8738d7d6b540bc52e1bc24c6e2c109dc44642f9e5d583be7e5d04f8bd282\",\n\t\t\twant:  []string{\"rootly_7f1e8738d7d6b540bc52e1bc24c6e2c109dc44642f9e5d583be7e5d04f8bd282\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: \"rootly keyword is not close to the real key in the data ='rootly_7f1e8738d7d6b540bc52e1bc24c6e2c109dc44642f9e5d583be7e5d04f8bd282'\",\n\t\t\twant:  []string{\"rootly_7f1e8738d7d6b540bc52e1bc24c6e2c109dc44642f9e5d583be7e5d04f8bd282\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: \"rootly_A$3b9f8c1e2d4f5b6c7d8e9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d\",\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/route4me/route4me.go",
    "content": "package route4me\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"route4me\"}) + `\\b([0-9A-Z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"route4me\"}\n}\n\n// FromData will find and optionally verify Route4me secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Route4me,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.route4me.com/api.v4/address_book.php?api_key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Route4me\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Route4Me is a route planning service that optimizes and manages routes for delivery and service fleets. Route4Me API keys can be used to access and modify routing data and services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/route4me/route4me_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage route4me\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRoute4me_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ROUTE4ME\")\n\tinactiveSecret := testSecrets.MustGetField(\"ROUTE4ME_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a route4me secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Route4me,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a route4me secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Route4me,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Route4me.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Route4me.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/route4me/route4me_test.go",
    "content": "package route4me\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"WLCO3GIZN6QD2OO9DRNLM4PD7PMH7WH9\"\n\tinvalidPattern = \"WLCO3GIZN6Q?2OO9DRNLM4PD7PMH7WH9\"\n\tkeyword        = \"route4me\"\n)\n\nfunc TestRoute4me_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword route4me\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rownd/rownd.go",
    "content": "package rownd\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"rownd\"}) + `\\b([a-z0-9]{8}\\-[a-z0-9]{4}\\-[a-z0-9]{4}\\-[a-z0-9]{4}\\-[a-z0-9]{12})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"rownd\"}) + `\\b([a-z0-9]{48})\\b`)\n\tidPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"rownd\"}) + `\\b([0-9]{18})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"rownd\"}\n}\n\n// FromData will find and optionally verify Rownd secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\tkeyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, idMatch := range idMatches {\n\t\tresId := strings.TrimSpace(idMatch[1])\n\n\t\tfor _, match := range keyMatches {\n\t\t\tkeyMatch := strings.TrimSpace(match[1])\n\n\t\t\tfor _, secret := range secretMatches {\n\n\t\t\t\tsecretMatch := strings.TrimSpace(secret[1])\n\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Rownd,\n\t\t\t\t\tRaw:          []byte(keyMatch),\n\t\t\t\t\tRawV2:        []byte(keyMatch + secretMatch),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.rownd.io/applications/\"+resId+\"/users/data\", nil)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\treq.Header.Add(\"x-rownd-app-key\", keyMatch)\n\t\t\t\t\treq.Header.Add(\"x-rownd-app-secret\", secretMatch)\n\t\t\t\t\tres, err := client.Do(req)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Rownd\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Rownd is a platform for user data management. Rownd credentials can be used to access and modify user data and application settings.\"\n}\n"
  },
  {
    "path": "pkg/detectors/rownd/rownd_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage rownd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRownd_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tid := testSecrets.MustGetField(\"ROWND_APP_ID\")\n\tkey := testSecrets.MustGetField(\"ROWND\")\n\tsecret := testSecrets.MustGetField(\"ROWND_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"ROWND_SECRET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rownd secret %s within rownd id %s and rownd key %s\", secret, id, key)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Rownd,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rownd secret %s within rownd id %s and rownd key %s but not valid\", inactiveSecret, id, key)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Rownd,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Rownd.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Rownd.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rownd/rownd_test.go",
    "content": "package rownd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey      = \"rvhpdb59-6ni1-60q3-jqng-orz5edgkq333\"\n\tinvalidKey    = \"rvhpdb59?6ni1-60q3-jqng-orz5edgkq333\"\n\tvalidSecret   = \"uuzywot3x1nlfeogbyft1r9ojb234smw0fnmn7s8kj1s1cc1\"\n\tinvalidSecret = \"uuzywot3x1nlfeogbyft1r9o?b234smw0fnmn7s8kj1s1cc1\"\n\tvalidId       = \"174079606475951818\"\n\tinvalidId     = \"17?079606475951818\"\n\tkeyword       = \"rownd\"\n)\n\nfunc TestRownd_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword rownd\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validSecret, keyword, validId),\n\t\t\twant:  []string{validKey + validSecret},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidSecret, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rubygems/rubygems.go",
    "content": "package rubygems\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(rubygems_[a-zA0-9]{48})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"rubygems\"}\n}\n\n// FromData will find and optionally verify RubyGems secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_RubyGems,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://rubygems.org/api/v1/gems.json\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"*/*\")\n\t\t\treq.Header.Add(\"Authorization\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 || res.StatusCode == http.StatusForbidden {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_RubyGems\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"RubyGems is a package manager for the Ruby programming language. RubyGems API keys can be used to publish and manage gems (libraries) on the RubyGems platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/rubygems/rubygems_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage rubygems\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRubyGems_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"RUBYGEMS_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"RUBYGEMS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rubygems secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RubyGems,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a rubygems secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RubyGems,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"RubyGems.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"RubyGems.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/rubygems/rubygems_test.go",
    "content": "package rubygems\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"rubygems_6gbxymmsz3xnk5dg1jc8lm9xekr652c1hr0xt5dp33pn8olh\"\n\tinvalidPattern = \"rubygems_6gbxymmsz3xnk5dg1jc?lm9xekr652c1hr0xt5dp33pn8olh\"\n\tkeyword        = \"rubygems\"\n)\n\nfunc TestRubyGems_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword rubygems\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/runrunit/runrunit.go",
    "content": "package runrunit\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat       = regexp.MustCompile(detectors.PrefixRegex([]string{\"runrunit\"}) + `\\b([0-9a-f]{32})\\b`)\n\tuserTokenPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"runrunit\"}) + `\\b([0-9A-Za-z]{18,20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"runrunit\"}\n}\n\n// FromData will find and optionally verify RunRunIt secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tuserTokenMatches := userTokenPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, userTokenMatch := range userTokenMatches {\n\t\t\tresUserTokenMatch := strings.TrimSpace(userTokenMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_RunRunIt,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://runrun.it/api/v1.0/users\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"App-Key\", resMatch)\n\t\t\t\treq.Header.Add(\"User-Token\", resUserTokenMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_RunRunIt\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"RunRunIt is a project management tool. App-Key and User-Token can be used to access and modify project data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/runrunit/runrunit_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage runrunit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestRunRunIt_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"RUNRUNIT\")\n\tinactiveSecret := testSecrets.MustGetField(\"RUNRUNIT_INACTIVE\")\n\tuserToken := testSecrets.MustGetField(\"RUNRUNIT_USERTOKEN\")\n\tinactiveUserToken := testSecrets.MustGetField(\"RUNRUNIT_USERTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a runrunit secret %s with runrunit user token %s within\", secret, userToken)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RunRunIt,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a runrunit secret %s with runrunit user token %s within but not valid\", inactiveSecret, inactiveUserToken)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_RunRunIt,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"RunRunIt.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"RunRunIt.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/runrunit/runrunit_test.go",
    "content": "package runrunit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey         = \"fdb658d66e09f2caaf9d452e544c3253\"\n    invalidKey       = \"fdb658d66e09f2c?af9d452e544c3253\"\n    validUserToken   = \"iUm7PtHOhdZxnn4jb5YR\"\n    invalidUserToken = \"iUm7PtHOhd?xnn4jb5YR\"\n    keyword          = \"runrunit\"\n)\n\nfunc TestRunRunIt_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword runrunit\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validUserToken),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidUserToken),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/saladcloudapikey/saladcloudapikey.go",
    "content": "package saladcloudapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\tapiKey        = regexp.MustCompile(`\\b(salad_cloud_[0-9A-Za-z]{1,7}_[0-9A-Za-z]{7,235})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"salad_cloud_\"}\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"SaladCloud is a cloud service provider offering GPUs and NPUs for high-performance computing. SaladCloud API keys can be used to access and modify compute and data resources in your account.\"\n}\n\n// FromData will find and optionally verify SaladCloud API Key secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range apiKey.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SaladCloudApiKey,\n\t\t\tRaw:          []byte(match),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/saladcloudapikey/\",\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.salad.com/api/public\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Set(\"Salad-Api-Key\", token)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusNoContent:\n\t\treturn true, nil, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SaladCloudApiKey\n}\n"
  },
  {
    "path": "pkg/detectors/saladcloudapikey/saladcloudapikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage saladcloudapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSaladCloudApiKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SALADCLOUDAPIKEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"SALADCLOUDAPIKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a SaladCloud API Key secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SaladCloudApiKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a SaladCloud API Key secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SaladCloudApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a SaladCloud API Key secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SaladCloudApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a SaladCloud API Key secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SaladCloudApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SaladCloudApiKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"SaladCloudApiKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/saladcloudapikey/saladcloudapikey_test.go",
    "content": "package saladcloudapikey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestSaladCloudApiKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"organization API key\",\n\t\t\tinput: \"api_key = 'salad_cloud_org_zYLYVpmHJ3oksnZ0l9RHJCf1ib2QvJOJztWukwYjtjB1kDIGP'\",\n\t\t\twant:  []string{\"salad_cloud_org_zYLYVpmHJ3oksnZ0l9RHJCf1ib2QvJOJztWukwYjtjB1kDIGP\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"user API key\",\n\t\t\tinput: \"api_key = 'salad_cloud_user_HZdqHUSBFLJI7LZjo1UcDORHIdi8wet37OMP01YTep82tdimF'\",\n\t\t\twant:  []string{\"salad_cloud_user_HZdqHUSBFLJI7LZjo1UcDORHIdi8wet37OMP01YTep82tdimF\"},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/salesblink/salesblink.go",
    "content": "package salesblink\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"salesblink\"}) + `\\b(key-[a-zA-Z0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"salesblink\"}\n}\n\n// FromData will find and optionally verify Salesblink secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Salesblink,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://run.salesblink.io/api/public/lists\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Salesblink\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Salesblink is a sales outreach tool that helps automate email, LinkedIn, and cold call outreach. Salesblink API keys can be used to access and manage sales outreach campaigns and data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/salesblink/salesblink_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage salesblink\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSalesblink_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SALESBLINK_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SALESBLINK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a salesblink secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Salesblink,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a salesblink secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Salesblink,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Salesblink.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Salesblink.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/salesblink/salesblink_test.go",
    "content": "package salesblink\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"key-8DbqWq7r9XKxhUqp3THfA1VMxggreCczfejJsT3UFGISAcEGrrhiA6Ju453dMUXB\"\n\tinvalidPattern = \"key-8DbqWq7r9XKxhUqp3THfA1VMxggreC?zfejJsT3UFGISAcEGrrhiA6Ju453dMUXB\"\n\tkeyword        = \"salesblink\"\n)\n\nfunc TestSalesblink_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword salesblink\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/salescookie/salescookie.go",
    "content": "package salescookie\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"salescookie\"}) + `\\b([a-zA-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"salescookie\"}\n}\n\n// FromData will find and optionally verify Salescookie secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Salescookie,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\"date\":\"2021-07-04T02:47:42.1442597Z\",\"uniqueId\":\"id-123\",\"revenue\":1.3,\"profit\":-2.5,\"currency\":\"USD\",\"transactionStatus\":\"closed won\",\"customer\":\"Candy By Mail\",\"product\":\"Lemon Cake\",\"owner1\":\"John Doe\",\"owner2\":\"Jane Doe\",\"owner3\":\"Bob Smith\",\"team1\":\"USA\",\"team2\":\"Washington\",\"team3\":\"98004\",\"quantity\":3,\"costPerUnit\":3.14,\"taxes\":6.54,\"otherText1\":\"additional data\",\"otherText2\":\"more data\",\"otherText3\":\"even more data\",\"otherNumeric1\":123.45,\"otherNumeric2\":54.321,\"otherNumeric3\":-98.76,\"otherDate1\":\"2019-01-03T06:03:01Z\",\"otherDate2\":\"2019-05-10T08:05:09Z\",\"otherDate3\":\"2019-09-04T12:17:33Z\"}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://salescookie.com/app/Api/CreateTransaction\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-ApiKey\", resMatch)\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Salescookie\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Salescookie is a sales performance management tool. Salescookie API keys can be used to create and manage transactions within the platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/salescookie/salescookie_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage salescookie\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSalescookie_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SALESCOOKIE\")\n\tinactiveSecret := testSecrets.MustGetField(\"SALESCOOKIE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a salescookie secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Salescookie,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a salescookie secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Salescookie,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Salescookie.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Salescookie.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/salescookie/salescookie_test.go",
    "content": "package salescookie\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"4nXYKv]mHDeom5esert30Ea7]4QEfMg6\"\n\tinvalidPattern = \"4nXYKv]mHD?om5esert30Ea7]4QEfMg6\"\n\tkeyword        = \"salescookie\"\n)\n\nfunc TestSalescookie_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword salescookie\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/salesflare/salesflare.go",
    "content": "package salesflare\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"salesflare\"}) + `\\b([a-zA-Z0-9_]{45})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"salesflare\"}\n}\n\n// FromData will find and optionally verify Salesflare secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Salesflare,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.salesflare.com/me/contacts\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Salesflare\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Salesflare is a simple yet powerful CRM tool for small businesses. Salesflare API keys can be used to access and manage customer data and interactions.\"\n}\n"
  },
  {
    "path": "pkg/detectors/salesflare/salesflare_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage salesflare\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSalesflare_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SALESFLARE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SALESFLARE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a salesflare secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Salesflare,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a salesflare secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Salesflare,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Salesflare.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Salesflare.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/salesflare/salesflare_test.go",
    "content": "package salesflare\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"7dgVfsiOYJhYmw5aQc9DfE6V5jLPuhN2LzsTkhn32KRaZ\"\n\tinvalidPattern = \"7dgVfsiOYJhYmw5aQc9DfE?V5jLPuhN2LzsTkhn32KRaZ\"\n\tkeyword        = \"salesflare\"\n)\n\nfunc TestSalesflare_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword salesflare\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/salesforce/salesforce.go",
    "content": "package salesforce\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nconst (\n\tcurrentVersion = \"58.0\" // current Salesforce version\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\taccessTokenPat = regexp.MustCompile(`\\b00[a-zA-Z0-9]{13}![a-zA-Z0-9_.]{96}\\b`)\n\tinstancePat    = regexp.MustCompile(`\\bhttps://[0-9a-zA-Z-\\.]{1,100}\\.my\\.salesforce\\.com\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"salesforce\"}\n}\n\n// FromData will find and optionally verify Salesforce secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tinstanceMatches := instancePat.FindAllStringSubmatch(dataStr, -1)\n\ttokenMatches := accessTokenPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, instance := range instanceMatches {\n\n\t\tinstanceMatch := strings.TrimSpace(instance[0])\n\n\t\tfor _, token := range tokenMatches {\n\n\t\t\ttokenMatch := strings.TrimSpace(token[0])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Salesforce,\n\t\t\t\tRaw:          []byte(tokenMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", instanceMatch+\"/services/data/v\"+currentVersion+\"/query?q=SELECT+name+from+Account\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Set(\"Authorization\", \"Bearer \"+tokenMatch)\n\n\t\t\t\tres, err := client.Do(req)\n\n\t\t\t\tif err != nil {\n\t\t\t\t\t// End execution, append Detector Result if request fails to prevent panic on response body checks\n\t\t\t\t\ts1.SetVerificationError(err, tokenMatch)\n\t\t\t\t\tresults = append(results, s1)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tverifiedBodyResponse, err := common.ResponseContainsSubstring(res.Body, \"records\")\n\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && verifiedBodyResponse {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode >= 200 && res.StatusCode < 300 && !verifiedBodyResponse {\n\t\t\t\t\ts1.Verified = false\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\ts1.Verified = false\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"request to %v returned status %d with error %+v\", res.Request.URL, res.StatusCode, err)\n\t\t\t\t\ts1.SetVerificationError(err, tokenMatch)\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Salesforce\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Salesforce is a cloud-based software company that provides customer relationship management (CRM) service. Salesforce access tokens can be used to authenticate and interact with Salesforce APIs.\"\n}\n"
  },
  {
    "path": "pkg/detectors/salesforce/salesforce_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage salesforce\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSalesforce_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tinstance := testSecrets.MustGetField(\"SALESFORCE_INSTANCE\")\n\t// Salesforce secrets are not valid for long, so we may need to regenerate them.\n\t// Steps to regenerate:\n\t// 1. Install Salesforce CLI\n\t// 2. Authenticate with `sfdx org web login`. This will open a browser window. Use the credentials in the detection test accounts 1Pass vault.\n\t// 3. Run `sfdx org display --targetusername zubairkhan@trufflesec.com`to obtain new token.\n\t// Source: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/quickstart_oauth.htm\n\ttoken := testSecrets.MustGetField(\"SALESFORCE_TOKEN\")\n\tinactiveToken := testSecrets.MustGetField(\"SALESFORCE_INACTIVE_TOKEN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a salesforce secret within %s for %s\", token, instance)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Salesforce,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a salesforce secret %s within but not valid for %s\", inactiveToken, instance)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Salesforce,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a salesforce secret within %s for %s\", token, instance)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Salesforce,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a salesforce secret within %s for %s\", token, instance)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Salesforce,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Salesforce.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\" wantVerificationError = %v, verification error = %v,\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Salesforce.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/salesforce/salesforce_test.go",
    "content": "package salesforce\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validAccessToken   = \"00MMW9rRmrlVvBi!gZTTPUfk_nkghR_01qsw3NdcHTeDF8dQKWJmiOcfSjfdymrqXH0_vMci6VmxHzrlw07JSfeyMJrA_N89fUJU9vrAYWn_isTE\"\n    invalidAccessToken = \"00MMW9rRmrlVvBi!gZTTPUfk_nkghR_01qsw3NdcHTeDF8dQKWJmiOcf?jfdymrqXH0_vMci6VmxHzrlw07JSfeyMJrA_N89fUJU9vrAYWn_isTE\"\n    validInstance      = \"https://wDIMT.HmGz15hePYJBiaiG4leH6y.my.salesforce.com\"\n    invalidInstance    = \"https://wDIMT.HmGz15hePYJBi?iG4leH6y.my.salesforce.com\"\n    keyword            = \"salesforce\"\n)\n\nfunc TestSalesforce_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword salesforce\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validAccessToken, keyword, validInstance),\n\t\t\twant:  []string{validAccessToken},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidAccessToken, keyword, invalidInstance),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/salesforceoauth2/salesforceoauth2.go",
    "content": "package salesforceoauth2\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tinstancePat       = regexp.MustCompile(`\\b(?:https?://)?([0-9a-zA-Z\\-\\.]{1,100}\\.my\\.salesforce\\.com)\\b`)\n\tconsumerKeyPat    = regexp.MustCompile(`\\b(3MVG9[0-9a-zA-Z._+/=]{80,251})`)\n\tconsumerSecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"salesforce\", \"consumer\", \"secret\"}) + `\\b([A-Za-z0-9+/=.]{64}|[0-9]{19})\\b`)\n\n\tinvalidHosts = simple.NewCache[struct{}]()\n\terrNoHost    = errors.New(\"no such host\")\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"salesforce\", \"3MVG9\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SalesforceOauth2\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Salesforce is a customer relationship management (CRM) platform that provides a suite of applications and a platform for custom development. Its APIs use OAuth 2.0, and credentials like the Consumer Key and Secret are used to grant applications access to an organization's data.\"\n}\n\n// FromData will find and optionally verify Salesforceoauth2 secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueInstanceMatches, uniqueKeyMatches, uniqueSecretMatches := make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{})\n\tfor _, match := range instancePat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueInstanceMatches[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range consumerKeyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeyMatches[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range consumerSecretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueSecretMatches[match[1]] = struct{}{}\n\t}\n\n\t// If we are missing any of the three components, we cannot form a valid credential.\n\tif len(uniqueInstanceMatches) == 0 || len(uniqueKeyMatches) == 0 || len(uniqueSecretMatches) == 0 {\n\t\treturn nil, nil\n\t}\n\ndomainLoop:\n\tfor domain := range uniqueInstanceMatches {\n\t\tif invalidHosts.Exists(domain) {\n\t\t\tcontinue domainLoop\n\t\t}\n\n\t\tfor key := range uniqueKeyMatches {\n\t\t\tfor secret := range uniqueSecretMatches {\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SalesforceOauth2,\n\t\t\t\t\tRaw:          []byte(secret),\n\t\t\t\t\tRawV2:        fmt.Appendf([]byte{}, \"%s:%s:%s\", domain, key, secret),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tisVerified, verificationErr := s.verifyMatch(ctx, s.getClient(), domain, key, secret)\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\tif verificationErr != nil {\n\t\t\t\t\t\tif errors.Is(verificationErr, errNoHost) {\n\t\t\t\t\t\t\tinvalidHosts.Set(domain, struct{}{})\n\t\t\t\t\t\t\tcontinue domainLoop\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\ts1.SetVerificationError(verificationErr, secret)\n\t\t\t\t\t}\n\n\t\t\t\t\tif isVerified {\n\t\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\t\"domain\":        domain,\n\t\t\t\t\t\t\t\"client_id\":     key,\n\t\t\t\t\t\t\t\"client_secret\": secret,\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\n// verifyMatch attempts to validate a Salesforce Client Credentials pair.\nfunc (s Scanner) verifyMatch(ctx context.Context, client *http.Client, domain, key, secret string) (bool, error) {\n\tform := url.Values{}\n\tform.Set(\"grant_type\", \"client_credentials\")\n\tform.Set(\"client_id\", key)\n\tform.Set(\"client_secret\", secret)\n\n\tauthURL := fmt.Sprintf(\"https://%s/services/oauth2/token\", domain)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, authURL, strings.NewReader(form.Encode()))\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to create request: %w\", err)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"no such host\") {\n\t\t\treturn false, errNoHost\n\t\t}\n\n\t\treturn false, fmt.Errorf(\"failed to perform request: %w\", err)\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusBadRequest:\n\t\treturn s.handleBadRequest(resp)\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\n// Reusable error response struct\ntype oauthErrorResponse struct {\n\tError            string `json:\"error\"`\n\tErrorDescription string `json:\"error_description\"`\n}\n\n// handleBadRequest processes 400 responses to determine if credentials are invalid or misconfigured\nfunc (s Scanner) handleBadRequest(resp *http.Response) (bool, error) {\n\tbodyBytes, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to read error response body: %w\", err)\n\t}\n\n\tvar errorResponse oauthErrorResponse\n\tif err := json.Unmarshal(bodyBytes, &errorResponse); err != nil {\n\t\treturn false, fmt.Errorf(\"failed to unmarshal error response: %w (body: %s)\", err, string(bodyBytes))\n\t}\n\n\tswitch errorResponse.Error {\n\tcase \"invalid_client_id\", \"invalid_client\":\n\t\t// This definitively means the key is invalid\n\t\t// Or the key is valid but the secret is wrong.\n\t\treturn false, nil\n\tcase \"invalid_grant\":\n\t\t// This can mean the secret is wrong OR the user isn't configured with the app secret.\n\t\t// We'll treat it as a VerificationError because the key might be valid but misconfigured.\n\t\treturn false, fmt.Errorf(\"verification failed: %s\", errorResponse.ErrorDescription)\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected OAuth error: %s - %s\", errorResponse.Error, errorResponse.ErrorDescription)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/salesforceoauth2/salesforceoauth2_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage salesforceoauth2\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSalesforceOauth2_FromData(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\tdefer cancel()\n\n\t// Fetch the correct secrets needed for the OAuth2 Client Credentials flow.\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tdomain := testSecrets.MustGetField(\"SALESFORCE_OAUTH2_DOMAIN\")\n\tconsumerKey := testSecrets.MustGetField(\"SALESFORCE_OAUTH2_CONSUMER_KEY\")\n\tconsumerSecret := testSecrets.MustGetField(\"SALESFORCE_OAUTH2_CONSUMER_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"SALESFORCE_OAUTH2_INACTIVE_CONSUMER_SECRET\")\n\n\ttype args struct {\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found one valid trio, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"domain: %s, key: %s, secret: %s\", domain, consumerKey, consumerSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SalesforceOauth2,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found one invalid trio, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"domain: %s, key: %s, secret: %s\", domain, consumerKey, inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SalesforceOauth2,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false, // An invalid secret is a clean \"no\", not an error.\n\t\t},\n\t\t{\n\t\t\tname: \"multiple findings, one verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"domain: %s, key: %s, valid_secret: %s, invalid_secret: %s\", domain, consumerKey, consumerSecret, inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SalesforceOauth2,\n\t\t\t\t\tVerified:     true, // The valid secret combination\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SalesforceOauth2,\n\t\t\t\t\tVerified:     false, // The invalid secret combination\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found (missing a component)\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"key: %s, secret: %s\", consumerKey, consumerSecret)), // No domain\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"domain: %s, key: %s, secret: %s\", domain, consumerKey, consumerSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SalesforceOauth2,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unexpected api response\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"domain: %s, key: %s, secret: %s\", domain, consumerKey, consumerSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SalesforceOauth2,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(context.Background(), tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Since the order of results can vary with maps, we use a more robust comparison.\n\t\t\t// This checks that for every `want` result, there is a matching `got` result.\n\t\t\topts := []cmp.Option{\n\t\t\t\tcmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"ExtraData\", \"VerificationFromCache\", \"primarySecret\"),\n\t\t\t\tcmpopts.SortSlices(func(a, b detectors.Result) bool { return a.Verified }),\n\t\t\t}\n\t\t\tif diff := cmp.Diff(tt.want, got, opts...); diff != \"\" {\n\t\t\t\tt.Errorf(\"FromData() results mismatch (-want +got):\\n%s\", diff)\n\t\t\t}\n\n\t\t\t// Also check that verification errors match expectations across all results.\n\t\t\tvar gotErr bool\n\t\t\tfor _, r := range got {\n\t\t\t\tif r.VerificationError() != nil {\n\t\t\t\t\tgotErr = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif gotErr != tt.wantVerificationErr {\n\t\t\t\tt.Errorf(\"wantVerificationErr = %v, but got an error state of %v\", tt.wantVerificationErr, gotErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/salesforceoauth2/salesforceoauth2_test.go",
    "content": "package salesforceoauth2\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestSalesforceOauth2_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"simple case: one of each component\",\n\t\t\tinput: `\n\t\t\t\tsalesforce_domain = \"my-test-org-123.my.salesforce.com\"\n\t\t\t\tsalesforce_key = \"3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7\"\n\t\t\t\tsalesforce_secret = \"A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\"\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"my-test-org-123.my.salesforce.com:3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7:A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"combinatorial test: multiple keys, one domain and secret\",\n\t\t\tinput: `\n\t\t\t\tsalesforce_domain = \"my-test-org-123.my.salesforce.com\"\n\t\t\t\tsalesforce_key1 = \"3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7\"\n\t\t\t\tsalesforce_key2 = \"3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54ElfWkce6JTB1LmsxhzVaaaaVZ8\"\n\t\t\t\tsalesforce_secret = \"A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\"\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"my-test-org-123.my.salesforce.com:3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7:A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\",\n\t\t\t\t\"my-test-org-123.my.salesforce.com:3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54ElfWkce6JTB1LmsxhzVaaaaVZ8:A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"combinatorial test: two domains, two keys, one secret\",\n\t\t\tinput: `\n\t\t\t\tsalesforce_domain1 = \"my-test-org-123.my.salesforce.com\"\n\t\t\t\tsalesforce_domain2 = \"another-dev-org.my.salesforce.com\"\n\t\t\t\tsalesforce_key1 = \"3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7\"\n\t\t\t\tsalesforce_key2 = \"3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54ElfWkce6JTB1LmsxhzVaaaaVZ8\"\n\t\t\t\tsalesforce_secret = \"A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\"\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"my-test-org-123.my.salesforce.com:3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7:A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\",\n\t\t\t\t\"my-test-org-123.my.salesforce.com:3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54ElfWkce6JTB1LmsxhzVaaaaVZ8:A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\",\n\t\t\t\t\"another-dev-org.my.salesforce.com:3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7:A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\",\n\t\t\t\t\"another-dev-org.my.salesforce.com:3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54ElfWkce6JTB1LmsxhzVaaaaVZ8:A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"negative case: missing secret component\",\n\t\t\tinput: `salesforce_domain = \"my-test-org-123.my.salesforce.com\", salesforce_key = \"3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7\"`,\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"negative case: invalid key format\",\n\t\t\tinput: `salesforce_domain = \"my-test-org-123.my.salesforce.com\", salesforce_key = \"invalid-key-format\", salesforce_secret = \"A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\"`,\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"negative case: invalid secret format (too short)\",\n\t\t\tinput: `salesforce_domain = \"my-test-org-123.my.salesforce.com\", salesforce_key = \"3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7\", salesforce_secret = \"ABCDEFG\"`,\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/salesforcerefreshtoken/salesforcerefreshtoken.go",
    "content": "package salesforcerefreshtoken\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\nvar (\n\t// Ensure the Scanner satisfies the interface at compile time.\n\t_             detectors.Detector = (*Scanner)(nil)\n\tdefaultClient                    = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\trefreshTokenPat   = regexp.MustCompile(`(?i)\\b(5AEP861[a-zA-Z0-9._=]{80,})\\b`)\n\tconsumerKeyPat    = regexp.MustCompile(`\\b(3MVG9[0-9a-zA-Z._+/=]{80,251})`)\n\tconsumerSecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"salesforce\", \"consumer\", \"secret\"}) + `\\b([A-Za-z0-9+/=.]{64}|[0-9]{19})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"salesforce\", \"5AEP861\", \"3MVG9\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SalesforceRefreshToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Salesforce is a customer relationship management (CRM) platform that provides a suite of applications and APIs. OAuth 2.0 refresh tokens are long-lived credentials that allow applications to obtain new access tokens without requiring user interaction. They enable continuous access to an organization's data and must be handled securely.\"\n}\n\n// FromData will find and optionally verify Salesforceoauth2 refresh token in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueTokenMatches, uniqueKeyMatches, uniqueSecretMatches := make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{})\n\tfor _, match := range refreshTokenPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokenMatches[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range consumerKeyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeyMatches[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range consumerSecretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueSecretMatches[match[1]] = struct{}{}\n\t}\n\n\tfor refreshToken := range uniqueTokenMatches {\n\t\tfor key := range uniqueKeyMatches {\n\t\t\tfor secret := range uniqueSecretMatches {\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SalesforceRefreshToken,\n\t\t\t\t\tRaw:          []byte(refreshToken),\n\t\t\t\t\tRawV2:        fmt.Appendf([]byte{}, \"%s:%s:%s\", refreshToken, key, secret),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tisVerified, verificationErr := s.verifyMatch(ctx, s.getClient(), refreshToken, key, secret)\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\tif verificationErr != nil {\n\t\t\t\t\t\ts1.SetVerificationError(verificationErr, secret)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\n// verifyMatch attempts to validate a Salesforce Refresh Token.\nfunc (s Scanner) verifyMatch(ctx context.Context, client *http.Client, refreshToken, key, secret string) (bool, error) {\n\tform := url.Values{}\n\tform.Set(\"token\", refreshToken)\n\tform.Set(\"client_id\", key)\n\tform.Set(\"client_secret\", secret)\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://login.salesforce.com/services/oauth2/introspect\",\n\t\tstrings.NewReader(form.Encode()))\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to create request: %w\", err)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to perform request: %w\", err)\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tbodyBytes, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false, fmt.Errorf(\"failed to read error response body: %w\", err)\n\t\t}\n\t\tvar apiResp introspectAPIResponse\n\t\tif err := json.Unmarshal(bodyBytes, &apiResp); err != nil {\n\t\t\treturn false, fmt.Errorf(\"failed to unmarshal error response: %w (body: %s)\", err, string(bodyBytes))\n\t\t}\n\n\t\tif !apiResp.Active {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn true, nil\n\n\tcase http.StatusBadRequest:\n\t\t// Salesforce returns a 400 Bad Request if the consumer key/secret are valid, but the refresh token is invalid or missing\n\t\treturn false, nil\n\n\tcase http.StatusUnauthorized:\n\t\t// Salesforce returns a 401 Unauthorized if the consumer key/secret are invalid.\n\t\t// This means that a 401 can also occur when the refresh token is valid but the consumer key or secret is incorrect.\n\t\treturn false, fmt.Errorf(\"unauthorized: invalid client credentials\")\n\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\ntype introspectAPIResponse struct {\n\tActive bool `json:\"active\"`\n}\n"
  },
  {
    "path": "pkg/detectors/salesforcerefreshtoken/salesforcerefreshtoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage salesforcerefreshtoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSalesforcerefreshtoken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\trefreshToken := testSecrets.MustGetField(\"SALESFORCE_REFRESH_TOKEN\")\n\tconsumerKey := testSecrets.MustGetField(\"SALESFORCE_REFRESH_TOKEN_KEY\")\n\tconsumerSecret := testSecrets.MustGetField(\"SALESFORCE_REFRESH_TOKEN_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"SALESFORCE_REFRESH_TOKEN_INACTIVE_SECRET\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found one valid trio, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"refresh_token: %s, key: %s, secret: %s\", refreshToken, consumerKey, consumerSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SalesforceRefreshToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found one invalid trio, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"refresh_token: %s, key: %s, secret: %s\", refreshToken, consumerKey, inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SalesforceRefreshToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true, // Verification fails because the credentials are invalid and we can't verify the refresh token.\n\t\t},\n\t\t{\n\t\t\tname: \"multiple findings, one verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(`\n\t\t\t\trefresh_token: %s, key: %s, valid_secret: %s, \n\t\t\t\tinvalid_refresh_token: 5Aep861eN26Sp9j0R5QPjh0AAAABBBBCCCCjcNqfo5kVBplkpP5tzyWXyVGAivx26AAAABBBBjYE133BBBBAAAA`,\n\t\t\t\t\trefreshToken, consumerKey, consumerSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SalesforceRefreshToken,\n\t\t\t\t\tVerified:     true, // The valid refresh token combination\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SalesforceRefreshToken,\n\t\t\t\t\tVerified:     false, // The invalid refresh token combination\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found (missing a component)\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"key: %s, secret: %s\", consumerKey, consumerSecret)), // No refresh token\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"refresh_token: %s, key: %s, secret: %s\", refreshToken, consumerKey, consumerSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SalesforceRefreshToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unexpected api response\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"refresh_token: %s, key: %s, secret: %s\", refreshToken, consumerKey, consumerSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SalesforceRefreshToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Since the order of results can vary with maps, we use a more robust comparison.\n\t\t\t// This checks that for every `want` result, there is a matching `got` result.\n\t\t\topts := []cmp.Option{\n\t\t\t\tcmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"ExtraData\", \"VerificationFromCache\", \"primarySecret\"),\n\t\t\t\tcmpopts.SortSlices(func(a, b detectors.Result) bool { return a.Verified }),\n\t\t\t}\n\t\t\tif diff := cmp.Diff(tt.want, got, opts...); diff != \"\" {\n\t\t\t\tt.Errorf(\"FromData() results mismatch (-want +got):\\n%s\", diff)\n\t\t\t}\n\n\t\t\t// Also check that verification errors match expectations across all results.\n\t\t\tvar gotErr bool\n\t\t\tfor _, r := range got {\n\t\t\t\tif r.VerificationError() != nil {\n\t\t\t\t\tgotErr = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif gotErr != tt.wantVerificationErr {\n\t\t\t\tt.Errorf(\"wantVerificationErr = %v, but got an error state of %v\", tt.wantVerificationErr, gotErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/salesforcerefreshtoken/salesforcerefreshtoken_test.go",
    "content": "package salesforcerefreshtoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestSalesforcerefreshtoken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"simple case: one of each component\",\n\t\t\tinput: `\n\t\t\t\tsalesforce_refresh_token = \"5Aep861eN26Sp9j0R5QPjh0AAAABBBBCCCCjcNqfo5kVBplkpP5tzyWXyVGAivx26AAAABBBBjYE133BBBBAAAA\"\n\t\t\t\tsalesforce_key = \"3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7\"\n\t\t\t\tsalesforce_secret = \"A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\"\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"5Aep861eN26Sp9j0R5QPjh0AAAABBBBCCCCjcNqfo5kVBplkpP5tzyWXyVGAivx26AAAABBBBjYE133BBBBAAAA:3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7:A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"combinatorial test: multiple keys, one refresh token and secret\",\n\t\t\tinput: `\n\t\t\t\tsalesforce_refresh_token = \"5Aep861eN26Sp9j0R5QPjh0AAAABBBBCCCCjcNqfo5kVBplkpP5tzyWXyVGAivx26AAAABBBBjYE133BBBBAAAA\"\n\t\t\t\tsalesforce_key1 = \"3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7\"\n\t\t\t\tsalesforce_key2 = \"3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54ElfWkce6JTB1LmsxhzVaaaaVZ8\"\n\t\t\t\tsalesforce_secret = \"A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\"\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"5Aep861eN26Sp9j0R5QPjh0AAAABBBBCCCCjcNqfo5kVBplkpP5tzyWXyVGAivx26AAAABBBBjYE133BBBBAAAA:3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7:A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\",\n\t\t\t\t\"5Aep861eN26Sp9j0R5QPjh0AAAABBBBCCCCjcNqfo5kVBplkpP5tzyWXyVGAivx26AAAABBBBjYE133BBBBAAAA:3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54ElfWkce6JTB1LmsxhzVaaaaVZ8:A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"combinatorial test: two refresh tokens, two keys, one secret\",\n\t\t\tinput: `\n\t\t\t\tsalesforce_refresh_token1 = \"5Aep861eN26Sp9j0R5QPjh0AAAABBBBCCCCjcNqfo5kVBplkpP5tzyWXyVGAivx26AAAABBBBjYE133BBBBDDDD\"\n\t\t\t\tsalesforce_refresh_token2 = \"5Aep861eN26Sp9j0R5QPjh0AAAABBBBCCCCjcNqfo5kVBplkpP5tzyWXyVGAivx26AAAABBBBjYE133BBBBEEEE\"\n\t\t\t\tsalesforce_key1 = \"3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7\"\n\t\t\t\tsalesforce_key2 = \"3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54ElfWkce6JTB1LmsxhzVaaaaVZ8\"\n\t\t\t\tsalesforce_secret = \"A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\"\n\t\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"5Aep861eN26Sp9j0R5QPjh0AAAABBBBCCCCjcNqfo5kVBplkpP5tzyWXyVGAivx26AAAABBBBjYE133BBBBDDDD:3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7:A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\",\n\t\t\t\t\"5Aep861eN26Sp9j0R5QPjh0AAAABBBBCCCCjcNqfo5kVBplkpP5tzyWXyVGAivx26AAAABBBBjYE133BBBBDDDD:3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54ElfWkce6JTB1LmsxhzVaaaaVZ8:A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\",\n\t\t\t\t\"5Aep861eN26Sp9j0R5QPjh0AAAABBBBCCCCjcNqfo5kVBplkpP5tzyWXyVGAivx26AAAABBBBjYE133BBBBEEEE:3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7:A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\",\n\t\t\t\t\"5Aep861eN26Sp9j0R5QPjh0AAAABBBBCCCCjcNqfo5kVBplkpP5tzyWXyVGAivx26AAAABBBBjYE133BBBBEEEE:3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54ElfWkce6JTB1LmsxhzVaaaaVZ8:A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"negative case: invalid refresh token component\",\n\t\t\tinput: `salesforce_refresh_token = \"5Aep86111\", salesforce_key = \"3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7\"`,\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"negative case: missing refresh token component\",\n\t\t\tinput: `salesforce_key = \"3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7\"`,\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"negative case: missing secret component\",\n\t\t\tinput: `salesforce_refresh_token = \"5Aep861eN26Sp9j0R5QPjh0AAAABBBBCCCCjcNqfo5kVBplkpP5tzyWXyVGAivx26AAAABBBBjYE133BBBBEEEE\", salesforce_key = \"3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7\"`,\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"negative case: invalid key format\",\n\t\t\tinput: `salesforce_refresh_token = \"5Aep861eN26Sp9j0R5QPjh0AAAABBBBCCCCjcNqfo5kVBplkpP5tzyWXyVGAivx26AAAABBBBjYE133BBBBEEEE\", salesforce_key = \"invalid-key-format\", salesforce_secret = \"A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2\"`,\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"negative case: invalid secret format (too short)\",\n\t\t\tinput: `salesforce_refresh_token = \"5Aep861eN26Sp9j0R5QPjh0AAAABBBBCCCCjcNqfo5kVBplkpP5tzyWXyVGAivx26AAAABBBBjYE133BBBBEEEE\", salesforce_key = \"3MVG9dBDux2v1sLoreCoilvmnP337XNeiV01JFJ8uAAVVyH5qX0NPaa0d54El.Wkce6JTB1LmsxhzVaaa.VZ7\", salesforce_secret = \"ABCDEFG\"`,\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/salesmate/salesmate.go",
    "content": "package salesmate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"salesmate\"}) + `\\b([0-9Aa-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\b`)\n\tdomainPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"salesmate\"}) + `\\b([a-z0-9A-Z]{3,22})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"salesmate\"}\n}\n\n// FromData will find and optionally verify Salesmate secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := domainPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Salesmate,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\turl := fmt.Sprintf(\"https://%s.salesmate.io/apis/v3/companies/1?trackingRecentSearch=true\", resIdMatch)\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", url, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\treq.Header.Add(\"sessionToken\", resMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Salesmate\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Salesmate is a customer relationship management (CRM) software. Salesmate keys can be used to access and manage customer data and interactions.\"\n}\n"
  },
  {
    "path": "pkg/detectors/salesmate/salesmate_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage salesmate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSalesmate_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SALESMATE\")\n\tinactiveSecret := testSecrets.MustGetField(\"SALESMATE_INACTIVE\")\n\tdomain := testSecrets.MustGetField(\"SALESMATE_DOMAIN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a salesmate secret %s within salesmateDomain %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Salesmate,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a salesmate secret %s within but not valid salesmateDomain %s\", inactiveSecret, domain)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Salesmate,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Salesmate.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Salesmate.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/salesmate/salesmate_test.go",
    "content": "package salesmate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey      = \"398A3699-a4ae-5e7d-ae08-48ef223f1c45\"\n\tinvalidKey    = \"398A3699?a4ae-5e7d-ae08-48ef223f1c45\"\n\tvalidDomain   = \"dte2oxDZZSbPhyX1\"\n\tinvalidDomain = \"?te2oxDZZSbPhyX?\"\n\tkeyword       = \"salesmate\"\n)\n\nfunc TestSalesmate_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword salesmate\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validDomain),\n\t\t\twant:  []string{validKey, validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidDomain),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sanity/sanity.go",
    "content": "package sanity\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tauthTokenPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"sanity\"}) + `\\b(sk[A-Za-z0-9]{79})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sanity\"}\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Sanity is the modern CMS that transforms content into a competitive advantage. Customize, collaborate, and scale your digital experiences seamlessly.\"\n}\n\n// FromData will find and optionally verify Meraki API Key secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// uniqueMatches will hold unique match values and ensure we only process unique matches found in the data string\n\tvar uniqueMatches = make(map[string]struct{})\n\n\tfor _, match := range authTokenPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Sanity,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tif s.client == nil {\n\t\t\t\ts.client = common.SaneHttpClient()\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifySanityAuthToken(ctx, s.client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Sanity\n}\n\n/*\nverifySanityAuthToken verifies if the passed matched auth token for sanity is active or not.\nauth docs: https://www.sanity.io/docs/http-auth\napi docs: https://www.sanity.io/docs/reference/http/access#tag/permissions/GET/vX/access/permissions/me\n*/\nfunc verifySanityAuthToken(ctx context.Context, client *http.Client, authToken string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.sanity.io/vX/access/permissions/me\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// set the required auth header\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", authToken))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sanity/sanity_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sanity\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSanity_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*10)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SANITY_AUTHTOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SANITY_AUTHTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sanity apikey %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sanity,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    ctx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sanity apikey secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sanity,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Sanity.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Sanity.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sanity/sanity_test.go",
    "content": "package sanity\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `Information used in API calls for sanity\nVariable name | Initial Value\nsanity auth token |skCrESWvpXysjmfakeaMGdMecRnw2mTVURqlVABArKApL1j4SLUhFAKEEizjp7ymM8pebv0ScqyqelbLD\nnetworkId |L_646829496481117067\nserial |`\n\n\tinvalidPattern = \"skCr_SWvpXysjmfakeaMGdMecRnw$mTVURqlVABArKApL1j4SLUhFAKEEizjp7ymM8pebv0ScqyqelbLD\"\n)\n\nfunc TestSanity_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{\"skCrESWvpXysjmfakeaMGdMecRnw2mTVURqlVABArKApL1j4SLUhFAKEEizjp7ymM8pebv0ScqyqelbLD\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"sanity = '%s'\", invalidPattern),\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/satismeterprojectkey/satismeterprojectkey.go",
    "content": "package satismeterprojectkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tprojectPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"satismeter\"}) + `\\b([a-zA-Z0-9]{24})\\b`)\n\ttokenPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"satismeter\"}) + `\\b([A-Za-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"satismeter\"}\n}\n\n// FromData will find and optionally verify SatismeterProjectkey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueProjectMatches, uniqueTokenMatches := make(map[string]struct{}), make(map[string]struct{})\n\tfor _, match := range projectPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueProjectMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor _, match := range tokenPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokenMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor projectID := range uniqueProjectMatches {\n\t\tfor token := range uniqueTokenMatches {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_SatismeterProjectkey,\n\t\t\t\tRaw:          []byte(projectID),\n\t\t\t\tRawV2:        []byte(projectID + token),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, verificationErr := verifySatisMeterApp(ctx, client, projectID, token)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, token)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SatismeterProjectkey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Satismeter is a customer feedback platform. Satismeter project keys can be used to access project-specific data and manage feedback settings.\"\n}\n\nfunc verifySatisMeterApp(ctx context.Context, client *http.Client, projectID, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.satismeter.com/api/users?project=\"+projectID, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tcase http.StatusNotFound:\n\t\t// if project id is not found, api return 401\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/satismeterprojectkey/satismeterprojectkey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage satismeterprojectkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSatismeterProjectkey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tprojectID := testSecrets.MustGetField(\"SATISMETERPROJECTKEY\")\n\tinactiveProjectID := testSecrets.MustGetField(\"SATISMETERPROJECTKEY_INACTIVE\")\n\ttoken := testSecrets.MustGetField(\"SATISMETER_TOKEN\")\n\tinactiveToken := testSecrets.MustGetField(\"SATISMETER_TOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a satismeterprojectkey id %s within satismetertoken %s\", projectID, token)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SatismeterProjectkey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRawV2:        []byte(projectID + token),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a satismeterprojectkey secret %s within satismetertoken: %s\", inactiveProjectID, inactiveToken)), // the secret would satisfy the regex but not pass validation),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SatismeterProjectkey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(inactiveProjectID + inactiveToken),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SatismeterProjectkey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SatismeterProjectkey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/satismeterprojectkey/satismeterprojectkey_test.go",
    "content": "package satismeterprojectkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\tsatismeter_project = satismeter12345678901234 \n\t\tsatusmeter_token = VVVCVDXuoVwRFAKEiCseXmDiaC32jq7x\n\t`\n\tinvalidPattern = \"satismeter45678901234/testing@go\"\n)\n\nfunc TestSatisMeterProjectKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{\"satismeter12345678901234VVVCVDXuoVwRFAKEiCseXmDiaC32jq7x\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"satismeter: %s\", invalidPattern),\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 && test.want != nil {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"expected %d results, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/satismeterwritekey/satismeterwritekey.go",
    "content": "package satismeterwritekey\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\twriteKeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"satismeter\"}) + `\\b([a-z0-9A-Z]{32})\\b`)\n\ttokenPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"satismeter\"}) + `\\b([A-Za-z0-9]{32})\\b`)\n\tprojectPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"satismeter\"}) + `\\b([a-zA-Z0-9]{24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"satismeter\"}\n}\n\n// FromData will find and optionally verify SatismeterWritekey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueProjectMatches, uniqueWriteKeyMatches, uniqueTokenMatches := make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, match := range projectPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueProjectMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor _, match := range writeKeyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueWriteKeyMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor _, match := range tokenPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokenMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor projectID := range uniqueProjectMatches {\n\t\tfor token := range uniqueTokenMatches {\n\t\t\tfor writeKey := range uniqueWriteKeyMatches {\n\t\t\t\t// writekey and token has same pattern, so skip the process when both values are same\n\t\t\t\tif token == writeKey {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SatismeterWritekey,\n\t\t\t\t\tRaw:          []byte(projectID),\n\t\t\t\t\tRawV2:        []byte(projectID + writeKey),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tisVerified, verificationErr := verifySatisMeterWriteKey(ctx, client, projectID, writeKey, token)\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\ts1.SetVerificationError(verificationErr, writeKey)\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SatismeterWritekey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Satismeter is a customer feedback platform. Satismeter Writekeys can be used to send event data to Satismeter's API.\"\n}\n\n// API Docs with payload details: https://support.satismeter.com/hc/en-us/articles/6980481518227-Track-event-API\nfunc verifySatisMeterWriteKey(ctx context.Context, client *http.Client, projectID, writeKey, token string) (bool, error) {\n\tpayload := createPayload(writeKey, projectID)\n\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://app.satismeter.com/api/users?type=track\", bytes.NewBuffer(payload))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusNoContent:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\n// createPayload creates a payload for POST api with writekey and projectID\nfunc createPayload(writeKey, projectID string) []byte {\n\t// create the payload as a map\n\tpayload := map[string]interface{}{\n\t\t\"type\":     \"track\",\n\t\t\"userId\":   \"007\",\n\t\t\"event\":    \"This is the event name\",\n\t\t\"writeKey\": writeKey,\n\t\t\"project\":  projectID,\n\t}\n\n\t// convert payload to JSON\n\tpayloadBytes, _ := json.Marshal(payload)\n\n\treturn payloadBytes\n}\n"
  },
  {
    "path": "pkg/detectors/satismeterwritekey/satismeterwritekey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage satismeterwritekey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSatismeterWritekey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tprojectID := testSecrets.MustGetField(\"SATISMETERPROJECTKEY\")\n\tinactiveProjectID := testSecrets.MustGetField(\"SATISMETERPROJECTKEY_INACTIVE\")\n\ttoken := testSecrets.MustGetField(\"SATISMETER_TOKEN\")\n\tinactiveToken := testSecrets.MustGetField(\"SATISMETER_TOKEN_INACTIVE\")\n\twriteKey := testSecrets.MustGetField(\"SATISMETER_WRITEKEY\")\n\tinactiveWriteKey := testSecrets.MustGetField(\"SATISMETER_WRITEKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a satismeterwritekey project %s satismeter writekey %s and satismeter token %s in here\", projectID, writeKey, token)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SatismeterWritekey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(projectID + token),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SatismeterWritekey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRawV2:        []byte(projectID + writeKey),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a satismeterwritekey project %s satismeter writekey %s and satismeter token %s in here but not valid\", inactiveProjectID, inactiveWriteKey, inactiveToken)), // the secret would satisfy the regex but not pass validation,\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SatismeterWritekey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(inactiveProjectID + inactiveToken),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SatismeterWritekey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(inactiveProjectID + inactiveWriteKey),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SatismeterWritekey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SatismeterWritekey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/satismeterwritekey/satismeterwritekey_test.go",
    "content": "package satismeterwritekey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tcomplexPattern = `\n\tfunc main() {\n\t\turl := \"https://api.example.com/v1/resource\"\n\n\t\t// Create a new request with the secret as a header\n\t\treq, err := http.NewRequest(\"GET\", url, http.NoBody)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"Error creating request:\", err)\n\t\t\treturn\n\t\t}\n\t\t\n\t\tsatismeterToken := os.GetEnv(\"SATISMETER_TOKEN\")\n\t\tif satismeterToken == \"\"{\n\t\t\tsatismeterToken = \"VVVCVDXuoVwRFAKEiCseXmDiaC32jq7x\"\n\t\t}\n\t\tsatismeter_projectID := \"satismeter12345678901234\"\n\t\tsatismeter_writekey := \"0TlknArPMr30WgJtL7SuM9V8LWGuqsxr\"\n\t\treq.Header.Set(\"Authorization\", \"Basic \" + satismeterToken)\n\n\t\t// Perform the request\n\t\tclient := &http.Client{}\n\t\tresp, _ := client.Do(req)\n\t\tdefer resp.Body.Close()\n\n\t\t// Check response status\n\t\tif resp.StatusCode == http.StatusNoContent {\n\t\t\tfmt.Println(\"Request successful!\")\n\t\t} else {\n\t\t\tfmt.Println(\"Request failed with status:\", resp.Status)\n\t\t}\n\t}\n\t`\n\tsecrets = []string{\n\t\t\"satismeter123456789012340TlknArPMr30WgJtL7SuM9V8LWGuqsxr\",\n\t\t\"satismeter12345678901234VVVCVDXuoVwRFAKEiCseXmDiaC32jq7x\",\n\t}\n)\n\nfunc TestSatisMeterWriteKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: complexPattern,\n\t\t\twant:  secrets,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 && test.want != nil {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"expected %d results, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/saucelabs/saucelabs.go",
    "content": "package saucelabs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\t// as per signup page username can be between 2 to 70 characters and must only contain letters, numbers, or characters (_-.)\n\tusernamePat = regexp.MustCompile(detectors.PrefixRegex([]string{\"saucelabs\", \"username\"}) + `\\b([a-zA-Z0-9_\\.-]{2,70})`)\n\tkeyPat      = regexp.MustCompile(detectors.PrefixRegex([]string{\"saucelabs\"}) + `\\b([a-z0-9]{8}\\-[a-z0-9]{4}\\-[a-z0-9]{4}\\-[a-z0-9]{4}\\-[a-z0-9]{12})\\b`)\n\tbaseUrlPat  = regexp.MustCompile(`\\b(api\\.(?:us|eu)-(?:west|east|central)-[0-9].saucelabs\\.com)\\b`)\n\n\tfixedBaseURL = \"api.us-west-1.saucelabs.com\"\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"saucelabs\"}\n}\n\n// FromData will find and optionally verify SauceLabs secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueUserNameMatches, uniqueKeyMatches, uniqueBaseURLMatches := make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, match := range usernamePat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueUserNameMatches[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeyMatches[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range baseUrlPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueBaseURLMatches[match[1]] = struct{}{}\n\t}\n\n\t// if no domain is found, add a fixed domain to try against\n\tif len(uniqueBaseURLMatches) == 0 {\n\t\tuniqueBaseURLMatches[fixedBaseURL] = struct{}{}\n\t}\n\n\tfor userName := range uniqueUserNameMatches {\n\t\tfor key := range uniqueKeyMatches {\n\t\t\tfor baseURL := range uniqueBaseURLMatches {\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SauceLabs,\n\t\t\t\t\tRaw:          []byte(userName),\n\t\t\t\t\tRawV2:        []byte(userName + key),\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t// add base url in extradata to know which base url was used for verification\n\t\t\t\t\t\t\"Base URL\": baseURL,\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tisVerified, verificationErr := verifySauceLabKey(ctx, client, userName, key, baseURL)\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\ts1.SetVerificationError(verificationErr, key)\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SauceLabs\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"A service for cross browser testing, API keys can create and access tests from potentially sensitive internal websites\"\n}\n\nfunc verifySauceLabKey(ctx context.Context, client *http.Client, userName, key, baseURL string) (bool, error) {\n\tapiURL := fmt.Sprintf(\"https://%s/team-management/v1/teams\", baseURL)\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.SetBasicAuth(userName, key)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK, http.StatusForbidden:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code %v\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/saucelabs/saucelabs_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage saucelabs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSauceLabs_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tid := testSecrets.MustGetField(\"SAUCELABS\")\n\tsecret := testSecrets.MustGetField(\"SAUCELABS_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"SAUCELABS_SECRET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(createFakeString(id, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SauceLabs,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRawV2:        []byte(id + secret),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SauceLabs,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(secret + secret),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"username: %s\\n saucelabskey: %s\", id, inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SauceLabs,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(id + inactiveSecret),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SauceLabs,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(inactiveSecret + inactiveSecret),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SauceLabs.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SauceLabs.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc createFakeString(username, key string) string {\n\treturn `\n\tusername: ` + username + `\n\tsaucelabskey: ` + key + `\n\t`\n}\n"
  },
  {
    "path": "pkg/detectors/saucelabs/saucelabs_test.go",
    "content": "package saucelabs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidUsername   = \"nBpeh2YF3tjMNmKAUHrDpfXJZJ.ZUhWmObRC735zdh99LeJh4Kcr_2\"\n\tinvalidUsername = \"?Bpeh?YF3tjMNmKAUHrDpfXJZJ.ZUhWmObRC735zdh99LeJh4Kcr_2\"\n\tvalidKey        = \"9toqmh6r-g2ly-k3fr-fiyr-5jnnewlxy3u9\"\n\tinvalidKey      = \"9toqmh6r?g2ly-k3fr-fiyr-5jnnewlxy3u9\"\n\tvalidBaseUrl    = \"api.eu-west-0,saucelabs.com\"\n\tinvalidBaseUrl  = \"?pi.eu-west-0,saucelabs.co?\"\n\tkeyword         = \"saucelabs\"\n)\n\nfunc TestSauceLabs_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword saucelabs\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, validUsername, keyword, validKey, keyword, validBaseUrl),\n\t\t\twant:  []string{\".com\" + validKey, \"token\" + validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidUsername, keyword, invalidKey, keyword, invalidBaseUrl),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scalewaykey/scalewaykey.go",
    "content": "package scalewaykey\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"scaleway\"}) + `\\b([0-9a-z]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[0-9a-z]{4}-[0-9a-z]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"scaleway\"}\n}\n\n// FromData will find and optionally verify ScalewayKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ScalewayKey,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.scaleway.com/instance/v1/zones/fr-par-1/servers\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-Auth-Token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ScalewayKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Scaleway is a cloud service provider offering various services including virtual instances, storage, and networking. Scaleway API keys can be used to access and manage these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/scalewaykey/scalewaykey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage scalewaykey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestScalewayKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SCALEWAYKEY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SCALEWAYKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scalewaykey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScalewayKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scalewaykey secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScalewayKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ScalewayKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ScalewayKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scalewaykey/scalewaykey_test.go",
    "content": "package scalewaykey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"lj6z1t7m-hdu4-647t-uoju-nlfnizsv7zry\"\n\tinvalidPattern = \"lj6z1t7m?hdu4-647t-uoju-nlfnizsv7zry\"\n\tkeyword        = \"scalewaykey\"\n)\n\nfunc TestScalewayKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword scalewaykey\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scalr/scalr.go",
    "content": "package scalr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"scalr\"}) + `\\b([0-9a-zA-Z._]{136})`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"scalr\"}) + `\\b([0-9a-z]{4,50})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"scalr\"}\n}\n\n// FromData will find and optionally verify Scalr secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Scalr,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\turl := fmt.Sprintf(\"https://%s.scalr.io/api/iacp/v3/agents\", resIdMatch)\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", url, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/vnd.api+json\")\n\t\t\t\treq.Header.Add(\"Prefer\", \"profile=preview\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Scalr\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Scalr is a cloud management platform that allows users to manage and automate cloud infrastructure. Scalr keys can be used to access and manage cloud resources within the Scalr platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/scalr/scalr_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage scalr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestScalr_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SCALR\")\n\tinactiveSecret := testSecrets.MustGetField(\"SCALR_INACTIVE\")\n\tid := testSecrets.MustGetField(\"SCALR_DOMAIN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scalr secret %s within scalr %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Scalr,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Scalr,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scalr secret %s within but not valid scalr %s\", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Scalr,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Scalr,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Scalr.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Scalr.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scalr/scalr_test.go",
    "content": "package scalr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey   = \"bmy2Q33nZnlwZ5wCu_umGbBHF_6547oPzvAGLpUEwhWwOJmU5DUlZIOWZ0Yr7g2Yt4s3ZyyfWxzOlLLOmkGXpzuQ3.gEhgGUHUngyyiPIZjVoYQ.W0O8k4G3JBC705AXkXxANwrF\"\n\tinvalidKey = \"bmy2Q33nZnlwZ5wCu?umGbBHF?6547oPzvAGLpUEwhWwOJmU5DUlZIOWZ0Yr7g2Yt4s3ZyyfWxzOlLLOmkGXpzuQ3?gEhgGUHUngyyiPIZjVoYQ.W0O8k4G3JBC705AXkXxANwrF\"\n\tvalidId    = \"h7bcggvlzhq6t5m47f37jav\"\n\tinvalidId  = \"?7bcgg?lzhq6t5m47f37jav\"\n\tkeyword    = \"scalr\"\n)\n\nfunc TestScalr_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword scalr\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey, validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scrapeowl/scrapeowl.go",
    "content": "package scrapeowl\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"scrapeowl\"}) + `\\b([0-9a-z]{30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"scrapeowl\"}\n}\n\n// FromData will find and optionally verify Scrapeowl secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Scrapeowl,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.scrapeowl.com/v1/scrape?api_key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Scrapeowl\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ScrapeOwl is a web scraping service that allows users to extract data from websites. ScrapeOwl API keys can be used to automate data extraction tasks.\"\n}\n"
  },
  {
    "path": "pkg/detectors/scrapeowl/scrapeowl_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage scrapeowl\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestScrapeowl_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SCRAPEOWL\")\n\tinactiveSecret := testSecrets.MustGetField(\"SCRAPEOWL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scrapeowl secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Scrapeowl,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scrapeowl secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Scrapeowl,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Scrapeowl.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Scrapeowl.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scrapeowl/scrapeowl_test.go",
    "content": "package scrapeowl\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"dwkiwm366z8gqutb1ukv9sztut04be\"\n\tinvalidPattern = \"dwkiwm366z8gqut?1ukv9sztut04be\"\n\tkeyword        = \"scrapeowl\"\n)\n\nfunc TestScrapeowl_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword scrapeowl\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scraperapi/scraperapi.go",
    "content": "package scraperapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"scraperapi\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"scraperapi\"}\n}\n\n// FromData will find and optionally verify ScraperAPI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ScraperAPI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := 15 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.scraperapi.com?api_key=%s&url=google.com\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ScraperAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ScraperAPI is a service that allows users to scrape websites efficiently. ScraperAPI keys can be used to access and scrape data from various websites.\"\n}\n"
  },
  {
    "path": "pkg/detectors/scraperapi/scraperapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage scraperapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestScraperAPI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SCRAPERAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"SCRAPERAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scraperapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScraperAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scraperapi secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScraperAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ScraperAPI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ScraperAPI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scraperapi/scraperapi_test.go",
    "content": "package scraperapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"28ksnqsrn7q12wp11onrmr4bh1dvi7ut\"\n\tinvalidPattern = \"28ksnqsrn7q?2wp11onrmr4bh1dvi7ut\"\n\tkeyword        = \"scraperapi\"\n)\n\nfunc TestScraperAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword scraperapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scraperbox/scraperbox.go",
    "content": "package scraperbox\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"scraperbox\"}) + `\\b([A-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"scraperbox\"}\n}\n\n// FromData will find and optionally verify ScraperBox secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ScraperBox,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := 10 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.scraperbox.com/scrape?token=%s&url=https://google.com\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ScraperBox\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ScraperBox is a web scraping service that allows users to extract data from websites. The provided token can be used to authenticate and perform scraping operations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/scraperbox/scraperbox_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage scraperbox\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestScraperBox_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SCRAPERBOX\")\n\tinactiveSecret := testSecrets.MustGetField(\"SCRAPERBOX_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scraperbox secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScraperBox,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scraperbox secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScraperBox,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ScraperBox.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ScraperBox.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scraperbox/scraperbox_test.go",
    "content": "package scraperbox\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"H8AOZKMTK849QUN2CAKZ8L77YS53I34L\"\n\tinvalidPattern = \"H8AOZKMTK849QUN2?AKZ8L77YS53I34L\"\n\tkeyword        = \"scraperbox\"\n)\n\nfunc TestScraperBox_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword scraperbox\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scrapestack/scrapestack.go",
    "content": "package scrapestack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"scrapestack\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"scrapestack\"}\n}\n\n// FromData will find and optionally verify ScrapeStack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ScrapeStack,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.scrapestack.com/scrape?access_key=%s&url=https://apple.com\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `html`) || strings.Contains(bodyString, `\"info\":\"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption.\"`)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ScrapeStack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ScrapeStack is a web scraping API service. ScrapeStack API keys can be used to scrape web data and access various scraping functionalities.\"\n}\n"
  },
  {
    "path": "pkg/detectors/scrapestack/scrapestack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage scrapestack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestScrapeStack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SCRAPESTACK\")\n\tinactiveSecret := testSecrets.MustGetField(\"SCRAPESTACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scrapestack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScrapeStack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scrapestack secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScrapeStack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ScrapeStack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ScrapeStack.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scrapestack/scrapestack_test.go",
    "content": "package scrapestack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"gdozp26v2hzq91fh8k412n5ik5klj59p\"\n\tinvalidPattern = \"gdozp26v2hzq91fh?k412n5ik5klj59p\"\n\tkeyword        = \"scrapestack\"\n)\n\nfunc TestScrapeStack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword scrapestack\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scrapfly/scrapfly.go",
    "content": "package scrapfly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\t// examples of api valid keys:\n\t//   - scp-live-03b9e7d0d0024e4b8fccc1ffe923e899 (new format)\n\t//   - scp-test-03b9e7d0d0024e4b8fccc1ffe923e899 (new format)\n\t//   - 03b9e7d0d0024e4b8fccc1ffe923e899 (old format)\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"scrapfly\"}) + `\\b([a-z0-9]{32}|scp-(?:live|test)-[a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"scrapfly\"}\n}\n\n// FromData will find and optionally verify Scrapfly secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Scrapfly,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := 10 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.scrapfly.io/scrape?key=%s&url=https://httpbin.org/status/200\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Scrapfly\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Scrapfly is a web scraping service providing APIs to extract and process web data. Scrapfly API keys can be used to access and manipulate this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/scrapfly/scrapfly_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage scrapfly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestScrapfly_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SCRAPFLY\")\n\tinactiveSecret := testSecrets.MustGetField(\"SCRAPFLY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scrapfly secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Scrapfly,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scrapfly secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Scrapfly,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Scrapfly.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Scrapfly.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scrapfly/scrapfly_test.go",
    "content": "package scrapfly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"ajw59xcg6r6uywscubwxh5t3qmji2tp2\"\n\tinvalidPattern = \"ajw59xcg6r6?ywscubwxh5t3qmji2tp2\"\n\tkeyword        = \"scrapfly\"\n)\n\nfunc TestScrapfly_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword scrapfly\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scrapingant/scrapingant.go",
    "content": "package scrapingant\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"scrapingant\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"scrapingant\"}\n}\n\n// FromData will find and optionally verify ScrapingAnt secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ScrapingAnt,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyScrapingAnt(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ScrapingAnt\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ScrapingAnt is a web scraping service that provides API keys to authenticate and make requests to their scraping endpoints.\"\n}\n\nfunc verifyScrapingAnt(ctx context.Context, client *http.Client, apiKey string) (bool, error) {\n\tctx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\tdefer cancel()\n\n\t// do not use google.com as url as it cannot be used under free subscription\n\tapiUrl := fmt.Sprintf(\"https://api.scrapingant.com/v1/general?url=example.com&x-api-key=%s\", apiKey)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, apiUrl, http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tif resp.StatusCode == http.StatusOK {\n\t\treturn true, nil\n\t} else if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {\n\t\treturn false, nil\n\t} else {\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scrapingant/scrapingant_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage scrapingant\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestScrapingAnt_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SCRAPINGANT\")\n\tinactiveSecret := testSecrets.MustGetField(\"SCRAPINGANT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scrapingant secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScrapingAnt,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scrapingant secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScrapingAnt,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ScrapingAnt.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ScrapingAnt.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scrapingant/scrapingant_test.go",
    "content": "package scrapingant\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"uifssyi7y40rf7ryyr3vczj8xbhbv49f\"\n\tinvalidPattern = \"uifss?i7y40rf7ryyr3vczj8xbhbv49f\"\n\tkeyword        = \"scrapingant\"\n)\n\nfunc TestScrapingAnt_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword scrapingant\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scrapingbee/scrapingbee.go",
    "content": "package scrapingbee\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ScrapingBee\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ScrapingBee is a web scraping service that handles headless browsers and proxies for you. ScrapingBee API keys can be used to access and control web scraping tasks.\"\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"scrapingbee\", \"scraping bee\", \"scraping-bee\", \"scraping_bee\"}\n}\n\nvar (\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"scraping[ _-]?bee\"}) + `\\b([A-Z0-9]{80})\\b`)\n)\n\n// FromData will find and optionally verify ScrapingBee secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tm := match[1]\n\t\tif detectors.StringShannonEntropy(m) < 3.5 {\n\t\t\tcontinue\n\t\t}\n\t\tuniqueMatches[m] = struct{}{}\n\t}\n\n\tfor key := range uniqueMatches {\n\t\tr := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ScrapingBee,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tif s.client == nil {\n\t\t\t\ts.client = common.SaneHttpClient()\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyMatch(ctx, s.client, key)\n\t\t\tr.Verified = isVerified\n\t\t\tr.SetVerificationError(verificationErr, key)\n\t\t}\n\n\t\tresults = append(results, r)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://app.scrapingbee.com/api/v1/?api_key=\"+key+\"&url=https://httpbin.org/anything?json&render_js=false\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scrapingbee/scrapingbee_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage scrapingbee\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestScrapingBee_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SCRAPINGBEE\")\n\tinactiveSecret := testSecrets.MustGetField(\"SCRAPINGBEE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scrapingbee secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScrapingBee,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scrapingbee secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScrapingBee,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ScrapingBee.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ScrapingBee.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scrapingbee/scrapingbee_test.go",
    "content": "package scrapingbee\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestScrapingBee_Pattern(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t// True positives\n\t\t{\n\t\t\tname: `valid_query_param`,\n\t\t\tinput: ` #CHANGE API KEY TO CURRENT API KEY ON SCRAPINGBEE BELOW:\n  uri = URI(\"https://app.scrapingbee.com/api/v1/?api_key=VNC7VJ04BQLZWL821KJ4ZLG17ON45K4Y56P59QZMDNZBWRFAS0LIK47I3KFH6AMLUXPHIUIFBDOMIOUE&url=#{url}&stealth_proxy=True&country_code=sg&wait_browser=networkidle2&json_response=True&block_resources=False&block_ads=True&js_scenario=\" + CGI.escape(js_scenario))`,\n\t\t\twant: []string{`VNC7VJ04BQLZWL821KJ4ZLG17ON45K4Y56P59QZMDNZBWRFAS0LIK47I3KFH6AMLUXPHIUIFBDOMIOUE`},\n\t\t},\n\t\t{\n\t\t\tname: `valid_function_comment`,\n\t\t\tinput: `func connectToScrapingBee() {\n\t// API KEY = M977YHXCMPJJ569DSB0B8KSKL9NRU2O2327MIDT55785T8LS9TJGDW4GFMCMOZNRVN3GPSXF0Y6DGC32`,\n\t\t\twant: []string{`M977YHXCMPJJ569DSB0B8KSKL9NRU2O2327MIDT55785T8LS9TJGDW4GFMCMOZNRVN3GPSXF0Y6DGC32`},\n\t\t},\n\t\t{\n\t\t\tname: `valid_csharp`,\n\t\t\tinput: `  class test{\n\n      string BASE_URL = @\"https://app.scrapingbee.com/api/v1/\";\n      string API_KEY = \"2OZ3HPYEUP9LVCN9TSMBEP5OU0C65AXL7MDO76VPYQNVAJW8NU0QUQQPEV7C51XQDLZUUYKZ5TAW2L85\";\n\n      public static string Get(string url)`,\n\t\t\twant: []string{`2OZ3HPYEUP9LVCN9TSMBEP5OU0C65AXL7MDO76VPYQNVAJW8NU0QUQQPEV7C51XQDLZUUYKZ5TAW2L85`},\n\t\t},\n\t\t{\n\t\t\tname: `valid_js1`,\n\t\t\tinput: `  const options = {\n    uri: \"https://app.scrapingbee.com/api/v1?\",\n    api_key: \"34TOQQ77QJALLR07ISPYL4B5EYHW3YLU5GM97GQOCA32BVW3S0S6RTVFCZGTHZ1Q5MHH1Z9GZ0B640LI\",\n\t};`,\n\t\t\twant: []string{`34TOQQ77QJALLR07ISPYL4B5EYHW3YLU5GM97GQOCA32BVW3S0S6RTVFCZGTHZ1Q5MHH1Z9GZ0B640LI`},\n\t\t},\n\t\t{\n\t\t\tname: `valid_js2`,\n\t\t\tinput: `  useEffect(() => {\n    setLoading(true)\n    base.get('https://app.scrapingbee.com/api/v1', {\nparams:{'api_key':'BYZCNNS0SOZCPC4EXD5SXSH0PWAXPWFMZ4SXVEQNEDMKSGBP57K31PJ44V46344XCYN7IARKQWLS0V3X',\n        'url': 'https://www.flipkart.com/search?q=${searchItem}',\n\t\t\t'block_resources': 'false',\n\t\t}\n\t}).then((response) => {`,\n\t\t\twant: []string{`BYZCNNS0SOZCPC4EXD5SXSH0PWAXPWFMZ4SXVEQNEDMKSGBP57K31PJ44V46344XCYN7IARKQWLS0V3X`},\n\t\t},\n\t\t{\n\t\t\tname: `valid_js3`,\n\t\t\tinput: `const scrapingBeeApiKey =\n  \"P5IS953T7OYL5KJG8J3SVPAV5VUJ49L2OXB7HIQDVL8SSG7O9A3J6DQ6CTK65KEAM7L7MQJIEW20ZOCP\"; // Replace 'YOUR_SCRAPING_BEE_API_KEY' with your actual API key`,\n\t\t\twant: []string{`P5IS953T7OYL5KJG8J3SVPAV5VUJ49L2OXB7HIQDVL8SSG7O9A3J6DQ6CTK65KEAM7L7MQJIEW20ZOCP`},\n\t\t},\n\t\t{\n\t\t\tname: `valid_php`,\n\t\t\tinput: `// Set base url & API key\n$BASE_URL = \"https://app.scrapingbee.com/api/v1/?\";\n$API_KEY = \"R4EEK5MWM2GXNK1TZUU9Z0EBA29ZUW7PW12MHI4T1BHSR7GM1G37C5BL2NHLPWC0J6VOQWP5IZJ15QV8\";\n`,\n\t\t\twant: []string{`R4EEK5MWM2GXNK1TZUU9Z0EBA29ZUW7PW12MHI4T1BHSR7GM1G37C5BL2NHLPWC0J6VOQWP5IZJ15QV8`},\n\t\t},\n\t\t{\n\t\t\tname:  `valid_python_sdk`,\n\t\t\tinput: `client = ScrapingBeeClient(api_key='MZ13G1AVV8C5MEYVOIMIGJEPUH0PBSJPYTCO6IUWRZS3BXNOLA4TUP27ZGQ97LS8NRBCO66WF3ZUKSFX')`,\n\t\t\twant:  []string{`MZ13G1AVV8C5MEYVOIMIGJEPUH0PBSJPYTCO6IUWRZS3BXNOLA4TUP27ZGQ97LS8NRBCO66WF3ZUKSFX`},\n\t\t},\n\t\t{\n\t\t\tname: `valid_python_sdk_newline`,\n\t\t\tinput: `def main():\n    client = ScrapingBeeClient(\n        api_key='E1PJA1D78TBTM320Z8O9XS2MTWHTCL1NSJXGFKIZO6TJB4XIM94OSR6KQNU415QB97MYJEP6T3O0IWR3')`,\n\n\t\t\twant: []string{`E1PJA1D78TBTM320Z8O9XS2MTWHTCL1NSJXGFKIZO6TJB4XIM94OSR6KQNU415QB97MYJEP6T3O0IWR3`},\n\t\t},\n\t\t{\n\t\t\tname: `valid_python_notebook`,\n\t\t\tinput: `   \"source\": [\n    \"Every time you call any function there is an HTTPS request to Google's servers. To prevent your servers IP address being locked by Google we should use a service that handles proxy rotation for us. In this case we are using **ScrapingBee API**.\\n\",\n    \"\\n\",\n    \"ScrapingBee API key:\\n\",\n    \"\\n\",\n    \"    QEUXIXLN8OULIISPZ1FXZUCWF7M42ZOUXRV7491R6RYQTFCSV8A4Y1B2YFPCD0HL2X62KPGTHFODSW6G\\n\",\n    \"\\n\",\n    \"NOTE: This API key is available till 08 March 2021 and expires after 200 requests  \\n\",\n    \"NOTE: **this Python package still works out of the box**.\"\n   ]`,\n\n\t\t\twant: []string{`QEUXIXLN8OULIISPZ1FXZUCWF7M42ZOUXRV7491R6RYQTFCSV8A4Y1B2YFPCD0HL2X62KPGTHFODSW6G`},\n\t\t},\n\t\t{\n\t\t\tname: `valid_python_nonapiurl`,\n\t\t\tinput: `##########################################################################################################\n# We use the best scraper service API, Scraping Bee.\napi_key = \"CXUWSH6Y2BRB8F07MB7YXWPYWV2TQ4K51G4N6SGEU1YDADAVDW35ZT7WNISZ8YMCQ810OP9KG22ZI2P2\"`,\n\t\t\twant: []string{`CXUWSH6Y2BRB8F07MB7YXWPYWV2TQ4K51G4N6SGEU1YDADAVDW35ZT7WNISZ8YMCQ810OP9KG22ZI2P2`},\n\t\t},\n\t\t{\n\t\t\tname: `valid_underscore`,\n\t\t\tinput: `\t\t\tgn = GoogleNews()\n\n\t\t\t# it's a fake API key, do not try to use it\n\t\t\tgn.top_news(scraping_bee = 'I5SYNPRFZI41WHVQWWUT0GNXFMO104343E7CXFIISR01E2V8ETSMXMJFK1XNKM7FDEEPUPRM0FYAHFF5')`,\n\n\t\t\twant: []string{`I5SYNPRFZI41WHVQWWUT0GNXFMO104343E7CXFIISR01E2V8ETSMXMJFK1XNKM7FDEEPUPRM0FYAHFF5`},\n\t\t},\n\t\t// TODO: support this\n\t\t//\t\t{\n\t\t//\t\t\tname: `valid_js_suffix`,\n\t\t//\t\t\tinput: `  do {\n\t\t//  //   const apiKey = 'TQ9CDAZSORUPU1NMZXZEM11VY7K3NC3HJPBNYP2V4CZZXUY9SWEULNDHOZ77XGWO9FA9A12XWFVWUBZJ';\n\t\t//  //   const client = new scrapingbee.ScrapingBeeClient(apiKey);\n\t\t// `,\n\t\t//\n\t\t//\t\t\twant: []string{       `TQ9CDAZSORUPU1NMZXZEM11VY7K3NC3HJPBNYP2V4CZZXUY9SWEULNDHOZ77XGWO9FA9A12XWFVWUBZJ`},\n\t\t//\t\t},\n\n\t\t// False positives\n\t\t{\n\t\t\tname:  `invalid - lowercase`,\n\t\t\tinput: `const scrapingbeeKey = 'tq9cdazsorupu1nmzxzem11vy7k3nc3hjpbnyp2v4czzxuy9sweulndhoz77xgwo9fa9a12xwfvwubzj'`,\n\t\t},\n\t}\n\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/screenshotapi/screenshotapi.go",
    "content": "package screenshotapi\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"screenshotapi\"}) + `\\b([0-9A-Z]{7}\\-[0-9A-Z]{7}\\-[0-9A-Z]{7}\\-[0-9A-Z]{7})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"screenshotapi\"}\n}\n\ntype response struct {\n\tScreenshot string `json:\"screenshot\"`\n}\n\n// FromData will find and optionally verify ScreenshotAPI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ScreenshotAPI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := 10 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://shot.screenshotapi.net/screenshot?token=\"+resMatch+\"&url=https://google.com&width=1920&height=1080\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tvar r response\n\t\t\t\tif err := json.NewDecoder(res.Body).Decode(&r); err != nil {\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && r.Screenshot != \"\" {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ScreenshotAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"A service for taking screenshots of websites. Websites can include internal or sensitive websites, API keys can access the screenshots.\"\n}\n"
  },
  {
    "path": "pkg/detectors/screenshotapi/screenshotapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage screenshotapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestScreenshotAPI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SCREENSHOTAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"SCREENSHOTAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a screenshotapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScreenshotAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a screenshotapi secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScreenshotAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ScreenshotAPI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ScreenshotAPI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/screenshotapi/screenshotapi_test.go",
    "content": "package screenshotapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"5QCXUYE-B9LNS58-X62XLXO-CHZJZDO\"\n\tinvalidPattern = \"5QCXUYE?B9LNS58-X62XLXO-CHZJZDO\"\n\tkeyword        = \"screenshotapi\"\n)\n\nfunc TestScreenshotAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword screenshotapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/screenshotlayer/screenshotlayer.go",
    "content": "package screenshotlayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClientTimeOut(10 * time.Second)\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"screenshotlayer\"}) + `\\b([a-zA-Z0-9_]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"screenshotlayer\"}\n}\n\n// FromData will find and optionally verify ScreenshotLayer secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ScreenshotLayer,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.screenshotlayer.com/api/capture?access_key=%s&url=https://google.com\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `PNG`)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ScreenshotLayer\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ScreenshotLayer is a service that provides website screenshots. Access keys can be used to capture and retrieve screenshots.\"\n}\n"
  },
  {
    "path": "pkg/detectors/screenshotlayer/screenshotlayer_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage screenshotlayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestScreenshotLayer_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SCREENSHOTLAYER\")\n\tinactiveSecret := testSecrets.MustGetField(\"SCREENSHOTLAYER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a screenshotlayer secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScreenshotLayer,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a screenshotlayer secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScreenshotLayer,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ScreenshotLayer.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ScreenshotLayer.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/screenshotlayer/screenshotlayer_test.go",
    "content": "package screenshotlayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"mbACjpi8P_c_6y2WMu4dUfvnvKWuDb_3\"\n\tinvalidPattern = \"mbACjpi8P_c_6y2W?u4dUfvnvKWuDb_3\"\n\tkeyword        = \"screenshotlayer\"\n)\n\nfunc TestScreenshotLayer_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword screenshotlayer\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scrutinizerci/scrutinizerci.go",
    "content": "package scrutinizerci\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"scrutinizer\"}) + `\\b([0-9a-z]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"scrutinizer\"}\n}\n\n// FromData will find and optionally verify ScrutinizerCi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ScrutinizerCi,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://scrutinizer-ci.com/api/user/repositories?access_token=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ScrutinizerCi\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Scrutinizer CI is a continuous integration service. The access token allows access to user repositories data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/scrutinizerci/scrutinizerci_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage scrutinizerci\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestScrutinizerCi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SCRUTINIZERCI\")\n\tinactiveSecret := testSecrets.MustGetField(\"SCRUTINIZERCI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scrutinizerci secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScrutinizerCi,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a scrutinizerci secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ScrutinizerCi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ScrutinizerCi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ScrutinizerCi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/scrutinizerci/scrutinizerci_test.go",
    "content": "package scrutinizerci\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"tj8dzw8on7hw5j5wtiphzc4hv5wfvb2kbk4djyz8r1oxqes8otd3ms0mrqw8jlwt\"\n\tinvalidPattern = \"tj8dzw8on7hw5j5wtiphzc4hv5wfv?2kbk4djyz8r1oxqes8otd3ms0mrqw8jlwt\"\n\tkeyword        = \"scrutinizerci\"\n)\n\nfunc TestScrutinizerCi_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword scrutinizerci\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/securitytrails/securitytrails.go",
    "content": "package securitytrails\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"securitytrails\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"securitytrails\", \"security trails\"}\n}\n\n// FromData will find and optionally verify SecurityTrails secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SecurityTrails,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.securitytrails.com/v1/ping\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"APIKEY\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SecurityTrails\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"SecurityTrails provides comprehensive domain and IP intelligence data. SecurityTrails API keys can be used to access this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/securitytrails/securitytrails_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage securitytrails\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSecurityTrails_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SECURITYTRAILS\")\n\tinactiveSecret := testSecrets.MustGetField(\"SECURITYTRAILS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a securitytrails secret\\n %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SecurityTrails,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified inline\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a securitytrails secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SecurityTrails,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a securitytrails secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SecurityTrails,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SecurityTrails.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SecurityTrails.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/securitytrails/securitytrails_test.go",
    "content": "package securitytrails\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"wZ8KxGUFd2G1IrazudQo9UpGyqr8jji6\"\n\tinvalidPattern = \"wZ8KxGUFd2G1Iraz?dQo9UpGyqr8jji6\"\n\tkeyword        = \"securitytrails\"\n)\n\nfunc TestSecurityTrails_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword securitytrails\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/segmentapikey/segmentapikey.go",
    "content": "package segmentapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"segment\"}) + `\\b([A-Za-z0-9_\\-a-zA-Z]{43}\\.[A-Za-z0-9_\\-a-zA-Z]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"segment\"}\n}\n\n// FromData will find and optionally verify SegmentApiKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SegmentApiKey,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://platform.segmentapis.com/v1beta/workspaces\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SegmentApiKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Segment is a customer data platform that helps you collect, clean, and control your customer data. Segment API keys can be used to access and manage this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/segmentapikey/segmentapikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage segmentapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSegmentApiKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SEGMENTAPIKEY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SEGMENTAPIKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a segmentapikey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SegmentApiKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a segmentapikey secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SegmentApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SegmentApiKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SegmentApiKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/segmentapikey/segmentapikey_test.go",
    "content": "package segmentapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"ENTvbxIazIP8g_chwXUfz4M930vr4YArS00iBPtBADI.E7RjExdT9CXgjFO-EhFE1WQGjRflI9YSJG-cCYPUB5x\"\n\tinvalidPattern = \"ENTvbxIazIP8g_chwXUfz4M930vr4YArS00iBPtBADI?E7RjExdT9CXgjFO-EhFE1WQGjRflI9YSJG-cCYPUB5x\"\n\tkeyword        = \"segmentapikey\"\n)\n\nfunc TestSegmentApiKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword segmentapikey\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/selectpdf/selectpdf.go",
    "content": "package selectpdf\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"selectpdf\"}) + `\\b([a-z0-9-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"selectpdf\"}\n}\n\n// FromData will find and optionally verify SelectPDF secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SelectPDF,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := 10 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://selectpdf.com/api2/convert/?key=%s&url=google.com\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SelectPDF\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"SelectPDF is a service that allows users to convert web pages to PDF documents. SelectPDF API keys can be used to perform these conversions programmatically.\"\n}\n"
  },
  {
    "path": "pkg/detectors/selectpdf/selectpdf_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage selectpdf\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSelectPDF_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SELECTPDF\")\n\tinactiveSecret := testSecrets.MustGetField(\"SELECTPDF_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a selectpdf secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SelectPDF,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a selectpdf secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SelectPDF,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SelectPDF.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SelectPDF.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/selectpdf/selectpdf_test.go",
    "content": "package selectpdf\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"fmajqnwjj-ia1l4in7-4r6ktkn1ikiy89x02\"\n\tinvalidPattern = \"fmajqnwjj-ia1l4in7?4r6ktkn1ikiy89x02\"\n\tkeyword        = \"selectpdf\"\n)\n\nfunc TestSelectPDF_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword selectpdf\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/semaphore/semaphore.go",
    "content": "package semaphore\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"semaphore\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"semaphore.co\"}\n}\n\n// FromData will find and optionally verify Semaphore secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tif !detectors.HasDigit(resMatch) {\n\t\t\tcontinue\n\t\t}\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Semaphore,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.semaphore.co/api/v4/account?apikey=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.semaphore+json; version=3\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && strings.Contains(body, \"account_id\") {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Semaphore\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Semaphore is a hosted continuous integration and deployment service used to automate software development workflows. Semaphore API keys can be used to access and manage CI/CD pipelines.\"\n}\n"
  },
  {
    "path": "pkg/detectors/semaphore/semaphore_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage semaphore\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSemaphore_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SEMAPHORE\")\n\tinactiveSecret := testSecrets.MustGetField(\"SEMAPHORE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a semaphore secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Semaphore,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a semaphore secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Semaphore,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Semaphore.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Semaphore.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/semaphore/semaphore_test.go",
    "content": "package semaphore\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"e90jcvaioqwdzi0thnnpx59nv2k4efom\"\n\tinvalidPattern = \"e90jcvaioqwdzi0t?nnpx59nv2k4efom\"\n\tkeyword        = \"semaphore.co\"\n)\n\nfunc TestSemaphore_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword semaphore.co\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sendbird/sendbird.go",
    "content": "package sendbird\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"sendbird\"}) + `\\b([0-9a-f]{40})\\b`)\n\tappIdPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"sendbird\"}) + `\\b([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})\\b`)\n)\n\ntype userResp struct {\n\tCode int `json:\"code\"`\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sendbird\"}\n}\n\n// FromData will find and optionally verify Sendbird secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tappIdMatches := appIdPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, appIdMatch := range appIdMatches {\n\t\tresAppIdMatch := strings.TrimSpace(appIdMatch[1])\n\n\t\tfor _, match := range matches {\n\t\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Sendbird,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\t\t\ts1.ExtraData = map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/sendbird/\",\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api-%s.sendbird.com/v3/users\", resAppIdMatch), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Api-Token\", resMatch)\n\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else if res.StatusCode == 400 { // Sendbird returns 400 for all errors\n\t\t\t\t\t\tvar userResp userResp\n\t\t\t\t\t\terr := json.NewDecoder(res.Body).Decode(&userResp)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\terr = fmt.Errorf(\"error decoding json response body: %w\", err)\n\t\t\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\t\t} else if userResp.Code != 400401 {\n\t\t\t\t\t\t\t// https://sendbird.com/docs/chat/platform-api/v3/error-codes\n\t\t\t\t\t\t\t// Sendbird always includes its own error codes with 400 responses\n\t\t\t\t\t\t\t// 400401 (InvalidApiToken) is the only one that indicates a bad token\n\t\t\t\t\t\t\terr = fmt.Errorf(\"unexpected response code: %d\", userResp.Code)\n\t\t\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Sendbird\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Sendbird is a communication platform providing chat, voice, and video services. Sendbird API tokens can be used to access and manage communication services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sendbird/sendbird_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sendbird\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSendbird_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SENDBIRD\")\n\tinactiveSecret := testSecrets.MustGetField(\"SENDBIRD_INACTIVE\")\n\tappId := testSecrets.MustGetField(\"SENDBIRD_APP_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sendbird application ID %s with sendbird secret %s within\", appId, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sendbird,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sendbird application ID %s with sendbird secret %s within but not valid\", appId, inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sendbird,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sendbird application ID %s with sendbird secret %s within\", appId, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sendbird,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sendbird application ID %s with sendbird secret %s within\", appId, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sendbird,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected error code\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(400, `{\"code\":400402}`)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sendbird application ID %s with sendbird secret %s within\", appId, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sendbird,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but failed to decode json\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(400, `{`)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sendbird application ID %s with sendbird secret %s within\", appId, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sendbird,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Sendbird.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Sendbird.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sendbird/sendbird_test.go",
    "content": "package sendbird\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey     = \"e9aeb40d34e6a7a9fb10e5876be968e344420b0c\"\n    invalidKey   = \"?9aeb40d34e6a7a9fb10e5876be968e344420b0c\"\n    validAppId   = \"FAB7BE5B-E6DD-670A-A3DC-91AF003DB677\"\n    invalidAppId = \"FAB7BE5B?E6DD-670A-A3DC-91AF003DB677\"\n    keyword      = \"sendbird\"\n)\n\nfunc TestSendbird_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sendbird\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validAppId),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidAppId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sendbirdorganizationapi/sendbirdorganizationapi.go",
    "content": "package sendbirdorganizationapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"sendbird\"}) + `\\b([0-9a-f]{24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sendbird\"}\n}\n\n// FromData will find and optionally verify SendbirdOrganizationAPI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SendbirdOrganizationAPI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://gate.sendbird.com/api/v2/applications\", nil)\n\t\t\tif err != nil {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t\treq.Header.Add(\"SENDBIRDORGANIZATIONAPITOKEN\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err != nil {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t} else {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode != http.StatusForbidden {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SendbirdOrganizationAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Sendbird is a chat and messaging platform. Sendbird API tokens can be used to access and manage Sendbird applications and services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sendbirdorganizationapi/sendbirdorganizationapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sendbirdorganizationapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSendbirdOrganizationAPI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SENDBIRDORGANIZATIONAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"SENDBIRDORGANIZATIONAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sendbird secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SendbirdOrganizationAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sendbird secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SendbirdOrganizationAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sendbird secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SendbirdOrganizationAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sendbird secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SendbirdOrganizationAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SendbirdOrganizationAPI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Sendbird.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sendbirdorganizationapi/sendbirdorganizationapi_test.go",
    "content": "package sendbirdorganizationapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"fd3ddab708ffd4b71f079272\"\n\tinvalidPattern = \"f?3ddab708ffd4b71f079272\"\n\tkeyword        = \"sendbirdorganizationapi\"\n)\n\nfunc TestSendbirdOrganizationAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sendbirdorganizationapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sendgrid/sendgrid.go",
    "content": "package sendgrid\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(`\\bSG\\.[\\w\\-]{20,24}\\.[\\w\\-]{39,50}\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"SG.\"}\n}\n\n// FromData will find and optionally verify Sendgrid secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[0]] = struct{}{}\n\t}\n\n\tfor token := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SendGrid,\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tverified, extraData, verificationErr := verifyToken(ctx, client, token)\n\t\t\ts1.Verified = verified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\ts1.AnalysisInfo = map[string]string{\"key\": token}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyToken(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\t// Check the scopes assigned to the api key.\n\t// https://docs.sendgrid.com/api-reference/api-key-permissions/retrieve-a-list-of-scopes-for-which-this-user-has-access\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.sendgrid.com/v3/scopes\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\textraData := map[string]string{\n\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/sendgrid/\",\n\t\t}\n\n\t\tvar scopesRes scopesResponse\n\t\tif err := json.NewDecoder(res.Body).Decode(&scopesRes); err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\n\t\tif len(scopesRes.Scopes) > 0 {\n\t\t\textraData[\"scopes\"] = strings.Join(scopesRes.Scopes, \",\")\n\t\t}\n\n\t\treturn true, extraData, nil\n\tcase http.StatusUnauthorized:\n\t\t// 401 means the key is definitively invalid.\n\t\treturn false, nil, nil\n\tcase http.StatusForbidden:\n\t\t// 403 means good key but not the right scope\n\t\treturn true, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\ntype scopesResponse struct {\n\tScopes []string `json:\"scopes\"`\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SendGrid\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"SendGrid is a cloud-based service that provides email delivery and marketing campaigns. SendGrid API keys can be used to send emails and manage other email-related tasks.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sendgrid/sendgrid_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sendgrid\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSendgrid_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SENDGRID\")\n\tsecretInactive := testSecrets.MustGetField(\"SENDGRID_INACTIVE\")\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sendgrid secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SendGrid,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sendgrid secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SendGrid,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sendgrid secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SendGrid,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sendgrid secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SendGrid,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Sendgrid.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"AnalysisInfo\", \"ExtraData\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Sendgrid.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sendgrid/sendgrid_test.go",
    "content": "package sendgrid\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"SG.ZV-VYwtHzJJW4wF8yPQk3.ZXg9c9DZuOgUcW1f2inP6SqfEsYG82zAe0wG7brZZ5OvruV-I\"\n\tinvalidPattern = \"SG.ZV-VYwtHzJJW4wF8yPQk3.ZXg9?9DZuOgUcW1f2inP6SqfEsYG82zAe0wG7brZZ5OvruV-I\"\n\tkeyword        = \"sendgrid\"\n)\n\nfunc TestSendgrid_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sendgrid\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sendinbluev2/sendinbluev2.go",
    "content": "package sendinbluev2\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(`\\b(xkeysib\\-[A-Za-z0-9_-]{81})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"xkeysib\"}\n}\n\n// FromData will find and optionally verify SendinBlueV2 secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SendinBlueV2,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.sendinblue.com/v3/account\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"api-key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SendinBlueV2\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"SendinBlue is a cloud-based marketing communication software. SendinBlue API keys can be used to access and modify marketing campaigns and contact data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sendinbluev2/sendinbluev2_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sendinbluev2\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSendinBlueV2_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SENDINBLUEV2_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SENDINBLUEV2_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sendinbluev2 secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SendinBlueV2,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sendinbluev2 secret %s within but unverified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SendinBlueV2,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SendinBlueV2.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SendinBlueV2.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sendinbluev2/sendinbluev2_test.go",
    "content": "package sendinbluev2\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"xkeysib-C6S1LXk_u4mw_uIss4MGmJpH8yrOwFep2aN5YLALYVpAb4buJ7uvxqYfrb3kZL5ao2JvUEFb1vRk79IXj\"\n\tinvalidPattern = \"xkeysib-C6S1LXk_u4mw_uIss4MGmJpH8yrOwFep2aN5?LALYVpAb4buJ7uvxqYfrb3kZL5ao2JvUEFb1vRk79IXj\"\n\tkeyword        = \"sendinbluev2\"\n)\n\nfunc TestSendinBlueV2_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sendinbluev2\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sentryorgtoken/sentryorgtoken.go",
    "content": "package sentryorgtoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\torgAuthTokenPat = regexp.MustCompile(`\\b(sntrys_eyJ[a-zA-Z0-9=_+/]{197})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sntrys_eyJ\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SentryOrgToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Sentry is an error tracking service that helps developers monitor and fix crashes in real time. Sentry Organization Auth Tokens can be used in many places to interact with Sentry programmatically. For example, they can be used for sentry-cli, bundler plugins, or similar use cases.\"\n}\n\n// FromData will find and optionally verify SentryToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// find all unique org auth tokens\n\tvar uniqueOrgTokens = make(map[string]struct{})\n\n\tfor _, orgToken := range orgAuthTokenPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueOrgTokens[orgToken[1]] = struct{}{}\n\t}\n\n\tfor orgToken := range uniqueOrgTokens {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SentryOrgToken,\n\t\t\tRaw:          []byte(orgToken),\n\t\t}\n\n\t\tif verify {\n\t\t\tif s.client == nil {\n\t\t\t\ts.client = common.SaneHttpClient()\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifySentryOrgToken(ctx, s.client, orgToken)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, orgToken)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\n// docs: https://docs.sentry.io/account/auth-tokens/#organization-auth-tokens\nfunc verifySentryOrgToken(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://sentry.io/api/0/auth/validate\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusForbidden, http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sentryorgtoken/sentryorgtoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sentryorgtoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSentryOrgToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SENTRY_ORG_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SENTRY_ORG_TOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sentry secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SentryOrgToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sentry secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SentryOrgToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SentryOrgToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v,\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\topts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, opts); diff != \"\" {\n\t\t\t\tt.Errorf(\"SentryOrgToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sentryorgtoken/sentryorgtoken_test.go",
    "content": "package sentryorgtoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\tsentry_token := sntrys_eyJFAKEiOjE3NDIzNjM1NTIuNTAzMzA5LCJ1cmwiOiJodHRwczovL3NlbnRyeS5pbyIsInJlZ2lvbl91cmwiOiJodHRwczovL3VzLnNlbnRyeS5pbfakem9yZyI6InRydWZmbGUtc2VjdXJpdHktamQifQ==_+zqSnKjs87cicc3FAK08vmZs5cWx9C5EARKHFtW5lqI\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", sentry_token))\n\t`\n\tinvalidPattern = \"sntrys_eyJFAKE-OjE3NDIzNjM1NTIuNTAzMzA5LCJ1cmwiOiJodHRwczovL3NlbnRyeS5pbyIsInJlZ2lvbl91cmwiOiJodHRwczovL3VzLnNlbnRyeS5pbfakem9yZyI6InRydWZmbGUtc2VjdXJpdHktamQifQ==_+zqSnKjs87cicc3FAK08vmZs5cWx9C5EARKHFtW5lqI\"\n\ttoken          = \"sntrys_eyJFAKEiOjE3NDIzNjM1NTIuNTAzMzA5LCJ1cmwiOiJodHRwczovL3NlbnRyeS5pbyIsInJlZ2lvbl91cmwiOiJodHRwczovL3VzLnNlbnRyeS5pbfakem9yZyI6InRydWZmbGUtc2VjdXJpdHktamQifQ==_+zqSnKjs87cicc3FAK08vmZs5cWx9C5EARKHFtW5lqI\"\n)\n\nfunc TestSentryToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sentry org token\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{token},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"token = '%s' | '%s'\", validPattern, validPattern),\n\t\t\twant:  []string{token},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: invalidPattern,\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sentrytoken/v1/sentrytoken.go",
    "content": "package sentrytoken\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\ntype Organization struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"sentry\"}) + `\\b([a-f0-9]{64})\\b`)\n\n\tforbiddenError = \"You do not have permission to perform this action.\"\n)\n\nfunc (s Scanner) Version() int {\n\treturn 1\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sentry\"}\n}\n\n// FromData will find and optionally verify SentryToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// find all unique auth tokens\n\tvar uniqueAuthTokens = make(map[string]struct{})\n\n\tfor _, authToken := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueAuthTokens[authToken[1]] = struct{}{}\n\t}\n\n\tfor authToken := range uniqueAuthTokens {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SentryToken,\n\t\t\tRaw:          []byte(authToken),\n\t\t}\n\n\t\tif verify {\n\t\t\tif s.client == nil {\n\t\t\t\ts.client = common.SaneHttpClient()\n\t\t\t}\n\t\t\textraData, isVerified, verificationErr := VerifyToken(ctx, s.client, authToken)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, authToken)\n\t\t\ts1.ExtraData = extraData\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc VerifyToken(ctx context.Context, client *http.Client, token string) (map[string]string, bool, error) {\n\t// api docs: https://docs.sentry.io/api/organizations/\n\t// this api will return 200 for user auth tokens with scope of org:<>\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://sentry.io/api/0/auth/validate/\", nil)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn nil, true, nil\n\tcase http.StatusForbidden:\n\t\tvar APIResp interface{}\n\t\tif err = json.NewDecoder(resp.Body).Decode(&APIResp); err != nil {\n\t\t\treturn nil, false, err\n\t\t}\n\n\t\t// if response contain the forbiddenError message it means the token is active but does not have the right scope for this API call\n\t\tif strings.Contains(fmt.Sprintf(\"%v\", APIResp), forbiddenError) {\n\t\t\treturn nil, true, nil\n\t\t}\n\n\t\treturn nil, false, nil\n\tcase http.StatusUnauthorized:\n\t\treturn nil, false, nil\n\tdefault:\n\t\treturn nil, false, fmt.Errorf(\"unexpected HTTP response status %d\", resp.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SentryToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Sentry is an error tracking service that helps developers monitor and fix crashes in real time. Sentry tokens can be used to access and manage projects and organizations within Sentry.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sentrytoken/v1/sentrytoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sentrytoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSentryToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SENTRYTOKEN_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SENTRYTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sentry secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SentryToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sentry secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SentryToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified but for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sentry super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SentryToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found and valid but unexpected api response\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sentry super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SentryToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v,\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\topts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, opts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nconst (\n\tresponseBody403 = `\n[\n  {\n    \"organization\": {\n      \"id\": \"911964\",\n      \"slug\": \"wigslap\",\n      \"status\": {\n        \"id\": \"active\",\n        \"name\": \"active\"\n      },\n      \"name\": \"wigslap\"\n    }\n  }\n]\n`\n\tresponseAccountDeactivated = `{\"detail\": \"Authentication credentials were not provided\"}`\n\tresponseEmpty              = `[]`\n)\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sentrytoken/v1/sentrytoken_test.go",
    "content": "package sentrytoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\tsentry_token := ad00eba0e2b5b057146e1b2b9373f86dbb0e712d106529111d97cb13f081de20\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", sentry_token))\n\t`\n\tinvalidPattern = \"28ab769ecf2b465fake#0ea877d6494feffe5017a5824ec2920f24451423fake\"\n\tkeyword        = \"sentry\"\n\ttoken          = \"ad00eba0e2b5b057146e1b2b9373f86dbb0e712d106529111d97cb13f081de20\"\n)\n\nfunc TestSentryToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sentrytoken\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{token},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{token},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sentrytoken/v2/sentrytoken.go",
    "content": "package sentrytoken\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\tv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sentrytoken/v1\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(sntryu_[a-f0-9]{64})\\b`)\n)\n\nfunc (s Scanner) Version() int {\n\treturn 2\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sentry\", \"sntryu\"}\n}\n\n// FromData will find and optionally verify SentryToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// find all unique auth tokens\n\tvar uniqueAuthTokens = make(map[string]struct{})\n\n\tfor _, authToken := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueAuthTokens[authToken[1]] = struct{}{}\n\t}\n\n\tfor authToken := range uniqueAuthTokens {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SentryToken,\n\t\t\tRaw:          []byte(authToken),\n\t\t}\n\n\t\tif verify {\n\t\t\tif s.client == nil {\n\t\t\t\ts.client = common.SaneHttpClient()\n\t\t\t}\n\t\t\textraData, isVerified, verificationErr := v1.VerifyToken(ctx, s.client, authToken)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, authToken)\n\t\t\ts1.ExtraData = extraData\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SentryToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Sentry is an error tracking service that helps developers monitor and fix crashes in real time. Sentry tokens can be used to access and manage projects and organizations within Sentry.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sentrytoken/v2/sentrytoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sentrytoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSentryToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SENTRYTOKEN_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SENTRYTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sentry secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SentryToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sentry secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SentryToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified but for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sentry super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SentryToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found and valid but unexpected api response\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sentry super secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SentryToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v,\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\topts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, opts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Gitlab.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nconst (\n\tresponseBody403 = `\n[\n  {\n    \"organization\": {\n      \"id\": \"911964\",\n      \"slug\": \"wigslap\",\n      \"status\": {\n        \"id\": \"active\",\n        \"name\": \"active\"\n      },\n      \"name\": \"wigslap\"\n    }\n  }\n]\n`\n\tresponseAccountDeactivated = `{\"detail\": \"Authentication credentials were not provided\"}`\n\tresponseEmpty              = `[]`\n)\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sentrytoken/v2/sentrytoken_test.go",
    "content": "package sentrytoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\tsentry_token := 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\n\treq.Header.Set(\"Authorization\", fmt.Sprintf(\"Bearer %s\", sentry_token))\n\t`\n\tinvalidPattern = \"sntryu_822255ea24285a3f8f863dee3g1720fff21628cf331fbde22a16f27bef9cd7a6\"\n\tkeyword        = \"sentry\"\n\ttoken          = \"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\"\n)\n\nfunc TestSentryToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sentrytoken\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{token},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{token},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/serphouse/serphouse.go",
    "content": "package serphouse\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"serphouse\"}) + `\\b([0-9A-Za-z]{60})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"serphouse\"}\n}\n\n// FromData will find and optionally verify Serphouse secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Serphouse,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.serphouse.com/account/info\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.serphouse+json; version=3\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Serphouse\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Serphouse is a service that provides search engine result page data. Serphouse API keys can be used to access this data and perform various operations on it.\"\n}\n"
  },
  {
    "path": "pkg/detectors/serphouse/serphouse_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage serphouse\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSerphouse_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SERPHOUSE\")\n\tinactiveSecret := testSecrets.MustGetField(\"SERPHOUSE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a serphouse secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Serphouse,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a serphouse secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Serphouse,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Serphouse.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Serphouse.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/serphouse/serphouse_test.go",
    "content": "package serphouse\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"H8VuKAcg3pmwH3NbhOSDJSbVqCxslaOQ7lVqF61be25HEGzCJXzzCGoxw8sE\"\n\tinvalidPattern = \"H8VuKAcg3pmwH3Nbh?SDJSbVqCxslaOQ7lVqF61be25HEGzCJXzzCGoxw8sE\"\n\tkeyword        = \"serphouse\"\n)\n\nfunc TestSerphouse_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword serphouse\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/serpstack/serpstack.go",
    "content": "package serpstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"serpstack\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"serpstack\"}\n}\n\n// FromData will find and optionally verify SerpStack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SerpStack,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := 10 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.serpstack.com/search?access_key=%s&query=Mcdonalds\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `search_url`) || strings.Contains(bodyString, `\"info\":\"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption.\"`)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SerpStack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"SerpStack is an API service used to scrape search engine results. SerpStack keys can be used to access and retrieve search data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/serpstack/serpstack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage serpstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSerpStack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SERPSTACK\")\n\tinactiveSecret := testSecrets.MustGetField(\"SERPSTACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a serpstack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SerpStack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a serpstack secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SerpStack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SerpStack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SerpStack.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/serpstack/serpstack_test.go",
    "content": "package serpstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"jeqozza8xili4i96pc1x1ww67lmvrw27\"\n\tinvalidPattern = \"jeqozza8xili4i96?c1x1ww67lmvrw27\"\n\tkeyword        = \"serpstack\"\n)\n\nfunc TestSerpStack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword serpstack\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sheety/sheety.go",
    "content": "package sheety\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"sheety\"}) + `\\b([0-9a-z]{64})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"sheety\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sheety\"}\n}\n\n// FromData will find and optionally verify Sheety secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Sheety,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.sheety.co/\"+resIdMatch+\"/restaurantMenu/menuItems\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Sheety\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Sheety is a service that allows you to turn Google Sheets into a REST API. Sheety API keys can be used to access and modify the data in your Google Sheets.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sheety/sheety_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sheety\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSheety_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SHEETY\")\n\tid := testSecrets.MustGetField(\"SHEETY_ID\")\n\n\tinactiveSecret := testSecrets.MustGetField(\"SHEETY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sheety secret %s within sheetyid %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sheety,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sheety secret %s within sheetyid %s but not valid\", inactiveSecret, id)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sheety,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Sheety.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Sheety.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sheety/sheety_test.go",
    "content": "package sheety\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey   = \"qtb537zc8slokboyekq08tsut9atqa4hnewgjsi2olklupy2z4s3wilqorymakjd\"\n    invalidKey = \"qtb537zc8slokboyekq08tsut9atqa4h?ewgjsi2olklupy2z4s3wilqorymakjd\"\n    validId    = \"81no0tu89hxci6swi979krdkv87f21z9\"\n    invalidId  = \"81no0tu89hxc?6swi979krdkv87f21z9\"\n    keyword    = \"sheety\"\n)\n\nfunc TestSheety_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sheety\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sherpadesk/sherpadesk.go",
    "content": "package sherpadesk\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"sherpadesk\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sherpadesk\"}\n}\n\n// FromData will find and optionally verify Sherpadesk secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Sherpadesk,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tdata := fmt.Sprintf(\"x:%s\", resMatch)\n\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.sherpadesk.com/organizations/\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Sherpadesk\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Sherpadesk is a helpdesk and service desk software. Sherpadesk keys can be used to access and manage support tickets and other related data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sherpadesk/sherpadesk_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sherpadesk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSherpadesk_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SHERPADESK\")\n\tinactiveSecret := testSecrets.MustGetField(\"SHERPADESK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sherpadesk secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sherpadesk,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sherpadesk secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sherpadesk,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Sherpadesk.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Sherpadesk.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sherpadesk/sherpadesk_test.go",
    "content": "package sherpadesk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"g8wjz4xjtdaoswrmbl2ypcu0neq4cvr4\"\n\tinvalidPattern = \"g8wjz4xjtdaoswrm?l2ypcu0neq4cvr4\"\n\tkeyword        = \"sherpadesk\"\n)\n\nfunc TestSherpadesk_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sherpadesk\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/shipday/shipday.go",
    "content": "package shipday\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"shipday\"}) + `\\b([a-zA-Z0-9.]{11}[a-zA-Z0-9]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"shipday\"}\n}\n\n// FromData will find and optionally verify Shipday secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Shipday,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.shipday.com/carriers\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Shipday\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Shipday is a delivery management software used by businesses to manage and track their deliveries. Shipday API keys can be used to access and modify delivery data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/shipday/shipday_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage shipday\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestShipday_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SHIPDAY\")\n\tinactiveSecret := testSecrets.MustGetField(\"SHIPDAY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a shipday secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Shipday,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a shipday secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Shipday,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Shipday.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Shipday.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/shipday/shipday_test.go",
    "content": "package shipday\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"3ajLBL6g6oj2rC9u2vtrkPt8qSTLp07\"\n\tinvalidPattern = \"3ajLBL6g6oj2rC9?2vtrkPt8qSTLp07\"\n\tkeyword        = \"shipday\"\n)\n\nfunc TestShipday_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword shipday\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/shodankey/shodankey.go",
    "content": "package shodankey\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"shodan\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"shodan\"}\n}\n\n// FromData will find and optionally verify ShodanKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ShodanKey,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ts1.Verified = verifyToken(ctx, client, resMatch)\n\t\t}\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\ntype shodanInfoRes struct {\n\tScanCredits int `json:\"scan_credits\"`\n\tUsageLimits struct {\n\t\tScanCredits  int `json:\"scan_credits\"`\n\t\tQueryCredits int `json:\"query_credits\"`\n\t\tMonitoredIps int `json:\"monitored_ips\"`\n\t} `json:\"usage_limits\"`\n\tPlan         string `json:\"plan\"`\n\tHTTPS        bool   `json:\"https\"`\n\tUnlocked     bool   `json:\"unlocked\"`\n\tQueryCredits int    `json:\"query_credits\"`\n\tMonitoredIps int    `json:\"monitored_ips\"`\n\tUnlockedLeft int    `json:\"unlocked_left\"`\n\tTelnet       bool   `json:\"telnet\"`\n}\n\nfunc verifyToken(ctx context.Context, client *http.Client, token string) bool {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.shodan.io/api-info?key=\"+token, nil)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false\n\t}\n\tdefer res.Body.Close()\n\n\tif res.StatusCode < 200 || res.StatusCode >= 300 {\n\t\treturn false\n\t}\n\n\tbytes, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tvar info shodanInfoRes\n\treturn json.Unmarshal(bytes, &info) == nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ShodanKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Shodan is a search engine for Internet-connected devices. Shodan API keys can be used to query the Shodan database for information on these devices.\"\n}\n"
  },
  {
    "path": "pkg/detectors/shodankey/shodankey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage shodankey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestShodanKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SHODANKEY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SHODANKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a shodankey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ShodanKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a shodankey secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ShodanKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ShodanKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ShodanKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/shodankey/shodankey_test.go",
    "content": "package shodankey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"28tEbDdpkFvftp9XkMzAf7O7uVTYlPYD\"\n\tinvalidPattern = \"28tEbDdp?Fvftp9XkMzAf7O7uVTYlPYD\"\n\tkeyword        = \"shodankey\"\n)\n\nfunc TestShodanKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword shodankey\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/shopify/shopify.go",
    "content": "package shopify\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(`\\b(shppa_|shpat_)([0-9A-Fa-f]{32})\\b`)\n\tdomainPat = regexp.MustCompile(`[a-zA-Z0-9-]+\\.myshopify\\.com`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"shppa_\", \"shpat_\"}\n}\n\n// FromData will find and optionally verify Shopify secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatches := keyPat.FindAllString(dataStr, -1)\n\tdomainMatches := domainPat.FindAllString(dataStr, -1)\n\n\tfor _, match := range keyMatches {\n\t\tkey := strings.TrimSpace(match)\n\n\t\tfor _, domainMatch := range domainMatches {\n\t\t\tdomainRes := strings.TrimSpace(domainMatch)\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Shopify,\n\t\t\t\tRedacted:     domainRes,\n\t\t\t\tRaw:          []byte(key + domainRes),\n\t\t\t}\n\n\t\t\t// set key as the primary secret for engine to find the line number\n\t\t\ts1.SetPrimarySecretValue(key)\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://\"+domainRes+\"/admin/oauth/access_scopes.json\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"X-Shopify-Access-Token\", key)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tshopifyTokenAccessScopes := shopifyTokenAccessScopes{}\n\t\t\t\t\t\terr := json.NewDecoder(res.Body).Decode(&shopifyTokenAccessScopes)\n\t\t\t\t\t\tif err == nil {\n\t\t\t\t\t\t\tvar handleArray []string\n\t\t\t\t\t\t\tfor _, handle := range shopifyTokenAccessScopes.AccessScopes {\n\t\t\t\t\t\t\t\thandleArray = append(handleArray, handle.Handle)\n\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t\ts1.ExtraData = map[string]string{\n\t\t\t\t\t\t\t\t\"access_scopes\": strings.Join(handleArray, \",\"),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\t\t\t\"key\":       key,\n\t\t\t\t\t\t\t\t\"store_url\": domainRes,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tres.Body.Close()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\n\t\t}\n\n\t}\n\n\treturn results, nil\n\n}\n\nfunc (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {\n\treturn false, \"\"\n}\n\ntype shopifyTokenAccessScopes struct {\n\tAccessScopes []struct {\n\t\tHandle string `json:\"handle\"`\n\t} `json:\"access_scopes\"`\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Shopify\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"An ecommerce platform, API keys can be used to access customer data\"\n}\n"
  },
  {
    "path": "pkg/detectors/shopify/shopify_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage shopify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestShopify_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SHOPIFY_ADMIN_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"SHOPIFY_ADMIN_SECRET_INACTIVE\")\n\tdomain := testSecrets.MustGetField(\"SHOPIFY_DOMAIN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a shopify secret %s domain https://%s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Shopify,\n\t\t\t\t\tRedacted:     domain,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"access_scopes\": \"read_analytics,read_apps,write_assigned_fulfillment_orders,read_assigned_fulfillment_orders,write_customers,read_customers,write_discovery,read_discovery,write_merchant_managed_fulfillment_orders,read_merchant_managed_fulfillment_orders,write_reports,read_reports,write_cart_transforms,read_cart_transforms\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a shopify secret %s within (domain https://%s) but not valid\", inactiveSecret, domain)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Shopify,\n\t\t\t\t\tRedacted:     domain,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData:    nil,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Shopify.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Shopify.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/shopify/shopify_test.go",
    "content": "package shopify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey      = \"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\"\n\tinvalidKey    = \"shpat_?23eEA6cA03dA2eF5CFe9002Be9F1BD9\"\n\tvalidDomain   = \"phfjg0Fv-Nn52QAMfsb4LfI7gVp7j-J11WykWugPHRhh3n6lGz8eP0.myshopify.com\"\n\tinvalidDomain = \"phfjg0Fv-Nn52QAMfsb4LfI7gVp7j-J11W?kWugPHRhh3n6lGz8eP0.myshopify.com\"\n\tkeyword       = \"shopify\"\n)\n\nfunc TestShopify_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword shopify\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validDomain),\n\t\t\twant:  []string{validKey + validDomain},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidDomain),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/shortcut/shortcut.go",
    "content": "package shortcut\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"shortcut\"}) + `\\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"shortcut\"}\n}\n\n// FromData will find and optionally verify Shortcut secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Shortcut,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyResult(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\treturn results, nil\n}\n\nfunc verifyResult(ctx context.Context, client *http.Client, apiKey string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.app.shortcut.com/api/v3/member\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Shortcut-Token\", apiKey)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tverifiedBodyResponse, err := common.ResponseContainsSubstring(res.Body, \"name\")\n\t_ = res.Body.Close()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif res.StatusCode >= 200 && res.StatusCode < 300 && verifiedBodyResponse {\n\t\treturn true, nil\n\t}\n\treturn false, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Shortcut\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Shortcut is a project management tool. Shortcut API keys can be used to access and modify project data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/shortcut/shortcut_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage shortcut\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestShortcut_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SHORTCUT\")\n\tinactiveSecret := testSecrets.MustGetField(\"SHORTCUT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a shortcut secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Shortcut,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a shortcut secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Shortcut,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Shortcut.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Shortcut.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/shortcut/shortcut_test.go",
    "content": "package shortcut\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"2e15518b-b3b1-5269-3746-4dd87951caa2\"\n\tinvalidPattern = \"2e15518b?b3b1-5269-3746-4dd87951caa2\"\n\tkeyword        = \"shortcut\"\n)\n\nfunc TestShortcut_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword shortcut\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/shotstack/shotstack.go",
    "content": "package shotstack\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"shotstack\"}) + `\\b([a-zA-Z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"shotstack\"}\n}\n\n// FromData will find and optionally verify Shotstack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Shotstack,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\n\t\t\t\t\"timeline\":{\n\t\t\t\t   \"soundtrack\":{\n\t\t\t\t\t  \"src\":\"https://s3-ap-southeast-2.amazonaws.com/shotstack-assets/music/moment.mp3\",\n\t\t\t\t\t  \"effect\":\"fadeOut\"\n\t\t\t\t   },\n\t\t\t\t   \"background\":\"#000000\",\n\t\t\t\t   \"tracks\":[\n\t\t\t\t\t  {\n\t\t\t\t\t\t \"clips\":[\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t   \"asset\": {\n\t\t\t\t\t\t\t\t  \"type\":\"title\",\n\t\t\t\t\t\t\t\t  \"text\":\"Hello World\",\n\t\t\t\t\t\t\t\t  \"style\":\"minimal\"\n\t\t\t\t\t\t\t   },\n\t\t\t\t\t\t\t   \"start\":0,\n\t\t\t\t\t\t\t   \"length\":5,\n\t\t\t\t\t\t\t   \"transition\":{\n\t\t\t\t\t\t\t\t  \"in\":\"fade\",\n\t\t\t\t\t\t\t\t  \"out\":\"fade\"\n\t\t\t\t\t\t\t   }\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t ]\n\t\t\t\t\t  }\n\t\t\t\t   ]\n\t\t\t\t},\n\t\t\t\t\"output\":{\"format\":\"mp4\", \"resolution\":\"sd\"\n\t\t\t\t}\n\t\t\t}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.shotstack.io/stage/render\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"x-api-key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Shotstack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Shotstack is a video editing API service. Shotstack API keys can be used to access and utilize the video rendering and editing capabilities of the Shotstack platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/shotstack/shotstack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage shotstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestShotstack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SHOTSTACK\")\n\tinactiveSecret := testSecrets.MustGetField(\"SHOTSTACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a shotstack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Shotstack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a shotstack secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Shotstack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Shotstack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Shotstack.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/shotstack/shotstack_test.go",
    "content": "package shotstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"3gGdjBjen4jcmMPJa2RJfg5suw47SHihtAznQd4X\"\n\tinvalidPattern = \"3gGdjBjen4jcmMPJa2RJ?g5suw47SHihtAznQd4X\"\n\tkeyword        = \"shotstack\"\n)\n\nfunc TestShotstack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword shotstack\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/shutterstock/shutterstock.go",
    "content": "package shutterstock\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"shutterstock\"}) + `\\b([0-9a-zA-Z]{32})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"shutterstock\"}) + `\\b([0-9a-zA-Z]{16})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"shutterstock\"}\n}\n\n// FromData will find and optionally verify Shutterstock secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, secretMatch := range secretMatches {\n\t\t\tresSecretMatch := strings.TrimSpace(secretMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Shutterstock,\n\t\t\t\tRaw:          []byte(resSecretMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.shutterstock.com/v2/images/search\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\t\treq.SetBasicAuth(resMatch, resSecretMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Shutterstock\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Shutterstock is a global provider of stock photography, footage, music, and editing tools. Shutterstock API keys can be used to access and modify this content.\"\n}\n"
  },
  {
    "path": "pkg/detectors/shutterstock/shutterstock_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage shutterstock\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestShutterstock_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tkey := testSecrets.MustGetField(\"SHUTTERSTOCK_KEY\")\n\tinactiveKey := testSecrets.MustGetField(\"SHUTTERSTOCK_KEY_INACTIVE\")\n\tsecret := testSecrets.MustGetField(\"SHUTTERSTOCK\")\n\tinactiveSecret := testSecrets.MustGetField(\"SHUTTERSTOCK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a shutterstock key %s with shutterstock secret %s within\", key, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Shutterstock,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a shutterstock key %s with shutterstock secret %s within but not valid\", inactiveKey, inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Shutterstock,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Shutterstock.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Shutterstock.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/shutterstock/shutterstock_test.go",
    "content": "package shutterstock\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey      = \"35rqejvHx8E83LC82j65uBHEz8VRlWSw\"\n\tinvalidKey    = \"35rqejvHx8E83LC8?j65uBHEz8VRlWSw\"\n\tvalidSecret   = \"IJ9u8C6AXnfTwuLd\"\n\tinvalidSecret = \"IJ9u8C6A?nfTwuLd\"\n\tkeyword       = \"shutterstock\"\n)\n\nfunc TestShutterstock_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword shutterstock\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validSecret),\n\t\t\twant:  []string{validSecret},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidSecret),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/shutterstockoauth/shutterstockoauth.go",
    "content": "package shutterstockoauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"shutterstock\"}) + `\\b(v2/[0-9A-Za-z]{388})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"shutterstock\"}\n}\n\n// FromData will find and optionally verify ShutterstockOAuth secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ShutterstockOAuth,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.shutterstock.com/v2/user\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ShutterstockOAuth\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"A service for saving and accessing photos. OAuth credentials can be used to potentially access user photos.\"\n}\n"
  },
  {
    "path": "pkg/detectors/shutterstockoauth/shutterstockoauth_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage shutterstockoauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestShutterstockOAuth_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SHUTTERSTOCKOAUTH\")\n\tinactiveSecret := testSecrets.MustGetField(\"SHUTTERSTOCKOAUTH_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a shutterstockoauth secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ShutterstockOAuth,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a shutterstockoauth secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ShutterstockOAuth,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ShutterstockOAuth.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ShutterstockOAuth.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/shutterstockoauth/shutterstockoauth_test.go",
    "content": "package shutterstockoauth\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"v2/G8QwLlOmlwlPzafRJTUNIotsfd3kTTRbtsRldZ3tKdQN98abypvIzPywyqnyAivF9Xev6xhEdJfVIRD8ZVA7YKOjsJ5WNz3QG5XUkw0AiCuBTwDLbeidOMGblASRPwnsGrkfYfN1jX6CPywJsOmfP63zXgBZB6H8iXswjYjj8iMV9NAPQ8OSyDnNuhlX4BGFZ9YdrCJEgzHiavGwteueFTpljPODS8oTXwNtAYV4x06eQ6s4bFAAL2nEzuuSakwnDiJd2wXsXW9cLuMHpawLszYYtDLqApOSyZ33KTfmtrvuWQgTcQv5JfYi93mvCdQS0FJyY5AnKSj9hdvqHf2bsXXK5nEcEN41YcKuU8ADk4uceYwEHWyve4xlIqCpdZRWlif3\"\n\tinvalidPattern = \"v1/G8QwLlOmlwlPzafRJTUNIotsfd3kTTRbtsRldZ3tKdQN98abypvIzPywyqnyAivF9Xev6xhEdJfVIRD8ZVA7YKOjsJ5WNz3QG5XUkw0AiCuBTwDLbeidOMGblASRPwnsGrkfYfN1jX6CPywJsOmfP63zXgBZB6H8iXswjYjj8iMV9NAPQ8OSyDnNuhlX4BGFZ9YdrCJEgzHiavGwteueFTpljPODS8oTXwNtAYV4x06eQ6s4bFAAL2nEzuuSakwnDiJd2wXsXW9cLuMHpawLszYYtDLqApOSyZ33KTfmtrvuWQgTcQv5JfYi93mvCdQS0FJyY5AnKSj9hdvqHf2bsXXK5nEcEN41YcKuU8ADk4uceYwEHWyve4xlIqCpdZRWlif3\"\n\tkeyword        = \"shutterstockoauth\"\n)\n\nfunc TestShutterstockOAuth_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword shutterstockoauth\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/signable/signable.go",
    "content": "package signable\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\ttokenPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\".{0,2}signable\"}) + `\\b([a-zA-Z-0-9]{32})\\b`)\n\tkeywordPat = regexp.MustCompile(`(?i)([a-z]{2})signable`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"signable\"}\n}\n\n// FromData will find and optionally verify Signable secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := tokenPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\n\t\tif isCommonFalsePositive(match[0]) {\n\t\t\tcontinue\n\t\t}\n\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Signable,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tif s.client == nil {\n\t\t\t\ts.client = defaultClient\n\t\t\t}\n\t\t\tisVerified, verificationErr := verifyResult(ctx, s.client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\n// Eliminate the most common false positive.\nfunc isCommonFalsePositive(line string) bool {\n\t// TODO: Skip lock files altogether. (https://github.com/trufflesecurity/trufflehog/issues/1517)\n\tif strings.Contains(line, \"helper-explode-assignable-expression\") {\n\t\treturn true\n\t}\n\n\t// Eliminate false positives from `assignable` and `designable`.\n\tfor _, m := range keywordPat.FindAllStringSubmatch(line, -1) {\n\t\tif strings.EqualFold(m[1], \"as\") || strings.EqualFold(m[1], \"de\") {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc verifyResult(ctx context.Context, client *http.Client, token string) (bool, error) {\n\tdata := fmt.Sprintf(\"%s:\", token)\n\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.signable.co.uk/v1/templates?offset=0&limit=5\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer res.Body.Close()\n\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\treturn true, nil\n\t}\n\treturn false, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Signable\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Signable is a service used for electronic signatures. Signable tokens can be used to authenticate and access Signable's API services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/signable/signable_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage signable\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSignable_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SIGNABLE\")\n\tinactiveSecret := testSecrets.MustGetField(\"SIGNABLE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a signable secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Signable,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a signable secret %s within but not valid\", inactiveSecret)), // the secret would satisfy the regex but not pass validation\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Signable,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Signable.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Signable.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/signable/signable_test.go",
    "content": "package signable\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\nfunc TestSignable_Pattern(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tdata        string\n\t\tshouldMatch bool\n\t\tmatch       string\n\t}{\n\t\t// True positives\n\t\t{\n\t\t\tname:        \"valid\",\n\t\t\tdata:        `const signableToken = '40a1cd917bff1288f699a94a75b37a1a'`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       \"40a1cd917bff1288f699a94a75b37a1a\",\n\t\t},\n\n\t\t// False positives\n\t\t{\n\t\t\tname: `invalid_assignable_yarn`,\n\t\t\tdata: `\"  babel-helper-explode-assignable-expression@^6.24.1:\n    version \"6.24.1\"\n    resolved \"https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa\"\n    dependencies:\n      babel-runtime \"^6.22.0\"\n      babel-traverse \"^6.24.1\"\n      babel-types \"^6.24.1\"`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\tname: `invalid_assignable_yarn`,\n\t\t\tdata: `\"@babel/helper-explode-assignable-expression@^7.16.7\":\n  version \"7.16.7\"\n  resolved \"https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz#12a6d8522fdd834f194e868af6354e8650242b7a\"\n  integrity sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==\n  dependencies:\n    \"@babel/types\" \"^7.16.7\"`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t// https://github.com/tbenst/purescript-nix-example/blob/558c8d6cb605742218cfa14a3fa93c062324b885/yarn.nix\n\t\t{\n\t\t\tname: `invalid_assignable_nix`,\n\t\t\tdata: `    {\n      name = \"_babel_helper_explode_assignable_expression___helper_explode_assignable_expression_7.8.3.tgz\";\n      path = fetchurl {\n        name = \"_babel_helper_explode_assignable_expression___helper_explode_assignable_expression_7.8.3.tgz\";\n        url  = \"https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz\";\n        sha1 = \"a728dc5b4e89e30fc2dfc7d04fa28a930653f982\";\n      };\n    }`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\tname: `invalid_assignable`,\n\t\t\tdata: `<tr><td colspan=\"2\"><br><h2>Public Member Functions</h2></td></tr>\n<tr><td class=\"memItemLeft\" nowrap align=\"right\" valign=\"top\"><a class=\"anchor\" name=\"b0a0dbf6ca9028bbbb2240cad5882537\"></a><!-- doxytag: member=\"boost::gil::Assignable::constraints\" ref=\"b0a0dbf6ca9028bbbb2240cad5882537\" args=\"()\" -->\nvoid&nbsp;</td><td class=\"memItemRight\" valign=\"bottom\"><b>constraints</b> ()</td></tr>\n`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\tname: `invalid_assignable`,\n\t\t\tdata: `File: enumIsAssignableToBuiltInEnum.kt - 6396cf8549625bfce8b8ca2511d7f347\n  NL(\"\\n\")\n  packageHeader`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\tname: `invalid_assignable_php`,\n\t\t\tdata: `'./include/SugarObjects/forms/PersonFormBase.php' => '2c1846ef127d60a40ecbab2c0b312ff5',\n  './include/SugarObjects/implements/assignable/language/en_us.lang.php' => '90f14b03e22e1eed2a1b93e10b975ef5',\n  './include/SugarObjects/implements/assignable/vardefs.php' => '358e0c47f753c5577fbdc0de08553c02',\n  './include/SugarObjects/implements/security_groups/language/en_us.lang.php' => 'ac1fd4817cb4662e3bdf973836558bdb',`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t// https://github.com/past-due/warzone2100/blob/3e5637a7ed3d67ab92e439b94bf93f89f7bbea51/ChangeLog#L318\n\t\t{\n\t\t\tname: `invalid_designable`,\n\t\t\tdata: `   * Fix: Prevent map selection button list from going off the form (commit:aea66eb1aa557c73d97b8019e5e66fccbb79f66e, #1347)\n   * Fix: Fix odd EMP mortar pathway; Add EMP mortar to designable weapons (commit:bcf93b7fe640c09e8b1239fabfd901fde9760259, #1535)`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t// https://github.com/exis-io/Exis/blob/5383174f7b52112a97aadd09e6b9ea837c2fa07b/CardsAgainstHumanityDemo/swiftCardsAgainst/Pods/Pods.xcodeproj/project.pbxproj\n\t\t{\n\t\t\tname: `invalid_designable`,\n\t\t\tdata: `\t\t570767CBD99941F484DED46232044DC3 /* DesignableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93111B182BD71051B9ED0B14A9EF6EB6 /* DesignableView.swift */; };\n\t\t57140D31D50A2FDE1E26729DDE7CB762 /* M13ProgressHUD.h in Headers */ = {isa = PBXBuildFile; fileRef = ECF777CB8B4C090E8D271E62729F7DD3 /* M13ProgressHUD.h */; settings = {ATTRIBUTES = (Public, ); }; };`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\tname:        `invalid_designable`,\n\t\t\tdata:        `{\"nick\":\"hamster88\",\"message\":\"this is my gist > https://gist.github.com/thedesignable/a05f628c649a81aae757945c352a8392\",\"date\":\"2016-06-19T11:05:13.776Z\",\"type\":\"message\"}`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\tname: `invalid_designable`,\n\t\t\tdata: `返回到故事板文件，选择视图(我将假设从现在起视图被选中)并打开 Identity Inspector。你会注意到一个*可设计的*状态指示器已经出现在自定义类部分。\n\n![Designables status](img/84bc9afc942815899347a31a425af7c6.png)`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\tname:        `invalid_designable`,\n\t\t\tdata:        `&lt;h3 id=&#34;ibdesignable-x-paintcode:0b699a3cd6d609650a3fca90a5cd32cc&#34;&gt;IBDesignable x PaintCode&lt;/h3&gt;`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\tname: `invalid_designable`,\n\t\t\tdata: `    </designables>\n    <resources>\n        <image name=\"a932cb605eb09bff88b88fdf1c3ef8aa\" width=\"736\" height=\"895\"/>\n        <image name=\"plus\" catalog=\"system\" width=\"128\" height=\"113\"/>`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\n\t\t\tresults, err := s.FromData(context.Background(), false, []byte(test.data))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Signable.FromData() error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif test.shouldMatch {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"%s: did not receive a match for '%v' when one was expected\", test.name, test.data)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\texpected := test.data\n\t\t\t\tif test.match != \"\" {\n\t\t\t\t\texpected = test.match\n\t\t\t\t}\n\t\t\t\tresult := results[0]\n\t\t\t\tresultData := string(result.Raw)\n\t\t\t\tif resultData != expected {\n\t\t\t\t\tt.Errorf(\"%s: did not receive expected match.\\n\\texpected: '%s'\\n\\t  actual: '%s'\", test.name, expected, resultData)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif len(results) > 0 {\n\t\t\t\t\tt.Errorf(\"%s: received a match for '%v' when one wasn't wanted\", test.name, test.data)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/signalwire/signalwire.go",
    "content": "package signalwire\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"signalwire\"}) + `\\b([0-9A-Za-z]{50})\\b`)\n\tidPat  = 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`)\n\turlPat = regexp.MustCompile(`\\b([0-9a-z-]{3,64}\\.signalwire\\.com)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"signalwire\"}\n}\n\n// FromData will find and optionally verify Signalwire secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\turlMatches := urlPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\t\t\tresID := strings.TrimSpace(idMatch[1])\n\n\t\t\tfor _, urlMatch := range urlMatches {\n\t\t\t\tresURL := strings.TrimSpace(urlMatch[1])\n\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Signalwire,\n\t\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tdata := fmt.Sprintf(\"%s:%s\", resID, resMatch)\n\t\t\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://%s/api/laml/2010-04-01/Accounts\", resURL), nil)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\t\t\tres, err := client.Do(req)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Signalwire\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/signalwire/signalwire_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage signalwire\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSignalwire_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tid := testSecrets.MustGetField(\"SIGNALWIRE_ID\")\n\tsecret := testSecrets.MustGetField(\"SIGNALWIRE_TOKEN\")\n\turl := testSecrets.MustGetField(\"SIGNALWIRE_URL\")\n\tinactiveSecret := testSecrets.MustGetField(\"SIGNALWIRE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a signalwire secret %s within signalwire url %s and signalwire id %s\", secret, url, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Signalwire,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Signalwire,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Signalwire.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Signalwire.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/signalwire/signalwire_test.go",
    "content": "package signalwire\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey   = \"rBqctDvrJyZ1MAj2aKHpi6wGcPiCzGtEbCSqtE46RB7yVCywVA\"\n    invalidKey = \"rBqctDvrJyZ1MAj2aKHpi6wGc?iCzGtEbCSqtE46RB7yVCywVA\"\n    validId    = \"dy0wgsgd-byq5-iqfb-ez1d-dvjepkyub9n9\"\n    invalidId  = \"dy0wgsgd?byq5-iqfb-ez1d-dvjepkyub9n9\"\n    validUrl   = \"09ft358n4gpa1c.signalwire.com\"\n    invalidUrl = \"09ft358n4gpa1c?signalwire.com\"\n    keyword    = \"signalwire\"\n)\n\nfunc TestSignalwire_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword signalwire\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId, keyword, validUrl),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId, keyword, invalidUrl),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/signaturit/signaturit.go",
    "content": "package signaturit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"signaturit\"}) + `\\b([0-9A-Za-z]{86})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"signaturit\"}\n}\n\n// FromData will find and optionally verify Signaturit secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Signaturit,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.sandbox.signaturit.com/v3/signatures.json\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Signaturit\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Signaturit is a service for electronic signatures. Signaturit API keys can be used to access and manage signature requests and related data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/signaturit/signaturit_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage signaturit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSignaturit_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SIGNATURIT\")\n\tinactiveSecret := testSecrets.MustGetField(\"SIGNATURIT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a signaturit secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Signaturit,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Signaturit,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Signaturit.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Signaturit.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/signaturit/signaturit_test.go",
    "content": "package signaturit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"4AgnWjwDhcgHULFkDkQCY7bra8XfsV9l72JYqsIAFnE9mEo6VusS5yKBJq3OSTEyJfsPoPhorAUc9yUiCxCIxz\"\n\tinvalidPattern = \"4AgnWjwDhcgHULFkDkQCY7bra8XfsV?l72JYqsIAFnE9mEo6VusS5yKBJq3OSTEyJfsPoPhorAUc9yUiCxCIxz\"\n\tkeyword        = \"signaturit\"\n)\n\nfunc TestSignaturit_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword signaturit\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/signupgenius/signupgenius.go",
    "content": "package signupgenius\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"signupgenius\"}) + `\\b([0-9A-Za-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"signupgenius\"}\n}\n\n// FromData will find and optionally verify Signupgenius secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Signupgenius,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.signupgenius.com/v2/k/user/profile/?user_key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Signupgenius\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/signupgenius/signupgenius_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage signupgenius\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSignupgenius_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SIGNUPGENIUS\")\n\tinactiveSecret := testSecrets.MustGetField(\"SIGNUPGENIUS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a signupgenius secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Signupgenius,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Signupgenius,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Signupgenius.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Signupgenius.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/signupgenius/signupgenius_test.go",
    "content": "package signupgenius\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"kARx6uGgPqBkho0jzPNfgnAduN3hDjBY\"\n\tinvalidPattern = \"kARx6uGgPqBkho0j?PNfgnAduN3hDjBY\"\n\tkeyword        = \"signupgenius\"\n)\n\nfunc TestSignupgenius_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword signupgenius\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sigopt/sigopt.go",
    "content": "package sigopt\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"sigopt\"}) + `\\b([A-Z0-9]{48})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sigopt\"}\n}\n\n// FromData will find and optionally verify Sigopt secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Sigopt,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tdata := fmt.Sprintf(\"%s:\", resMatch)\n\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.sigopt.com/v1/experiments\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Sigopt\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Sigopt is a platform for optimizing machine learning models. Sigopt API keys can be used to access and manage experiments on the Sigopt platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sigopt/sigopt_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sigopt\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSigopt_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SIGOPT\")\n\tinactiveSecret := testSecrets.MustGetField(\"SIGOPT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sigopt secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sigopt,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sigopt,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Sigopt.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Sigopt.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sigopt/sigopt_test.go",
    "content": "package sigopt\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"GF67UZ5P7UX2A6ELCAE11YDCOYJABCBDYK26O76HRIPU294T\"\n\tinvalidPattern = \"GF67UZ5P7UX2A6ELCAE11YDC?YJABCBDYK26O76HRIPU294T\"\n\tkeyword        = \"sigopt\"\n)\n\nfunc TestSigopt_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sigopt\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/simfin/simfin.go",
    "content": "package simfin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"simfin\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"simfin\"}\n}\n\n// FromData will find and optionally verify SimFin secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SimFin,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://simfin.com/api/v2/companies/list?api-key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\tvalidResponse := !strings.Contains(bodyString, `\"error\"`)\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && validResponse {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SimFin\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"SimFin provides financial data and APIs for accessing this data. SimFin API keys can be used to access and retrieve financial data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/simfin/simfin_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage simfin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSimFin_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SIMFIN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SIMFIN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a simfin secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SimFin,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SimFin,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SimFin.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SimFin.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/simfin/simfin_test.go",
    "content": "package simfin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"Y28WVaw01VjEEZQ6zn4HvmfpEWT70nPC\"\n\tinvalidPattern = \"Y28WVaw01VjEEZQ6?n4HvmfpEWT70nPC\"\n\tkeyword        = \"simfin\"\n)\n\nfunc TestSimFin_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword simfin\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/simplesat/simplesat.go",
    "content": "package simplesat\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"simplesat\"}) + `\\b([a-z0-9]{40})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"simplesat\"}\n}\n\n// FromData will find and optionally verify Simplesat secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Simplesat,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.simplesat.io/api/answers/\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-Simplesat-Token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Simplesat\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Simplesat is a customer satisfaction survey tool. Simplesat API keys can be used to access and manage customer feedback data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/simplesat/simplesat_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage simplesat\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSimplesat_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SIMPLESAT_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SIMPLESAT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a simplesat secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Simplesat,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Simplesat,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Simplesat.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Simplesat.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/simplesat/simplesat_test.go",
    "content": "package simplesat\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"52dfk83ssx3hnz3ke35jqqucwy5ojdtyd0at5v4c\"\n\tinvalidPattern = \"52dfk83ssx3hnz3ke35j?qucwy5ojdtyd0at5v4c\"\n\tkeyword        = \"simplesat\"\n)\n\nfunc TestSimplesat_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword simplesat\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/simplynoted/simplynoted.go",
    "content": "package simplynoted\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"simplynoted\"}) + `\\b([a-zA-Z0-9\\S]{340,360})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"simplynoted\"}\n}\n\n// FromData will find and optionally verify SimplyNoted secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SimplyNoted,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.simplynoted.com/api/products\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SimplyNoted\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"SimplyNoted is a service used for sending personalized handwritten notes. SimplyNoted API keys can be used to access and send these notes programmatically.\"\n}\n"
  },
  {
    "path": "pkg/detectors/simplynoted/simplynoted_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage simplynoted\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSimplyNoted_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SIMPLYNOTED\")\n\tinactiveSecret := testSecrets.MustGetField(\"SIMPLYNOTED_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a simplynoted secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SimplyNoted,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SimplyNoted,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SimplyNoted.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SimplyNoted.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/simplynoted/simplynoted_test.go",
    "content": "package simplynoted\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"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\"\n\tinvalidPattern = \"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\"\n\tkeyword        = \"simplynoted\"\n)\n\nfunc TestSimplyNoted_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword simplynoted\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/simvoly/simvoly.go",
    "content": "package simvoly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"simvoly\"}) + `\\b([a-z0-9]{33})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"simvoly\"}\n}\n\n// FromData will find and optionally verify Simvoly secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Simvoly,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://rendyplayground.simvoly.com/api/site/members?group_id=12&limit=25&skip=25\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Simvoly\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Simvoly is a platform for building websites and online stores. Simvoly API keys can be used to access and manage site data and functionalities.\"\n}\n"
  },
  {
    "path": "pkg/detectors/simvoly/simvoly_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage simvoly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSimvoly_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SIMVOLY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SIMVOLY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a simvoly secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Simvoly,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Simvoly,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Simvoly.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Simvoly.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/simvoly/simvoly_test.go",
    "content": "package simvoly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"zu0rpr33yvk0c18vs5wzpx2nq9ww1bh2b\"\n\tinvalidPattern = \"zu0rpr33yvk0c18v?5wzpx2nq9ww1bh2b\"\n\tkeyword        = \"simvoly\"\n)\n\nfunc TestSimvoly_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword simvoly\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sinchmessage/sinchmessage.go",
    "content": "package sinchmessage\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"sinch\"}) + `\\b([a-z0-9]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"sinch\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sinch\"}\n}\n\n// FromData will find and optionally verify SinchMessage secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_SinchMessage,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tpayload := strings.NewReader(`\n\t\t\t\t{\n\t\t\t\t\"from\": \"447537454435\",\n\t\t\t\t\"to\": [ \"639668957581\" ],\n\t\t\t\t\"body\": \"This is a test message from your Sinch account\"\n\t\t\t\t}`)\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://sms.api.sinch.com/xms/v1/\"+resIdMatch+\"/batches\", payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SinchMessage\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sinchmessage/sinchmessage_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sinchmessage\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSinchMessage_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SINCHMESSAGE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SINCHMESSAGE_INACTIVE\")\n\tid := testSecrets.MustGetField(\"SINCHMESSAGE_PLANID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sinchmessage secret %s within sinchid %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SinchMessage,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SinchMessage,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SinchMessage.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SinchMessage.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sinchmessage/sinchmessage_test.go",
    "content": "package sinchmessage\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey   = \"k255nuzmx8fzlkjfjz5unlrdbd658bon\"\n\tinvalidKey = \"k255nuzmx8fzlk?fjz5unlrdbd658bon\"\n\tvalidId    = \"y9yvuc6hl2up5qyohet77j23ntll7pr7\"\n\tinvalidId  = \"y9yvuc6?l2up5qyohet77j23ntll7pr7\"\n\tkeyword    = \"sinchmessage\"\n)\n\nfunc TestSinchMessage_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sinchmessage\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey, validId, validKey, validId},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sirv/sirv.go",
    "content": "package sirv\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"sirv\"}) + `\\b([a-zA-Z0-9\\S]{88})`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"sirv\"}) + `\\b([a-zA-Z0-9]{26})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sirv\"}\n}\n\n// FromData will find and optionally verify Sirv secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Sirv,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\ttimeout := 10 * time.Second\n\t\t\t\tclient.Timeout = timeout\n\t\t\t\tpayload := strings.NewReader(fmt.Sprintf(`{\"clientId\":\"%s\",\"clientSecret\":\"%s\"}`, resIdMatch, resMatch))\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.sirv.com/v2/token\", payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Sirv\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Sirv is a media management service used for image optimization and delivery. Sirv API keys can be used to access and manage media files.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sirv/sirv_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sirv\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSirv_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SIRV\")\n\tid := testSecrets.MustGetField(\"SIRV_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"SIRV_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sirv secret %s within sirv %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sirv,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sirv,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Sirv.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Sirv.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sirv/sirv_test.go",
    "content": "package sirv\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey   = \"Ivus86]AJDk:rghv;8,o00SVP0efds5BBh453jew~mnC$K8elZI-OGU^v0AA#`>vek1cKlkipm]!n/f1xTDlUn1D\"\n\tinvalidKey = \"?vus86]AJDk:rghv;8,o00SVP0efds5BBh453jew~mnC$K8elZI-OGU^v0AA#`>vek1cKlkipm]!n/f1xTDlUn1D\"\n\tvalidId    = \"n64cjK3Dg9Xyr2icHYypLwXl6V\"\n\tinvalidId  = \"n64cjK3Dg9Xyr?icHYypLwXl6V\"\n\tkeyword    = \"sirv\"\n)\n\nfunc TestSirv_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sirv\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/siteleaf/siteleaf.go",
    "content": "package siteleaf\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"siteleaf\"}) + `\\b([0-9Aa-z]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"siteleaf\"}) + `\\b([0-9Aa-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"siteleaf\"}\n}\n\n// FromData will find and optionally verify Siteleaf secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Siteleaf,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\n\t\t\t\tdata := fmt.Sprintf(\"%s:%s\", resIdMatch, resMatch)\n\t\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.siteleaf.com/v2/sites\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Siteleaf\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/siteleaf/siteleaf_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage siteleaf\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSiteleaf_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SITELEAF_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SITELEAF_INACTIVE\")\n\tid := testSecrets.MustGetField(\"SITELEAF_USERID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a siteleafkey secret %s within siteleafid %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Siteleaf,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Siteleaf,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Siteleaf.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Siteleaf.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/siteleaf/siteleaf_test.go",
    "content": "package siteleaf\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey   = \"lklep3linr2itd42nkncw9b8s9g1yrgb\"\n\tinvalidKey = \"lklep3li?r2itd42nkncw9b8s9g1yrgb\"\n\tvalidId    = \"zoc07j72vs6q1lxs6j5vkzz7mefhiii6\"\n\tinvalidId  = \"zoc07j72vs?q1lxs6j5vkzz7mefhiii6\"\n\tkeyword    = \"siteleaf\"\n)\n\nfunc TestSiteleaf_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword siteleaf\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey, validId, validKey, validId},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/skrappio/skrappio.go",
    "content": "package skrappio\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"skrapp\"}) + `\\b([a-z0-9A-Z]{42})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"skrapp\"}\n}\n\n// FromData will find and optionally verify Skrapio secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Skrappio,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.skrapp.io/api/v2/account\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"X-Access-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Skrappio\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/skrappio/skrappio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage skrappio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSkrapio_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SKRAPIO_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SKRAPIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a skrapp secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Skrappio,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Skrappio,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Skrapio.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Skrapio.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/skrappio/skrappio_test.go",
    "content": "package skrappio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"rAheANMWIEHz9GfmtocXjvG8ESfdZvgSMTbDBIR7SA\"\n\tinvalidPattern = \"rAheANMWIEHz9GfmtocXj?G8ESfdZvgSMTbDBIR7SA\"\n\tkeyword        = \"skrappio\"\n)\n\nfunc TestSkrapio_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword skrappio\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/skybiometry/skybiometry.go",
    "content": "package skybiometry\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"skybiometry\"}) + `\\b([0-9a-z]{25,26})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"skybiometry\"}) + `\\b([0-9a-z]{25,26})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"skybiometry\"}\n}\n\n// FromData will find and optionally verify SkyBiometry secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueKeyMatches, uniqueSecretMatches := make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeyMatches[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range secretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueSecretMatches[match[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeyMatches {\n\t\tfor secret := range uniqueSecretMatches {\n\t\t\tif key == secret {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_SkyBiometry,\n\t\t\t\tRaw:          []byte(secret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, verificationErr := verifySkyBiometery(ctx, client, key, secret)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SkyBiometry\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"SkyBiometry is a facial recognition service. SkyBiometry API keys can be used to access and utilize their facial recognition API.\"\n}\n\nfunc verifySkyBiometery(ctx context.Context, client *http.Client, apiKey, apiSecret string) (bool, error) {\n\tapiURL := fmt.Sprintf(\"https://api.skybiometry.com/fc/account/authenticate?api_key=%s&api_secret=%s\", apiKey, apiSecret)\n\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", apiURL, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusBadRequest, http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/skybiometry/skybiometry_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage skybiometry\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSkyBiometry_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*10)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tkey := testSecrets.MustGetField(\"SKYBIOMETRY_KEY\")\n\tinactiveKey := testSecrets.MustGetField(\"SKYBIOMETRY_KEY_INACTIVE\")\n\tsecret := testSecrets.MustGetField(\"SKYBIOMETRY\")\n\tinactiveSecret := testSecrets.MustGetField(\"SKYBIOMETRY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a skybiometry key %s with skybiometry secret %s within\", key, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SkyBiometry,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SkyBiometry,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SkyBiometry,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SkyBiometry,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SkyBiometry.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SkyBiometry.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/skybiometry/skybiometry_test.go",
    "content": "package skybiometry\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey      = \"qdmxigxqpkpo8i38zxfuxct3d2\"\n\tinvalidKey    = \"qdmx?gxqpkpo8i38zxfuxct3d2\"\n\tvalidSecret   = \"ko86ttplsbrao397eub88pqniu\"\n\tinvalidSecret = \"ko86ttplsbrao?97eub88pqniu\"\n\tkeyword       = \"skybiometry\"\n)\n\nfunc TestSkyBiometry_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword skybiometry\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validSecret),\n\t\t\twant:  []string{validKey, validSecret},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidSecret),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/slack/slack.go",
    "content": "package slack\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\n// Check that the Slack scanner implements the SecretScanner interface at compile time.\nvar _ detectors.Detector = Scanner{}\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\ttokenPats     = map[string]*regexp.Regexp{\n\t\t\"Slack Bot Token\":               regexp.MustCompile(`xoxb\\-[0-9]{10,13}\\-[0-9]{10,13}[a-zA-Z0-9\\-]*`),\n\t\t\"Slack User Token\":              regexp.MustCompile(`xoxp\\-[0-9]{10,13}\\-[0-9]{10,13}[a-zA-Z0-9\\-]*`),\n\t\t\"Slack Workspace Access Token\":  regexp.MustCompile(`xoxa\\-[0-9]{10,13}\\-[0-9]{10,13}[a-zA-Z0-9\\-]*`),\n\t\t\"Slack Workspace Refresh Token\": regexp.MustCompile(`xoxr\\-[0-9]{10,13}\\-[0-9]{10,13}[a-zA-Z0-9\\-]*`),\n\t}\n\tverifyURL = \"https://slack.com/api/auth.test\"\n)\n\ntype authRes struct {\n\tOk     bool   `json:\"ok\"`\n\tURL    string `json:\"url\"`  // Workspace URL\n\tTeam   string `json:\"team\"` // Human friendly workspace name\n\tUser   string `json:\"user\"` // Username\n\tTeamID string `json:\"team_id\"`\n\tUserID string `json:\"user_id\"`\n\tBotID  string `json:\"bot_id\"`\n\tError  string `json:\"error\"`\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"xoxb-\", \"xoxp-\", \"xoxa-\", \"xoxr-\"}\n}\n\n// FromData will find and optionally verify Slack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tfor key, tokenPat := range tokenPats {\n\t\ttokens := tokenPat.FindAllString(dataStr, -1)\n\n\t\tfor _, token := range tokens {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Slack,\n\t\t\t\tRaw:          []byte(token),\n\t\t\t}\n\t\t\ts1.ExtraData = map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/slack/\",\n\t\t\t\t\"token_type\":     key,\n\t\t\t}\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif s.client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", verifyURL, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json; charset=utf-8\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tvar authResponse authRes\n\t\t\t\t\tif err := json.NewDecoder(res.Body).Decode(&authResponse); err != nil {\n\t\t\t\t\t\terr = fmt.Errorf(\"failed to decode auth response: %w\", err)\n\t\t\t\t\t\ts1.SetVerificationError(err, token)\n\t\t\t\t\t}\n\n\t\t\t\t\tif authResponse.Ok {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t// Store name of user and team in extra data received from slack's api\n\t\t\t\t\t\ts1.ExtraData[\"team\"] = authResponse.Team\n\t\t\t\t\t\ts1.ExtraData[\"name\"] = authResponse.User\n\t\t\t\t\t\t// Slack API returns 200 even if the token is invalid. We need to check the error field.\n\t\t\t\t\t} else if authResponse.Error == \"invalid_auth\" {\n\t\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t\t} else if authResponse.Error == \"account_inactive\" {\n\t\t\t\t\t\t// \"Authentication token is for a deleted user or workspace when using a bot token.\"\n\t\t\t\t\t\t// https://api.slack.com/methods/auth.test) (Per\n\t\t\t\t\t\t// https://slack.com/help/articles/360000446446-Manage-deactivated-members-apps-and-integrations,\n\t\t\t\t\t\t// reactivating a bot regenerates its tokens, so this candidate is determinately unverified.)\n\t\t\t\t\t} else if authResponse.Error == \"token_revoked\" {\n\t\t\t\t\t\t// \"Authentication token is for a deleted user or workspace, or the app has been removed when using a user token.\"\n\t\t\t\t\t\t// This indicates the token is no longer valid and determinately unverified.\n\t\t\t\t\t\t// https://api.slack.com/methods/auth.test\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr = fmt.Errorf(\"unexpected error auth response %+v\", authResponse.Error)\n\t\t\t\t\t\ts1.SetVerificationError(err, token)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ts1.SetVerificationError(err, token)\n\t\t\t\t}\n\t\t\t\ts1.AnalysisInfo = map[string]string{\n\t\t\t\t\t\"key\": token,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Slack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Slack tokens can be used to authenticate API requests to the Slack platform, allowing access to various workspace resources and functionalities.\"\n}\n"
  },
  {
    "path": "pkg/detectors/slack/slack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage slack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSlack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SLACK\")\n\tsecretInactive := testSecrets.MustGetField(\"SLACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twantResults         []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a slack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantResults: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Slack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"name\":           \"marge.haskell.bridge\",\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/slack/\",\n\t\t\t\t\t\t\"team\":           \"ct.org\",\n\t\t\t\t\t\t\"token_type\":     \"Slack User Token\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found but unverified\",\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a slack secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantResults: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Slack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/slack/\",\n\t\t\t\t\t\t\"token_type\":     \"Slack User Token\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"account_inactive\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(200, `{\"ok\": false, \"error\": \"account_inactive\"}`)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a slack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantResults: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Slack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/slack/\",\n\t\t\t\t\t\t\"token_type\":     \"Slack User Token\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"token_revoked\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(200, `{\"ok\": false, \"error\": \"token_revoked\"}`)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a slack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantResults: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Slack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/slack/\",\n\t\t\t\t\t\t\"token_type\":     \"Slack User Token\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a slack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantResults: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Slack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/slack/\",\n\t\t\t\t\t\t\"token_type\":     \"Slack User Token\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"unexpected auth response\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(200, `{\"ok\": false, \"error\": \"unexpected_error\"}`)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a slack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantResults: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Slack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/slack/\",\n\t\t\t\t\t\t\"token_type\":     \"Slack User Token\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Slack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].AnalysisInfo = nil\n\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.wantResults, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Slack.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/slack/slack_test.go",
    "content": "package slack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidBotToken                = \"xoxb-65677559833-9613778673399u\"\n\tinvalidBotToken              = \"xoxb-65677559833?9613778673399u\"\n\tvalidUserToken               = \"xoxp-24232636415-0285024315463c\"\n\tinvalidUserToken             = \"xoxp-24232636415?0285024315463c\"\n\tvalidWorkspaceAccessToken    = \"xoxa-08532509747-07570405353c\"\n\tinvalidWorkspaceAccessToken  = \"xoxa-08532509747?07570405353c\"\n\tvalidWorkspaceRefreshToken   = \"xoxr-833485595373-24619897332l\"\n\tinvalidWorkspaceRefreshToken = \"xoxr-833485595373?24619897332l\"\n\tkeyword                      = \"slack\"\n)\n\nfunc TestSlack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword slack\",\n\t\t\tinput: 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),\n\t\t\twant:  []string{validBotToken, validUserToken, validWorkspaceAccessToken, validWorkspaceRefreshToken},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: 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),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/slackwebhook/slackwebhook.go",
    "content": "package slackwebhook\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\tregexp \"github.com/wasilibs/go-re2\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPats = map[string]*regexp.Regexp{\n\t\t\"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})`),\n\t\t\"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})`),\n\t}\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"hooks.slack.com\"}\n}\n\n// FromData will find and optionally verify SlackWebhook secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tfor _, keyPat := range keyPats {\n\t\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\t\tfor _, match := range matches {\n\t\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_SlackWebhook,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\t\t\ts1.ExtraData = map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/slack-webhook/\",\n\t\t\t}\n\n\t\t\tif verify {\n\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\t// We don't want to actually send anything to webhooks we find. To verify them without spamming them, we\n\t\t\t\t// send an intentionally malformed message and look for a particular expected error message.\n\t\t\t\tpayload := strings.NewReader(`intentionally malformed JSON from TruffleHog scan`)\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", resMatch, payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tdefer res.Body.Close()\n\n\t\t\t\t\tswitch {\n\t\t\t\t\tcase res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusMultipleChoices:\n\t\t\t\t\t\t// Hopefully this never happens - it means we actually sent something to a channel somewhere. But\n\t\t\t\t\t\t// we at least know the secret is verified.\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\tcase res.StatusCode == http.StatusBadRequest && bytes.Equal(bodyBytes, []byte(\"invalid_payload\")):\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\tcase res.StatusCode == http.StatusNotFound || res.StatusCode == http.StatusForbidden:\n\t\t\t\t\t\t// Not a real webhook or the owning app's OAuth token has been revoked or the app has been deleted\n\t\t\t\t\t\t// You might want to handle this case or log it.\n\t\t\t\t\tdefault:\n\t\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d: %s\", res.StatusCode, bodyBytes)\n\t\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SlackWebhook\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Slack webhooks are used to send messages from external sources into Slack channels. If compromised, they can be used to send unauthorized messages.\"\n}\n\nfunc (s Scanner) IsFalsePositive(result detectors.Result) (bool, string) {\n\t// ignore \"https:\" as a false positive for slack webhook detector\n\tif strings.Contains(string(result.Raw), \"https:\") {\n\t\treturn false, \"\"\n\t}\n\n\t// back to the default false positive checks\n\treturn detectors.IsKnownFalsePositive(string(result.Raw), detectors.DefaultFalsePositives, true)\n\n}\n"
  },
  {
    "path": "pkg/detectors/slackwebhook/slackwebhook_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage slackwebhook\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSlackWebhook_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SLACKWEBHOOK_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SLACKWEBHOOK_INACTIVE\")\n\tdeletedWebhook := testSecrets.MustGetField(\"SLACKWEBHOOK_DELETED\")\n\tdeletedUserActiveWebhook := testSecrets.MustGetField(\"SLACKWEBHOOK_DELETED_USER_ACTIVE_WEBHOOK\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"active webhook\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a slackwebhook secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SlackWebhook,\n\t\t\t\t\tExtraData:    map[string]string{\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/slack-webhook/\"},\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"active webhook created by a deactivated user\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a slackwebhook secret %s within\", deletedUserActiveWebhook)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SlackWebhook,\n\t\t\t\t\tExtraData:    map[string]string{\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/slack-webhook/\"},\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"deleted webhook\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a slackwebhook secret %s within\", deletedWebhook)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SlackWebhook,\n\t\t\t\t\tExtraData:    map[string]string{\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/slack-webhook/\"},\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"webhook from app with revoked token\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a slackwebhook secret %s within\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SlackWebhook,\n\t\t\t\t\tExtraData:    map[string]string{\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/slack-webhook/\"},\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"unexpected webhook response\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"oh no\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a slackwebhook secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SlackWebhook,\n\t\t\t\t\tExtraData:    map[string]string{\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/slack-webhook/\"},\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a slackwebhook secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SlackWebhook,\n\t\t\t\t\tExtraData:    map[string]string{\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/slack-webhook/\"},\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SlackWebhook.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"SlackWebhook.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/slackwebhook/slackwebhook_test.go",
    "content": "package slackwebhook\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"https://hooks.slack.com/services/TAGGINGEXAMPLE/BASE/91nziTEEzAAcaNZiz1mPPoXyS\"\n\tinvalidPattern = \"https://hooks.slack.com/apps/LAGGINGEXAMPLE/BASE/91nziTEEzAAcaNZiz1mPPoXyS\"\n)\n\nfunc TestSlackWebHook_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: invalidPattern,\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/smartsheets/smartsheets.go",
    "content": "package smartsheets\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"sheet\"}) + `\\b([a-zA-Z0-9]{26}|[a-zA-Z0-9]{37})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"smartsheet\"}\n}\n\n// FromData will find and optionally verify Smartsheets secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueKeys = make(map[string]struct{})\n\n\tfor _, matche := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeys[matche[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeys {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Smartsheets,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifySmartSheetsToken(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Smartsheets\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc verifySmartSheetsToken(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.smartsheet.com/2.0/sheets\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/smartsheets/smartsheets_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage smartsheets\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSmartsheets_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SMARTSHEETS_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SMARTSHEETS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a smartsheets secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Smartsheets,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Smartsheets,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Smartsheets.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Smartsheets.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/smartsheets/smartsheets_test.go",
    "content": "package smartsheets\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestSmartsheets_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"valid pattern - with keyword smartsheet and sheet\",\n\t\t\tinput: `\n\t\t\t# do not share these secrets\n\t\t\t# list all sheets\n\t\t\tsheets := getsmartsheet(\"MVE7zmdxouvunYkowLzaudyX7tvMpkqJ3q52C\")\n\t\t\t`,\n\t\t\twant: []string{\"MVE7zmdxouvunYkowLzaudyX7tvMpkqJ3q52C\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - with prefixRegex sheet\",\n\t\t\tinput: `\n\t\t\t# smartsheet credentials\n\t\t\tsheet_id := \"MVE7zmdxouvunFAKELzaudyX7tvMpkqJ3q52d\"\n\t\t\t`,\n\t\t\twant: []string{\"MVE7zmdxouvunFAKELzaudyX7tvMpkqJ3q52d\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - ignore duplicate\",\n\t\t\tinput: `\n\t\t\t# smartsheet duplicate credentials\n\t\t\tsheet_id1 := \"MVE7zmdxouvunFAKELzaudyX7tvMpkqJ3q52d\"\n\t\t\tsheet_id2 := \"MVE7zmdxouvunFAKELzaudyX7tvMpkqJ3q52d\"\n\t\t\t`,\n\t\t\twant: []string{\"MVE7zmdxouvunFAKELzaudyX7tvMpkqJ3q52d\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - key out of prefix range\",\n\t\t\tinput: `\n\t\t\t# below is the smartsheet secret\n\t\t\t# use this secret to list sheets\n\t\t\t# do not share this\n\n\t\t\tsslist := listAll(\"MVE7zmdxouvunFAKELzaudyX7tvMpkqJ3q52d\")\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - 26 characters\",\n\t\t\tinput: `\n\t\t\t# smartsheet credentials\n\t\t\tsheet_token := \"fakeiq999fakeecyfake3ifake\"\n\t\t\t`,\n\t\t\twant: []string{\"fakeiq999fakeecyfake3ifake\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid pattern - 26 and 37 characters\",\n\t\t\tinput: `\n\t\t\t# smartsheet multiple length credentials\n\t\t\tsheet_token := \"fakeiq999fakeecyfake3ifake\"\n\t\t\tsheet_token2 := \"fakezmdxfakenFAKELzhonda7tvMpkqJ3fake\"\n\t\t\t`,\n\t\t\twant: []string{\"fakeiq999fakeecyfake3ifake\", \"fakezmdxfakenFAKELzhonda7tvMpkqJ3fake\"},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern - 30 characters\",\n\t\t\tinput: `\n\t\t\t# smartsheet invalid credentials\n\t\t\tsheet_token := \"fakeiq999fakeecyfake3ifakeuiop\"\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid pattern\",\n\t\t\tinput: `\n\t\t\t# smartsheet secret\n\t\t\tsheet_id = MVE7?mdxouvunYkowLzaudyX7tvMpkqJ3q52C\n\t\t\t`,\n\t\t\twant: []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/smartystreets/smartystreets.go",
    "content": "package smartystreets\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"smartystreets\"}) + `\\b([a-zA-Z0-9]{20})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"smartystreets\"}) + `\\b([a-z0-9-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"smartystreets\"}\n}\n\n// FromData will find and optionally verify SmartyStreets secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_SmartyStreets,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, 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)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SmartyStreets\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"SmartyStreets provides address validation services. The detected API keys can be used to access and validate addresses.\"\n}\n"
  },
  {
    "path": "pkg/detectors/smartystreets/smartystreets_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage smartystreets\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSmartyStreets_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SMARTYSTREETS\")\n\tid := testSecrets.MustGetField(\"SMARTYSTREETS_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"SMARTYSTREETS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a smartystreets secret %s within smartystreets %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SmartyStreets,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SmartyStreets,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SmartyStreets.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SmartyStreets.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/smartystreets/smartystreets_test.go",
    "content": "package smartystreets\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey   = \"UzwkWoJlOqT5q9pXbX2n\"\n    invalidKey = \"UzwkWoJlOq?5q9pXbX2n\"\n    validId    = \"pers5g2aa5icwflc1cmqjrs3ij8iqjmsg1x3\"\n    invalidId  = \"pers5g2aa5icwflc1c?qjrs3ij8iqjmsg1x3\"\n    keyword    = \"smartystreets\"\n)\n\nfunc TestSmartyStreets_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword smartystreets\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/smooch/smooch.go",
    "content": "package smooch\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"smooch\"}) + `\\b(act_[0-9a-z]{24})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"smooch\"}) + `\\b([0-9a-zA-Z_-]{86})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"smooch\"}\n}\n\n// FromData will find and optionally verify Smooch secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, secretMatch := range secretMatches {\n\t\t\tresSecret := strings.TrimSpace(secretMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Smooch,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resSecret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tdata := fmt.Sprintf(\"%s:%s\", resMatch, resSecret)\n\t\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.smooch.io/v2/apps\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Accept\", \"application/vnd.smooch+json; version=3\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Smooch\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/smooch/smooch_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage smooch\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSmooch_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SMOOCH_SECRET\")\n\tid := testSecrets.MustGetField(\"SMOOCH_ID\")\n\tinactiveID := testSecrets.MustGetField(\"SMOOCH_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a smooch secret %s within smooch id %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Smooch,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Smooch,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Smooch.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Smooch.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/smooch/smooch_test.go",
    "content": "package smooch\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey      = \"act_svf99w1upfk2syajjx6wxxg9\"\n\tinvalidKey    = \"act_svf99w1upf?2syajjx6wxxg9\"\n\tvalidSecret   = \"w2XXiJgDfMJnkKQMPKEF4AzeHnQseEcIsK0C7Gm0icA5PtRM4GIZl82B84n01RJMmkz3aGHtpP_44UXXKhW76L\"\n\tinvalidSecret = \"w2XXiJgDfMJnkKQMPKEF4AzeHnQseEcIsK0C7Gm0icA?PtRM4GIZl82B84n01RJMmkz3aGHtpP_44UXXKhW76L\"\n\tkeyword       = \"smooch\"\n)\n\nfunc TestSmooch_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword smooch\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validSecret),\n\t\t\twant:  []string{validKey + validSecret},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidSecret),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/snipcart/snipcart.go",
    "content": "package snipcart\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"snipcart\"}) + `\\b([0-9A-Za-z_]{75})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"snipcart\"}\n}\n\n// FromData will find and optionally verify Snipcart secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Snipcart,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tdata := fmt.Sprintf(\"%s:\", resMatch)\n\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.snipcart.com/api/orders\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Snipcart\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Snipcart is an easy-to-implement shopping cart platform for developers. Snipcart API keys can be used to access and manage e-commerce functionalities.\"\n}\n"
  },
  {
    "path": "pkg/detectors/snipcart/snipcart_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage snipcart\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSnipcart_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SNIPCART\")\n\tinactiveSecret := testSecrets.MustGetField(\"SNIPCART_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a snipcart secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Snipcart,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Snipcart,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Snipcart.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Snipcart.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/snipcart/snipcart_test.go",
    "content": "package snipcart\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"zc62fqbZm4zyVsdPFAcaRhFnSFpO12Lpmas3PU9IK5CJEklhJDeybwRr8c7ibBG3fL3iWoXTHbb\"\n\tinvalidPattern = \"zc62fqbZm4zyVsdPFAcaRhFnSFpO12Lpmas3P?9IK5CJEklhJDeybwRr8c7ibBG3fL3iWoXTHbb\"\n\tkeyword        = \"snipcart\"\n)\n\nfunc TestSnipcart_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword snipcart\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/snowflake/snowflake.go",
    "content": "package snowflake\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\t// accountIdentifierPat matches Snowflake account identifiers in the format: XXXXXXX-XXXXX\n\t// Example: ABC1234-EXAMPLE\n\taccountIdentifierPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"account\"}) + `\\b([a-zA-Z]{7}-[0-9a-zA-Z-_]{1,255}(.privatelink)?)\\b`)\n\t// usernameExclusionPat defines characters that should not be present in usernames\n\tusernameExclusionPat = `!@#$%^&*{}:<>,.;?()/\\+=\\s\\n`\n)\n\nconst (\n\ttimeout           = 3 * time.Second\n\tminPasswordLength = 8\n)\n\n// loginRequest represents the payload for Snowflake's login endpoint.\ntype loginRequest struct {\n\tData struct {\n\t\tLoginName   string `json:\"LOGIN_NAME\"`\n\t\tPassword    string `json:\"PASSWORD\"`\n\t\tAccountName string `json:\"ACCOUNT_NAME\"`\n\t} `json:\"data\"`\n}\n\ntype loginResponse struct {\n\tSuccess bool `json:\"success\"`\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"snowflake\"}\n}\n\n// meetsSnowflakePasswordRequirements checks if a password meets Snowflake's requirements:\n// - Minimum length of 8 characters\n// - Contains at least one lowercase letter\n// - Contains at least one uppercase letter\n// - Contains at least one number\nfunc meetsSnowflakePasswordRequirements(password string) bool {\n\tif len(password) < minPasswordLength {\n\t\treturn false\n\t}\n\n\tvar hasLower, hasUpper, hasNumber bool\n\tfor _, char := range password {\n\t\tswitch {\n\t\tcase unicode.IsLower(char):\n\t\t\thasLower = true\n\t\tcase unicode.IsUpper(char):\n\t\t\thasUpper = true\n\t\tcase unicode.IsNumber(char):\n\t\t\thasNumber = true\n\t\t}\n\n\t\tif hasLower && hasUpper && hasNumber {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// FromData will find and optionally verify Snowflake secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// Find all unique account identifiers\n\tuniqueAccountMatches := make(map[string]struct{})\n\tfor _, match := range accountIdentifierPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueAccountMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tif len(uniqueAccountMatches) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tusernameRegexState := common.UsernameRegexCheck(usernameExclusionPat)\n\tusernameMatches := usernameRegexState.Matches(data)\n\tif len(usernameMatches) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tpasswordRegexState := common.PasswordRegexCheck(\" \\r\\n\") // Exclude spaces, carriage returns, and line feeds\n\tpasswordMatches := passwordRegexState.Matches(data)\n\tif len(passwordMatches) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tfor resAccountMatch := range uniqueAccountMatches {\n\t\tfor _, resUsernameMatch := range usernameMatches {\n\t\t\tfor _, resPasswordMatch := range passwordMatches {\n\t\t\t\tmetPasswordRequirements := meetsSnowflakePasswordRequirements(resPasswordMatch)\n\t\t\t\tif !metPasswordRequirements {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Snowflake,\n\t\t\t\t\tRaw:          []byte(resPasswordMatch),\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"account\":  resAccountMatch,\n\t\t\t\t\t\t\"username\": resUsernameMatch,\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\tif !verify {\n\t\t\t\t\tresults = append(results, s1)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tverified, err := verifyMatch(ctx, resAccountMatch, resUsernameMatch, resPasswordMatch)\n\t\t\t\ts1.SetVerificationError(err, resPasswordMatch)\n\t\t\t\ts1.Verified = verified\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\treturn results, nil\n}\n\n// verifyMatch attempts to verify a Snowflake credential by making a login request.\nfunc verifyMatch(ctx context.Context, account, username, password string) (bool, error) {\n\tloginReq := loginRequest{}\n\tloginReq.Data.LoginName = username\n\tloginReq.Data.Password = password\n\tloginReq.Data.AccountName = account\n\n\tjsonData, err := json.Marshal(loginReq)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to marshal login request: %w\", err)\n\t}\n\n\t// Note: This endpoint is undocumented in Snowflake's public API documentation.\n\turl := fmt.Sprintf(\"https://%s.snowflakecomputing.com/session/v1/login-request\", account)\n\treq, err := http.NewRequestWithContext(ctx, \"POST\", url, bytes.NewBuffer(jsonData))\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to create request: %w\", err)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Accept\", \"application/json\")\n\treq.Header.Set(\"X-Snowflake-Authorization-Token-Type\", \"BASIC\")\n\n\tclient := &http.Client{Timeout: timeout}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to send request: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to read response body: %w\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn false, nil\n\t}\n\n\tvar loginResp loginResponse\n\tif err := json.Unmarshal(body, &loginResp); err != nil {\n\t\treturn false, fmt.Errorf(\"failed to parse response: %w\", err)\n\t}\n\n\treturn loginResp.Success, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Snowflake\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/snowflake/snowflake_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage snowflake\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSnowflake_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\taccountIdentifier := testSecrets.MustGetField(\"SNOWFLAKE_ACCOUNT\")\n\tusername := testSecrets.MustGetField(\"SNOWFLAKE_USERNAME\")\n\tpassword := testSecrets.MustGetField(\"SNOWFLAKE_PASS\")\n\tinactivePassword := testSecrets.MustGetField(\"SNOWFLAKE_PASS_INACTIVE\")\n\n\t// Create a context with a past deadline to simulate DeadlineExceeded error\n\tpastTime := time.Now().Add(-time.Second) // Set the deadline in the past\n\terrorCtx, cancel := context.WithDeadline(context.Background(), pastTime)\n\tdefer cancel()\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"snowflake: \\n account=%s \\n username=%s \\n password=%s \\n database=SNOWFLAKE\", accountIdentifier, username, password)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Snowflake,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"account\":  accountIdentifier,\n\t\t\t\t\t\t\"username\": username,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"snowflake: \\n account=%s \\n username=%s \\n password=%s \\n database=SNOWFLAKE\", accountIdentifier, username, inactivePassword)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Snowflake,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"account\":  accountIdentifier,\n\t\t\t\t\t\t\"username\": username,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, indeterminate error (timeout)\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    errorCtx,\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"snowflake: \\n account=%s \\n username=%s \\n password=%s \\n database=SNOWFLAKE\", accountIdentifier, username, password)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Snowflake,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"account\":  accountIdentifier,\n\t\t\t\t\t\t\"username\": username,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Snowflake.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tkeysToCopy := []string{\"account\", \"username\"}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\n\t\t\t\tgot[i].ExtraData = newMap(got[i].ExtraData, keysToCopy)\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Snowflake.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc newMap(extraMap map[string]string, keysToCopy []string) map[string]string {\n\tnewExtraDataMap := make(map[string]string)\n\tfor _, key := range keysToCopy {\n\t\tif value, ok := extraMap[key]; ok {\n\t\t\tnewExtraDataMap[key] = value\n\t\t}\n\t}\n\treturn newExtraDataMap\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/snowflake/snowflake_test.go",
    "content": "package snowflake\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/brianvoe/gofakeit/v7\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestSnowflake_Pattern(t *testing.T) {\n\n\tvalidAccount := \"tuacoip-zt74995\"\n\tvalidPrivateLinkAccount := \"tuacoip-zt74995.privatelink\"\n\tvalidSingleCharacterAccount := \"tuacoip-z\"\n\tvalidUsername := gofakeit.Username()\n\tinvalidUsername := \"Spencer@5091.com\" // special characters not allowed\n\n\tvalidPassword := common.GenerateRandomPassword(true, true, true, false, 10)\n\tinvalidPassword := \"!12\" // invalid length\n\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  [][]string\n\t}{\n\t\t{\n\t\t\tname:  \"Snowflake Credentials\",\n\t\t\tinput: fmt.Sprintf(\"snowflake: \\n account=%s \\n username=%s \\n password=%s \\n database=SNOWFLAKE\", validAccount, validUsername, validPassword),\n\t\t\twant: [][]string{\n\t\t\t\t{validAccount, validUsername, validPassword},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"Private Snowflake Credentials\",\n\t\t\tinput: fmt.Sprintf(\"snowflake: \\n account=%s \\n username=%s \\n password=%s \\n database=SNOWFLAKE\", validPrivateLinkAccount, validUsername, validPassword),\n\t\t\twant: [][]string{\n\t\t\t\t{validPrivateLinkAccount, validUsername, validPassword},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"Snowflake Credentials - Single Character account\",\n\t\t\tinput: fmt.Sprintf(\"snowflake: \\n account=%s \\n username=%s \\n password=%s \\n database=SNOWFLAKE\", validSingleCharacterAccount, validUsername, validPassword),\n\t\t\twant: [][]string{\n\t\t\t\t{validSingleCharacterAccount, validUsername, validPassword},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"Snowflake Credentials - Invalid Username & Password\",\n\t\t\tinput: fmt.Sprintf(\"snowflake: \\n account=%s \\n username=%s \\n password=%s \\n database=SNOWFLAKE\", validAccount, invalidUsername, invalidPassword),\n\t\t\twant:  [][]string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tdetectorMatches := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(detectorMatches) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresultsArray := make([][]string, len(results))\n\t\t\tfor i, r := range results {\n\t\t\t\tresultsArray[i] = []string{r.ExtraData[\"account\"], r.ExtraData[\"username\"], string(r.Raw)}\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t\tactual[r.ExtraData[\"account\"]] = struct{}{}\n\t\t\t\tactual[r.ExtraData[\"username\"]] = struct{}{}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\tfor _, value := range v {\n\t\t\t\t\texpected[value] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/snykkey/snykkey.go",
    "content": "package snykkey\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar 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`)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"snyk\"}\n}\n\n// FromData will find and optionally verify SnykKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\ttokens := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\ttokens[match[1]] = struct{}{}\n\t}\n\n\tfor token := range tokens {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: s.Type(),\n\t\t\tRaw:          []byte(token),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, extraData, verificationErr := s.doVerification(ctx, token)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, token)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) doVerification(ctx context.Context, token string) (bool, map[string]string, error) {\n\tclient := s.client\n\tif client == nil {\n\t\tclient = common.SaneHttpClient()\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://snyk.io/api/v1/user/me\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"token %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\tif res.StatusCode == http.StatusOK {\n\t\tuserDetails := userDetailsResponse{}\n\t\terr := json.NewDecoder(res.Body).Decode(&userDetails)\n\t\tif err != nil {\n\t\t\treturn true, nil, err\n\t\t} else if userDetails.Username == \"\" {\n\t\t\treturn false, nil, fmt.Errorf(\"failed to decode JSON response\")\n\t\t}\n\n\t\textraData := map[string]string{\n\t\t\t\"Username\": userDetails.Username,\n\t\t\t\"Email\":    userDetails.Email,\n\t\t}\n\n\t\t// Condense a list of organizations\n\t\tif len(userDetails.Organizations) > 0 {\n\t\t\tvar orgs []string\n\t\t\tfor _, org := range userDetails.Organizations {\n\t\t\t\torgs = append(orgs, org.Name)\n\t\t\t}\n\t\t\textraData[\"Organizations\"] = strings.Join(orgs, \",\")\n\t\t}\n\t\treturn true, extraData, nil\n\t} else if res.StatusCode == http.StatusUnauthorized {\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\t} else {\n\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\treturn false, nil, err\n\t}\n}\n\n// https://snyk.docs.apiary.io/#reference/users/my-user-details/get-my-details\ntype userDetailsResponse struct {\n\tUsername      string         `json:\"username\"`\n\tEmail         string         `json:\"email\"`\n\tOrganizations []organization `json:\"orgs\"`\n}\n\ntype organization struct {\n\tName string `json:\"name\"`\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SnykKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/snykkey/snykkey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage snykkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSnykKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SNYKKEY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SNYKKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a snykkey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SnykKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"Email\":         \"rendyplayground@gmail.com\",\n\t\t\t\t\t\t\"Organizations\": \"rendyplayground\",\n\t\t\t\t\t\t\"Username\":      \"rendyplayground\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SnykKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SnykKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SnykKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/snykkey/snykkey_test.go",
    "content": "package snykkey\n\nimport (\n\t\"context\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"testing\"\n)\n\nfunc TestSnyk_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"typical pattern\",\n\t\t\tinput: `set NODE_REQUIRED_VERSION=12.13.1\nset SNYK_API_TOKEN=885953dc-2469-443c-983d-5243d2d54116\n\nset PATH=%PATH%;C:\\Program Files\\nodejs\\;C:\\Program Files\\Git\\cmd`,\n\t\t\twant: []string{\"885953dc-2469-443c-983d-5243d2d54116\"},\n\t\t},\n\t\t// https://docs.snyk.io/snyk-api/get-a-projects-sbom-document-endpoint#how-to-generate-the-sbom-for-a-project\n\t\t//\t{\n\t\t//\t\tname: \"curl example\",\n\t\t//\t\t`curl --get \\\n\t\t// -H \"Authorization: token ccc9ae71-913f-46bd-9d23-03356323400a\" \\\n\t\t// --data-urlencode \"version=2023-03-20\" \\\n\t\t// --data-urlencode \"format=cyclonedx1.4%2Bjson\" \\\n\t\t// https://api.snyk.io/rest/orgs/1234/projects/1234/sbom`,\n\t\t//\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatches := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matches) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sonarcloud/sonarcloud.go",
    "content": "package sonarcloud\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"sonar\"}) + `(?:^|[^@])\\b([0-9a-z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sonar\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify SonarCloud secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueTokenMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokenMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueTokenMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SonarCloud,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := s.verifyMatch(ctx, s.getClient(), match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\n// verifyMatch attempts to validate a SonarCloud token.\nfunc (s Scanner) verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\turl := \"https://sonarcloud.io/api/authentication/validate\"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to create request: %w\", err)\n\t}\n\n\treq.SetBasicAuth(token, \"\")\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to perform request: %w\", err)\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\t// The SonarCloud API always returns 200 OK, even for invalid tokens,\n\t// with the validity indicated in the JSON body.\n\tif res.StatusCode != http.StatusOK {\n\t\t// Treat any non-200 status as a failed attempt to verify.\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", res.StatusCode)\n\t}\n\n\tbodyBytes, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to read response body: %w\", err)\n\t}\n\n\tvar resp struct {\n\t\tValid bool `json:\"valid\"`\n\t}\n\n\tif err := json.Unmarshal(bodyBytes, &resp); err != nil {\n\t\treturn false, fmt.Errorf(\"invalid JSON: %w\", err)\n\t}\n\n\treturn resp.Valid, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SonarCloud\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"SonarCloud is a cloud-based code quality and security service. SonarCloud tokens can be used to access project analysis and quality reports.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sonarcloud/sonarcloud_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sonarcloud\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSonarCloud_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SONARCLOUD\")\n\tinactiveSecret := testSecrets.MustGetField(\"SONARCLOUD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sonarcloud secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SonarCloud,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SonarCloud,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SonarCloud.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SonarCloud.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sonarcloud/sonarcloud_test.go",
    "content": "package sonarcloud\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"hxrxxgxtcjxj7ta3dn33c5r5i2h0i6cqjv9kwkye\"\n\tinvalidPattern = \"hxrxxgxt?jxj7ta3dn33c5r5i2h0i6cqjv9kwkye\"\n\tkeyword        = \"sonarcloud\"\n)\n\nfunc TestSonarCloud_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sonarcloud\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern - token directly preceded by @\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '@%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sourcegraph/sourcegraph.go",
    "content": "package sourcegraph\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sgp_\"}\n}\n\n// FromData will find and optionally verify Sourcegraph secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Sourcegraph,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\t\ts1.ExtraData = map[string]string{\n\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/sourcegraph/\",\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\tpayload := strings.NewReader(\"{\\\"query\\\": \\\"query { currentUser { username } }\\\"}\")\n\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://sourcegraph.com/.api/graphql\", payload)\n\t\t\treq.Header.Add(\"Authorization\", \"token \"+resMatch)\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t\ts1.AnalysisInfo = map[string]string{\"key\": resMatch}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Sourcegraph\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Sourcegraph is a code search and navigation engine. Sourcegraph tokens can be used to access and interact with Sourcegraph APIs.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sourcegraph/sourcegraph_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sourcegraph\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSourcegraph_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tsecretV1 := testSecrets.MustGetField(\"SOURCEGRAPH_V1\")\n\tsecretV2 := testSecrets.MustGetField(\"SOURCEGRAPH_V2\")\n\tsecretV3 := testSecrets.MustGetField(\"SOURCEGRAPH_V3\")\n\n\tinactiveSecretV1 := testSecrets.MustGetField(\"SOURCEGRAPH_INACTIVE_V1\")\n\tinactiveSecretV2 := testSecrets.MustGetField(\"SOURCEGRAPH_INACTIVE_V2\")\n\tinactiveSecretV3 := testSecrets.MustGetField(\"SOURCEGRAPH_INACTIVE_V3\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified v1\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sourcegraph secret %s within\", secretV1)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sourcegraph,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified v1\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sourcegraph secret %s within\", secretV1)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sourcegraph,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified v2\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sourcegraph secret %s within\", secretV2)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sourcegraph,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified v3\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sourcegraph secret %s within\", secretV3)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sourcegraph,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified v1\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sourcegraph,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified v2\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sourcegraph,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified v3\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sourcegraph,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sourcegraph secret %s within\", secretV1)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sourcegraph,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sourcegraph secret %s within\", secretV1)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sourcegraph,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Sourcegraph.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"VerificationError\", \"ExtraData\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Sourcegraph.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sourcegraph/sourcegraph_test.go",
    "content": "package sourcegraph\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"sgp_4a2aD05B07721CE0_EEBdEacB356CdC6Dc8CD317A4276e2cEE9cb118a\"\n\tinvalidPattern = \"sgp_4a2aD0?B07721CE0_EEBdEacB356CdC6Dc8CD317A4276e2cEE9cb118a\"\n\tkeyword        = \"sourcegraph\"\n)\n\nfunc TestSourcegraph_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sourcegraph\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sourcegraphcody/sourcegraphcody.go",
    "content": "package sourcegraphcody\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(slk_[a-f0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"slk_\"}\n}\n\n// FromData will find and optionally verify Sourcegraphcody secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SourcegraphCody,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://cody-gateway.sourcegraph.com/v1/limits\", nil)\n\t\t\treq.Header.Add(\"Authorization\", \"Bearer \"+resMatch)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 403 || res.StatusCode == 401 {\n\t\t\t\t\t// 403 when the gateway is not configured to accept the token,\n\t\t\t\t\t// 401 when the token is invalid.\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SourcegraphCody\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Sourcegraph Cody is a tool for integrating code intelligence into your development workflow. The detected key is used for accessing Sourcegraph Cody services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sourcegraphcody/sourcegraphcody_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sourcegraphcody\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSourcegraphcody_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SOURCEGRAPHCODY\")\n\tinactiveSecret := testSecrets.MustGetField(\"SOURCEGRAPHCODY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sourcegraphcody secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SourcegraphCody,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SourcegraphCody,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sourcegraphcody secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SourcegraphCody,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sourcegraphcody secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SourcegraphCody,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Sourcegraphcody.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Sourcegraphcody.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sourcegraphcody/sourcegraphcody_test.go",
    "content": "package sourcegraphcody\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"slk_3c6669a93b14fbd6061e00fb40876b327826df4fe393e760d3109de08165e793\"\n\tinvalidPattern = \"slk_?c6669a93b14fbd6061e00fb40876b327826df4fe393e760d3109de08165e793\"\n\tkeyword        = \"sourcegraphcody\"\n)\n\nfunc TestSourcegraphcody_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sourcegraphcody\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sparkpost/sparkpost.go",
    "content": "package sparkpost\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b([a-zA-Z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sparkpost\"}\n}\n\n// FromData will find and optionally verify Sparkpost secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Sparkpost,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.sparkpost.com/api/v1/account\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Sparkpost\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Sparkpost is an email delivery service. Sparkpost API keys can be used to send and manage emails through Sparkpost's API.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sparkpost/sparkpost_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sparkpost\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSparkpost_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SPARKPOST_API_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SPARKPOST_API_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sparkpost secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sparkpost,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sparkpost,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Sparkpost.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\n\t\t\t\tt.Errorf(\"Sparkpost.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sparkpost/sparkpost_test.go",
    "content": "package sparkpost\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"VlsweyCt4GRglwZ8G3xVo5lY8Z4Wo9cLxyHFWh9V\"\n\tinvalidPattern = \"VlsweyCt4GRglwZ8G3xV?5lY8Z4Wo9cLxyHFWh9V\"\n\tkeyword        = \"sparkpost\"\n)\n\nfunc TestSparkpost_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sparkpost\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/speechtextai/speechtextai.go",
    "content": "package speechtextai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"speechtext\"}) + common.BuildRegex(common.HexPattern, \"\", 32))\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"speechtext\"}\n}\n\n// FromData will find and optionally verify SpeechTextAI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SpeechTextAI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", fmt.Sprintf(\"https://api.speechtext.ai/recognize?key=%s&language=en-US&punctuation=true&format=m4a\", resMatch), nil)\n\t\t\treq.Header.Add(\"Content-Type\", \"application/octet-stream\")\n\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tres, err := client.Do(req)\n\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SpeechTextAI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/speechtextai/speechtextai_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage speechtextai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSpeechTextAI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SPEECHTEXTAI\")\n\tinactiveSecret := testSecrets.MustGetField(\"SPEECHTEXTAI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a speechtext secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SpeechTextAI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SpeechTextAI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SpeechTextAI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SpeechTextAI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/speechtextai/speechtextai_test.go",
    "content": "package speechtextai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"51bb70d3ab4f9e0ecd17ec0a6f8ed289\"\n\tinvalidPattern = \"G1bb70d3ab4f9e0ecd17ec0a6f8ed289\"\n\tkeyword        = \"speechtextai\"\n)\n\nfunc TestSpeechTextAI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword speechtextai\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/splunkobservabilitytoken/splunkobservabilitytoken.go",
    "content": "package splunkobservabilitytoken\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"splunk\"}) + `\\b([a-z0-9A-Z]{22})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"splunk\"}\n}\n\n// FromData will find and optionally verify SplunkAccessToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SplunkOberservabilityToken,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.us1.signalfx.com/v2/dashboard\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Set(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Set(\"X-Sf-Token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SplunkOberservabilityToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Splunk Observability Tokens are used to access and interact with Splunk's observability suite, allowing for monitoring and analyzing application performance and infrastructure.\"\n}\n"
  },
  {
    "path": "pkg/detectors/splunkobservabilitytoken/splunkobservabilitytoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage splunkobservabilitytoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSplunkObservabilityToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SPLUNK_ACCESS_TOKEN\") // TODO: rename\n\tinactiveSecret := testSecrets.MustGetField(\"SPLUNK_ACCESS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a splunk secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SplunkOberservabilityToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SplunkOberservabilityToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SplunkObservabilityToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SplunkObservabilityToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/splunkobservabilitytoken/splunkobservabilitytoken_test.go",
    "content": "package splunkobservabilitytoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"OlKU6qOr3U4AlCziwvFCY2\"\n\tinvalidPattern = \"OlKU6qOr3U4?lCziwvFCY2\"\n\tkeyword        = \"splunkobservabilitytoken\"\n)\n\nfunc TestSplunkObservabilityToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword splunkobservabilitytoken\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/spoonacular/spoonacular.go",
    "content": "package spoonacular\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"spoonacular\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"spoonacular\"}\n}\n\n// FromData will find and optionally verify Spoonacular secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Spoonacular,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.spoonacular.com/recipes/random?apiKey=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Spoonacular\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Spoonacular provides a food and recipe API. Spoonacular API keys can be used to access and manage food and recipe data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/spoonacular/spoonacular_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage spoonacular\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSpoonacular_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SPOONACULAR\")\n\tinactiveSecret := testSecrets.MustGetField(\"SPOONACULAR_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a spoonacular secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Spoonacular,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Spoonacular,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Spoonacular.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Spoonacular.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/spoonacular/spoonacular_test.go",
    "content": "package spoonacular\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"xyclb6zdiq9hyexe74ah4lvp3ea02sbv\"\n\tinvalidPattern = \"xyclb6zdiq9hyexe?4ah4lvp3ea02sbv\"\n\tkeyword        = \"spoonacular\"\n)\n\nfunc TestSpoonacular_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword spoonacular\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sportsmonk/sportsmonk.go",
    "content": "package sportsmonk\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"sportsmonk\"}) + `\\b([0-9a-zA-Z]{60})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sportsmonk\"}\n}\n\n// FromData will find and optionally verify Sportsmonk secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Sportsmonk,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://soccer.sportmonks.com/api/v2.0/leagues?api_token=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Sportsmonk\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sportsmonk/sportsmonk_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sportsmonk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSportsmonk_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SPORTSMONK\")\n\tinactiveSecret := testSecrets.MustGetField(\"SPORTSMONK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sportsmonk secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sportsmonk,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sportsmonk,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Sportsmonk.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Sportsmonk.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sportsmonk/sportsmonk_test.go",
    "content": "package sportsmonk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"GxSYzYakznVR2ZSu7cdIGjj2g3m4VD7vkhToot3WKwEJlgibw4a6akEeV55V\"\n\tinvalidPattern = \"GxSYzYakznVR2ZSu?cdIGjj2g3m4VD7vkhToot3WKwEJlgibw4a6akEeV55V\"\n\tkeyword        = \"sportsmonk\"\n)\n\nfunc TestSportsmonk_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sportsmonk\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/spotifykey/spotifykey.go",
    "content": "package spotifykey\n\nimport (\n\t\"context\"\n\t\"golang.org/x/oauth2\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"golang.org/x/oauth2/clientcredentials\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"key\", \"secret\"}) + `\\b([A-Za-z0-9]{32})\\b`)\n\tidPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"id\"}) + `\\b([A-Za-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"spotify\"}\n}\n\n// FromData will find and optionally verify SpotifyKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tctx = context.WithValue(ctx, oauth2.HTTPClient, common.SaneHttpClient())\n\n\tdataStr := string(data)\n\n\tmatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, idMatch := range idMatches {\n\t\t\tidresMatch := strings.TrimSpace(idMatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_SpotifyKey,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tconfig := &clientcredentials.Config{\n\t\t\t\t\tClientID:     idresMatch,\n\t\t\t\t\tClientSecret: resMatch,\n\t\t\t\t\tTokenURL:     \"https://accounts.spotify.com/api/token\",\n\t\t\t\t}\n\t\t\t\ttoken, err := config.Token(ctx)\n\t\t\t\tif err == nil {\n\t\t\t\t\tif token.Type() == \"Bearer\" {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SpotifyKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Spotify API keys can be used to access and modify data within Spotify's services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/spotifykey/spotifykey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage spotifykey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSpotifyKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tclientID := testSecrets.MustGetField(\"SPOTIFY_CLIENTID\")\n\tclientSecret := testSecrets.MustGetField(\"SPOTIFY_CLIENT_SECRET\")\n\tinactiveClientSecret := testSecrets.MustGetField(\"SPOTIFY_CLIENT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a spotifyKey secret %s within spotifyId %s\", clientSecret, clientID)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SpotifyKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SpotifyKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SpotifyKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SpotifyKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/spotifykey/spotifykey_test.go",
    "content": "package spotifykey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidSecret   = \"MJsy6euxw0GKljGYZLZGnn0PvKuZB0V8\"\n\tinvalidSecret = \"MJsy6euxw0GKljGY?LZGnn0PvKuZB0V8\"\n\tvalidId       = \"JykXADk7r8ZegkS0emhtpFSWNqGVAIgm\"\n\tinvalidId     = \"JykXADk7r8Z?gkS0emhtpFSWNqGVAIgm\"\n\tkeyword       = \"spotifykey\"\n)\n\nfunc TestSpotifyKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword spotifykey\",\n\t\t\tinput: fmt.Sprintf(\"%s key - '%s'\\n%s id - '%s'\\n\", keyword, validSecret, keyword, validId),\n\t\t\twant:  []string{validSecret, validId},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s key - '%s'\\n%s id - '%s'\\n\", keyword, invalidSecret, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sqlserver/sqlserver.go",
    "content": "package sqlserver\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\tmssql \"github.com/microsoft/go-mssqldb\"\n\t\"github.com/microsoft/go-mssqldb/msdsn\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n)\n\ntype Scanner struct {\n\tignorePatterns []*regexp.Regexp\n}\n\nfunc New(opts ...func(*Scanner)) *Scanner {\n\tscanner := &Scanner{\n\t\tignorePatterns: []*regexp.Regexp{},\n\t}\n\tfor _, opt := range opts {\n\t\topt(scanner)\n\t}\n\n\treturn scanner\n}\n\nfunc WithIgnorePattern(ignoreStrings []string) func(*Scanner) {\n\treturn func(s *Scanner) {\n\t\tvar ignorePatterns []*regexp.Regexp\n\t\tfor _, ignoreString := range ignoreStrings {\n\t\t\tignorePattern, err := regexp.Compile(ignoreString)\n\t\t\tif err != nil {\n\t\t\t\tpanic(fmt.Sprintf(\"%s is not a valid regex, error received: %v\", ignoreString, err))\n\t\t\t}\n\t\t\tignorePatterns = append(ignorePatterns, ignorePattern)\n\t\t}\n\n\t\ts.ignorePatterns = ignorePatterns\n\t}\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\t// SQLServer connection string is a semicolon delimited set of case-insensitive parameters which may go in any order.\n\tpattern = regexp.MustCompile(\"(?:\\n|`|'|\\\"| )?((?:[A-Za-z0-9_ ]+=[^;$'`\\\"$]+;?){3,})(?:'|`|\\\"|\\r\\n|\\n)?\")\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sql\", \"database\", \"Data Source\", \"Server=\", \"Network address=\"}\n}\n\n// FromData will find and optionally verify SQL Server credentials in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tmatches := pattern.FindAllStringSubmatch(string(data), -1)\n\tfor _, match := range matches {\n\t\tif shouldIgnore(match[1], s.ignorePatterns) {\n\t\t\tcontinue\n\t\t}\n\t\tparamsUnsafe, err := msdsn.Parse(match[1])\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif paramsUnsafe.Password == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SQLServer,\n\t\t\tRaw:          []byte(paramsUnsafe.Password),\n\t\t\tRawV2:        []byte(paramsUnsafe.URL().String()),\n\t\t\tRedacted:     detectors.RedactURL(*paramsUnsafe.URL()),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, err := ping(ctx, paramsUnsafe)\n\n\t\t\ts1.Verified = isVerified\n\t\t\tmssqlErr, isMssqlErr := err.(mssql.Error)\n\t\t\tif isMssqlErr {\n\t\t\t\tif mssqlErr.Number == 18456 {\n\t\t\t\t\t// Login failed\n\t\t\t\t\t// Number taken from https://learn.microsoft.com/en-us/sql/relational-databases/errors-events/database-engine-events-and-errors?view=sql-server-ver16\n\t\t\t\t\t// Nothing to do; determinate failure to verify\n\t\t\t\t} else {\n\t\t\t\t\t// If it is a MSSQL error, format the error with error number and message\n\t\t\t\t\ts1.SetVerificationError(fmt.Errorf(\"SQL Server error %d: %s\", mssqlErr.Number, mssqlErr.Message), paramsUnsafe.Password)\n\t\t\t\t}\n\t\t\t} else if err != nil {\n\t\t\t\t// If it is an error but not of MSSQL error type, just set error as verification error\n\t\t\t\ts1.SetVerificationError(err, paramsUnsafe.Password)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc shouldIgnore(uri string, ignorePatterns []*regexp.Regexp) bool {\n\tfor _, ignore := range ignorePatterns {\n\t\tif ignore.MatchString(uri) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nvar ping = func(ctx context.Context, config msdsn.Config) (bool, error) {\n\t// TCP connectivity check to prevent indefinite hangs\n\taddress := net.JoinHostPort(config.Host, strconv.Itoa(int(config.Port)))\n\n\tdialer := &net.Dialer{\n\t\tTimeout: 3 * time.Second,\n\t}\n\n\ttcpConn, err := dialer.DialContext(ctx, \"tcp\", address) // respects context timeout\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer tcpConn.Close()\n\n\tcleanConfig := msdsn.Config{}\n\tcleanConfig.Host = config.Host\n\tcleanConfig.Port = config.Port\n\tcleanConfig.User = config.User\n\tcleanConfig.Password = config.Password\n\tcleanConfig.Database = config.Database\n\tcleanConfig.DisableRetry = true\n\tcleanConfig.Encryption = config.Encryption\n\tcleanConfig.TLSConfig = config.TLSConfig\n\tcleanConfig.Instance = config.Instance\n\tcleanConfig.DialTimeout = time.Second * 3\n\tcleanConfig.ConnTimeout = time.Second * 3\n\n\turl := cleanConfig.URL()\n\tquery := url.Query()\n\turl.RawQuery = query.Encode()\n\n\tconn, err := sql.Open(\"mssql\", url.String())\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\terr = conn.PingContext(ctx) // this doesn't seem to respect the context timeout\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SQLServer\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"SQL Server is a relational database management system developed by Microsoft. SQL Server credentials can be used to access and manage databases.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sqlserver/sqlserver_integration_test.go",
    "content": "//go:build detectors && integration\n// +build detectors,integration\n\npackage sqlserver\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github.com/brianvoe/gofakeit/v7\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/microsoft/go-mssqldb/msdsn\"\n\t\"github.com/testcontainers/testcontainers-go\"\n\t\"github.com/testcontainers/testcontainers-go/modules/mssql\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSQLServerIntegration_FromChunk(t *testing.T) {\n\tctx := context.Background()\n\n\tpassword := gofakeit.Password(true, true, true, false, false, 10)\n\n\tcontainer, err := mssql.RunContainer(\n\t\tctx,\n\t\ttestcontainers.WithImage(\"mcr.microsoft.com/azure-sql-edge\"),\n\t\tmssql.WithAcceptEULA(),\n\t\tmssql.WithPassword(password))\n\tif err != nil {\n\t\tt.Fatalf(\"could not start container: %v\", err)\n\t}\n\n\tdefer container.Terminate(ctx)\n\n\tport, err := container.MappedPort(ctx, \"1433\")\n\tif err != nil {\n\t\tt.Fatalf(\"could get mapped port: %v\", err)\n\t}\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(\"Server=localhost;Port=%s;Initial Catalog=master;User ID=sa;Password=%s;Persist Security Info=true;MultipleActiveResultSets=true;\",\n\t\t\t\t\tport.Port(),\n\t\t\t\t\tpassword)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SQLServer,\n\t\t\t\t\tRaw:          []byte(password),\n\t\t\t\t\tRawV2: []byte(urlEncode(fmt.Sprintf(\"sqlserver://sa:%s@localhost:%s?database=master&dial+timeout=15&disableretry=false\",\n\t\t\t\t\t\tpassword,\n\t\t\t\t\t\tport.Port()))),\n\t\t\t\t\tRedacted: fmt.Sprintf(\"sqlserver://sa:********@localhost:%s?database=master&dial+timeout=15&disableretry=false\",\n\t\t\t\t\t\tport.Port()),\n\t\t\t\t\tVerified: true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"Server=localhost;Port=%s;User ID=sa;Password=123\", port.Port())),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SQLServer,\n\t\t\t\t\tRaw:          []byte(\"123\"),\n\t\t\t\t\tRawV2: []byte(fmt.Sprintf(\"sqlserver://sa:123@localhost:%s?dial+timeout=15&disableretry=false\",\n\t\t\t\t\t\tport.Port())),\n\t\t\t\t\tRedacted: fmt.Sprintf(\"sqlserver://sa:********@localhost:%s?dial+timeout=15&disableretry=false\",\n\t\t\t\t\t\tport.Port()),\n\t\t\t\t\tVerified: false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found, in XML, missing password param (pwd is not valid)\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(`<add name=\"Sample2\" value=\"SERVER=server_name;DATABASE=database_name;user=user_name;pwd=plaintextpassword;Timeout=120;MultipleActiveResultSets=True;\" />`),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified, in XML\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(`<add name=\"test db\" value=\"SERVER=localhost;PORT=%s;DATABASE=master;user=sa;password=%s;Timeout=120;MultipleActiveResultSets=True;\" />`,\n\t\t\t\t\tport.Port(),\n\t\t\t\t\tpassword)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SQLServer,\n\t\t\t\t\tRedacted: fmt.Sprintf(\"sqlserver://sa:********@localhost:%s?database=master&dial+timeout=15&disableretry=false\",\n\t\t\t\t\t\tport.Port()),\n\t\t\t\t\tRaw: []byte(password),\n\t\t\t\t\tRawV2: []byte(urlEncode(fmt.Sprintf(\"sqlserver://sa:%s@localhost:%s?database=master&dial+timeout=15&disableretry=false\",\n\t\t\t\t\t\tpassword,\n\t\t\t\t\t\tport.Port()))),\n\t\t\t\t\tVerified: true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unreachable host\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"Server=unreachablehost;Initial Catalog=master;User ID=sa;Password=P@ssw0rd!;Persist Security Info=true;MultipleActiveResultSets=true;\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SQLServer,\n\t\t\t\t\tRaw:          []byte(\"P@ssw0rd!\"),\n\t\t\t\t\tRawV2:        []byte(\"sqlserver://sa:P%40ssw0rd%21@unreachablehost?database=master&dial+timeout=15&disableretry=false\"),\n\t\t\t\t\tRedacted:     \"sqlserver://sa:********@unreachablehost?database=master&dial+timeout=15&disableretry=false\",\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SQLServer.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"SQLServer.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSQLServer_FromChunk(t *testing.T) {\n\tsecret := \"Server=localhost;Initial Catalog=Demo;User ID=sa;Password=P@ssw0rd!;Persist Security Info=true;MultipleActiveResultSets=true;\"\n\tinactiveSecret := \"Server=localhost;User ID=sa;Password=123\"\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname     string\n\t\ts        Scanner\n\t\targs     args\n\t\twant     []detectors.Result\n\t\twantErr  bool\n\t\tmockFunc func()\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sqlserver secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SQLServer,\n\t\t\t\t\tRedacted:     \"sqlserver://sa:********@localhost?database=Demo&dial+timeout=15&disableretry=false\",\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t\tmockFunc: func() {\n\t\t\t\tping = func(config msdsn.Config) (bool, error) {\n\t\t\t\t\treturn true, nil\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SQLServer,\n\t\t\t\t\tRedacted:     \"sqlserver://sa:********@localhost?dial+timeout=15&disableretry=false\",\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t\tmockFunc: func() {\n\t\t\t\tping = func(config msdsn.Config) (bool, error) {\n\t\t\t\t\treturn false, nil\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"not found, in XML, missing password param (pwd is not valid)\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(`<add name=\"Sample2\" value=\"SERVER=server_name;DATABASE=database_name;user=user_name;pwd=plaintextpassword;encrypt=true;Timeout=120;MultipleActiveResultSets=True;\" />`),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t\tmockFunc: func() {\n\t\t\t\tping = func(config msdsn.Config) (bool, error) {\n\t\t\t\t\treturn true, nil\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified, in XML\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(`<add name=\"test db\" value=\"SERVER=server_name;DATABASE=testdb;user=username;password=badpassword;encrypt=true;Timeout=120;MultipleActiveResultSets=True;\" />`),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SQLServer,\n\t\t\t\t\tRedacted:     \"sqlserver://username:********@server_name?database=testdb&dial+timeout=15&disableretry=false&encrypt=true\",\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t\tmockFunc: func() {\n\t\t\t\tping = func(config msdsn.Config) (bool, error) {\n\t\t\t\t\tif config.Host != \"server_name\" {\n\t\t\t\t\t\treturn false, errors.New(\"invalid host\")\n\t\t\t\t\t}\n\n\t\t\t\t\tif config.User != \"username\" {\n\t\t\t\t\t\treturn false, errors.New(\"invalid database\")\n\t\t\t\t\t}\n\n\t\t\t\t\tif config.Password != \"badpassword\" {\n\t\t\t\t\t\treturn false, errors.New(\"invalid password\")\n\t\t\t\t\t}\n\n\t\t\t\t\tif config.Database != \"testdb\" {\n\t\t\t\t\t\treturn false, errors.New(\"invalid database\")\n\t\t\t\t\t}\n\n\t\t\t\t\treturn true, nil\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:     nil,\n\t\t\twantErr:  false,\n\t\t\tmockFunc: func() {},\n\t\t},\n\t}\n\n\t// preserve the original function\n\toriginalPing := ping\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.mockFunc()\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SQLServer.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tignoreOpts := []cmp.Option{\n\t\t\t\tcmpopts.IgnoreFields(detectors.Result{}, \"RawV2\"),\n\t\t\t\tcmpopts.IgnoreUnexported(detectors.Result{}),\n\t\t\t}\n\t\t\tif diff := cmp.Diff(tt.want, got, ignoreOpts...); diff != \"\" {\n\t\t\t\tt.Errorf(\"SQLServer.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t\tfor _, g := range got {\n\t\t\t\t\tt.Error(g.Redacted)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n\n\tping = originalPing\n}\n\nfunc urlEncode(s string) string {\n\tparsed, _ := url.Parse(s)\n\treturn parsed.String()\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sqlserver/sqlserver_test.go",
    "content": "package sqlserver\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestSQLServer_Pattern(t *testing.T) {\n\tif !pattern.Match([]byte(`builder.Services.AddDbContext<Database>(optionsBuilder => optionsBuilder.UseSqlServer(\"Server=localhost;Initial Catalog=master;User ID=sa;Password=P@ssw0rd!;Persist Security Info=true;MultipleActiveResultSets=true;\"));`)) {\n\t\tt.Errorf(\"SQLServer.pattern: did not find connection string from Program.cs\")\n\t}\n\tif !pattern.Match([]byte(`{\"ConnectionStrings\": {\"Demo\": \"Server=localhost;Initial Catalog=master;User ID=sa;Password=P@ssw0rd!;Persist Security Info=true;MultipleActiveResultSets=true;\"}}`)) {\n\t\tt.Errorf(\"SQLServer.pattern: did not find connection string from appsettings.json\")\n\t}\n\tif !pattern.Match([]byte(`CONNECTION_STRING: Server=localhost;Initial Catalog=master;User ID=sa;Password=P@ssw0rd!;Persist Security Info=true;MultipleActiveResultSets=true`)) {\n\t\tt.Errorf(\"SQLServer.pattern: did not find connection string from .env\")\n\t}\n\tif !pattern.Match([]byte(`<add name=\"Sample2\" value=\"SERVER=server_name;DATABASE=database_name;user=user_name;pwd=plaintextpassword;encrypt=true;Timeout=120;MultipleActiveResultSets=True;\" />`)) {\n\t\tt.Errorf(\"SQLServer.pattern: did not find connection string in xml format\")\n\t}\n}\n\nconst (\n\tvalidConnStr   = `builder.Services.AddDbContext<Database>(optionsBuilder => optionsBuilder.UseSqlServer(\"Server=localhost;Initial Catalog=master;User ID=sa;Password=P@ssw0rd!;Persist Security Info=true;MultipleActiveResultSets=true;\"));`\n\tvalidParsedStr = `sqlserver://sa:P%40ssw0rd%21@localhost?database=master&dial+timeout=15&disableretry=false`\n\tinvalidConnStr = `some random text without connection string`\n)\n\nfunc TestSqlServer_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sql\",\n\t\t\tinput: fmt.Sprintf(`sql - %s`, validConnStr),\n\t\t\twant:  []string{validParsedStr},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"sql=%s\", invalidConnStr),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSqlServer_FromDataWithIgnorePattern(t *testing.T) {\n\ts := New(\n\t\tWithIgnorePattern([]string{\n\t\t\t`^Server=localhost`,\n\t\t}))\n\tgot, err := s.FromData(context.Background(), false, []byte(\"Server=localhost;Initial Catalog=master;User ID=sa;Password=P@ssw0rd!;Persist Security Info=true;MultipleActiveResultSets=true\"))\n\tif err != nil {\n\t\tt.Errorf(\"FromData() error = %v\", err)\n\t\treturn\n\t}\n\tif len(got) != 0 {\n\t\tt.Errorf(\"expected no results, but got %d\", len(got))\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/square/square.go",
    "content": "package square\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\t// more context to be added if this is too generic\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"square\"}) + `(EAAA[a-zA-Z0-9\\-\\+\\=]{60})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"EAAA\"}\n}\n\n// FromData will find and optionally verify Square secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// Surprisingly there are still a lot of false positives! So, also doing substring check for square.\n\tif !strings.Contains(strings.ToLower(dataStr), \"square\") {\n\t\treturn\n\t}\n\n\tsecMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, secMatch := range secMatches {\n\t\tresMatch := strings.TrimSpace(secMatch[1])\n\n\t\tresult := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Square,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\t\tresult.ExtraData = map[string]string{\n\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/square/\",\n\t\t}\n\n\t\tif verify {\n\t\t\t// there are a few endpoints we can check, but templates seems the least sensitive.\n\t\t\t// 403 will be issued if the scope is wrong but the key is correct\n\t\t\tbaseURL := \"https://connect.squareupsandbox.com/v2/merchants\"\n\n\t\t\tclient := common.SaneHttpClient()\n\n\t\t\t// test `merchants` scope - its commonly allowed and low sensitivity\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", baseURL, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t// unclear if this version needs to be set or matters, seems to work without, but docs want it\n\t\t\t// req.Header.Add(\"Square-Version\", \"2020-08-12\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tres.Body.Close() // The request body is unused.\n\n\t\t\t\t// 200 means good key and has `merchants` scope - default allowed by square\n\t\t\t\t// 401 is bad key\n\t\t\t\tif res.StatusCode == http.StatusOK || res.StatusCode == http.StatusForbidden {\n\t\t\t\t\tresult.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult.AnalysisInfo = map[string]string{\"key\": resMatch}\n\t\t}\n\n\t\tresults = append(results, result)\n\t}\n\n\treturn\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Square\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/square/square_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage square\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSquare_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SQUARE_SECRET\")\n\tsecretInactive := testSecrets.MustGetField(\"SQUARE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a square secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Square,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unvierifed\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a square secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Square,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Square.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Square.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/square/square_test.go",
    "content": "package square\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"EAAAYmTgYL4kQ-65xyZ4zANgipYVJQFTrb=roK23I=iFebzL4rjYbUg10N6o1l--\"\n\tinvalidPattern = \"EAAAYmTgYL4kQ-65xyZ4zANgipYVJQFT?b=roK23I=iFebzL4rjYbUg10N6o1l--\"\n\tkeyword        = \"square\"\n)\n\nfunc TestSquare_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword square\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/squareapp/squareapp.go",
    "content": "package squareapp\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\t/*\n\t\tThe sandbox id and secret has word `sandbox-` as prefix\n\t\tpossibly always `sq0csp` for secret and `sq0idb` for app\n\t*/\n\tkeyPat = regexp.MustCompile(`(?:sandbox-)?sq0i[a-z]{2}-[0-9A-Za-z_-]{22,43}`)\n\tsecPat = regexp.MustCompile(`(?:sandbox-)?sq0c[a-z]{2}-[0-9A-Za-z_-]{40,50}`)\n\n\t// api endpoints\n\tsandboxEndpoint = \"https://connect.squareupsandbox.com/oauth2/revoke\"\n\tprodEndpoint    = \"https://connect.squareup.com/oauth2/revoke\"\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sq0i\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SquareApp\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Square is a financial services and mobile payment company. Square credentials can be used to access and manage payment processing and other financial services.\"\n}\n\n// FromData will find and optionally verify SquareApp secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueIDMatches, uniqueSecretMatches = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, match := range keyPat.FindAllString(dataStr, -1) {\n\t\tuniqueIDMatches[match] = struct{}{}\n\t}\n\n\tfor _, match := range secPat.FindAllString(dataStr, -1) {\n\t\tuniqueSecretMatches[match] = struct{}{}\n\t}\n\n\tfor id := range uniqueIDMatches {\n\t\tfor secret := range uniqueSecretMatches {\n\t\t\t// if both are not from same env, continue\n\t\t\tif !hasSamePrefix(id, secret) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tresult := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_SquareApp,\n\t\t\t\tRaw:          []byte(id),\n\t\t\t\tRedacted:     id,\n\t\t\t\tExtraData:    map[string]string{},\n\t\t\t}\n\n\t\t\tvar isVerified bool\n\t\t\tvar verificationErr error\n\n\t\t\t// verify against sandbox endpoint\n\t\t\tif verify && isSandbox(id) {\n\t\t\t\tisVerified, verificationErr = verifySquareApp(ctx, client, sandboxEndpoint, id, secret)\n\t\t\t\tresult.ExtraData[\"Env\"] = \"Sandbox\"\n\t\t\t}\n\n\t\t\t// verify against prod endpoint\n\t\t\tif verify && !isSandbox(id) {\n\t\t\t\tisVerified, verificationErr = verifySquareApp(ctx, client, prodEndpoint, id, secret)\n\t\t\t\tresult.ExtraData[\"Env\"] = \"Production\"\n\t\t\t}\n\n\t\t\tresult.Verified = isVerified\n\t\t\tresult.SetVerificationError(verificationErr)\n\n\t\t\tresults = append(results, result)\n\n\t\t\t// once a secret is verified with id, remove it from the list\n\t\t\tif isVerified {\n\t\t\t\tdelete(uniqueSecretMatches, secret)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifySquareApp(ctx context.Context, client *http.Client, endpoint, id, secret string) (bool, error) {\n\treqData, err := json.Marshal(map[string]string{\n\t\t\"client_id\":    id,\n\t\t\"access_token\": \"fakeTruffleHogAccessTokenForVerification\",\n\t})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, \"POST\", endpoint, bytes.NewReader(reqData))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Client %s\", secret))\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusNotFound:\n\t\treturn true, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\nfunc hasSamePrefix(id, secret string) bool {\n\tidHasPrefix := strings.HasPrefix(id, \"sandbox-\")\n\tsecretHasPrefix := strings.HasPrefix(secret, \"sandbox-\")\n\n\treturn idHasPrefix == secretHasPrefix\n}\n\n// isSandbox check if provided key(id or secret) is of sandbox env\nfunc isSandbox(key string) bool {\n\treturn strings.HasPrefix(key, \"sandbox-\")\n}\n"
  },
  {
    "path": "pkg/detectors/squareapp/squareapp_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage squareapp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSquareApp_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tid := testSecrets.MustGetField(\"SQUAREAPP_ID\")\n\tsecret := testSecrets.MustGetField(\"SQUAREAPP_SECRET\")\n\tsecretInactive := testSecrets.MustGetField(\"SQUAREAPP_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a squareapp secret %s within awsId %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SquareApp,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     id,\n\t\t\t\t\tExtraData:    map[string]string{\"Env\": \"Sandbox\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified - detected but not added in result due to mismatch of env\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a squareapp secret %s within awsId %s\", secretInactive, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    []detectors.Result{},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SquareApp.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SquareApp.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/squareapp/squareapp_test.go",
    "content": "package squareapp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey   = \"sq0ige-a9khwVJOSwlzBvX0wp4j8t90s2d\"\n\tinvalidKey = \"YJsq0ige-a9kh?VJOSwlzBvX0wp4j8t90s2d\"\n\tvalidSec   = \"4sSPeeM_jk0VZiTFZJqEwzvXHjcCd6fsq0cvn-4pvyBIQ1OvY6dOv4X2AK5r6UJaFf0Xkp5NjV6lGhtbM\"\n\tinvalidSec = \"4sSPeeM_jk0VZiTFZJqEwz?XHjcCd6fsq0cvn-4pvyBIQ1OvY6dOv4X2AK5r6UJaFf0Xkp5NjV6lGhtbM\"\n\n\t// sandbox\n\tvalidSandboxKey    = \"sandbox-sq0idb-hFAKEQrhLGgFAKELZEDgpo\"\n\tvalidSandboxSecret = \"sandbox-sq0csb-o6cs8xFAKExEgIDGbzn2hFAKEZPbzhe713Q-FAKEfbY\"\n\tkeyword            = \"squareapp\"\n)\n\nfunc TestSquareApp_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword squareapp\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validSec),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid sandbox pattern - with keyword squareapp\",\n\t\t\tinput: fmt.Sprintf(\"token - '%s'\\n secret - '%s'\\n\", validSandboxKey, validSandboxSecret),\n\t\t\twant:  []string{validSandboxKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidSec),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/squarespace/squarespace.go",
    "content": "package squarespace\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"squarespace\"}\n}\n\n// FromData will find and optionally verify Squarespace secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Squarespace,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.squarespace.com/1.0/profiles?sortField=email&sortDirection=asc&filter=isCustomer,true;hasAccount,true\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"User-Agent\", \"YOUR_CUSTOM_APP_DESCRIPTION\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Squarespace\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Squarespace is a website building and hosting service. Squarespace API keys can be used to manage and modify website content and configuration.\"\n}\n"
  },
  {
    "path": "pkg/detectors/squarespace/squarespace_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage squarespace\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSquarespace_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SQUARESPACE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SQUARESPACE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a squarespace secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Squarespace,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Squarespace,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Squarespace.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Squarespace.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/squarespace/squarespace_test.go",
    "content": "package squarespace\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"9308f78b-8315-a8a6-9c9c-a4b41488508f\"\n\tinvalidPattern = \"9308f78b?8315-a8a6-9c9c-a4b41488508f\"\n\tkeyword        = \"squarespace\"\n)\n\nfunc TestSquarespace_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword squarespace\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/squareup/squareup.go",
    "content": "package squareup\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(sq0idp-[0-9A-Za-z]{22})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sq0idp-\"}\n}\n\n// FromData will find and optionally verify Squareup secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Squareup,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\n\t\t\treq, 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)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Squareup\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/squareup/squareup_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage squareup\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSquareup_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SQUAREUP\")\n\tinactiveSecret := testSecrets.MustGetField(\"SQUAREUP_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a squareup secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Squareup,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Squareup,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Squareup.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Squareup.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/squareup/squareup_test.go",
    "content": "package squareup\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"sq0idp-CIlelDA4a0mIksYXPGwUzy\"\n\tinvalidPattern = \"sq0idp-CIlelDA?a0mIksYXPGwUzy\"\n\tkeyword        = \"squareup\"\n)\n\nfunc TestSquareup_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword squareup\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sslmate/sslmate.go",
    "content": "package sslmate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"sslmate\"}) + `\\b([a-zA-Z0-9]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sslmate\"}\n}\n\n// FromData will find and optionally verify SslMate secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SslMate,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://sslmate.com/api/v2/certs/example.com?expand=current.crt\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SslMate\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"SslMate is a service that helps manage and automate SSL certificates. SslMate API keys can be used to access and manage SSL certificates.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sslmate/sslmate_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sslmate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSslMate_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SSLMATE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SSLMATE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sslmate secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SslMate,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SslMate,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SslMate.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SslMate.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sslmate/sslmate_test.go",
    "content": "package sslmate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"4RCKoI9YgcI43rpDbhyYpGdHtNaG1DLcwEYR\"\n\tinvalidPattern = \"4RCKoI9YgcI43rpDbh?YpGdHtNaG1DLcwEYR\"\n\tkeyword        = \"sslmate\"\n)\n\nfunc TestSslMate_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sslmate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/statuscake/statuscake.go",
    "content": "package statuscake\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"statuscake\"}) + `\\b([a-zA-Z0-9]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"statuscake\"}\n}\n\n// FromData will find and optionally verify Statuscake secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Statuscake,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.statuscake.com/v1/ssl\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Statuscake\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Statuscake is a website monitoring service. Statuscake API keys can be used to access and manage website monitoring configurations and data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/statuscake/statuscake_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage statuscake\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestStatuscake_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"STATUSCAKE\")\n\tinactiveSecret := testSecrets.MustGetField(\"STATUSCAKE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a statuscake secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Statuscake,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Statuscake,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Statuscake.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Statuscake.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/statuscake/statuscake_test.go",
    "content": "package statuscake\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"jwI4HrRAbQV4rUYYD76C\"\n\tinvalidPattern = \"jwI4HrRAbQ?4rUYYD76C\"\n\tkeyword        = \"statuscake\"\n)\n\nfunc TestStatuscake_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword statuscake\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/statuspage/statuspage.go",
    "content": "package statuspage\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"statuspage\"}) + `\\b([0-9a-z-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"statuspage\"}\n}\n\n// FromData will find and optionally verify Statuspage secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Statuspage,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.statuspage.io/v1/pages\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Statuspage\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/statuspage/statuspage_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage statuspage\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestStatuspage_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"STATUSPAGE\")\n\tinactiveSecret := testSecrets.MustGetField(\"STATUSPAGE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a statuspage secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Statuspage,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Statuspage,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Statuspage.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Statuspage.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/statuspage/statuspage_test.go",
    "content": "package statuspage\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"0c8xiakjyy42sep7fs17pzq08ra0e4aj04uq\"\n\tinvalidPattern = \"0c8xiakjyy42sep7fs?7pzq08ra0e4aj04uq\"\n\tkeyword        = \"statuspage\"\n)\n\nfunc TestStatuspage_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword statuspage\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/statuspal/statuspal.go",
    "content": "package statuspal\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"statuspal\"}) + `\\b([0-9a-zA-Z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"statuspal\"}\n}\n\n// FromData will find and optionally verify Statuspal secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Statuspal,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://statuspal.io/api/v1/status_pages/secretscanner/subscriptions\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.statuspal+json; version=3\")\n\t\t\treq.Header.Add(\"Authorization\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Statuspal\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Statuspal is a status page service. Statuspal API keys can be used to manage status pages and subscriptions.\"\n}\n"
  },
  {
    "path": "pkg/detectors/statuspal/statuspal_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage statuspal\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestStatuspal_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"STATUSPAL\")\n\tinactiveSecret := testSecrets.MustGetField(\"STATUSPAL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a statuspal secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Statuspal,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Statuspal,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Statuspal.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Statuspal.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/statuspal/statuspal_test.go",
    "content": "package statuspal\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"FIYuLMuzF8XPqwPQzEiQFzJziMhcrAfH\"\n\tinvalidPattern = \"FIYuLMu?F8XPqwPQzEiQFzJziMhcrAfH\"\n\tkeyword        = \"statuspal\"\n)\n\nfunc TestStatuspal_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword statuspal\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/stitchdata/stitchdata.go",
    "content": "package stitchdata\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"stitchdata\"}) + `\\b([0-9a-z_]{35})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"stitchdata\"}\n}\n\n// FromData will find and optionally verify Stitchdata secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Stitchdata,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.stitchdata.com/v4/sources\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Stitchdata\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/stitchdata/stitchdata_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage stitchdata\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestStitchdata_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"STITCHDATA\")\n\tinactiveSecret := testSecrets.MustGetField(\"STITCHDATA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a stitchdata secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Stitchdata,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Stitchdata,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Stitchdata.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Stitchdata.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/stitchdata/stitchdata_test.go",
    "content": "package stitchdata\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"8n4bfv0w_ygprt3xkr3gz1ez6jmz0oo9eva\"\n\tinvalidPattern = \"8n4bfv0w_ygp?t3xkr3gz1ez6jmz0oo9eva\"\n\tkeyword        = \"stitchdata\"\n)\n\nfunc TestStitchdata_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword stitchdata\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/stockdata/stockdata.go",
    "content": "package stockdata\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"stockdata\"}) + `\\b([0-9A-Za-z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"stockdata\"}\n}\n\n// FromData will find and optionally verify Stockdata secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Stockdata,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.stockdata.org/v1/data/quote?symbols=AAPL,TSLA,MSFT&api_token=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Stockdata\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Stockdata is a service providing stock market data. Stockdata API keys can be used to access and retrieve stock market information.\"\n}\n"
  },
  {
    "path": "pkg/detectors/stockdata/stockdata_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage stockdata\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestStockdata_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"STOCKDATA\")\n\tinactiveSecret := testSecrets.MustGetField(\"STOCKDATA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a stockdata secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Stockdata,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Stockdata,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Stockdata.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Stockdata.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/stockdata/stockdata_test.go",
    "content": "package stockdata\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"94ny9HMpy5rGR7jI3Z2fjBQpuU8gQofhlw4u3sRr\"\n\tinvalidPattern = \"94ny9HMpy5rGR7?I3Z2fjBQpuU8gQofhlw4u3sRr\"\n\tkeyword        = \"stockdata\"\n)\n\nfunc TestStockdata_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword stockdata\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/storecove/storecove.go",
    "content": "package storecove\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"storecove\"}) + `\\b([a-zA-Z0-9_-]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"storecove\"}\n}\n\n// FromData will find and optionally verify Storecove secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Storecove,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.storecove.com/api/v2/discovery/identifiers\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Storecove\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Storecove is an API service for electronic invoicing. Storecove API keys can be used to access and manage electronic invoicing data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/storecove/storecove_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage storecove\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestStorecove_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"STORECOVE\")\n\tinactiveSecret := testSecrets.MustGetField(\"STORECOVE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a storecove secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Storecove,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Storecove,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Storecove.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Storecove.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/storecove/storecove_test.go",
    "content": "package storecove\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"yDDqj4B00b1m_HymYpqVRbPHWi7njN60NYsT0tpHvEe\"\n\tinvalidPattern = \"yDDqj4B00?1m_HymYpqVRbPHWi7njN60NYsT0tpHvEe\"\n\tkeyword        = \"storecove\"\n)\n\nfunc TestStorecove_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword storecove\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/stormboard/stormboard.go",
    "content": "package stormboard\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"stormboard\"}) + `\\b([a-f0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"stormboard\"}\n}\n\n// FromData will find and optionally verify Stormboard secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Stormboard,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.stormboard.com/users/profile\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"X-API-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Stormboard\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Stormboard is a digital workspace for brainstorming and collaboration. Stormboard API keys can be used to access and modify data within the workspace.\"\n}\n"
  },
  {
    "path": "pkg/detectors/stormboard/stormboard_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage stormboard\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestStormboard_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"STORMBOARD\")\n\tinactiveSecret := testSecrets.MustGetField(\"STORMBOARD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a stormboard secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Stormboard,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Stormboard,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Stormboard.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Stormboard.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/stormboard/stormboard_test.go",
    "content": "package stormboard\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"46e7fa9b370dd56209a5610630a45bb9a54fa1e9\"\n\tinvalidPattern = \"4?e7fa9b370dd56209a5610630a45bb9a54fa1e9\"\n\tkeyword        = \"stormboard\"\n)\n\nfunc TestStormboard_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword stormboard\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/stormglass/stormglass.go",
    "content": "package stormglass\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"stormglass\"}) + `\\b([0-9Aa-z-]{73})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"stormglass\"}\n}\n\n// FromData will find and optionally verify Stormglass secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Stormglass,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.stormglass.io/v2/weather/point?lat=58.7984&lng=17.8081&params=windSpeed\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Stormglass\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Stormglass is a weather data provider. Stormglass API keys can be used to access weather data services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/stormglass/stormglass_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage stormglass\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestStormglass_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"STORMGLASS\")\n\tinactiveSecret := testSecrets.MustGetField(\"STORMGLASS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a stormglass secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Stormglass,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Stormglass,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Stormglass.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Stormglass.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/stormglass/stormglass_test.go",
    "content": "package stormglass\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"1sijzovx0w8ru40qteilgbxej6a5A1moc7s9b1p3mx9472dho5svv5yA7b0aqzu-Ai-z-r1xz\"\n\tinvalidPattern = \"1sijzovx0w8ru40qteilg?xej6a5A1moc7s9b1p3mx9472dho5svv5yA7b0aqzu-Ai-z-r1xz\"\n\tkeyword        = \"stormglass\"\n)\n\nfunc TestStormglass_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword stormglass\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/storyblok/storyblok.go",
    "content": "package storyblok\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"storyblok\"}) + `\\b([0-9A-Za-z]{22}tt)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"storyblok\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_StoryblokAccessToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// FromData will find and optionally verify Storyblok secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueAccessTokens = make(map[string]struct{})\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueAccessTokens[match[1]] = struct{}{}\n\t}\n\n\tfor accessToken := range uniqueAccessTokens {\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_StoryblokAccessToken,\n\t\t\tRaw:          []byte(accessToken),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyStoryBlokAccessToken(ctx, client, accessToken)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\n// docs: https://www.storyblok.com/docs/api/content-delivery/v2/getting-started/authentication\nfunc verifyStoryBlokAccessToken(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.storyblok.com/v1/cdn/spaces/me/?token=\"+token, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/storyblok/storyblok_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage storyblok\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestStoryblok_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"STORYBLOK_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"STORYBLOK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a storyblok secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_StoryblokAccessToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_StoryblokAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Storyblok.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Storyblok.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/storyblok/storyblok_test.go",
    "content": "package storyblok\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"ZXIic758xXghN5SOkRI57ytt\"\n\tinvalidPattern = \"ZXIic758xXgh?5SOkRI57ytt\"\n\tkeyword        = \"storyblok\"\n)\n\nfunc TestStoryblok_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword storyblok\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/storyblokpersonalaccesstoken/storyblok.go",
    "content": "package storyblokpersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"storyblok\"}) + `\\b([0-9A-Za-z]{22}tt-[0-9]{6}-[A-Za-z0-9_-]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"storyblok\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_StoryblokPersonalAccessToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn `Storyblok is a headless CMS that allows developers to build flexible and powerful content management solutions.\n\t\t\tStoryblok personal access tokens can be used with management APIs.\n\t\t\tThe Storyblok Management API allows you to create, edit, update, and delete content using a common interface`\n}\n\n// FromData will find and optionally verify Storyblok secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniquePATs = make(map[string]struct{})\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniquePATs[match[1]] = struct{}{}\n\t}\n\n\tfor pat := range uniquePATs {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_StoryblokPersonalAccessToken,\n\t\t\tRaw:          []byte(pat),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyStoryBlokPersonalAccessToken(ctx, client, pat)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\n// docs: http://storyblok.com/docs/api/management/core-resources/spaces/retrieve-multiple-spaces\nfunc verifyStoryBlokPersonalAccessToken(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://mapi.storyblok.com/v1/spaces/\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// docs: https://www.storyblok.com/docs/api/management/getting-started/authentication\n\treq.Header.Set(\"Authorization\", token)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/storyblokpersonalaccesstoken/storyblok_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage storyblokpersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestStoryblokPersonalAccessToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tsecret := testSecrets.MustGetField(\"STORYBLOK_PAT\")\n\tinactiveSecret := testSecrets.MustGetField(\"STORYBLOK_PAT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a storyblok secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_StoryblokPersonalAccessToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_StoryblokPersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Storyblok.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Storyblok.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/storyblokpersonalaccesstoken/storyblok_test.go",
    "content": "package storyblokpersonalaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"5r7EgNfakeXi6ZoEls1twAtt-001100-Q5E2fKfakeRqsUjwmsJn\"\n\tinvalidPattern = \"5r7EgNfakeXi6ZoEls1twAt-001100-Q5E2fKfakeRqsUjwmsJn\"\n\tkeyword        = \"storyblok\"\n)\n\nfunc TestStoryblokPersonalAccessToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword storyblok\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/storychief/storychief.go",
    "content": "package storychief\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"storychief\"}) + `\\b([a-zA-Z0-9_\\-.]{940,1000})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"storychief\"}\n}\n\n// FromData will find and optionally verify Storychief secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Storychief,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.storychief.io/1.0/users?page=1&count=10\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Storychief\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/storychief/storychief_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage storychief\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestStorychief_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"STORYCHIEF\")\n\tinactiveSecret := testSecrets.MustGetField(\"STORYCHIEF_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a storychief secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Storychief,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Storychief,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Storychief.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Storychief.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/storychief/storychief_test.go",
    "content": "package storychief\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"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\"\n\tinvalidPattern = \"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\"\n\tkeyword        = \"storychief\"\n)\n\nfunc TestStorychief_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword storychief\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/strava/strava.go",
    "content": "package strava\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tidPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"strava\"}) + `\\b([0-9]{5})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"strava\"}) + `\\b([0-9a-z]{40})\\b`)\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"strava\"}) + `\\b([0-9a-z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"strava\"}\n}\n\n// FromData will find and optionally verify Strava secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\tkeyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range idMatches {\n\t\tresId := strings.TrimSpace(match[1])\n\n\t\tfor _, secretMatch := range secretMatches {\n\t\t\tresSecret := strings.TrimSpace(secretMatch[1])\n\n\t\t\tfor _, keyMatch := range keyMatches {\n\t\t\t\tresKey := strings.TrimSpace(keyMatch[1])\n\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Strava,\n\t\t\t\t\tRaw:          []byte(resId),\n\t\t\t\t\tRawV2:        []byte(resId + resSecret),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tpayload := strings.NewReader(\"grant_type=refresh_token&client_id=\" + resId + \"&client_secret=\" + resSecret + \"&refresh_token=\" + resKey)\n\n\t\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://www.strava.com/oauth/token\", payload)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\t\t\tres, err := client.Do(req)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Strava\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"An workout app, Oauth API keys can potentially be used to access user workout data\"\n}\n"
  },
  {
    "path": "pkg/detectors/strava/strava_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage strava\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestStrava_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tid := testSecrets.MustGetField(\"STRAVA_ID\")\n\tsecret := testSecrets.MustGetField(\"STRAVA_SECRET\")\n\ttoken := testSecrets.MustGetField(\"STRAVA_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"STRAVA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a strava secret %s within strava id %s and strava token %s\", secret, id, token)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Strava,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Strava,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Strava.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Strava.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/strava/strava_test.go",
    "content": "package strava\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidId       = \"57732\"\n\tinvalidId     = \"5?732\"\n\tvalidSecret   = \"ateo889bcja9fyt4gff2kqe187uhy8l0il4vf2cw\"\n\tinvalidSecret = \"ateo889bcja9fyt4gff2?qe187uhy8l0il4vf2cw\"\n\tvalidKey      = \"e8qmzsdc0qbq4hsal5m4q1pz5s2yloydbl1bjc1u\"\n\tinvalidKey    = \"e8?mzsdc0qbq4hsal5m4q1pz5s2yloydbl1bjc1u\"\n\tkeyword       = \"strava\"\n)\n\nfunc TestStrava_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword strava\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, validId, keyword, validSecret, keyword, validKey),\n\t\t\twant:  []string{validId + validSecret, validId + validKey, validId + validKey, validId + validSecret},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidId, keyword, invalidSecret, keyword, invalidKey),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/streak/streak.go",
    "content": "package streak\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"streak\"}) + `\\b([0-9Aa-f]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"streak\"}\n}\n\n// FromData will find and optionally verify Streak secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Streak,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tdata := fmt.Sprintf(\"%s:\", resMatch)\n\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.streak.com/api/v1/pipelines\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Streak\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Streak is a CRM software that integrates with Gmail. Streak API keys can be used to access and manage CRM data within Streak.\"\n}\n"
  },
  {
    "path": "pkg/detectors/streak/streak_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage streak\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestStreak_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"STREAK\")\n\tinactiveSecret := testSecrets.MustGetField(\"STREAK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a streak secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Streak,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Streak,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Streak.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Streak.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/streak/streak_test.go",
    "content": "package streak\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"bf1949fA2574fb6b2deb94AdA51f0719\"\n\tinvalidPattern = \"bf1949fA?574fb6b2deb94AdA51f0719\"\n\tkeyword        = \"streak\"\n)\n\nfunc TestStreak_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword streak\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/stripe/stripe.go",
    "content": "package stripe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\t// doesn't include test keys with \"sk_test\"\n\tsecretKey = regexp.MustCompile(`[rs]k_live_[a-zA-Z0-9]{20,247}`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"k_live\"}\n}\n\n// FromData will find and optionally verify Stripe secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\n\tdataStr := string(data)\n\n\tmatches := secretKey.FindAllString(dataStr, -1)\n\n\tfor _, match := range matches {\n\n\t\tresult := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Stripe,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\t\tresult.ExtraData = map[string]string{\n\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/stripe/\",\n\t\t}\n\n\t\tif verify {\n\n\t\t\tbaseURL := \"https://api.stripe.com/v1/charges\"\n\n\t\t\tclient := common.SaneHttpClient()\n\n\t\t\t// test `read_user` scope\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", baseURL, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", match))\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tres.Body.Close() // The request body is unused.\n\n\t\t\t\tif res.StatusCode == http.StatusOK || res.StatusCode == http.StatusForbidden {\n\t\t\t\t\tresult.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult.AnalysisInfo = map[string]string{\"key\": match}\n\t\t}\n\n\t\tresults = append(results, result)\n\t}\n\n\treturn\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Stripe\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/stripe/stripe_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage stripe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestStripe_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"STRIPE_SECRET\")\n\tsecretInactive := testSecrets.MustGetField(\"STRIPE_INACTIVE\")\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a stripe secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Stripe,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/stripe/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a stripe secret %s within\", secretInactive)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Stripe,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/stripe/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Stripe.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].AnalysisInfo = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Stripe.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/stripe/stripe_test.go",
    "content": "package stripe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"rk_live_HUOlkIKhNEYOPS0oDSwGwJHbg4xGaXNeJZ2CdvDGeVZQHljoq5TuFwQHgME3W\"\n\tinvalidPattern = \"?k_live_HUOlkIKhNEYOPS0oDSwGwJHbg4xGaXNeJZ2CdvDGeVZQHljoq5TuFwQHgME3?\"\n\tkeyword        = \"stripe\"\n)\n\nfunc TestStripe_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword stripe\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/stripepaymentintent/stripepaymentintent.go",
    "content": "package stripepaymentintent\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tClient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tclientSecretPat   = regexp.MustCompile(`\\b(pi_[a-zA-Z0-9]{24}_secret_[a-zA-Z0-9]{25})\\b`)\n\tsecretKeyPat      = regexp.MustCompile(`\\b([rs]k_live_[a-zA-Z0-9]{20,247})\\b`)\n\tpublishableKeyPat = regexp.MustCompile(`\\b(pk_live_[a-zA-Z0-9]{20,247})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"_secret_\"}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.Client != nil {\n\t\treturn s.Client\n\t}\n\treturn defaultClient\n}\n\n// FromData will find and optionally verify Stripe Payment Intent secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) ([]detectors.Result, error) {\n\tdataStr := string(data)\n\n\t// Stripe client secrets can't be verified on their own, they must be paired with a secret or publishable key.\n\t// Secret keys are preferred for verification, but in some real-world cases only publishable keys are present.\n\t// While typically used client-side, publishable keys can still confirm certain PaymentIntents.\n\t// To avoid missing valid detections, we verify using both key types.\n\t// If no keys are found, we skip detection since client secrets alone are not actionable.\n\tclientSecrets := extractMatches(clientSecretPat, dataStr)\n\tsecretKeys := extractMatches(secretKeyPat, dataStr)\n\tpublishableKeys := extractMatches(publishableKeyPat, dataStr)\n\n\tresults := make([]detectors.Result, 0, len(clientSecrets)*(len(secretKeys)+len(publishableKeys)))\n\n\t// Process each client secret against all keys\n\tfor clientSecret := range clientSecrets {\n\t\tfor key := range secretKeys {\n\t\t\tresult := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_StripePaymentIntent,\n\t\t\t\tRaw:          []byte(clientSecret),\n\t\t\t\tRawV2:        []byte(clientSecret + key),\n\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\"key_type\": \"secret\",\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tverified, err := verifyPaymentIntentWithSecretKey(ctx, s.getClient(), clientSecret, key)\n\t\t\t\tresult.Verified = verified\n\t\t\t\tresult.SetVerificationError(err)\n\t\t\t}\n\n\t\t\tresults = append(results, result)\n\t\t}\n\n\t\tfor key := range publishableKeys {\n\t\t\tresult := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_StripePaymentIntent,\n\t\t\t\tRaw:          []byte(clientSecret),\n\t\t\t\tRawV2:        []byte(clientSecret + key),\n\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\"key_type\": \"publishable\",\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tverified, err := verifyPaymentIntentWithPublishableKey(ctx, s.getClient(), clientSecret, key)\n\t\t\t\tresult.Verified = verified\n\t\t\t\tresult.SetVerificationError(err)\n\t\t\t}\n\n\t\t\tresults = append(results, result)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\n// Helper function to extract matches into a map for uniqueness\nfunc extractMatches(pattern *regexp.Regexp, data string) map[string]struct{} {\n\tmatches := pattern.FindAllStringSubmatch(data, -1)\n\tresult := make(map[string]struct{}, len(matches))\n\n\tfor _, match := range matches {\n\t\tif len(match) >= 2 {\n\t\t\tresult[match[1]] = struct{}{}\n\t\t}\n\t}\n\n\treturn result\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_StripePaymentIntent\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// VerifyPaymentIntentWithSecretKey verifies a Stripe PaymentIntent using the secret key.\n// It checks if the PaymentIntent ID is valid and if the secret key has access to it.\n// It returns a VerificationResult indicating the validity of the PaymentIntent and any error messages.\nfunc verifyPaymentIntentWithSecretKey(ctx context.Context, client *http.Client, clientSecret, secretKey string) (bool, error) {\n\turl := fmt.Sprintf(\"https://api.stripe.com/v1/payment_intents/%s\", extractIntentID(clientSecret))\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"error creating request: %v\", err)\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", secretKey))\n\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"request failed: %v\", err)\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn isClientSecretValid(resp.Body, clientSecret)\n\tcase http.StatusUnauthorized, http.StatusNotFound:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\n// verifyPaymentIntentWithPublishableKey verifies a Stripe PaymentIntent using the publishable key.\n// It checks if the PaymentIntent ID is valid and if the publishable key has access to it.\n// It returns a VerificationResult indicating the validity of the PaymentIntent and any error messages.\n// Note: It should only be used for client-side verification or in scenarios where the secret key is unavailable.\nfunc verifyPaymentIntentWithPublishableKey(ctx context.Context, client *http.Client, clientSecret, publishableKey string) (bool, error) {\n\tpaymentIntentId := extractIntentID(clientSecret)\n\tif paymentIntentId == \"\" {\n\t\treturn false, fmt.Errorf(\"payment intent ID is required\")\n\t}\n\n\t// Construct the request URL and add publishable key as a query parameter (this is how Stripe.js works)\n\turl := fmt.Sprintf(\"https://api.stripe.com/v1/payment_intents/%s\", paymentIntentId)\n\turl = url + fmt.Sprintf(\"?key=%s\", publishableKey)\n\turl = url + fmt.Sprintf(\"&client_secret=%s\", clientSecret)\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"error creating request: %v\", err)\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"request failed: %v\", err)\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn isClientSecretValid(resp.Body, clientSecret)\n\tcase http.StatusUnauthorized, http.StatusNotFound:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n\nfunc extractIntentID(clientSecret string) string {\n\tparts := strings.SplitN(clientSecret, \"_secret_\", 2)\n\tif len(parts) != 2 {\n\t\treturn \"\"\n\t}\n\treturn parts[0]\n}\n\nfunc isClientSecretValid(body io.Reader, expectedSecret string) (bool, error) {\n\tvar respBody struct {\n\t\tClientSecret string `json:\"client_secret\"`\n\t}\n\n\tif err := json.NewDecoder(body).Decode(&respBody); err != nil {\n\t\treturn false, fmt.Errorf(\"failed to decode response body: %v\", err)\n\t}\n\n\treturn respBody.ClientSecret == expectedSecret, nil\n}\n"
  },
  {
    "path": "pkg/detectors/stripepaymentintent/stripepaymentintent_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage stripepaymentintent\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestStripepaymentintent_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"STRIPE_SECRET\")\n\tpaymentIntent := testSecrets.MustGetField(\"STRIPE_PAYMENT_INTENT\")\n\tsecretInactive := testSecrets.MustGetField(\"STRIPE_INACTIVE\")\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twantVerified        bool // Instead of expecting exact results, check if any result is verified\n\t\twantResultCount     int  // Expected number of results\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a stripepaymentintent secret %s and payment intent: %s within\", secret, paymentIntent)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantVerified:        true, // At least one result should be verified\n\t\t\twantResultCount:     1,    // 1 client secret × 1 key = 1 result\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a stripepaymentintent secret %s and payment intent %s within but not valid\", secretInactive, paymentIntent)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantVerified:        false, // No results should be verified\n\t\t\twantResultCount:     1,     // 1 client secret × 1 key = 1 result\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantVerified:        false,\n\t\t\twantResultCount:     0, // No results expected\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Stripepaymentintent.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Check result count\n\t\t\tif len(got) != tt.wantResultCount {\n\t\t\t\tt.Errorf(\"Stripepaymentintent.FromData() got %d results, want %d\", len(got), tt.wantResultCount)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Check each result\n\t\t\thasVerified := false\n\t\t\tfor i := range got {\n\t\t\t\t// Check that all results have the correct detector type\n\t\t\t\tif got[i].DetectorType != detectorspb.DetectorType_StripePaymentIntent {\n\t\t\t\t\tt.Errorf(\"Stripepaymentintent.FromData() result %d has wrong DetectorType\", i)\n\t\t\t\t}\n\n\t\t\t\t// Check that raw secret is present\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present in result %d: \\n %+v\", i, got[i])\n\t\t\t\t}\n\n\t\t\t\t// Check that RawV2 is present (should contain client secret + key)\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no rawV2 present in result %d: \\n %+v\", i, got[i])\n\t\t\t\t}\n\n\t\t\t\t// Check verification error expectation\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\n\t\t\t\t// Track if any result is verified\n\t\t\t\tif got[i].Verified {\n\t\t\t\t\thasVerified = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check verification expectation\n\t\t\tif hasVerified != tt.wantVerified {\n\t\t\t\tt.Errorf(\"Stripepaymentintent.FromData() hasVerified = %v, want %v\", hasVerified, tt.wantVerified)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/stripepaymentintent/stripepaymentintent_test.go",
    "content": "package stripepaymentintent\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestStripepaymentintent_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname          string\n\t\tinput         string\n\t\texpectedPairs []string\n\t}{\n\t\t{\n\t\t\tname: \"client secret with secret key\",\n\t\t\tinput: \"stripepaymentintent_token = '\" + \"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" +\n\t\t\t\t\"' and stripe_key = '\" + \"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\" + \"'\",\n\t\t\texpectedPairs: []string{\"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" +\n\t\t\t\t\"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\"},\n\t\t},\n\t\t{\n\t\t\tname: \"client secret with publishable key\",\n\t\t\tinput: \"stripepaymentintent_token = '\" + \"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" +\n\t\t\t\t\"' and stripe_key = '\" + \"pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456\" + \"'\",\n\t\t\texpectedPairs: []string{\"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" +\n\t\t\t\t\"pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456\"},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple client secrets with single key\",\n\t\t\tinput: `stripepaymentintent_token1 = '` + \"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" + `'\n\t\t\tstripepaymentintent_token2 = '` + \"pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456\" + `'\n\t\t\tstripe_key = '` + \"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\" + `'`,\n\t\t\texpectedPairs: []string{\n\t\t\t\t\"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" +\n\t\t\t\t\t\"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\",\n\t\t\t\t\"pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456\" +\n\t\t\t\t\t\"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\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"single client secret with multiple keys\",\n\t\t\tinput: `stripepaymentintent_token = '` + \"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" + `'\n\t\t\tstripe_secret_key = '` + \"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\" + `'\n\t\t\tstripe_publishable_key = '` + \"pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456\" + `'`,\n\t\t\texpectedPairs: []string{\n\t\t\t\t\"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" +\n\t\t\t\t\t\"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\",\n\t\t\t\t\"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" +\n\t\t\t\t\t\"pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple client secrets with multiple keys\",\n\t\t\tinput: `stripepaymentintent_token1 = '` + \"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" + `'\n\t\t\tstripepaymentintent_token2 = '` + \"pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456\" + `'\n\t\t\tstripe_secret_key = '` + \"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\" + `'\n\t\t\tstripe_publishable_key = '` + \"pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456\" + `'`,\n\t\t\texpectedPairs: []string{\n\t\t\t\t\"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" +\n\t\t\t\t\t\"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\",\n\t\t\t\t\"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" +\n\t\t\t\t\t\"pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456\",\n\t\t\t\t\"pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456\" +\n\t\t\t\t\t\"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\",\n\t\t\t\t\"pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456\" +\n\t\t\t\t\t\"pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:          \"only client secret without keys\",\n\t\t\tinput:         \"stripepaymentintent_token = '\" + \"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" + \"'\",\n\t\t\texpectedPairs: []string{},\n\t\t},\n\t\t{\n\t\t\tname:          \"only keys without client secret\",\n\t\t\tinput:         \"stripe_key = '\" + \"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\" + \"'\",\n\t\t\texpectedPairs: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid client secret with valid key\",\n\t\t\tinput: \"stripepaymentintent_token = '\" + \"test_secret_test_1234567890abcdefg\" + \"' and stripe_key = '\" +\n\t\t\t\t\"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\" + \"'\",\n\t\t\texpectedPairs: []string{},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed valid and invalid client secrets with key\",\n\t\t\tinput: `some_token = '` + \"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" + `'\n\t\t\tother_token = '` + \"test_secret_test_1234567890abcdefg\" + `'\n\t\t\tstripe_key = '` + \"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\" + `'`,\n\t\t\texpectedPairs: []string{\"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" +\n\t\t\t\t\"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\"},\n\t\t},\n\t\t{\n\t\t\tname: \"complex scenario with multiple valid combinations\",\n\t\t\tinput: `\n\t\t\t# Multiple client secrets\n\t\t\tpi_token_1 = '` + \"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" + `'\n\t\t\tpi_token_2 = '` + \"pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456\" + `'\n\t\t\t\n\t\t\t# Multiple secret keys\n\t\t\tsecret_key_1 = '` + \"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\" + `'\n\t\t\tsecret_key_2 = '` + \"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\" + `'\n\t\t\t\n\t\t\t# Multiple publishable keys  \n\t\t\tpub_key_1 = '` + \"pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456\" + `'\n\t\t\tpub_key_2 = '` + \"pk_live_51NuxCxMleJwHu7ix0BItRxY7OXh9Gr8RLr6ZHGz8XHCrSGIeUeF9MgLdLaRl8oKw4y6ZKwoKZHHa2o4HzGeIK6abc123456\" + `'`,\n\t\t\texpectedPairs: []string{\n\t\t\t\t\"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" +\n\t\t\t\t\t\"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\",\n\t\t\t\t\"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" +\n\t\t\t\t\t\"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\",\n\t\t\t\t\"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" +\n\t\t\t\t\t\"pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456\",\n\t\t\t\t\"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" +\n\t\t\t\t\t\"pk_live_51NuxCxMleJwHu7ix0BItRxY7OXh9Gr8RLr6ZHGz8XHCrSGIeUeF9MgLdLaRl8oKw4y6ZKwoKZHHa2o4HzGeIK6abc123456\",\n\t\t\t\t\"pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456\" +\n\t\t\t\t\t\"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\",\n\t\t\t\t\"pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456\" +\n\t\t\t\t\t\"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\",\n\t\t\t\t\"pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456\" +\n\t\t\t\t\t\"pk_live_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5abc123456\",\n\t\t\t\t\"pi_4NuxCxMleJwHu7ix28a3tqPb_secret_ZsLKVLsjdcCkdH9IWig123456\" +\n\t\t\t\t\t\"pk_live_51NuxCxMleJwHu7ix0BItRxY7OXh9Gr8RLr6ZHGz8XHCrSGIeUeF9MgLdLaRl8oKw4y6ZKwoKZHHa2o4HzGeIK6abc123456\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"test keys should not match (only live keys are detected)\",\n\t\t\tinput: `stripepaymentintent_token = '` + \"pi_3MtwBwLkdIwHu7ix28a3tqPa_secret_YrKJUKribcBjcG8HVhf123456\" + `'\n\t\t\ttest_secret_key = 'sk_test_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5nJGydHjE'\n\t\t\ttest_pub_key = 'pk_test_51MtwBwLkdIwHu7ix0AHtQwX6NWg8Fq7QKq5YGFy7WGBqRFHdTdE8LfKcKzQk7nJv3x5YJvnJYGGz1n3GyFdHJ5nJGydHjE'`,\n\t\t\texpectedPairs: []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(test.expectedPairs) > 0 {\n\t\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.expectedPairs) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.expectedPairs), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.expectedPairs))\n\t\t\tfor _, v := range test.expectedPairs {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/stripo/stripo.go",
    "content": "package stripo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// JWT token\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"stripo\"}) + `\\b(eyJhbGciOiJIUzI1NiJ9\\.[0-9A-Za-z]{130}\\.[0-9A-Za-z_-]{43})\\b`)\n)\n\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"stripo\"}\n}\n\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Stripo,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\n\t\t\t// API docs: https://api.stripo.email/reference/findemails\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://stripo.email/emailgeneration/v1/emails?parameters=sortingAsc=true\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Stripo-Api-Auth\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Stripo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Stripo is an email template builder. Stripo API keys can be used to access and modify email templates.\"\n}\n"
  },
  {
    "path": "pkg/detectors/stripo/stripo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage stripo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestStripo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"STRIPO\")\n\tinactiveSecret := testSecrets.MustGetField(\"STRIPO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a stripo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Stripo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Stripo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Stripo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Stripo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/stripo/stripo_test.go",
    "content": "package stripo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"eyJhbGciOiJIUzI1NiJ9.qflXV62P6Y51gAFD9pnq9FpcgDQOfr68GtBUARUvk7gzbAfg6eF66Txb4R9idT41JsG5CqTKXMfMnzpPVByJRFjtfXIFasywYCd53PqB774bhbBFgfEznjdQjOu8ui910q.8QQbrVsL16ya98sn8yyjLbBX21bix2G_QhFg_rKN9Bv\"\n\tinvalidPattern = \"ayJhbGciOiJIUzI1NiJ9.qflXV62P6Y51gAFD9pnq9FpcgDQOfr68GtBUARUvk7gzbAfg6eF66Txb4R9idT41JsG5CqTKXMfMnzpPVByJRFjtfXIFasywYCd53PqB774bhbBFgfEznjdQjOu8ui910q.8QQbrVsL16ya98sn8yyjLbBX21bix2G_QhFg_rKN9Bv\"\n\tkeyword        = \"stripo\"\n)\n\nfunc TestStripo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword stripo\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/stytch/stytch.go",
    "content": "package stytch\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"stytch\"}) + `\\b([a-zA-Z0-9-_]{47}=)`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"stytch\"}) + `\\b([a-z0-9-]{49})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"stytch\"}\n}\n\n// FromData will find and optionally verify Stytch secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\ttokenPatMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tuserPatMatch := strings.TrimSpace(idMatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Stytch,\n\t\t\t\tRaw:          []byte(tokenPatMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.stytch.com/v1/users/pending\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.SetBasicAuth(userPatMatch, tokenPatMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Stytch\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Stytch is a platform for passwordless authentication. Stytch API keys can be used to access and manage authentication services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/stytch/stytch_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage stytch\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestStytch_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"STYTCH\")\n\tid := testSecrets.MustGetField(\"STYTCH_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"STYTCH_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a stytch secret %s within stytch %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Stytch,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Stytch,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Stytch.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Stytch.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/stytch/stytch_test.go",
    "content": "package stytch\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey   = \"TSIDpkOi1MKTudKSFs-KXGdOeH3ECTeXWtX7OAr4Y9k31m8=\"\n    invalidKey = \"TSIDpkOi1MKTudKSFs-KXGdO?H3ECTeXWtX7OAr4Y9k31m8=\"\n    validId    = \"k7hi7kqgb6a8rqi0b8nbna1p-o32rv9xzlnvzbd5oa9yr-vtp\"\n    invalidId  = \"k7hi7kqgb6a8rqi0b8nbna1p?o32rv9xzlnvzbd5oa9yr-vtp\"\n    keyword    = \"stytch\"\n)\n\nfunc TestStytch_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword stytch\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sugester/sugester.go",
    "content": "package sugester\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"sugester\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n\tdomainPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"sugester\"}) + `\\b([a-zA-Z0-9_.!+$#^*%]{3,32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sugester\"}\n}\n\n// FromData will find and optionally verify Sugester secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tdomainMatches := domainPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, domainmatch := range domainMatches {\n\t\t\tresDomainMatch := strings.TrimSpace(domainmatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Sugester,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://\"+resDomainMatch+\".sugester.com/app/clients.json?api_token=\"+resMatch, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Accept\", \"application/vnd.sugester+json; version=3\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Sugester\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sugester/sugester_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sugester\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSugester_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SUGESTER\")\n\tdomain := testSecrets.MustGetField(\"SUGESTER_DOMAIN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SUGESTER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sugester secret %s within sugesterdomain %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sugester,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Sugester,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Sugester.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Sugester.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sugester/sugester_test.go",
    "content": "package sugester\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey      = \"RfQEMjlVIolNGUebkCS7iw0MevjgAD06\"\n\tinvalidKey    = \"RfQEMjlVIolNGUeb?CS7iw0MevjgAD06\"\n\tvalidDomain   = \"h$6o0+jTX\"\n\tinvalidDomain = \"?$6o0+jT?\"\n\tkeyword       = \"sugester\"\n)\n\nfunc TestSugester_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword sugester\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validDomain),\n\t\t\twant:  []string{validKey, validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidDomain),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sumologickey/sumologickey.go",
    "content": "package sumologickey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.EndpointSetter\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar (\n\t_ detectors.Detector           = (*Scanner)(nil)\n\t_ detectors.EndpointCustomizer = (*Scanner)(nil)\n)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Detect which instance the key is associated with.\n\t// https://help.sumologic.com/docs/api/getting-started/#documentation\n\turlPat = regexp.MustCompile(`(?i)api\\.(?:au|ca|de|eu|fed|jp|kr|in|us2)\\.sumologic\\.com`)\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"sumo\", \"accessId\"}) + `\\b(su[A-Za-z0-9]{12})\\b`)\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"sumo\", \"accessKey\"}) + `\\b([A-Za-z0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sumo\", \"accessId\", \"accessKey\"}\n}\n\n// Default US API endpoint.\nfunc (Scanner) CloudEndpoint() string { return \"api.sumologic.com\" }\n\n// FromData will find and optionally verify SumoLogicKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tidMatches := make(map[string]struct{})\n\tfor _, match := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tidMatches[match[1]] = struct{}{}\n\t}\n\tkeyMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tkeyMatches[match[1]] = struct{}{}\n\t}\n\tendpointMatches := make(map[string]struct{})\n\tfor _, match := range urlPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tendpointMatches[match[0]] = struct{}{}\n\t}\n\tif len(endpointMatches) == 0 {\n\t\tendpointMatches[s.CloudEndpoint()] = struct{}{}\n\t}\n\n\tfor accessKey := range keyMatches {\n\t\tvar (\n\t\t\tr           *detectors.Result\n\t\t\taccessId    string\n\t\t\tapiEndpoint string\n\t\t)\n\n\t\tfor id := range idMatches {\n\t\t\taccessId = id\n\n\t\t\tfor e := range endpointMatches {\n\t\t\t\tapiEndpoint = e\n\n\t\t\t\tif verify {\n\t\t\t\t\tclient := s.client\n\t\t\t\t\tif client == nil {\n\t\t\t\t\t\tclient = defaultClient\n\t\t\t\t\t}\n\n\t\t\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, apiEndpoint, accessId, accessKey)\n\t\t\t\t\tif isVerified {\n\t\t\t\t\t\tr = createResult(accessId, accessKey, apiEndpoint, isVerified, verificationErr)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif r == nil {\n\t\t\t// Only include the accessId if we're confident which one it is.\n\t\t\tif len(idMatches) != 1 {\n\t\t\t\taccessId = \"\"\n\t\t\t}\n\t\t\tif len(endpointMatches) != 1 || apiEndpoint == s.CloudEndpoint() {\n\t\t\t\tapiEndpoint = \"\"\n\t\t\t}\n\t\t\tr = createResult(accessId, accessKey, apiEndpoint, false, nil)\n\t\t}\n\t\tresults = append(results, *r)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, endpoint string, id string, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"https://%s/api/v1/users\", endpoint), nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.SetBasicAuth(id, key)\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\t// If the endpoint returns useful information, we can return it as a map.\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc createResult(accessId string, accessKey string, endpoint string, verified bool, err error) *detectors.Result {\n\tr := &detectors.Result{\n\t\tDetectorType: detectorspb.DetectorType_SumoLogicKey,\n\t\tRaw:          []byte(accessKey),\n\t\tVerified:     verified,\n\t\tExtraData: map[string]string{\n\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/sumologic/\",\n\t\t},\n\t}\n\tr.SetVerificationError(err, accessKey)\n\n\t// |endpoint| and |accessId| won't be specified unless there's a confident match.\n\tif accessId != \"\" {\n\t\tvar sb strings.Builder\n\t\tsb.WriteString(`{`)\n\t\tsb.WriteString(`\"accessId\":\"` + accessId + `\"`)\n\t\tsb.WriteString(`,\"accessKey\":\"` + accessKey + `\"`)\n\t\tif endpoint != \"\" {\n\t\t\tsb.WriteString(`,\"url\":\"` + endpoint + `\"`)\n\t\t}\n\t\tsb.WriteString(`}`)\n\t\tr.RawV2 = []byte(sb.String())\n\t}\n\n\treturn r\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SumoLogicKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/sumologickey/sumologickey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage sumologickey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSumoLogicKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tid := testSecrets.MustGetField(\"SUMOLOGIC_ACCESSID\")\n\tsecret := testSecrets.MustGetField(\"SUMOLOGIC_ACCESSKEY\")\n\tinactiveId := testSecrets.MustGetField(\"SUMOLOGIC_ACCESSKEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a sumologickey secret %s within %s\", id, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SumoLogicKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SumoLogicKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SumoLogicKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SumoLogicKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/sumologickey/sumologickey_test.go",
    "content": "package sumologickey\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\n\t\"github.com/google/go-cmp/cmp\"\n)\n\nfunc TestSumoLogicKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"typical pattern\",\n\t\t\tinput: `sumologic:\n  accessId: suDkVYKjXZAwsz\n  accessKey: Khk3i2ugMxMgkb8bIA2auj4I8juZ3HiimDNssjzYdGqfizPZcxHK70a0LckgRSCL\n  clusterName: Kubernetes_cluster-2024-10-25T21:34:23.096Z`,\n\t\t\twant: []string{`{\"accessId\":\"suDkVYKjXZAwsz\",\"accessKey\":\"Khk3i2ugMxMgkb8bIA2auj4I8juZ3HiimDNssjzYdGqfizPZcxHK70a0LckgRSCL\"}`},\n\t\t},\n\t\t{\n\t\t\tname: \"pattern with url\",\n\t\t\tinput: `sumologic:\n  baseUrl: api.us2.sumologic.com\n  accessId: suDkVYKjXZAwsz\n  accessKey: Khk3i2ugMxMgkb8bIA2auj4I8juZ3HiimDNssjzYdGqfizPZcxHK70a0LckgRSCL\n  clusterName: Kubernetes_cluster-2024-10-25T21:34:23.096Z`,\n\t\t\twant: []string{`{\"accessId\":\"suDkVYKjXZAwsz\",\"accessKey\":\"Khk3i2ugMxMgkb8bIA2auj4I8juZ3HiimDNssjzYdGqfizPZcxHK70a0LckgRSCL\",\"url\":\"api.us2.sumologic.com\"}`},\n\t\t},\n\t\t{\n\t\t\tname: \"finds all matches\",\n\t\t\tinput: `sumoId1 = 'suaRYt6iLL8cxl'\nsumoKey1 = 'CzrMhR8zzy1eH1F0XlY1tu5ywqa2yaSFoWGg2cqE43XkfnUVCytnPQfv1enUYrzv'\nsumoId2 = 'suDkVYKjXZBwsz'\nsumoKey2 = 'Khk3i2ugMxMgkb8bIA2auj4I8juZ3HiimDNssjzYdGqfizPZcxHK21a0LckgRSCL'`,\n\t\t\twant: []string{\"CzrMhR8zzy1eH1F0XlY1tu5ywqa2yaSFoWGg2cqE43XkfnUVCytnPQfv1enUYrzv\", \"Khk3i2ugMxMgkb8bIA2auj4I8juZ3HiimDNssjzYdGqfizPZcxHK21a0LckgRSCL\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: \"sumoId = 'doDkVYKjXZAwsz'\",\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/supabasetoken/supabasetoken.go",
    "content": "package supabasetoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(sbp_[a-z0-9]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sbp_\"}\n}\n\n// FromData will find and optionally verify Supabasetoken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SupabaseToken,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.supabase.com/v1/projects\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SupabaseToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Supabase is an open source Firebase alternative. Supabase tokens can be used to access and manage Supabase projects and data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/supabasetoken/supabasetoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage supabasetoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSupabasetoken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SUPABASETOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SUPABASETOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a supabasetoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SupabaseToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SupabaseToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Supabasetoken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Supabasetoken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/supabasetoken/supabasetoken_test.go",
    "content": "package supabasetoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"sbp_6corwd8ltv0m2hx5av0bqswrldi43hk8qdtnxcs7\"\n\tinvalidPattern = \"sbp_6corwd8ltv?m2hx5av0bqswrldi43hk8qdtnxcs7\"\n\tkeyword        = \"supabasetoken\"\n)\n\nfunc TestSupabasetoken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword supabasetoken\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/supernotesapi/supernotesapi.go",
    "content": "package supernotesapi\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"supernotes\"}) + `([ \\r\\n]{0,1}[0-9A-Za-z\\-_]{43}[ \\r\\n]{1})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"supernotes\"}\n}\n\n// FromData will find and optionally verify SuperNotesAPI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SuperNotesAPI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.supernotes.app/v1/user\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Api-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SuperNotesAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"SuperNotes is a note-taking application. SuperNotes API keys can be used to access and manage user notes and other data within the application.\"\n}\n"
  },
  {
    "path": "pkg/detectors/supernotesapi/supernotesapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage supernotesapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSuperNotesAPI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SUPERNOTESAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"SUPERNOTESAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a supernotes secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SuperNotesAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SuperNotesAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SuperNotesAPI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SuperNotesAPI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/supernotesapi/supernotesapi_test.go",
    "content": "package supernotesapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"V77f640DIDmrPcYFMwCKPhAR7CkXDwXjmMGpzHVm6yC\"\n\tinvalidPattern = \"V77f640DIDmrPcYFMwCKPh?R7CkXDwXjmMGpzHVm6yC\"\n\tkeyword        = \"supernotesapi\"\n)\n\nfunc TestSuperNotesAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword supernotesapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token = ' %s '\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = ' %s ' | ' %s '\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = ' %s '\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = ' %s '\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/surveyanyplace/surveyanyplace.go",
    "content": "package surveyanyplace\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"survey\"}) + `\\b([a-z0-9A-Z]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"survey\"}) + `\\b([a-z0-9A-Z-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"surveyanyplace\"}\n}\n\n// FromData will find and optionally verify SurveyAnyplace secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_SurveyAnyplace,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tpayload := strings.NewReader(`{\n\t\t\t\t\t\"codes\": [\n\t\t\t\t\t\"code1\",\n\t\t\t\t\t\"code2\"\n\t\t\t\t\t]\n\t\t\t\t\t}`)\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.surveyanyplace.com/v1/surveys/\"+resIdMatch+\"/accesscodes\", payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"API %s\", resMatch))\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SurveyAnyplace\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"SurveyAnyplace is a platform for creating surveys and quizzes. The detected credential can be used to access and manage surveys on this platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/surveyanyplace/surveyanyplace_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage surveyanyplace\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSurveyAnyplace_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SURVEYANYPLACE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SURVEYANYPLACE_INACTIVE\")\n\tid := testSecrets.MustGetField(\"SURVEYANYPLACE_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a surveyanyplace secret %s within surveyanyplaceid %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SurveyAnyplace,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SurveyAnyplace,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SurveyAnyplace.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SurveyAnyplace.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/surveyanyplace/surveyanyplace_test.go",
    "content": "package surveyanyplace\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey   = \"zDbWB2TozuNJj8o56FIB0PVfE7LfjHFd\"\n    invalidKey = \"zDbWB2TozuNJj8o5?FIB0PVfE7LfjHFd\"\n    validId    = \"UEN9VEUk4z1oY-pV2CPLZBAnn0rPWY3LCqNv\"\n    invalidId  = \"UEN9VEUk4z1oY-pV2C?LZBAnn0rPWY3LCqNv\"\n    keyword    = \"surveyanyplace\"\n)\n\nfunc TestSurveyAnyplace_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword surveyanyplace\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/surveybot/surveybot.go",
    "content": "package surveybot\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"surveybot\"}) + `\\b([A-Za-z0-9-]{80})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"surveybot\"}\n}\n\ntype response struct {\n\tSurveys []interface{} `json:\"surveys\"`\n}\n\n// FromData will find and optionally verify SurveyBot secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SurveyBot,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.surveybot.io/api/v1/surveys?page=1&per_page=20&offset=0\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"X-Api-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\tvar r response\n\t\t\t\t\tif err := json.NewDecoder(res.Body).Decode(&r); err != nil {\n\t\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif len(r.Surveys) > 0 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SurveyBot\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"SurveyBot is a service used for conducting surveys. SurveyBot API keys can be used to access and manage surveys.\"\n}\n"
  },
  {
    "path": "pkg/detectors/surveybot/surveybot_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage surveybot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSurveyBot_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SURVEYBOT_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SURVEYBOT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a surveybot secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SurveyBot,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SurveyBot,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SurveyBot.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SurveyBot.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/surveybot/surveybot_test.go",
    "content": "package surveybot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"ZGPxFPcng3xOumtiY14y8Gtp8IAp5pHlnxOf28l3ThncGCOHdU4o01muxq4UqAJP28s4hfkASbzVY0Qi\"\n\tinvalidPattern = \"ZGPxFPcng3xOumtiY14y8Gtp8IAp5pHlnxOf28l3?hncGCOHdU4o01muxq4UqAJP28s4hfkASbzVY0Qi\"\n\tkeyword        = \"surveybot\"\n)\n\nfunc TestSurveyBot_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword surveybot\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/surveysparrow/surveysparrow.go",
    "content": "package surveysparrow\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"surveysparrow\"}) + `\\b([a-zA-Z0-9-_]{88})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"surveysparrow\"}\n}\n\n// FromData will find and optionally verify SurveySparrow secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_SurveySparrow,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.surveysparrow.com/v1/contacts\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_SurveySparrow\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"SurveySparrow is a survey platform for collecting feedback and data. The detected key can be used to access and manage surveys and responses.\"\n}\n"
  },
  {
    "path": "pkg/detectors/surveysparrow/surveysparrow_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage surveysparrow\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSurveySparrow_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SURVEYSPARROW_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SURVEYSPARROW_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a surveysparrow secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SurveySparrow,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_SurveySparrow,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"SurveySparrow.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"SurveySparrow.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/surveysparrow/surveysparrow_test.go",
    "content": "package surveysparrow\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"nRTqAmYY7E8pi6CaSf4JGgvxreYC-DEI6Q9De1EYLilWDuSHGOugKnWYdIz7ifGlFDol4v2hsYH7C9lu5Z63XmwP\"\n\tinvalidPattern = \"nRTqAmYY7E8pi6CaSf4JGgvxreYC-?EI6Q9De1EYLilWDuSHGOugKnWYdIz7ifGlFDol4v2hsYH7C9lu5Z63XmwP\"\n\tkeyword        = \"surveysparrow\"\n)\n\nfunc TestSurveySparrow_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword surveysparrow\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/survicate/survicate.go",
    "content": "package survicate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"survicate\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"survicate\"}\n}\n\n// FromData will find and optionally verify Survicate secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Survicate,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://data-api.survicate.com/v1/surveys\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Survicate\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/survicate/survicate_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage survicate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSurvicate_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SURVICATE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"SURVICATE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a survicate secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Survicate,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Survicate,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Survicate.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Survicate.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/survicate/survicate_test.go",
    "content": "package survicate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"lztej917o59y8x82f311v8d684eb2t6k\"\n\tinvalidPattern = \"lztej917o59y8x82?311v8d684eb2t6k\"\n\tkeyword        = \"survicate\"\n)\n\nfunc TestSurvicate_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword survicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/swell/swell.go",
    "content": "package swell\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"swell\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"swell\"}) + `\\b([a-zA-Z0-9]{6,24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"swell\"}\n}\n\n// FromData will find and optionally verify Swell secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\ttokenPatMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tuserPatMatch := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Swell,\n\t\t\t\tRaw:          []byte(tokenPatMatch),\n\t\t\t}\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.swell.store/products?limit=100\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.SetBasicAuth(userPatMatch, tokenPatMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Swell\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/swell/swell_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage swell\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSwell_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SWELL\")\n\tid := testSecrets.MustGetField(\"SWELL_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"SWELL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a swell secret %s within swell %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Swell,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Swell,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Swell.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Swell.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/swell/swell_test.go",
    "content": "package swell\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey   = \"Mkff1bQx72mxhvNdCsudapMP7eery91G\"\n    invalidKey = \"Mkff1bQx72mxhvNd?sudapMP7eery91G\"\n    validId    = \"0aOWuIMHYK6F30I1\"\n    invalidId  = \"?aOWuIMHYK6F30I?\"\n    keyword    = \"swell\"\n)\n\nfunc TestSwell_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword swell\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/swiftype/swiftype.go",
    "content": "package swiftype\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"swiftype\"}\n}\n\n// FromData will find and optionally verify Swiftype secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Swiftype,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\"auth_token\":\"` + resMatch + `\",\"q\": \"gatsby\"}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://search-api.swiftype.com/api/v1/engines/bookstore/document_types/books/search.json\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Swiftype\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/swiftype/swiftype_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage swiftype\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestSwiftype_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"SWIFTYPE\")\n\tinactiveSecret := testSecrets.MustGetField(\"SWIFTYPE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a swiftype secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Swiftype,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Swiftype,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Swiftype.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Swiftype.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/swiftype/swiftype_test.go",
    "content": "package swiftype\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"hJgyi1_y8ohpS-N2vlXE\"\n\tinvalidPattern = \"?Jgyi1_y8ohpS-N2vlXE\"\n\tkeyword        = \"swiftype\"\n)\n\nfunc TestSwiftype_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword swiftype\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tableau/tableau.go",
    "content": "package tableau\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"maps\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tdetectors.EndpointSetter\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.EndpointCustomizer = (*Scanner)(nil)\n\nfunc (Scanner) CloudEndpoint() string { return \"\" }\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Simplified token name pattern using PrefixRegex\n\ttokenNamePat = regexp.MustCompile(detectors.PrefixRegex([]string{\"pat\", \"token\", \"name\", \"tableau\"}) + `([a-zA-Z][a-zA-Z0-9_-]{2,50})`)\n\n\t// Pattern for Personal Access Token Secrets\n\ttokenSecretPat = regexp.MustCompile(`\\b([A-Za-z0-9+/]{22}==:[A-Za-z0-9]{32})\\b`)\n\n\t// Simplified Tableau Server URLs pattern\n\ttableauURLPat = regexp.MustCompile(`\\b([a-zA-Z0-9\\-]+\\.online\\.tableau\\.com)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\n\t\t\"tableau\",\n\t\t\"online.tableau.com\",\n\t}\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// Extract token names, secrets, and URLs separately\n\ttokenNames := extractTokenNames(dataStr)\n\ttokenSecrets := extractTokenSecrets(dataStr)\n\tfoundURLs := extractTableauURLs(dataStr)\n\n\t// If no names or secrets found, return empty results\n\tif len(tokenNames) == 0 || len(tokenSecrets) == 0 {\n\t\treturn results, nil\n\t}\n\n\t// Use maps to deduplicate endpoints\n\tvar uniqueEndpoints = make(map[string]struct{})\n\n\t// Add endpoints to the list\n\tfor _, endpoint := range s.Endpoints(foundURLs...) {\n\t\t// Remove https:// prefix if present since we add it during verification\n\t\tendpoint = strings.TrimPrefix(endpoint, \"https://\")\n\t\tuniqueEndpoints[endpoint] = struct{}{}\n\t}\n\n\t// Process each combination of token name, token secret, and endpoint\n\tfor _, tokenName := range tokenNames {\n\t\tfor _, tokenSecret := range tokenSecrets {\n\t\t\tfor endpoint := range uniqueEndpoints {\n\t\t\t\tresult := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,\n\t\t\t\t\tRaw:          []byte(tokenName),\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s:%s\", tokenName, tokenSecret, endpoint)),\n\t\t\t\t\tExtraData:    make(map[string]string),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tclient := s.getClient()\n\t\t\t\t\tisVerified, extraData, verificationErr := verifyTableauPAT(ctx, client, tokenName, tokenSecret, endpoint)\n\t\t\t\t\tresult.Verified = isVerified\n\t\t\t\t\tmaps.Copy(result.ExtraData, extraData)\n\t\t\t\t\tresult.SetVerificationError(verificationErr, tokenName, tokenSecret, endpoint)\n\t\t\t\t\tif isVerified {\n\t\t\t\t\t\tresult.AnalysisInfo = map[string]string{\"token_name\": tokenName, \"pat_secret\": tokenSecret, \"endpoint\": endpoint}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tresults = append(results, result)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\n// extractTokenNames finds all potential token names in the data\nfunc extractTokenNames(data string) []string {\n\tvar names []string\n\t// Create a map of false positive terms\n\tfalsePositives := map[detectors.FalsePositive]struct{}{\n\t\tdetectors.FalsePositive(\"com\"): {},\n\t}\n\n\tfor _, match := range tokenNamePat.FindAllStringSubmatch(data, -1) {\n\t\tif len(match) >= 2 {\n\t\t\tname := strings.TrimSpace(match[1])\n\t\t\tisFalsePositive, _ := detectors.IsKnownFalsePositive(name, falsePositives, false)\n\t\t\tif !isFalsePositive {\n\t\t\t\tnames = append(names, name)\n\t\t\t}\n\t\t}\n\t}\n\treturn names\n}\n\n// extractTokenSecrets finds all potential token secrets in the data\nfunc extractTokenSecrets(data string) []string {\n\tvar secrets []string\n\tfor _, match := range tokenSecretPat.FindAllStringSubmatch(data, -1) {\n\t\tif len(match) >= 2 {\n\t\t\tsecret := strings.TrimSpace(match[1])\n\t\t\tsecrets = append(secrets, secret)\n\t\t}\n\t}\n\treturn secrets\n}\n\n// extractTableauURLs finds all potential Tableau server URLs in the data\nfunc extractTableauURLs(data string) []string {\n\tvar urls []string\n\n\tfor _, match := range tableauURLPat.FindAllStringSubmatch(data, -1) {\n\t\tif len(match) >= 2 {\n\t\t\turl := strings.TrimSpace(match[1])\n\t\t\tif url != \"\" {\n\t\t\t\turls = append(urls, url)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn urls\n}\n\n// TableauAuthRequest represents the authentication request structure\ntype TableauAuthRequest struct {\n\tCredentials TableauCredentials `json:\"credentials\"`\n}\n\ntype TableauCredentials struct {\n\tPersonalAccessTokenName   string      `json:\"personalAccessTokenName\"`\n\tPersonalAccessTokenSecret string      `json:\"personalAccessTokenSecret\"`\n\tSite                      interface{} `json:\"site\"`\n}\n\n// TableauAuthResponse represents the authentication response structure\ntype TableauAuthResponse struct {\n\tCredentials struct {\n\t\tSite struct {\n\t\t\tID         string `json:\"id\"`\n\t\t\tContentURL string `json:\"contentUrl\"`\n\t\t} `json:\"site\"`\n\t\tUser struct {\n\t\t\tID string `json:\"id\"`\n\t\t} `json:\"user\"`\n\t\tToken string `json:\"token\"`\n\t} `json:\"credentials\"`\n}\n\n// verifyTableauPAT verifies a Tableau Personal Access Token by attempting authentication\nfunc verifyTableauPAT(ctx context.Context, client *http.Client, tokenName, tokenSecret, endpoint string) (bool, map[string]string, error) {\n\t// Build the verification URL\n\tverifyURL := fmt.Sprintf(\"https://%s/api/3.26/auth/signin\", endpoint)\n\n\t// Prepare metadata early - before any potential errors\n\textraData := map[string]string{\n\t\t\"verification_endpoint\": verifyURL,\n\t\t\"verification_method\":   \"tableau_pat_auth\",\n\t\t\"tableau_endpoint\":      endpoint,\n\t}\n\n\t// Rest of your verification logic...\n\tauthReq := TableauAuthRequest{\n\t\tCredentials: TableauCredentials{\n\t\t\tPersonalAccessTokenName:   tokenName,\n\t\t\tPersonalAccessTokenSecret: tokenSecret,\n\t\t\tSite:                      map[string]interface{}{},\n\t\t},\n\t}\n\n\t// Marshal to JSON\n\tjsonData, err := json.Marshal(authReq)\n\tif err != nil {\n\t\treturn false, nil, fmt.Errorf(\"failed to marshal auth request: %v\", err)\n\t}\n\n\t// Create HTTP request\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, verifyURL, bytes.NewBuffer(jsonData))\n\tif err != nil {\n\t\treturn false, nil, fmt.Errorf(\"failed to create request: %v\", err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\t// Execute request\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\t// Check if it's a DNS/network error\n\t\tif strings.Contains(err.Error(), \"no such host\") ||\n\t\t\tstrings.Contains(err.Error(), \"dial tcp\") ||\n\t\t\tstrings.Contains(err.Error(), \"connection refused\") {\n\t\t\textraData[\"network_error\"] = \"true\"\n\t\t\treturn false, extraData, nil // No error, just invalid endpoint\n\t\t}\n\t\treturn false, nil, fmt.Errorf(\"request failed: %v\", err)\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\t// Read the response\n\tbodyBytes, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn false, nil, fmt.Errorf(\"failed to read response body: %v\", err)\n\t}\n\n\t// Status code handling...\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tvar authResp TableauAuthResponse\n\t\tif err := json.Unmarshal(bodyBytes, &authResp); err != nil {\n\t\t\treturn true, extraData, err\n\t\t}\n\t\treturn true, extraData, nil\n\n\tcase http.StatusUnauthorized, http.StatusBadRequest, http.StatusForbidden:\n\t\treturn false, extraData, nil\n\n\tdefault:\n\t\treturn false, extraData, fmt.Errorf(\"unexpected HTTP response status %d\", resp.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TableauPersonalAccessToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/tableau/tableau_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage tableau\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTableau_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttokenName := testSecrets.MustGetField(\"TABLEAU_TOKEN_NAME\")\n\ttokenSecret := testSecrets.MustGetField(\"TABLEAU_TOKEN_SECRET\")\n\tinactiveTokenName := testSecrets.MustGetField(\"TABLEAU_INACTIVE_TOKEN_NAME\")\n\tinactiveTokenSecret := testSecrets.MustGetField(\"TABLEAU_INACTIVE_TOKEN_SECRET\")\n\ttableauURL := testSecrets.MustGetField(\"TABLEAU_VALID_POD_NAME\")\n\tinvalidURL := testSecrets.MustGetField(\"TABLEAU_INVALID_POD_NAME\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified with URL\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"token=%s\\nsecret=%s\\nserver=%s\", tokenName, tokenSecret, tableauURL)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"token_name\":            tokenName,\n\t\t\t\t\t\t\"token_secret\":          tokenSecret,\n\t\t\t\t\t\t\"endpoint\":              tableauURL,\n\t\t\t\t\t\t\"credential_type\":       \"personal_access_token\",\n\t\t\t\t\t\t\"verification_endpoint\": \"https://\" + tableauURL + \"/api/3.26/auth/signin\",\n\t\t\t\t\t\t\"http_status\":           \"200\",\n\t\t\t\t\t\t\"verification_method\":   \"tableau_pat_auth\",\n\t\t\t\t\t\t\"verification_status\":   \"valid\",\n\t\t\t\t\t\t\"auth_token_received\":   \"true\",\n\t\t\t\t\t},\n\t\t\t\t\tAnalysisInfo: map[string]string{\n\t\t\t\t\t\t\"endpoint\":   tableauURL,\n\t\t\t\t\t\t\"pat_secret\": tokenSecret,\n\t\t\t\t\t\t\"token_name\": tokenName,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(500, \"Internal Server Error\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"name = '%s'\\n secret = '%s'\\nserver = '%s'\", tokenName, tokenSecret, tableauURL)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"token_name\":            tokenName,\n\t\t\t\t\t\t\"token_secret\":          tokenSecret,\n\t\t\t\t\t\t\"endpoint\":              tableauURL,\n\t\t\t\t\t\t\"credential_type\":       \"personal_access_token\",\n\t\t\t\t\t\t\"verification_endpoint\": \"https://\" + tableauURL + \"/api/3.26/auth/signin\",\n\t\t\t\t\t\t\"http_status\":           \"500\",\n\t\t\t\t\t\t\"verification_method\":   \"tableau_pat_auth\",\n\t\t\t\t\t\t\"verification_status\":   \"error\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified with invalid URL\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"name = '%s'\\nsecret = '%s'\\nserver = '%s'\", tokenName, tokenSecret, invalidURL)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"token_name\":            tokenName,\n\t\t\t\t\t\t\"token_secret\":          tokenSecret,\n\t\t\t\t\t\t\"endpoint\":              invalidURL,\n\t\t\t\t\t\t\"credential_type\":       \"personal_access_token\",\n\t\t\t\t\t\t\"verification_endpoint\": \"https://\" + invalidURL + \"/api/3.26/auth/signin\",\n\t\t\t\t\t\t\"http_status\":           \"404\", // or whatever status invalid URL returns\n\t\t\t\t\t\t\"verification_method\":   \"tableau_pat_auth\",\n\t\t\t\t\t\t\"verification_status\":   \"error\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified with inactive token\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"name = '%s'\\n secret = '%s'\\nserver = '%s'\", inactiveTokenName, inactiveTokenSecret, tableauURL)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"token_name\":            inactiveTokenName,\n\t\t\t\t\t\t\"token_secret\":          inactiveTokenSecret,\n\t\t\t\t\t\t\"endpoint\":              tableauURL,\n\t\t\t\t\t\t\"credential_type\":       \"personal_access_token\",\n\t\t\t\t\t\t\"verification_endpoint\": \"https://\" + tableauURL + \"/api/3.26/auth/signin\",\n\t\t\t\t\t\t\"http_status\":           \"401\",\n\t\t\t\t\t\t\"verification_method\":   \"tableau_pat_auth\",\n\t\t\t\t\t\t\"verification_status\":   \"invalid\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the tableau secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified - malformed token\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"tableau pat_name = 'TestToken'\\ntableau pat_secret = 'malformed_secret_format'\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil, // Should not find due to invalid secret format\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// Enable found endpoints for tests that need URL detection\n\t\t\tscanner := tt.s\n\t\t\tscanner.UseFoundEndpoints(true)\n\n\t\t\tgot, err := scanner.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Tableau.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Check that we got the expected number of results\n\t\t\tif len(got) != len(tt.want) {\n\t\t\t\tt.Errorf(\"Tableau.FromData() got %d results, want %d\", len(got), len(tt.want))\n\t\t\t}\n\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Errorf(\"Tableau.FromData() verificationError = %v, wantVerificationErr %v\", got[i].VerificationError(), tt.wantVerificationErr)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tignoreOpts := []cmp.Option{\n\t\t\t\tcmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"ExtraData\"),\n\t\t\t\tcmpopts.IgnoreUnexported(detectors.Result{}),\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts...); diff != \"\" {\n\t\t\t\tt.Errorf(\"Tableau.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTableau_FromChunk_MultipleMixedVerificationResults(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\ttokenName := testSecrets.MustGetField(\"TABLEAU_TOKEN_NAME\")\n\ttokenSecret := testSecrets.MustGetField(\"TABLEAU_TOKEN_SECRET\")\n\tinactiveTokenName := testSecrets.MustGetField(\"TABLEAU_INACTIVE_TOKEN_NAME\")\n\tinactiveTokenSecret := testSecrets.MustGetField(\"TABLEAU_INACTIVE_TOKEN_SECRET\")\n\ttableauURL := testSecrets.MustGetField(\"TABLEAU_VALID_POD_NAME\")\n\tinvalidURL := testSecrets.MustGetField(\"TABLEAU_INVALID_POD_NAME\")\n\n\tscanner := Scanner{}\n\tscanner.UseFoundEndpoints(true)\n\n\tdata := []byte(fmt.Sprintf(`\n\t\tname1 = '%s'\n\t\tname2 = '%s'\n\t\tsecret = '%s'\n\t\tsecret2 = '%s'\n\t\tserver1 = '%s'\n\t\tserver2 = '%s'\n\t`, tokenName, inactiveTokenName, tokenSecret, inactiveTokenSecret, tableauURL, invalidURL))\n\n\twant := []detectors.Result{\n\t\t{\n\t\t\tDetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,\n\t\t\tVerified:     true, // tokenName + tokenSecret + valid URL\n\t\t},\n\t\t{\n\t\t\tDetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,\n\t\t\tVerified:     false, // tokenName + tokenSecret + invalid URL\n\t\t},\n\t\t{\n\t\t\tDetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,\n\t\t\tVerified:     false, // tokenName + inactiveTokenSecret + valid URL\n\t\t},\n\t\t{\n\t\t\tDetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,\n\t\t\tVerified:     false, // tokenName + inactiveTokenSecret + invalid URL\n\t\t},\n\t\t{\n\t\t\tDetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,\n\t\t\tVerified:     false, // inactiveTokenName + tokenSecret + valid URL\n\t\t},\n\t\t{\n\t\t\tDetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,\n\t\t\tVerified:     false, // inactiveTokenName + tokenSecret + invalid URL\n\t\t},\n\t\t{\n\t\t\tDetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,\n\t\t\tVerified:     false, // inactiveTokenName + inactiveTokenSecret + valid URL\n\t\t},\n\t\t{\n\t\t\tDetectorType: detectorspb.DetectorType_TableauPersonalAccessToken,\n\t\t\tVerified:     false, // inactiveTokenName + inactiveTokenSecret + invalid URL\n\t\t},\n\t}\n\n\tgot, err := scanner.FromData(context.Background(), true, data)\n\tif err != nil {\n\t\tt.Fatalf(\"Tableau.FromData() unexpected error: %v\", err)\n\t}\n\n\tif len(got) != len(want) {\n\t\tt.Errorf(\"Tableau.FromData() got %d results, want %d\", len(got), len(want))\n\t}\n\tfor i := range got {\n\t\tif len(got[i].Raw) == 0 {\n\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t}\n\t\tif (got[i].VerificationError() != nil) != false {\n\t\t\tt.Errorf(\"Tableau.FromData() verificationError = %v, wantVerificationErr %v\", got[i].VerificationError(), false)\n\t\t}\n\t}\n\tignoreOpts := []cmp.Option{\n\t\tcmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\", \"ExtraData\", \"AnalysisInfo\"),\n\t\tcmpopts.IgnoreUnexported(detectors.Result{}),\n\t}\n\n\tif diff := cmp.Diff(got, want, ignoreOpts...); diff != \"\" {\n\t\tt.Errorf(\"Tableau.FromData() diff: (-got +want)\\n%s\", diff)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tableau/tableau_test.go",
    "content": "package tableau\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPATName     = \"test-token-6\"\n\tvalidPATSecret   = \"YMqNfVWiTSa0QgpoJ9GpCw==:KTamjuKrXF5gVETjIJafBBvP8Ctj5aJ3\"\n\tinvalidPATSecret = \"invalid-secret-format\"\n\t// Tableau Online endpoints for testing\n\tvalidTableauURL    = \"prod-ansouthgest-a.online.tableau.com\"\n\tvalid2ndTableauURL = \"prod.online.tableau.com\"\n\tinvalidTableauURL  = \"prod.tabeau.com\"\n)\n\nfunc TestTableau_Pattern(t *testing.T) {\n\td := Scanner{}\n\td.UseFoundEndpoints(true) // Enable found endpoints for tests\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname        string\n\t\tinput       string\n\t\twant        []string\n\t\tdescription string\n\t}{\n\t\t{\n\t\t\tname:        \"tableau_prefix_with_token_name\",\n\t\t\tinput:       fmt.Sprintf(\"token=%s\\nsecret=%s\\nserver=%s\", validPATName, validPATSecret, validTableauURL),\n\t\t\twant:        []string{fmt.Sprintf(\"%s:%s:%s\", validPATName, validPATSecret, validTableauURL)},\n\t\t\tdescription: \"Tests tableau prefix directly followed by token name\",\n\t\t},\n\t\t{\n\t\t\tname:        \"pat_prefix_with_name\",\n\t\t\tinput:       fmt.Sprintf(\"pat %s\\n%s\\nurl=%s\", validPATName, validPATSecret, validTableauURL),\n\t\t\twant:        []string{fmt.Sprintf(\"%s:%s:%s\", validPATName, validPATSecret, validTableauURL)},\n\t\t\tdescription: \"Tests pat prefix triggering detection\",\n\t\t},\n\t\t{\n\t\t\tname:        \"name_prefix_with_token\",\n\t\t\tinput:       fmt.Sprintf(\"name %s\\n%s\\nurl=%s\", validPATName, validPATSecret, validTableauURL),\n\t\t\twant:        []string{fmt.Sprintf(\"%s:%s:%s\", validPATName, validPATSecret, validTableauURL)},\n\t\t\tdescription: \"Tests name prefix triggering detection\",\n\t\t},\n\t\t{\n\t\t\tname:  \"single_token_multiple_urls\",\n\t\t\tinput: fmt.Sprintf(\"token %s\\n%s\\nserver1=%s\\nserver2=%s\", validPATName, validPATSecret, validTableauURL, valid2ndTableauURL),\n\t\t\twant: []string{\n\t\t\t\tfmt.Sprintf(\"%s:%s:%s\", validPATName, validPATSecret, validTableauURL),\n\t\t\t\tfmt.Sprintf(\"%s:%s:%s\", validPATName, validPATSecret, valid2ndTableauURL),\n\t\t\t},\n\t\t\tdescription: \"Tests single token with multiple URLs\",\n\t\t},\n\t\t{\n\t\t\tname:        \"invalid_secret_format\",\n\t\t\tinput:       fmt.Sprintf(\"tableau %s\\n%s\\nserver=%s\", validPATName, invalidPATSecret, validTableauURL),\n\t\t\twant:        []string{},\n\t\t\tdescription: \"Tests that invalid secret format is not detected\",\n\t\t},\n\t\t{\n\t\t\tname:        \"invalid_tableau_url\",\n\t\t\tinput:       fmt.Sprintf(\"tableau %s\\n%s\\nserver=%s\", validPATName, validPATSecret, invalidTableauURL),\n\t\t\twant:        []string{},\n\t\t\tdescription: \"Tests that invalid Tableau URL is not detected when useFoundEndpoints is true\",\n\t\t},\n\t\t{\n\t\t\tname:        \"missing_token_name\",\n\t\t\tinput:       fmt.Sprintf(\"\\n%s\\nserver=%s\", validPATSecret, validTableauURL),\n\t\t\twant:        []string{},\n\t\t\tdescription: \"Tests that missing token name produces no results\",\n\t\t},\n\t\t{\n\t\t\tname:        \"missing_secret\",\n\t\t\tinput:       fmt.Sprintf(\"tableau %s\\nserver=%s\", validPATName, validTableauURL),\n\t\t\twant:        []string{},\n\t\t\tdescription: \"Tests that missing secret produces no results\",\n\t\t},\n\t\t{\n\t\t\tname:        \"no_tableau_keywords\",\n\t\t\tinput:       \"username=test\\npassword=secret123\\nhost=example.com\",\n\t\t\twant:        []string{},\n\t\t\tdescription: \"Tests that non-Tableau config produces no results\",\n\t\t},\n\t\t{\n\t\t\tname:        \"token_name_with_whitespace\",\n\t\t\tinput:       fmt.Sprintf(\"name   %s\\n%s\\nserver=%s\", validPATName, validPATSecret, validTableauURL),\n\t\t\twant:        []string{fmt.Sprintf(\"%s:%s:%s\", validPATName, validPATSecret, validTableauURL)},\n\t\t\tdescription: \"Tests token name with extra whitespace\",\n\t\t},\n\t\t{\n\t\t\tname:        \"pat_with_single_quotes\",\n\t\t\tinput:       fmt.Sprintf(\"pat '%s'\\n%s\\nserver=%s\", validPATName, validPATSecret, validTableauURL),\n\t\t\twant:        []string{fmt.Sprintf(\"%s:%s:%s\", validPATName, validPATSecret, validTableauURL)},\n\t\t\tdescription: \"Tests token name with single quotes\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(test.want) > 0 {\n\t\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\t\tt.Errorf(\"keywords '%v' not matched in input: %s\", d.Keywords(), test.input)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif len(matchedDetectors) == 0 && len(test.want) == 0 {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"FromData error: %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"expected %d results, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tailscale/tailscale.go",
    "content": "package tailscale\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\btskey-[a-z]+-[0-9A-Za-z_]+-[0-9A-Za-z_]+\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"tskey-\", \"tskey-api-\", \"tskey-oauth-\"}\n}\n\n// FromData will find and optionally verify Tailscaleapi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[0])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Tailscale,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tconst u = \"https://api.tailscale.com/api/v2/secret-scanning/verify\"\n\t\t\tvals := url.Values{\"key\": []string{resMatch}}\n\t\t\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, u, strings.NewReader(vals.Encode()))\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tswitch res.StatusCode {\n\t\t\t\tcase http.StatusNoContent:\n\t\t\t\t\ts1.Verified = true\n\t\t\t\tcase http.StatusUnauthorized:\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\tdefault:\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Tailscale\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Tailscale is a zero-config VPN for building secure networks. Tailscale API keys can be used to authenticate and interact with the Tailscale API.\"\n}\n"
  },
  {
    "path": "pkg/detectors/tailscale/tailscale_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage tailscale\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTailscaleapi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TAILSCALEAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"TAILSCALEAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a tailscaleapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tailscale,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tailscale,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Tailscaleapi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Tailscaleapi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tailscale/tailscale_test.go",
    "content": "package tailscale\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"tskey-rtzgayeq-RLL0xBAIXBhkYRhir0gmxHNoARkj0CqpNTj_xM2Zpm4lDxEIGsYbwO_kzlNSwrQrAOL4yacZGlBfj37e3WRRlmYfKtrgC-xmk0NNQFLQGCTPwcwQT7d6YipCXS1ScCVL8So\"\n\tinvalidPattern = \"tskey-rtzgayeq-RLL0xBAIXBhkYRhir0gmxH?oARkj0CqpNTj_xM2Zpm4lDxEIGsYbwO_kzlNSwrQrAOL4yacZGlBfj37e3WRRlmYfKtrgC-xmk0NNQFLQGCTPwcwQT7d6YipCXS1ScCVL8So\"\n\tkeyword        = \"tailscale\"\n)\n\nfunc TestTailscaleapi_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword tailscale\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tallyfy/tallyfy.go",
    "content": "package tallyfy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"tallyfy\"}) + `\\b([0-9A-Za-z]{36}\\.[0-9A-Za-z]{264}\\.[0-9A-Za-z\\-\\_]{683})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"tallyfy\"}\n}\n\n// FromData will find and optionally verify Tallyfy secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Tallyfy,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.tallyfy.com/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Tallyfy\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/tallyfy/tallyfy_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage tallyfy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTallyfy_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TALLYFY\")\n\tinactiveSecret := testSecrets.MustGetField(\"TALLYFY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a tallyfy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tallyfy,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tallyfy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Tallyfy.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Tallyfy.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tallyfy/tallyfy_test.go",
    "content": "package tallyfy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"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\"\n\tinvalidPattern = \"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\"\n\tkeyword        = \"tallyfy\"\n)\n\nfunc TestTallyfy_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword tallyfy\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tatumio/tatumio.go",
    "content": "package tatumio\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"tatum\"}) + `\\b([0-9a-z-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"tatum\"}\n}\n\n// FromData will find and optionally verify TatumIO secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TatumIO,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api-eu1.tatum.io/v3/ledger/account?pageSize=10&offset=0&accountCode=AC_1011_B\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.tatumio+json; version=3\")\n\t\t\treq.Header.Add(\"x-api-key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TatumIO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Tatum is a blockchain infrastructure platform. Tatum API keys can be used to access and interact with blockchain services provided by Tatum.\"\n}\n"
  },
  {
    "path": "pkg/detectors/tatumio/tatumio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage tatumio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTatumIO_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TATUMIO\")\n\tinactiveSecret := testSecrets.MustGetField(\"TATUMIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a tatumio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TatumIO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TatumIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TatumIO.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TatumIO.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tatumio/tatumio_test.go",
    "content": "package tatumio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"xu4iyzk6fa6ld8nz9ayms4yq3wbh48qmiu8k\"\n\tinvalidPattern = \"xu4i?zk6fa6ld8nz9ayms4yq3wbh48qmiu8k\"\n\tkeyword        = \"tatumio\"\n)\n\nfunc TestTatumIO_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword tatumio\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/taxjar/taxjar.go",
    "content": "package taxjar\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"taxjar\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"taxjar\"}\n}\n\n// FromData will find and optionally verify Taxjar secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Taxjar,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.taxjar.com/v2/categories\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Taxjar\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/taxjar/taxjar_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage taxjar\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTaxjar_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TAXJAR_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"TAXJAR_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a taxjar secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Taxjar,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Taxjar,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Taxjar.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Taxjar.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/taxjar/taxjar_test.go",
    "content": "package taxjar\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"2jnsyu2ebcnaemws9b2rmh6w6e12i8j7\"\n\tinvalidPattern = \"2jnsyu2ebcnaemws?b2rmh6w6e12i8j7\"\n\tkeyword        = \"taxjar\"\n)\n\nfunc TestTaxjar_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword taxjar\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/teamgate/teamgate.go",
    "content": "package teamgate\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\ttokenPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"teamgate\"}) + `\\b([a-z0-9]{40})\\b`)\n\tkeyPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"teamgate\"}) + `\\b([a-zA-Z0-9]{80})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"teamgate\"}\n}\n\n// FromData will find and optionally verify Teamgate secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := tokenPat.FindAllStringSubmatch(dataStr, -1)\n\tkeyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, keyMatch := range keyMatches {\n\n\t\t\tresKeyMatch := strings.TrimSpace(keyMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Teamgate,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.teamgate.com/v4/users\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"X-Auth-Token\", resMatch)\n\t\t\t\treq.Header.Add(\"X-App-Key\", resKeyMatch)\n\n\t\t\t\tres, err := client.Do(req)\n\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Teamgate\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Teamgate is a sales CRM platform. Teamgate tokens and keys can be used to access and manage sales data and CRM functionalities.\"\n}\n"
  },
  {
    "path": "pkg/detectors/teamgate/teamgate_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage teamgate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTeamgate_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TEAMGATE\")\n\tkey := testSecrets.MustGetField(\"TEAMGATE_KEY\")\n\tinactiveSecret := testSecrets.MustGetField(\"TEAMGATE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a teamgate secret %s within teamgate %s\", secret, key)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Teamgate,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Teamgate,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Teamgate.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Teamgate.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/teamgate/teamgate_test.go",
    "content": "package teamgate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validToken   = \"78fohntospcdns4n7zokz7zr134vn7ua7io7ehp1\"\n    invalidToken = \"78fohntospcdns4n7?okz7zr134vn7ua7io7ehp1\"\n    validKey     = \"AdPZhlbr8bIaIUomTizYvauT2HMNUfm6oK4Aft8JICXKvdKbHOEeRVLPycmGLi60QBksu5tPvD8X4ciX\"\n    invalidKey   = \"AdPZhlbr8b?aIUomTizYvauT2HMNUfm6oK4Aft8JICXKvdKbHOEeRVLPycmGLi60QBksu5tPvD8X4ciX\"\n    keyword      = \"teamgate\"\n)\n\nfunc TestTeamgate_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword teamgate\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validToken, keyword, validKey),\n\t\t\twant:  []string{validToken},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidToken, keyword, invalidKey),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/teamworkcrm/teamworkcrm.go",
    "content": "package teamworkcrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"teamwork\", \"teamworkcrm\"}) + `\\b(tkn\\.v1_[0-9A-Za-z]{71}=[ \\r\\n]{1})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"teamwork\", \"teamworkcrm\"}\n}\n\n// FromData will find and optionally verify TeamworkCRM secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TeamworkCRM,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://example.teamwork.com/crm/api/v2/users.json\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TeamworkCRM\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/teamworkcrm/teamworkcrm_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage teamworkcrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTeamworkCRM_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TEAMWORKCRM\")\n\tinactiveSecret := testSecrets.MustGetField(\"TEAMWORKCRM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a teamworkcrm secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TeamworkCRM,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TeamworkCRM,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TeamworkCRM.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TeamworkCRM.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/teamworkcrm/teamworkcrm_test.go",
    "content": "package teamworkcrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"tkn.v1_LggER0OG5HCo5DCy1FAHXVJiNKat8RbSygBGt0a5dbZlMN1hwlgSXx2ZzfFg4Ax98BbIIlH=\"\n\tinvalidPattern = \"tkn.v1_L?gER0OG5HCo5DCy1FAHXVJiNKat8RbSygBGt0a5dbZlMN1hwlgSXx2ZzfFg4Ax98BbIIlH=\"\n\tkeyword        = \"teamworkcrm\"\n)\n\nfunc TestTeamworkCRM_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword teamworkcrm\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s '\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s ' | '%s '\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s '\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/teamworkdesk/teamworkdesk.go",
    "content": "package teamworkdesk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"teamwork\", \"teamworkdesk\"}) + `\\b(tkn\\.v1_[0-9A-Za-z]{71}=[ \\r\\n]{1})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"teamwork\", \"teamworkdesk\"}\n}\n\n// FromData will find and optionally verify TeamworkDesk secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TeamworkDesk,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://example.teamwork.com/desk/api/v2/me.json\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TeamworkDesk\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/teamworkdesk/teamworkdesk_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage teamworkdesk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTeamworkDesk_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TEAMWORKDESK\")\n\tinactiveSecret := testSecrets.MustGetField(\"TEAMWORKDESK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a teamworkdesk secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TeamworkDesk,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TeamworkDesk,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TeamworkDesk.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TeamworkDesk.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/teamworkdesk/teamworkdesk_test.go",
    "content": "package teamworkdesk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"tkn.v1_PQD5xBDqpPQHJjX1CFtPaM1xgf0vyrmVkpUtN0aMpkBjBnG8Ifg3ohgpd4w1gG1Otaf0O1v=\"\n\tinvalidPattern = \"tkn.v1_PQD5xBDq?PQHJjX1CFtPaM1xgf0vyrmVkpUtN0aMpkBjBnG8Ifg3ohgpd4w1gG1Otaf0O1v=\"\n\tkeyword        = \"teamworkdesk\"\n)\n\nfunc TestTeamworkDesk_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword teamworkdesk\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s '\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s ' | '%s '\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s '\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/teamworkspaces/teamworkspaces.go",
    "content": "package teamworkspaces\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"teamwork\", \"teamworkspaces\"}) + `\\b(tkn\\.v1_[0-9A-Za-z]{71}=[ \\r\\n]{1})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"teamwork\", \"teamworkspaces\"}\n}\n\n// FromData will find and optionally verify TeamworkSpaces secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TeamworkSpaces,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://example.teamwork.com/spaces/api/v1/users.json\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TeamworkSpaces\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/teamworkspaces/teamworkspaces_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage teamworkspaces\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTeamworkSpaces_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TEAMWORKSPACES\")\n\tinactiveSecret := testSecrets.MustGetField(\"TEAMWORKSPACES_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a teamworkspaces secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TeamworkSpaces,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TeamworkSpaces,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TeamworkSpaces.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TeamworkSpaces.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/teamworkspaces/teamworkspaces_test.go",
    "content": "package teamworkspaces\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"tkn.v1_O7NGF5FYyhV8NYI7vKc5ulE5pQkRKghGRW1o96i3PixggnAhs0IprhOJK2jMfSF8bCx2qaM=\"\n\tinvalidPattern = \"tkn.v1_O7NGF5FYyhV8NYI7vKc5ulE5pQkRKghGR?1o96i3PixggnAhs0IprhOJK2jMfSF8bCx2qaM=\"\n\tkeyword        = \"teamworkspaces\"\n)\n\nfunc TestTeamworkSpaces_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword teamworkspaces\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s '\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s ' | '%s '\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s '\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/technicalanalysisapi/technicalanalysisapi.go",
    "content": "package technicalanalysisapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"technicalanalysisapi\"}) + `\\b([A-Z0-9]{48})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"technicalanalysisapi\"}\n}\n\n// FromData will find and optionally verify TechnicalAnalysisApi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TechnicalAnalysisApi,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ttimeout := 10 * time.Second\n\t\t\tclient.Timeout = timeout\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://technical-analysis-api.com/api/v1/analysis/BTC?apiKey=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TechnicalAnalysisApi\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"TechnicalAnalysisApi is a service used for technical analysis of financial markets. The API key can be used to access and retrieve market analysis data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/technicalanalysisapi/technicalanalysisapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage technicalanalysisapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTechnicalAnalysisApi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TECHNICALANALYSISAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"TECHNICALANALYSISAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a technicalanalysisapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TechnicalAnalysisApi,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TechnicalAnalysisApi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TechnicalAnalysisApi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TechnicalAnalysisApi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/technicalanalysisapi/technicalanalysisapi_test.go",
    "content": "package technicalanalysisapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"UNVGPP73JF6PMNU362Q6OEDI2AS6E24BMSAFVAPIXW7XI5O8\"\n\tinvalidPattern = \"UNVGPP73JF6PMNU36?Q6OEDI2AS6E24BMSAFVAPIXW7XI5O8\"\n\tkeyword        = \"technicalanalysisapi\"\n)\n\nfunc TestTechnicalAnalysisApi_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword technicalanalysisapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tefter/tefter.go",
    "content": "package tefter\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"tefter\"}) + `\\b([0-9a-zA-Z]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"tefter\"}\n}\n\n// FromData will find and optionally verify Tefter secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Tefter,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://www.tefter.io/api/bookmarks?url=google.com\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"X-User-Token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tvalidResponse := json.Valid(bodyBytes)\n\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && validResponse {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Tefter\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Tefter is a bookmarking service. Tefter API keys can be used to access and manage bookmarks.\"\n}\n"
  },
  {
    "path": "pkg/detectors/tefter/tefter_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage tefter\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTefter_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TEFTER\")\n\tinactiveSecret := testSecrets.MustGetField(\"TEFTER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a tefter secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tefter,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tefter,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Tefter.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Tefter.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tefter/tefter_test.go",
    "content": "package tefter\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"gnKLZ6PNGylpRrrJjiWe\"\n\tinvalidPattern = \"gnKLZ6PNGy?pRrrJjiWe\"\n\tkeyword        = \"tefter\"\n)\n\nfunc TestTefter_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword tefter\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/telegrambottoken/telegrambottoken.go",
    "content": "package telegrambottoken\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\n\t\"net/http\"\n\t\"strings\"\n\n\t//\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// https://core.telegram.org/bots#6-botfather\n\t// thanks https://stackoverflow.com/questions/61868770/tegram-bot-api-token-format\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"telegram\", \"tgram://\"}) + `\\b([0-9]{8,10}:[a-zA-Z0-9_-]{35})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\t// Apprise uses the `tgram://` url scheme.\n\t// https://github.com/caronc/apprise/wiki/Notify_telegram\n\treturn []string{\"telegram\", \"tgram\"}\n}\n\n// FromData will find and optionally verify TelegramBotToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tkey := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TelegramBotToken,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\t// https://core.telegram.org/bots/api#getme\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.telegram.org/bot\"+key+\"/getMe\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\n\t\t\t\t\tapiRes := apiResponse{}\n\t\t\t\t\terr := json.NewDecoder(res.Body).Decode(&apiRes)\n\t\t\t\t\tif err == nil && apiRes.Ok {\n\t\t\t\t\t\ts1.ExtraData = map[string]string{\n\t\t\t\t\t\t\t\"username\": apiRes.Result.Username,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\n// https://core.telegram.org/bots/api#making-requests\ntype apiResponse struct {\n\tOk     bool          `json:\"ok\"`\n\tResult *userResponse `json:\"result\"`\n}\n\n// https://core.telegram.org/bots/api#user\ntype userResponse struct {\n\tIsBot    bool   `json:\"is_bot\"`\n\tUsername string `json:\"username\"`\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TelegramBotToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/telegrambottoken/telegrambottoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage telegrambottoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTelegramBotToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TELEGRAMBOTTOKEN_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"TELEGRAMBOTTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a telegrambottoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TelegramBotToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a telegrambottoken secret %s within\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TelegramBotToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TelegramBotToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TelegramBotToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/telegrambottoken/telegrambottoken_test.go",
    "content": "package telegrambottoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"869511423:BctOM4-oBANUkq653PAPbo4HLs3DCd9NCDe\"\n\tinvalidPattern = \"869511423:BctOM4-oBANU?q653PAPbo4HLs3DCd9NCDe\"\n\tkeyword        = \"telegrambottoken\"\n)\n\nfunc TestTelegramBotToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword telegrambottoken\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/teletype/teletype.go",
    "content": "package teletype\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"teletype\"}) + `\\b([0-9a-zA-Z-]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"teletype\"}\n}\n\n// FromData will find and optionally verify Teletype secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Teletype,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.teletype.app/public/api/v1/messages\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"X-Auth-Token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\tvalidResponse := strings.Contains(bodyString, `\"code\":401`)\n\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\tif validResponse {\n\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Teletype\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Teletype is a messaging service. Teletype API keys can be used to access and send messages through the Teletype API.\"\n}\n"
  },
  {
    "path": "pkg/detectors/teletype/teletype_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage teletype\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTeletype_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TELETYPE\")\n\tinactiveSecret := testSecrets.MustGetField(\"TELETYPE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a teletype secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Teletype,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Teletype,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Teletype.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Teletype.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/teletype/teletype_test.go",
    "content": "package teletype\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"WdEUY8VfTBjpSpHlrhjbsdarYSVf-jXZfwYPCt1ckQLBlJ3GX-p9xs3dBor0ekHT\"\n\tinvalidPattern = \"WdEUY8V?TBjpSpHlrhjbsdarYSVf-jXZfwYPCt1ckQLBlJ3GX-p9xs3dBor0ekHT\"\n\tkeyword        = \"teletype\"\n)\n\nfunc TestTeletype_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword teletype\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/telnyx/telnyx.go",
    "content": "package telnyx\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"telnyx\"}) + `\\b(KEY[0-9A-Za-z_-]{55})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"telnyx\"}\n}\n\n// FromData will find and optionally verify Telnyx secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Telnyx,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.telnyx.com/v2/messaging_profiles\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.telnyx+json; version=3\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Telnyx\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Telnyx is a communications platform offering voice, messaging, and other communication services. Telnyx keys can be used to access and manage these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/telnyx/telnyx_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage telnyx\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTelnyx_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TELNYX\")\n\tinactiveSecret := testSecrets.MustGetField(\"TELNYX_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a telnyx secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Telnyx,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Telnyx,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Telnyx.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Telnyx.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/telnyx/telnyx_test.go",
    "content": "package telnyx\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"KEYAptrcGNPkrkfIifpxSr1PAf5ChNcx-APIMKz9_ImMMXli1w16QD97i5\"\n\tinvalidPattern = \"KEYAptrcG?PkrkfIifpxSr1PAf5ChNcx-APIMKz9_ImMMXli1w16QD97i5\"\n\tkeyword        = \"telnyx\"\n)\n\nfunc TestTelnyx_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword telnyx\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/terraformcloudpersonaltoken/terraformcloudpersonaltoken.go",
    "content": "package terraformcloudpersonaltoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(`\\b([A-Za-z0-9]{14}.atlasv1.[A-Za-z0-9]{67})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\".atlasv1.\"}\n}\n\n// FromData will find and optionally verify TerraformCloudPersonalToken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TerraformCloudPersonalToken,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.terraform.io/api/v2/account/details\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TerraformCloudPersonalToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Terraform Cloud is a service that provides infrastructure automation. Terraform Cloud personal tokens can be used to access and manage infrastructure as code.\"\n}\n"
  },
  {
    "path": "pkg/detectors/terraformcloudpersonaltoken/terraformcloudpersonaltoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage terraformcloudpersonaltoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTerraformCloudPersonalToken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TERRAFORMCLOUDPERSONALTOKEN_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"TERRAFORMCLOUDPERSONALTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a terra secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TerraformCloudPersonalToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\n\t\t{\n\t\t\tname: \"found, unverified in json\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx: context.Background(),\n\t\t\t\tdata: []byte(fmt.Sprintf(`{\n\t\t\t  \"credentials\": {\n\t\t\t    \"app.terraform.io\": {\n\t\t\t      \"token\": \"%s\"\n\t\t\t    }\n\t\t\t  }\n\t\t\t}`, inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TerraformCloudPersonalToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a terra secret %s within but unverified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TerraformCloudPersonalToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TerraformCloudPersonalToken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TerraformCloudPersonalToken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/terraformcloudpersonaltoken/terraformcloudpersonaltoken_test.go",
    "content": "package terraformcloudpersonaltoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"HMu97kXVPMUVOv.atlasv1.RjI0iSluLRxMTVU9x04xUV5iwpgGkHXnLtyVgNg2v7iedttRzvjMNuvisQob0OVaDLH\"\n\tinvalidPattern = \"HMu97kXVPMUVOv.atlasv1.RjI0iSluLRxMTVU9x04xUV?iwpgGkHXnLtyVgNg2v7iedttRzvjMNuvisQob0OVaDLH\"\n\tkeyword        = \"terraformcloudpersonaltoken\"\n)\n\nfunc TestTerraformCloudPersonalToken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword terraformcloudpersonaltoken\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/testingbot/testingbot.go",
    "content": "package testingbot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"testingbot\"}) + `\\b([0-9a-z]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"testingbot\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"testingbot\"}\n}\n\n// FromData will find and optionally verify TestingBot secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueIDMatches, uniqueKeyMatches := make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, match := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueIDMatches[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeyMatches[match[1]] = struct{}{}\n\t}\n\n\tfor id := range uniqueIDMatches {\n\t\tfor key := range uniqueKeyMatches {\n\t\t\tif id == key {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_TestingBot,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, verificationErr := verifyTestingBot(ctx, client, id, key)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, key)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TestingBot\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"TestingBot provides cross-browser testing services. TestingBot credentials can be used to automate tests on various browsers and devices.\"\n}\n\nfunc verifyTestingBot(ctx context.Context, client *http.Client, id, secret string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.testingbot.com/v1/user\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.SetBasicAuth(id, secret)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/testingbot/testingbot_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage testingbot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTestingBot_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TESTINGBOT\")\n\tid := testSecrets.MustGetField(\"TESTINGBOT_USER\")\n\tinactiveSecret := testSecrets.MustGetField(\"TESTINGBOT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a testingbot secret %s within testingbot %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TestingBot,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TestingBot,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TestingBot,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TestingBot,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TestingBot.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TestingBot.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/testingbot/testingbot_test.go",
    "content": "package testingbot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey   = \"tyg635h477e5zf1cbvk94xjwam20mjn1\"\n\tinvalidKey = \"tyg635h477e5zf1c?vk94xjwam20mjn1\"\n\tvalidId    = \"apelbd5muwijaybdgn920qn64xjgnsf1\"\n\tinvalidId  = \"apelbd5muwijaybd?n920qn64xjgnsf1\"\n\tkeyword    = \"testingbot\"\n)\n\nfunc TestTestingBot_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword testingbot\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validId, validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/textmagic/textmagic.go",
    "content": "package textmagic\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"textmagic\"}) + `\\b([0-9A-Za-z]{30})\\b`)\n\tuserPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"textmagic\"}) + `\\b([0-9A-Za-z]{1,25})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"textmagic\"}\n}\n\n// FromData will find and optionally verify Textmagic secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tuserMatches := userPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, userMatch := range userMatches {\n\t\t\tresUser := strings.TrimSpace(userMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Textmagic,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resUser),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tdata := fmt.Sprintf(\"%s:%s\", resUser, resMatch)\n\t\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://rest.textmagic.com/api/v2/user\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Textmagic\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Textmagic is a service for sending and receiving text messages. Textmagic API keys can be used to access and manage text messaging services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/textmagic/textmagic_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage textmagic\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTextmagic_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TEXTMAGIC\")\n\tuser := testSecrets.MustGetField(\"SCANNER_USERNAME\")\n\tinactiveSecret := testSecrets.MustGetField(\"TEXTMAGIC_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a textmagic secret %s within textmagic user %s\", secret, user)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Textmagic,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Textmagic,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Textmagic.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Textmagic.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/textmagic/textmagic_test.go",
    "content": "package textmagic\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey    = \"hnNIvpj0lNNChF5z5sAzsgku4CuL0u\"\n\tinvalidKey  = \"hnNIvpj0lNNChF5?5sAzsgku4CuL0u\"\n\tvalidUser   = \"kKVO9ZYWvpcZS0Zozc\"\n\tinvalidUser = \"?KVO9ZYWvp?ZS0Zozc\"\n\tkeyword     = \"textmagic\"\n)\n\nfunc TestTextmagic_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword textmagic\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s'\\n'%s'\\n\", keyword, validKey, validUser),\n\t\t\twant:  []string{validKey + validUser},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s'\\n'%s'\\n\", keyword, invalidKey, invalidUser),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/theoddsapi/theoddsapi.go",
    "content": "package theoddsapi\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"theoddsapi\", \"the-odds-api\"}) + `\\b([0-9a-f]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"theoddsapi\", \"the-odds-api\"}\n}\n\n// FromData will find and optionally verify TheOddsApi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TheOddsApi,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.the-odds-api.com/v4/sports/?apiKey=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TheOddsApi\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"TheOddsApi provides sports data and odds. TheOddsApi keys can be used to access this data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/theoddsapi/theoddsapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage theoddsapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTheOddsApi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"THEODDSAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"THEODDSAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a theoddsapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TheOddsApi,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TheOddsApi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TheOddsApi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TheOddsApi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/theoddsapi/theoddsapi_test.go",
    "content": "package theoddsapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"237eb668c01429518426aa82b374c9da\"\n\tinvalidPattern = \"237eb66?c01429518426aa82b374c9da\"\n\tkeyword        = \"theoddsapi\"\n)\n\nfunc TestTheOddsApi_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword theoddsapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/thinkific/thinkific.go",
    "content": "package thinkific\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"thinkific\"}) + `\\b([0-9a-f]{32})\\b`)\n\tdomainPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"thinkific\"}) + `\\b([0-9A-Za-z]{4,40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"thinkific\"}\n}\n\n// FromData will find and optionally verify Thinkific secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tdomainMatches := domainPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, domainMatch := range domainMatches {\n\t\t\tresDomainMatch := strings.TrimSpace(domainMatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Thinkific,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tdomainRes := fmt.Sprintf(\"%s-s-school\", resDomainMatch)\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.thinkific.com/api/public/v1/collections\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"X-Auth-API-Key\", resMatch)\n\t\t\t\treq.Header.Add(\"X-Auth-Subdomain\", domainRes)\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tbody := string(bodyBytes)\n\n\t\t\t\t\tif strings.Contains(body, \"API Access is not available\") {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Thinkific\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/thinkific/thinkific_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage thinkific\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestThinkific_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"THINKIFIC\")\n\tinactiveSecret := testSecrets.MustGetField(\"THINKIFIC_INACTIVE\")\n\tdomain := testSecrets.MustGetField(\"THINKIFIC_DOMAIN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a thinkific secret %s within thinkificdom %s\", secret, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Thinkific,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Thinkific,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Thinkific.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Thinkific.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/thinkific/thinkific_test.go",
    "content": "package thinkific\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey      = \"35992abe3b6b861c66d4e1d668ac9367\"\n\tinvalidKey    = \"35992abe3b?b861c66d4e1d668ac9367\"\n\tvalidDomain   = \"orzBcpvjg21Oj2NPayZ3cZOOfaJpS7GwXLt6pP\"\n\tinvalidDomain = \"?rzBcpvjg21Oj2N?ayZ3cZOOfaJpS7GwXLt6pP\"\n\tkeyword       = \"thinkific\"\n)\n\nfunc TestThinkific_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword thinkific\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validDomain),\n\t\t\twant:  []string{validKey, validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidDomain),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/thousandeyes/thousandeyes.go",
    "content": "package thousandeyes\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"thousandeyes\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n\temail  = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"thousandeyes\"}\n}\n\n// FromData will find and optionally verify ThousandEyes secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\temailMatches := email.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\ttokenPatMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, emailMatch := range emailMatches {\n\n\t\t\tuserPatMatch := strings.TrimSpace(emailMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_ThousandEyes,\n\t\t\t\tRaw:          []byte(tokenPatMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.thousandeyes.com/v6/endpoint-data/user-sessions/web.json\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.SetBasicAuth(userPatMatch, tokenPatMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ThousandEyes\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ThousandEyes provides network intelligence and monitoring services. ThousandEyes API keys can be used to access and manage network performance data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/thousandeyes/thousandeyes_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage thousandeyes\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestThousandEyes_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"THOUSANDEYES\")\n\temail := testSecrets.MustGetField(\"THOUSANDEYES_USER\")\n\tinactiveSecret := testSecrets.MustGetField(\"THOUSANDEYES_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a thousandeyes secret %s within thousandeyes %s\", secret, email)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ThousandEyes,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ThousandEyes,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ThousandEyes.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ThousandEyes.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/thousandeyes/thousandeyes_test.go",
    "content": "package thousandeyes\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey     = \"QRv5GRXQjWmQ6esvXuvk3pcw0IMAw5NO\"\n    invalidKey   = \"QRv5GR?QjWmQ6esvXuvk3pcw0IMAw5NO\"\n    validEmail   = \"ejtaK2kkwuVx@1U6Evvih9YeT\"\n    invalidEmail = \"ejtaK2kkwuVx?1U6Evvih9YeT\"\n    keyword      = \"thousandeyes\"\n)\n\nfunc TestThousandEyes_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword thousandeyes\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validEmail),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidEmail),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ticketmaster/ticketmaster.go",
    "content": "package ticketmaster\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"ticketmaster\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"ticketmaster\"}\n}\n\n// FromData will find and optionally verify TicketMaster secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TicketMaster,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://app.ticketmaster.com/discovery/v2/events.json?apikey=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TicketMaster\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"TicketMaster API keys can be used to access and modify event data and other related services provided by TicketMaster.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ticketmaster/ticketmaster_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ticketmaster\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTicketMaster_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TICKETMASTER\")\n\tinactiveSecret := testSecrets.MustGetField(\"TICKETMASTER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ticketmaster secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TicketMaster,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TicketMaster,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TicketMaster.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TicketMaster.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ticketmaster/ticketmaster_test.go",
    "content": "package ticketmaster\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"QSLwTBLaOcyPYh9KW1edgeiyPxqLT1KK\"\n\tinvalidPattern = \"QSLwTBLaOcyPYh9K?1edgeiyPxqLT1KK\"\n\tkeyword        = \"ticketmaster\"\n)\n\nfunc TestTicketMaster_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword ticketmaster\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tickettailor/tickettailor.go",
    "content": "package tickettailor\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"tickettailor\"}) + `\\b(sk_[0-9]{4}_[0-9]{6}_[a-f0-9]{32})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"tickettailor\"}\n}\n\n// FromData will find and optionally verify Tickettailor secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueKeyMatches := make(map[string]struct{})\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeyMatches[match[1]] = struct{}{}\n\t}\n\n\tfor key := range uniqueKeyMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Tickettailor,\n\t\t\tRaw:          []byte(key),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyTicketTailor(ctx, client, key)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Tickettailor\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc verifyTicketTailor(ctx context.Context, client *http.Client, apiKey string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.tickettailor.com/v1/orders\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Accept\", \"application/json\")\n\t// 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\n\treq.SetBasicAuth(apiKey, \"\")\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tickettailor/tickettailor_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage tickettailor\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTickettailor_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TICKETTAILOR\")\n\tinactiveSecret := testSecrets.MustGetField(\"TICKETTAILOR_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a tickettailor secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tickettailor,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tickettailor,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Tickettailor.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Tickettailor.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tickettailor/tickettailor_test.go",
    "content": "package tickettailor\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"sk_6551_225099_d9a4d4b7d506fba4d2cbb2ed803d088b\"\n\tinvalidPattern = \"sk_1234_225099_WW6E3TND0PXT5L?LPeOfVG7c2Y92fWNR\"\n\tkeyword        = \"tickettailor\"\n)\n\nfunc TestTickettailor_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword tickettailor\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tiingo/tiingo.go",
    "content": "package tiingo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"tiingo\"}) + `\\b([0-9a-z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"tiingo\"}\n}\n\n// FromData will find and optionally verify Tiingo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Tiingo,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.tiingo.com/tiingo/fundamentals/definitions\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Token %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Tiingo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/tiingo/tiingo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage tiingo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTiingo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TIINGO\")\n\tinactiveSecret := testSecrets.MustGetField(\"TIINGO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a tiingo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tiingo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tiingo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Tiingo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Tiingo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tiingo/tiingo_test.go",
    "content": "package tiingo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"r2ofhhzv4tb7mv2c7o32fd9vlusg03kc76dygnk2\"\n\tinvalidPattern = \"r2o?hhzv4tb7mv2c7o32fd9vlusg03kc76dygnk2\"\n\tkeyword        = \"tiingo\"\n)\n\nfunc TestTiingo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword tiingo\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/timecamp/timecamp.go",
    "content": "package timecamp\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"timecamp\"}) + `\\b([0-9a-z]{26})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"timecamp\"}\n}\n\n// FromData will find and optionally verify TimeCamp secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TimeCamp,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.timecamp.com/third_party/api/user?format=json\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.timecamp+json; version=3\")\n\t\t\treq.Header.Add(\"Authorization\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TimeCamp\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"TimeCamp is a time tracking software for teams and freelancers. TimeCamp API keys can be used to access and modify time tracking data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/timecamp/timecamp_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage timecamp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTimeCamp_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TIMECAMP\")\n\tinactiveSecret := testSecrets.MustGetField(\"TIMECAMP_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a timecamp secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TimeCamp,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TimeCamp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TimeCamp.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TimeCamp.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/timecamp/timecamp_test.go",
    "content": "package timecamp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"66mtci6qdo8ccczw48j5lpt2uw\"\n\tinvalidPattern = \"66mt?i6qdo8ccczw48j5lpt2uw\"\n\tkeyword        = \"timecamp\"\n)\n\nfunc TestTimeCamp_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword timecamp\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/timezoneapi/timezoneapi.go",
    "content": "package timezoneapi\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"timezoneapi\"}) + `\\b([a-zA-Z]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"timezoneapi\"}\n}\n\n// FromData will find and optionally verify Timezoneapi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Timezoneapi,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://timezoneapi.io/api/ip/?token=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tverifiedBodyResponse, err := common.ResponseContainsSubstring(res.Body, \"date\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && verifiedBodyResponse {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Timezoneapi\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Timezoneapi is a service that provides time zone information. Timezoneapi keys can be used to access and retrieve time zone data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/timezoneapi/timezoneapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage timezoneapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTimezoneapi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TIMEZONEAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"TIMEZONEAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a timezoneapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Timezoneapi,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Timezoneapi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Timezoneapi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Timezoneapi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/timezoneapi/timezoneapi_test.go",
    "content": "package timezoneapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"SSVmFDNCFfOHKXQAEckx\"\n\tinvalidPattern = \"SSVmFDNCFf?HKXQAEckx\"\n\tkeyword        = \"timezoneapi\"\n)\n\nfunc TestTimezoneapi_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword timezoneapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tineswebhook/tineswebhook.go",
    "content": "package tineswebhook\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`(https://[\\w-]+\\.tines\\.com/webhook/[a-z0-9]{32}/[a-z0-9]{32})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"tines.com\"}\n}\n\n// FromData will find and optionally verify TinesWebhook secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TinesWebhook,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(``)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", resMatch, payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TinesWebhook\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Tines is an automation platform. Tines Webhook URLs can be used to trigger and interact with Tines workflows.\"\n}\n"
  },
  {
    "path": "pkg/detectors/tineswebhook/tineswebhook_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage tineswebhook\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTinesWebhook_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TINESWEBHOOK_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"TINESWEBHOOK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a tineswebhook secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TinesWebhook,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TinesWebhook,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TinesWebhook.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TinesWebhook.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tineswebhook/tineswebhook_test.go",
    "content": "package tineswebhook\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"https://efT.tines.com/webhook/4j55coiuyiladq9ydaoo4oan89u56vkm/u37txygi11dou8u2ttwe4rl63ax4lnw9\"\n\tinvalidPattern = \"https://efT.tines.com/webhook/4j55coiuyil?dq9ydaoo4oan89u56vkm/u37txygi11dou8u2ttwe4rl63ax4lnw9\"\n\tkeyword        = \"tineswebhook\"\n)\n\nfunc TestTinesWebhook_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword tineswebhook\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tly/tly.go",
    "content": "package tly\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"tly\"}) + `\\b([0-9A-Za-z]{60})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"tly\"}\n}\n\n// FromData will find and optionally verify TLy secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TLy,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://t.ly/api/v1/link/stats?api_token=\"+resMatch+\"&short_url=https://t.ly/h9YS\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TLy\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"TLy is a URL shortening service. TLy API keys can be used to access and manage shortened URLs.\"\n}\n"
  },
  {
    "path": "pkg/detectors/tly/tly_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage tly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTLy_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TLY\")\n\tinactiveSecret := testSecrets.MustGetField(\"TLY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a tly secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TLy,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TLy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TLy.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TLy.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tly/tly_test.go",
    "content": "package tly\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"VLmhHAQq7kjrhxrt7x07pJjnafVRara2aoqPndSOepXH2MOY3CcloWwG6ZcD\"\n\tinvalidPattern = \"VLmhHAQq7kjrhxrt7x07pJjn?fVRara2aoqPndSOepXH2MOY3CcloWwG6ZcD\"\n\tkeyword        = \"tly\"\n)\n\nfunc TestTLy_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword tly\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tmetric/tmetric.go",
    "content": "package tmetric\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"tmetric\"}) + `\\b([0-9A-Z]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"tmetric\"}\n}\n\n// FromData will find and optionally verify Tmetric secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Tmetric,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.tmetric.com/api/v3/user\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Tmetric\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Tmetric is a time tracking service. Tmetric API keys can be used to access and manage time tracking data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/tmetric/tmetric_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage tmetric\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTmetric_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TMETRIC\")\n\tinactiveSecret := testSecrets.MustGetField(\"TMETRIC_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a tmetric secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tmetric,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tmetric,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Tmetric.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Tmetric.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tmetric/tmetric_test.go",
    "content": "package tmetric\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"379NU836GQGRFS7Q6MBPKY5GX8F16AF2EKSUB3X4KA2RWKZPU3I3S7R7QVJPPMG5\"\n\tinvalidPattern = \"379NU836GQGRFS7Q6MBPKY5GX8F16AF2?KSUB3X4KA2RWKZPU3I3S7R7QVJPPMG5\"\n\tkeyword        = \"tmetric\"\n)\n\nfunc TestTmetric_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword tmetric\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/todoist/todoist.go",
    "content": "package todoist\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"todoist\"}) + `\\b([0-9a-z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"todoist\"}\n}\n\n// FromData will find and optionally verify Todoist secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Todoist,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.todoist.com/rest/v2/projects\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Todoist\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Todoist is a task management application. Todoist API keys can be used to access and manage tasks and projects within a user's account.\"\n}\n"
  },
  {
    "path": "pkg/detectors/todoist/todoist_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage todoist\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTodoist_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TODOIST\")\n\tinactiveSecret := testSecrets.MustGetField(\"TODOIST_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a todoist secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Todoist,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Todoist,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Todoist.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Todoist.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/todoist/todoist_test.go",
    "content": "package todoist\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"qpgv7z8amkp4ln55znaacezm9jy35wcayy6bya2r\"\n\tinvalidPattern = \"qpgv7z8amkp4ln55znaa?ezm9jy35wcayy6bya2r\"\n\tkeyword        = \"todoist\"\n)\n\nfunc TestTodoist_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword todoist\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/toggltrack/toggltrack.go",
    "content": "package toggltrack\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"toggl\"}) + `\\b([0-9Aa-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"toggl\"}\n}\n\n// FromData will find and optionally verify TogglTrack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TogglTrack,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tdata := fmt.Sprintf(\"%s:api_token\", resMatch)\n\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.track.toggl.com/api/v8/me\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TogglTrack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"TogglTrack is a time tracking tool. TogglTrack API keys can be used to access and manage time tracking data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/toggltrack/toggltrack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage toggltrack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTogglTrack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TOGGLTRACK\")\n\tinactiveSecret := testSecrets.MustGetField(\"TOGGLTRACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a toggltrack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TogglTrack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TogglTrack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TogglTrack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TogglTrack.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/toggltrack/toggltrack_test.go",
    "content": "package toggltrack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"5hj12f7pnz8ha0rqxewpqvsuc8hzgy9h\"\n\tinvalidPattern = \"5hj12f7pnz8ha0rq?ewpqvsuc8hzgy9h\"\n\tkeyword        = \"toggltrack\"\n)\n\nfunc TestTogglTrack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword toggltrack\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tokeet/tokeet.go",
    "content": "package tokeet\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = 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`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"tokeet\"}) + `\\b([0-9]{10}.[0-9]{4})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"tokeet\"}\n}\n\n// FromData will find and optionally verify Tokeet secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Tokeet,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://capi.tokeet.com/v1/user?account=%s\", resIdMatch), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\t\treq.Header.Add(\"Authorization\", resMatch)\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Tokeet\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/tokeet/tokeet_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage tokeet\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTokeet_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TOKEET\")\n\tinactiveSecret := testSecrets.MustGetField(\"TOKEET_INACTIVE\")\n\tid := testSecrets.MustGetField(\"TOKEET_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a tokeet secret %s within tokeet %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tokeet,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tokeet,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Tokeet.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Tokeet.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tokeet/tokeet_test.go",
    "content": "package tokeet\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey   = \"d57fe072-befa-4df5-42f1-014f9d253699\"\n    invalidKey = \"d57fe072?befa-4df5-42f1-014f9d253699\"\n    validId    = \"5573995420@7486\"\n    invalidId  = \"5573995?20@7486\"\n    keyword    = \"tokeet\"\n)\n\nfunc TestTokeet_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword tokeet\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tomorrowio/tomorrowio.go",
    "content": "package tomorrowio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"tomorrow\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"tomorrow\"}\n}\n\n// FromData will find and optionally verify TomorrowIO secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TomorrowIO,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.tomorrow.io/v4/alerts?apikey=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} \n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TomorrowIO\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"TomorrowIO is a weather intelligence platform providing weather data and insights. TomorrowIO API keys can be used to access and retrieve weather information.\"\n}\n"
  },
  {
    "path": "pkg/detectors/tomorrowio/tomorrowio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage tomorrowio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTomorrowIO_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TOMORROWIO\")\n\tinactiveSecret := testSecrets.MustGetField(\"TOMORROWIO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a tomorrowio secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TomorrowIO,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TomorrowIO,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TomorrowIO.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TomorrowIO.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tomorrowio/tomorrowio_test.go",
    "content": "package tomorrowio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"Ha1FtSsu5YhtFJixA3j5cIzgooDbVzz1\"\n\tinvalidPattern = \"Ha1FtSsu5YhtFJix?3j5cIzgooDbVzz1\"\n\tkeyword        = \"tomorrowio\"\n)\n\nfunc TestTomorrowIO_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword tomorrowio\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tomtom/tomtom.go",
    "content": "package tomtom\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"tomtom\"}) + `\\b([0-9Aa-zA-Z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"tomtom\"}\n}\n\n// FromData will find and optionally verify Tomtom secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Tomtom,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.tomtom.com/map/1/tile/basic/main/0/0/0.png?view=Unified&key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Tomtom\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"TomTom provides mapping and location technologies. TomTom API keys can be used to access and manipulate mapping data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/tomtom/tomtom_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage tomtom\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTomtom_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TOMTOM\")\n\tinactiveSecret := testSecrets.MustGetField(\"TOMTOM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a tomtom secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tomtom,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tomtom,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Tomtom.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Tomtom.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tomtom/tomtom_test.go",
    "content": "package tomtom\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"M9g89QjhYpjXUjMwHEUNLz1C9t58osdF\"\n\tinvalidPattern = \"M9g89QjhYpjXUjMw?EUNLz1C9t58osdF\"\n\tkeyword        = \"tomtom\"\n)\n\nfunc TestTomtom_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword tomtom\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tradier/tradier.go",
    "content": "package tradier\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"tradier\"}) + `\\b([a-zA-Z0-9]{28})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"tradier\"}\n}\n\n// FromData will find and optionally verify Tradier secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Tradier,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.tradier.com/v1/watchlists\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Tradier\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/tradier/tradier_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage tradier\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTradier_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TRADIER\")\n\tinactiveSecret := testSecrets.MustGetField(\"TRADIER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a tradier secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tradier,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tradier,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Tradier.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Tradier.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tradier/tradier_test.go",
    "content": "package tradier\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"RgH1pJ0XZKv9jh5UXHP1qGDsAxaw\"\n\tinvalidPattern = \"RgH1pJ0XZKv9jh?UXHP1qGDsAxaw\"\n\tkeyword        = \"tradier\"\n)\n\nfunc TestTradier_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword tradier\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/transferwise/transferwise.go",
    "content": "package transferwise\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"transferwise\"}\n}\n\n// FromData will find and optionally verify Transferwise secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Transferwise,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.transferwise.com/v2/profiles\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.transferwise+json; version=3\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Transferwise\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"TransferWise (now known as Wise) is an international money transfer service. TransferWise API keys can be used to access and manage money transfers.\"\n}\n"
  },
  {
    "path": "pkg/detectors/transferwise/transferwise_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage transferwise\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTransferwise_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TRANSFERWISE\")\n\tinactiveSecret := testSecrets.MustGetField(\"TRANSFERWISE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a transferwise secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Transferwise,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Transferwise,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Transferwise.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Transferwise.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/transferwise/transferwise_test.go",
    "content": "package transferwise\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"e99848c6-aeb1-e339-935d-fa7e0d0-4160\"\n\tinvalidPattern = \"e99848c6?aeb1-e339-935d-fa7e0d0-4160\"\n\tkeyword        = \"transferwise\"\n)\n\nfunc TestTransferwise_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword transferwise\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/travelpayouts/travelpayouts.go",
    "content": "package travelpayouts\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"travelpayouts\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"travelpayouts\"}\n}\n\n// FromData will find and optionally verify TravelPayouts secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TravelPayouts,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.travelpayouts.com/v2/prices/latest?currency=usd&limit=5&token=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TravelPayouts\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/travelpayouts/travelpayouts_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage travelpayouts\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTravelPayouts_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TRAVELPAYOUTS\")\n\tinactiveSecret := testSecrets.MustGetField(\"TRAVELPAYOUTS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a travelpayouts secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TravelPayouts,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TravelPayouts,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TravelPayouts.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TravelPayouts.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/travelpayouts/travelpayouts_test.go",
    "content": "package travelpayouts\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"au3cjfqsfb1wtolub0py5cayxliubnxg\"\n\tinvalidPattern = \"au3cjfqsf?1wtolub0py5cayxliubnxg\"\n\tkeyword        = \"travelpayouts\"\n)\n\nfunc TestTravelPayouts_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword travelpayouts\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/travisci/travisci.go",
    "content": "package travisci\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"travis\"}) + `\\b([a-zA-Z0-9A-Z_]{22})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"travis\"}\n}\n\n// FromData will find and optionally verify TravisCI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TravisCI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.travis-ci.com/user\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"token %s\", resMatch))\n\t\t\treq.Header.Add(\"User-Agent\", \"API Explorer\")\n\t\t\treq.Header.Add(\"Travis-API-Version\", \"3\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TravisCI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/travisci/travisci_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage travisci\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTravisCI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TRAVISCI_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"TRAVISCI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a travisci secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TravisCI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a travisci secret %s within but unverified\", inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TravisCI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TravisCI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TravisCI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/travisci/travisci_test.go",
    "content": "package travisci\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"M1BeOsJ75okEpkQUqupx0Q\"\n\tinvalidPattern = \"M1BeOsJ75ok?pkQUqupx0Q\"\n\tkeyword        = \"travisci\"\n)\n\nfunc TestTravisCI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword travisci\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/trelloapikey/trelloapikey.go",
    "content": "package trelloapikey\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient   = common.SaneHttpClient()\n\ttokenPat = regexp.MustCompile(`\\b([a-zA-Z-0-9]{64})\\b`)\n\tkeyPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"trello\"}) + `\\b([a-zA-Z-0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"trello\"}\n}\n\n// FromData will find and optionally verify TrelloApiKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\ttokenMatches := tokenPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor i, match := range matches {\n\t\tif i == 0 {\n\t\t\tresMatch := strings.TrimSpace(match[1])\n\t\t\tfor _, tokenMatch := range tokenMatches {\n\n\t\t\t\ttoken := strings.TrimSpace(tokenMatch[1])\n\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TrelloApiKey,\n\t\t\t\t\tRedacted:     resMatch,\n\t\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.trello.com/1/members/me?key=\"+resMatch+\"&token=\"+token, nil)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tres, err := client.Do(req)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tresults = append(results, s1)\n\n\t\t\t}\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TrelloApiKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Trello is a collaboration tool that organizes your projects into boards. Trello API keys can be used to access and modify data within Trello.\"\n}\n"
  },
  {
    "path": "pkg/detectors/trelloapikey/trelloapikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage trelloapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTrelloApiKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\ttoken := testSecrets.MustGetField(\"TRELLOAPIKEY_TOKEN\")\n\tapiKey := testSecrets.MustGetField(\"TRELLO_API_KEY\")\n\tinactiveApiKey := testSecrets.MustGetField(\"TRELLO_API_KEY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a trello secret within https://api.trello.com/1/members/me?key=%s&token=%s\", apiKey, token)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TrelloApiKey,\n\t\t\t\t\tRedacted:     apiKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TrelloApiKey,\n\t\t\t\t\tRedacted:     \"6abe2cb200b2edy1666avq5325476dfp\",\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TrelloApiKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TrelloApiKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/trelloapikey/trelloapikey_test.go",
    "content": "package trelloapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidToken   = \"Zqpox-FOtGtvV58NCfjox3y2KROmjsbvAUW2jLSL9XfPFs3P98BwmjzMKJA1KVPa\"\n\tinvalidToken = \"Zqpox-FOtGtvV58NCfjox3y2KROmjsbv?UW2jLSL9XfPFs3P98BwmjzMKJA1KVPa\"\n\tvalidKey     = \"NKVmEKtpJvQXU95OfApuFxCOmaRLcRsc\"\n\tinvalidKey   = \"NKVmEKtpJvQXU95O?ApuFxCOmaRLcRsc\"\n\tkeyword      = \"trelloapikey\"\n)\n\nfunc TestTrelloApiKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword trelloapikey\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validToken, keyword, validKey),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidToken, keyword, invalidKey),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tru/tru.go",
    "content": "package tru\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat   = 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`)\n\tsecrePat = regexp.MustCompile(detectors.PrefixRegex([]string{\"tru\"}) + `\\b([0-9a-zA-Z.-_]{26})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"tru\"}\n}\n\n// FromData will find and optionally verify Tru secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secrePat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, secretMatch := range secretMatches {\n\t\t\tresSecret := strings.TrimSpace(secretMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Tru,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + resSecret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tdata := fmt.Sprintf(\"%s:%s\", resMatch, resSecret)\n\t\t\t\tbaseToken := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\t\tpayload := strings.NewReader(\"grant_type=client_credentials\")\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://eu.api.tru.id/oauth2/v1/token\", payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", baseToken))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Tru\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/tru/tru_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage tru\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTru_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tid := testSecrets.MustGetField(\"TRU_ID\")\n\tsecret := testSecrets.MustGetField(\"TRU_SECRET\")\n\tinactiveID := testSecrets.MustGetField(\"TRU_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a tru secret %s within tru id %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tru,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tru,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Tru.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Tru.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tru/tru_test.go",
    "content": "package tru\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey      = \"o23j7m2a-23ux-0hnl-0k57-4ph2omvcanxg\"\n\tinvalidKey    = \"o23j7m2a?23ux-0hnl-0k57-4ph2omvcanxg\"\n\tvalidSecret   = \"PJ]IEWcY9HOIqDGfUqlFGRL1En\"\n\tinvalidSecret = \"PJ]IEWcY9HOIq!GfUqlFGRL1En\"\n\tkeyword       = \"tru\"\n)\n\nfunc TestTru_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword tru\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validSecret),\n\t\t\twant:  []string{validKey + validSecret},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidSecret),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/trufflehogenterprise/trufflehogenterprise.go",
    "content": "package trufflehogenterprise\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat      = regexp.MustCompile(`\\bthog-key-[0-9a-f]{16}\\b`)\n\tsecretPat   = regexp.MustCompile(`\\bthog-secret-[0-9a-f]{32}\\b`)\n\thostnamePat = regexp.MustCompile(`\\b[a-z]+-[a-z]+-[a-z]+\\.[a-z][0-9]\\.[a-z]+\\.trufflehog\\.org\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"thog\"}\n}\n\n// FromData will find and optionally verify TruffleHog Enterprise secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsecretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)\n\thostnameMatches := hostnamePat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, keyMatch := range keyMatches {\n\t\tresKeyMatch := strings.TrimSpace(keyMatch[0])\n\t\tfor _, secretMatch := range secretMatches {\n\t\t\tresSecretMatch := strings.TrimSpace(secretMatch[0])\n\n\t\t\tfor _, hostnameMatch := range hostnameMatches {\n\n\t\t\t\tresHostnameMatch := strings.TrimSpace(hostnameMatch[0])\n\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TrufflehogEnterprise,\n\t\t\t\t\tRaw:          []byte(resKeyMatch),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tendpoint := fmt.Sprintf(\"https://%s/api/v1/sources\", resHostnameMatch)\n\t\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", endpoint, nil)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\treq.Header.Add(\"Accept\", \"application/vnd.trufflehogenterprise+json; version=3\")\n\t\t\t\t\treq.Header.Add(\"X-Thog-Secret\", resSecretMatch)\n\t\t\t\t\treq.Header.Add(\"X-Thog-Key\", resKeyMatch)\n\n\t\t\t\t\tres, err := client.Do(req)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tverifiedBodyResponse, err := common.ResponseContainsSubstring(res.Body, \"data\")\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdefer res.Body.Close()\n\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && verifiedBodyResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TrufflehogEnterprise\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/trufflehogenterprise/trufflehogenterprise_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage trufflehogenterprise\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTrufflehogenterprise_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\thost := testSecrets.MustGetField(\"THOG_HOSTNAME\")\n\tkey := testSecrets.MustGetField(\"THOG_WEB_KEY\")\n\tsecret := testSecrets.MustGetField(\"THOG_WEB_SECRET\")\n\tinactiveSecret := testSecrets.MustGetField(\"THOG_WEB_INACTIVE_SECRET\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a thog secret %s for %s with key %s within\", secret, host, key)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TrufflehogEnterprise,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TrufflehogEnterprise,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Trufflehogenterprise.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Trufflehogenterprise.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/trufflehogenterprise/trufflehogenterprise_test.go",
    "content": "package trufflehogenterprise\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey        = \"thog-key-946c7e0fbee2baff\"\n    invalidKey      = \"thog-key-946?7e0fbee2baff\"\n    validSecret     = \"thog-secret-8fbfc135085421e62d8f1982af17bbf6\"\n    invalidSecret   = \"thog-secret-8fbfc13?085421e62d8f1982af17bbf6\"\n    validHostname   = \"uryfanaextzftqnwkordjwtrascqbihyctwttsntssxgbmgtnghmaiossoiablwqntsnudfdz-grthynfwdntsfdyuvk-tqfecqndhkmebecezcyzptxnsgprzkdcwzwnzdxpm.v6.zfjkrzjmutvvwwqftipvtkdwg.trufflehog.org\"\n    invalidHostname = \"?ryfanaextzftqnwkordjwtrascqbihyctwttsntssxgbmgtnghmaiossoiablwqntsnudfdz-grthynfwdntsfdyuvk-tqfecqndhkmebecezcyzptxnsgprzkdcwzwnzdxpm.v6.zfjkrzjmutvvwwqftipvtkdwg.trufflehog.org\"\n    keyword         = \"trufflehogenterprise\"\n)\n\nfunc TestTrufflehogenterprise_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword trufflehogenterprise\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validSecret, keyword, validHostname),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidSecret, keyword, invalidHostname),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twelvedata/twelvedata.go",
    "content": "package twelvedata\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"twelvedata\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"twelvedata\"}\n}\n\n// FromData will find and optionally verify TwelveData secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TwelveData,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.twelvedata.com/earliest_timestamp?symbol=AAPL&interval=1day&apikey=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\n\t\t\t\t// if client_id and client_secret is valid -> 403 {\"error\":\"invalid_grant\",\"error_description\":\"Invalid authorization code\"}\n\t\t\t\t// if invalid -> 401 {\"error\":\"access_denied\",\"error_description\":\"Unauthorized\"}\n\t\t\t\t// ingenious!\n\n\t\t\t\tif !strings.Contains(body, \"401\") {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TwelveData\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"TwelveData provides financial data APIs for stock, forex, cryptocurrency, and more. TwelveData API keys can be used to access and retrieve this financial data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/twelvedata/twelvedata_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage twelvedata\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTwelveData_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TWELVEDATA_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"TWELVEDATA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twelvedata secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TwelveData,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TwelveData,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TwelveData.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"TwelveData.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twelvedata/twelvedata_test.go",
    "content": "package twelvedata\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"8qevq8517nx58ja9e7jrhe4uom3qbo3v\"\n\tinvalidPattern = \"8q?vq8517nx58ja9e7jrhe4uom3qbo3v\"\n\tkeyword        = \"twelvedata\"\n)\n\nfunc TestTwelveData_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword twelvedata\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twilio/twilio.go",
    "content": "package twilio\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.RetryableHTTPClient()\n\tsidPat        = regexp.MustCompile(`\\bAC[0-9a-f]{32}\\b`)\n\tkeyPat        = regexp.MustCompile(`\\b[0-9a-f]{32}\\b`)\n)\n\ntype serviceResponse struct {\n\tServices []service `json:\"services\"`\n}\n\ntype service struct {\n\tFriendlyName string `json:\"friendly_name\"` // friendly name of a service\n\tSID          string `json:\"sid\"`           // object id of service\n\tAccountSID   string `json:\"account_sid\"`   // account sid\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"sid\", \"twilio\"}\n}\n\n// FromData will find and optionally verify Twilio secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatches := keyPat.FindAllString(dataStr, -1)\n\tsidMatches := sidPat.FindAllString(dataStr, -1)\n\n\tfor _, sid := range sidMatches {\n\t\tfor _, key := range keyMatches {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Twilio,\n\t\t\t\tRaw:          []byte(sid),\n\t\t\t\tRawV2:        []byte(sid + key),\n\t\t\t\tRedacted:     sid,\n\t\t\t}\n\n\t\t\ts1.ExtraData = map[string]string{\n\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/twilio/\",\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\textraData, isVerified, verificationErr := verifyTwilio(ctx, s.getClient(), key, sid)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\n\t\t\t\tfor key, value := range extraData {\n\t\t\t\t\ts1.ExtraData[key] = value\n\t\t\t\t}\n\n\t\t\t\tif s1.Verified {\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\"key\": key, \"sid\": sid}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Twilio\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc verifyTwilio(ctx context.Context, client *http.Client, key, sid string) (map[string]string, bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://verify.twilio.com/v2/Services\", nil)\n\tif err != nil {\n\t\treturn nil, false, nil\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\treq.Header.Add(\"Accept\", \"*/*\")\n\treq.SetBasicAuth(sid, key)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, false, nil\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\textraData := make(map[string]string)\n\t\tvar serviceResponse serviceResponse\n\n\t\tif err := json.NewDecoder(resp.Body).Decode(&serviceResponse); err == nil && len(serviceResponse.Services) > 0 { // no error in parsing and have at least one service\n\t\t\tservice := serviceResponse.Services[0]\n\t\t\textraData[\"friendly_name\"] = service.FriendlyName\n\t\t\textraData[\"account_sid\"] = service.AccountSID\n\t\t}\n\n\t\treturn extraData, true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil, false, nil\n\tdefault:\n\t\treturn nil, false, fmt.Errorf(\"unexpected HTTP response status %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twilio/twilio_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage twilio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTwilio_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TWILLIO_API\")\n\tsecretInactive := testSecrets.MustGetField(\"TWILLIO_API_INACTIVE\")\n\tid := testSecrets.MustGetField(\"TWILLIO_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twillio secret %s within awsId %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twilio,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     id,\n\t\t\t\t\tRawV2:        []byte(id + secret),\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"account_sid\":    \"ACa5b6165773490f33f226d71e7ffacff5\",\n\t\t\t\t\t\t\"friendly_name\":  \"MyServiceName\",\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/twilio/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, not verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twillio secret %s within awsId %s\", secretInactive, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twilio,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     id,\n\t\t\t\t\tRawV2:        []byte(id + secretInactive),\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/twilio/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(100 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twillio secret %s within awsId %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twilio,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     id,\n\t\t\t\t\tRawV2:        []byte(id + secret),\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/twilio/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twillio secret %s within awsId %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twilio,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     id,\n\t\t\t\t\tRawV2:        []byte(id + secret),\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"rotation_guide\": \"https://howtorotate.com/docs/tutorials/twilio/\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Twilio.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"AnalysisInfo\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Twilio.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twilio/twilio_test.go",
    "content": "package twilio\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidSid   = \"AC1b3f0bddbb6887d68d8454e66c749c6a\"\n\tinvalidSid = \"AC1b3f0bddbb?887d68d8454e66c749c6a\"\n\tvalidKey   = \"daf7b3d34b9787f1212316eea62ba186\"\n\tinvalidKey = \"daf7b3d34b9787f1?12316eea62ba186\"\n\tkeyword    = \"twilio\"\n)\n\nfunc TestTwilio_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword twilio\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validSid, keyword, validKey),\n\t\t\twant:  []string{validSid + validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidSid, keyword, invalidKey),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twilioapikey/twilioapikey.go",
    "content": "package twilioapikey\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\tapiKeyPat     = regexp.MustCompile(`\\bSK[a-zA-Z0-9]{32}\\b`)\n\tsecretPat     = regexp.MustCompile(`\\b[0-9a-zA-Z]{32}\\b`)\n)\n\ntype serviceResponse struct {\n\tServices []struct {\n\t\tFriendlyName string `json:\"friendly_name\"` // friendly name of a service\n\t\tSID          string `json:\"sid\"`           // object id of service\n\t\tAccountSID   string `json:\"account_sid\"`   // account sid\n\t} `json:\"services\"`\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn defaultClient\n}\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"twilio\"}\n}\n\n// FromData will find and optionally verify Twilio secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tapiKeyMatches := apiKeyPat.FindAllString(dataStr, -1)\n\tsecretMatches := secretPat.FindAllString(dataStr, -1)\n\n\tfor _, apiKey := range apiKeyMatches {\n\t\tfor _, secret := range secretMatches {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Twilio,\n\t\t\t\tRaw:          []byte(apiKey),\n\t\t\t\tRawV2:        []byte(apiKey + secret),\n\t\t\t\tRedacted:     secret[:5] + \"...\",\n\t\t\t\tExtraData:    make(map[string]string),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\textraData, isVerified, verificationErr := verifyTwilioAPIKey(ctx, s.getClient(), apiKey, secret)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr)\n\n\t\t\t\tfor key, value := range extraData {\n\t\t\t\t\ts1.ExtraData[key] = value\n\t\t\t\t}\n\n\t\t\t\tif s1.Verified {\n\t\t\t\t\ts1.AnalysisInfo = map[string]string{\"key\": apiKey, \"sid\": secret}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TwilioApiKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc verifyTwilioAPIKey(ctx context.Context, client *http.Client, apiKey, secret string) (map[string]string, bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://verify.twilio.com/v2/Services\", nil)\n\tif err != nil {\n\t\treturn nil, false, nil\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\treq.Header.Add(\"Accept\", \"*/*\")\n\treq.SetBasicAuth(apiKey, secret)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, false, nil\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\textraData := make(map[string]string)\n\t\tvar serviceResponse serviceResponse\n\n\t\tif err := json.NewDecoder(resp.Body).Decode(&serviceResponse); err == nil && len(serviceResponse.Services) > 0 { // no error in parsing and have at least one service\n\t\t\tservice := serviceResponse.Services[0]\n\t\t\textraData[\"friendly_name\"] = service.FriendlyName\n\t\t\textraData[\"account_sid\"] = service.AccountSID\n\t\t}\n\n\t\treturn extraData, true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn nil, false, nil\n\tdefault:\n\t\treturn nil, false, fmt.Errorf(\"unexpected HTTP response status %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twilioapikey/twilioapikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage twilioapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTwilioAPIKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\n\tsecret := testSecrets.MustGetField(\"TWILLIO_SECRET\")\n\tsecretInactive := testSecrets.MustGetField(\"TWILLIO_SECRET_INACTIVE\")\n\tapiKey := testSecrets.MustGetField(\"TWILLIO_APIKEY\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twillio secret %s within awsId %s\", secret, apiKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twilio,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     secret,\n\t\t\t\t\tRawV2:        []byte(apiKey + secret),\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"account_sid\":   \"ACa5b6165773490f33f226d71e7ffacff5\",\n\t\t\t\t\t\t\"friendly_name\": \"MyServiceName\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, not verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twillio secret %s within awsId %s\", secretInactive, apiKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twilio,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     secretInactive,\n\t\t\t\t\tRawV2:        []byte(apiKey + secretInactive),\n\t\t\t\t\tExtraData:    map[string]string{},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Twilio.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"AnalysisInfo\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Twilio.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twilioapikey/twilioapikey_test.go",
    "content": "package twilioapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidAPIKey   = \"SKbddcfb88fake8f7c4d9aefake7de1fc5\"\n\tinvalidAPIKey = \"SK_bddcfb88fake8f7c4d9aefake7de1fc\"\n\tvalidSecret   = \"k7JXtY3WBtUqthisisfakeZDqVcjZxYI\"\n\tinvalidSecret = \"k6JXtY3WBtU$thisisfakeZDqVcjZxYI\"\n\tkeyword       = \"twilio\"\n)\n\nfunc TestTwilioAPIKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword twilio\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validAPIKey, keyword, validSecret),\n\t\t\twant:  []string{validAPIKey + validSecret},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidAPIKey, keyword, invalidSecret),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTwilioAPIKey_SecretRedacted(t *testing.T) {\n\td := Scanner{}\n\n\tresults, err := d.FromData(\n\t\tcontext.Background(),\n\t\tfalse,\n\t\t[]byte(fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validAPIKey, keyword, validSecret)))\n\tif err != nil {\n\t\tt.Errorf(\"error = %v\", err)\n\t\treturn\n\t}\n\n\tif len(results) == 0 {\n\t\tt.Errorf(\"did not receive result\")\n\t}\n\n\tif results[0].Redacted != validSecret[:5]+\"...\" {\n\t\tt.Errorf(\"expected redacted secret to be '%s', got '%s'\", validSecret[:5]+\"...\", results[0].Redacted)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twist/twist.go",
    "content": "package twist\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\taccessToken = regexp.MustCompile(detectors.PrefixRegex([]string{\"twist\"}) + `\\b(?:oauth2:)?([0-9a-f]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"twist\"}\n}\n\n// FromData will find and optionally verify Twist secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := accessToken.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tsetAuth := resMatch\n\n\t\tif strings.Contains(match[0], \"oauth\") {\n\t\t\tsetAuth = fmt.Sprintf(\"oauth2:%s\", strings.TrimSpace(match[1]))\n\t\t}\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Twist,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.twist.com/api/v3/users/get_session_user\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", setAuth))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} \n\t\t\t}\n\t\t}\n\t\tresults = append(results, s1)\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Twist\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Twist is a team communication app. Twist access tokens can be used to access and manage Twist accounts and data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/twist/twist_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage twist\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTwist_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TWIST\")\n\tinactiveSecret := testSecrets.MustGetField(\"TWIST_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twist secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twist,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twist,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Twist.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Twist.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twist/twist_test.go",
    "content": "package twist\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tprefix         = \"oauth2:\"\n\tvalidPattern   = \"dba06b506389c94f2bfc010fa83690cf7171dd40\"\n\tinvalidPattern = \"dga06b506389c94f2bfc010fa83690cf7171dd40\"\n\tkeyword        = \"twist\"\n)\n\nfunc TestTwist_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword twist\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s%s'\", keyword, prefix, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s%s' | '%s%s'\", keyword, prefix, validPattern, prefix, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s%s'\", keyword, prefix, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s%s'\", keyword, prefix, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twitch/twitch.go",
    "content": "package twitch\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nconst verifyURL = \"https://id.twitch.tv/oauth2/token\"\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tidPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"twitch\"}) + `\\b([0-9a-z]{30})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"twitch\"}) + `\\b([0-9a-z]{30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"twitch\"}\n}\n\n// FromData will find and optionally verify Twitch secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueIDMatches, uniqueSecretMatches = make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, match := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueIDMatches[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range secretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueSecretMatches[match[1]] = struct{}{}\n\t}\n\n\tfor id := range uniqueIDMatches {\n\t\tfor secret := range uniqueSecretMatches {\n\t\t\t// as both patterns are same, to avoid same strings\n\t\t\tif id == secret {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Twitch,\n\t\t\t\tRaw:          []byte(id),\n\t\t\t\tRawV2:        []byte(id + \":\" + secret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.getClient()\n\t\t\t\tisVerified, verificationErr := verifyTwitch(ctx, client, secret, id)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, id)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\treturn defaultClient\n}\n\nfunc verifyTwitch(ctx context.Context, client *http.Client, resMatch string, resIdMatch string) (bool, error) {\n\tdata := url.Values{}\n\tdata.Set(\"client_id\", resIdMatch)\n\tdata.Set(\"client_secret\", resMatch)\n\tdata.Set(\"grant_type\", \"client_credentials\")\n\tencodedData := data.Encode()\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, verifyURL, strings.NewReader(encodedData))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\treq.Header.Add(\"Content-Length\", strconv.Itoa(len(data.Encode())))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer res.Body.Close()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusBadRequest, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected http response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Twitch\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Twitch is a live streaming service. Twitch client credentials can be used to access and modify data on the Twitch platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/twitch/twitch_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage twitch\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTwitch_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TWITCH\")\n\tid := testSecrets.MustGetField(\"TWITCH_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"TWITCH_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twitch secret %s within twitch %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\t// the detector will try every combination of the secret and id for\n\t\t\t// client_id and client_secret, so we expect 4 results\n\t\t\t// but only 1 of them will be verified\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twitch,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twitch,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twitch secret %s within twitch %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twitch,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twitch,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twitch secret %s within twitch %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twitch,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twitch,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twitch,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twitch,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Twitch.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Errorf(\"Twitch.FromData() verificationError = %v, wantVerificationErr %v\", got[i].VerificationError(), tt.wantVerificationErr)\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Twitch.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twitch/twitch_test.go",
    "content": "package twitch\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey   = \"k0lj2mwl8n3ztmrivlpbc7obk914sj\"\n\tinvalidKey = \"k0lj2mwl8n3ztmr?vlpbc7obk914sj\"\n\tvalidId    = \"kn64qw9jt39bhni04h2k5jc7ebefyn\"\n\tinvalidId  = \"kn64qw9jt39bhni?4h2k5jc7ebefyn\"\n\tkeyword    = \"twitch\"\n)\n\nfunc TestTwitch_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword twitch\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validId + \":\" + validKey, validKey + \":\" + validId},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twitchaccesstoken/twitchaccesstoken.go",
    "content": "package twitchaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"twitch\"}) + `\\b([0-9a-z]{30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"twitch\"}\n}\n\n// FromData will find and optionally verify Twitchaccesstoken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_TwitchAccessToken,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://id.twitch.tv/oauth2/validate\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"OAuth %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\t// If the endpoint returns useful information, we can return it as a map.\n\t\treturn true, nil, nil\n\tcase http.StatusUnauthorized:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TwitchAccessToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Twitch is a live streaming service. Twitch access tokens can be used to access and modify data on the Twitch platform.\"\n}\n"
  },
  {
    "path": "pkg/detectors/twitchaccesstoken/twitchaccesstoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage twitchaccesstoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTwitchaccesstoken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TWITCHACCESSTOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"TWITCHACCESSTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twitchaccesstoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TwitchAccessToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TwitchAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twitchaccesstoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TwitchAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twitchaccesstoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TwitchAccessToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Twitchaccesstoken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Twitchaccesstoken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twitchaccesstoken/twitchaccesstoken_test.go",
    "content": "package twitchaccesstoken\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestTwitchaccesstoken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: \"twitchaccesstoken_token = 'abc123def456ghi789jkl012mno345'\",\n\t\t\twant:  []string{\"abc123def456ghi789jkl012mno345\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"env variable pattern\",\n\t\t\tinput: \"'twitch_access_token': 'abc123def456ghi789jkl012mno345'\",\n\t\t\twant:  []string{\"abc123def456ghi789jkl012mno345\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"get request pattern - keyword out of range\",\n\t\t\tinput: \"curl -X GET 'https://id.twitch.tv/oauth2/validate' -H 'Authorization: OAuth xbc123def456ghi789jkl012mno345'\",\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"finds all matches\",\n\t\t\tinput: \"twitchaccesstoken_token1 = 'z9y8x7w6v5u4t3s2r1q0p9o8n7m6l5' twitchaccesstoken_token2 = '123abc456def789ghi012jkl345mno'\",\n\t\t\twant:  []string{\"z9y8x7w6v5u4t3s2r1q0p9o8n7m6l5\", \"123abc456def789ghi012jkl345mno\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invald pattern\",\n\t\t\tinput: \"twitchaccesstoken_token = '1a2b3c4d'\",\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twitter/v1/twitter_v1.go",
    "content": "package twitter\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\nfunc (s Scanner) Version() int { return 1 }\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"twitter\"}\n}\n\n// FromData will find and optionally verify Twitter secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tkeyMatches[match[1]] = struct{}{}\n\t}\n\n\tfor resMatch := range keyMatches {\n\t\tresMatch = strings.TrimSpace(resMatch)\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Twitter,\n\t\t\tRaw:          []byte(resMatch),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"version\": fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\tisVerified, err := s.VerifyTwitterToken(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(err)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Twitter\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Twitter API keys can be used to interact with the Twitter API to post tweets, read timelines, and access other Twitter functionalities.\"\n}\n\nfunc (s Scanner) VerifyTwitterToken(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.twitter.com/2/tweets/20\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer res.Body.Close()\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code %d\", res.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twitter/v1/twitter_v1_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage twitter\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTwitter_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TWITTER\")\n\tinactiveSecret := testSecrets.MustGetField(\"TWITTER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twitter  secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twitter,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twitter,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Twitter.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Twitter.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twitter/v1/twitter_v1_test.go",
    "content": "package twitter\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"EGGIFBZPKVTIKCEJCSRHQY%5PabdkLS20yFOCKlOiXqbTG%PHGkqp%Ybm%2YgUAvoI-%3ZXVX8KgdsYSJ1DEgoxhGkoY6cNMN8EZidvGnJQ4Z8-4V2fSbdMU\"\n\tinvalidPattern = \"EGGIFBZPKVTIKCEJCSRHQY%5PabdkLS20yFOCKlOiXqbTG%PHGkqp%Ybm%2Y?UAvoI-%3ZXVX8KgdsYSJ1DEgoxhGkoY6cNMN8EZidvGnJQ4Z8-4V2fSbdMU\"\n\tkeyword        = \"twitter\"\n)\n\nfunc TestTwitter_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword twitter\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twitter/v2/twitter_v2.go",
    "content": "package twitter\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\tv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twitter/v1\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tv1.Scanner\n\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\nvar _ detectors.Versioner = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\nfunc (s Scanner) Version() int { return 2 }\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"twitter\"}\n}\n\n// FromData will find and optionally verify Twitter secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tkeyMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range keyMatches {\n\t\tmatch = strings.TrimSpace(match)\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Twitter,\n\t\t\tRaw:          []byte(match),\n\t\t\tExtraData: map[string]string{\n\t\t\t\t\"version\": fmt.Sprintf(\"%d\", s.Version()),\n\t\t\t},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\tisVerified, err := s.VerifyTwitterToken(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(err)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Twitter\n}\n\n// Description returns a description for the result being detected\nfunc (s Scanner) Description() string {\n\treturn \"Twitter API keys can be used to access and interact with Twitter's platform programmatically.\"\n}\n"
  },
  {
    "path": "pkg/detectors/twitter/v2/twitter_v2_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage twitter\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTwitter_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TWITTER_V2_ACTIVE\")\n\tinactiveSecret := testSecrets.MustGetField(\"TWITTER_V2_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twitter  secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twitter,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Twitter,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"version\": \"2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Twitter.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Twitter.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twitter/v2/twitter_v2_test.go",
    "content": "package twitter\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"6pvFJmFvkNjdMImKVWF1NOZ%9n5K8rIH4JOfczZSg5uAsHErlQY6c5TrAygcZYF9WttO1m4LGhg0IUbFgXcaXtAmH4SnIb5cHac0sNcQHVKXZgaKxMqw1Fvt87RcyYKJ1bSP0BGPE%p912wZKbAkTcRfglLMyrcV6aMnuxbj7ctFoulIgFw8Aljzv87cs4\"\n\tinvalidPattern = \"6pvFJmFvkNjdMImKVWF1NOZ%9n5K8rIH4JOf?zZSg5uAsHErlQY6c5TrAygcZYF9WttO1m4LGhg0IUbFgXcaXtAmH4SnIb5cHac0sNcQHVKXZgaKxMqw1Fvt87RcyYKJ1bSP0BGPE%p912wZKbAkTcRfglLMyrcV6aMnuxbj7ctFoulIgFw8Aljzv87cs4\"\n\tkeyword        = \"twitter\"\n)\n\nfunc TestTwitter_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword twitter\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twitterconsumerkey/twitterconsumerkey.go",
    "content": "package twitterconsumerkey\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(detectors.PrefixRegex([]string{\"twitter\", \"consumer\", \"key\"}) + `\\b([a-zA-Z0-9]{25})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"twitter\", \"consumer\", \"secret\"}) + `\\b([a-zA-Z0-9]{50})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"twitter\"}\n}\n\n// FromData will find and optionally verify Twitter secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\t// find for consumer key + secrets\n\tkeyMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tkeyMatches[match[1]] = struct{}{}\n\t}\n\tsecretMatches := make(map[string]struct{})\n\tfor _, match := range secretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tsecretMatches[match[1]] = struct{}{}\n\t}\n\n\tfor key := range keyMatches {\n\t\tfor secret := range secretMatches {\n\t\t\tkey := strings.TrimSpace(key)\n\t\t\tsecret := strings.TrimSpace(secret)\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_TwitterConsumerkey,\n\t\t\t\tRaw:          []byte(key),\n\t\t\t\tRawV2:        []byte(key + secret),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\t\t\t\tbearerToken, err := fetchBearerToken(ctx, client, key, secret)\n\t\t\t\tif err == nil {\n\t\t\t\t\tisVerified, err := verifyBearerToken(ctx, client, bearerToken)\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\ts1.SetVerificationError(err, key)\n\t\t\t\t} else {\n\t\t\t\t\ts1.SetVerificationError(err, key)\n\t\t\t\t}\n\t\t\t}\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_TwitterConsumerkey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc verifyBearerToken(ctx context.Context, client *http.Client, token string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.twitter.com/2/tweets/20\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", token))\n\tres, err := client.Do(req)\n\tif err == nil {\n\t\tdefer res.Body.Close()\n\t\tswitch res.StatusCode {\n\t\tcase http.StatusOK, http.StatusForbidden:\n\t\t\t// 403 indicates lack of permission, but valid token (could be due to twitter free tier)\n\t\t\treturn true, nil\n\t\tcase http.StatusUnauthorized:\n\t\t\treturn false, nil\n\t\tdefault:\n\t\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t}\n\t}\n\n\treturn false, err\n}\n\nfunc fetchBearerToken(ctx context.Context, client *http.Client, key, secret string) (string, error) {\n\tpayload := strings.NewReader(\"grant_type=client_credentials\")\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://api.twitter.com/oauth2/token\", payload)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tsEnc := b64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(\"%s:%s\", key, secret)))\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded;charset=UTF-8\")\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer res.Body.Close()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tvar token tokenResponse\n\t\tif err = json.NewDecoder(res.Body).Decode(&token); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn token.AccessToken, nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\ntype tokenResponse struct {\n\tTokenType   string `json:\"token_type\"`\n\tAccessToken string `json:\"access_token\"`\n}\n"
  },
  {
    "path": "pkg/detectors/twitterconsumerkey/twitterconsumerkey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage twitterconsumerkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTwitterConsumerKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tkey := testSecrets.MustGetField(\"TWITTER_CONSUMER_KEY\")\n\tsecret := testSecrets.MustGetField(\"TWITTER_CONSUMER_SECRET\")\n\n\tinactiveKey := testSecrets.MustGetField(\"TWITTER_CONSUMER_KEY_INACTIVE\")\n\tinactiveSecret := testSecrets.MustGetField(\"TWITTER_CONSUMER_SECRET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twitter key %s and secret %s within\", key, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TwitterConsumerkey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRaw:          []byte(key),\n\t\t\t\t\tRawV2:        []byte(key + secret),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a twitter key %s and secret %s within\", inactiveKey, inactiveSecret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_TwitterConsumerkey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRaw:          []byte(inactiveKey),\n\t\t\t\t\tRawV2:        []byte(inactiveKey + inactiveSecret),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the key & secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TwitterConsumerKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif len(got[i].RawV2) == 0 {\n\t\t\t\t\tt.Fatalf(\"no rawV2 secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"TwitterConsumerKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/twitterconsumerkey/twitterconsumerkey_test.go",
    "content": "package twitterconsumerkey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey      = \"xdIAmQrrNE8knt6fwO6BQ0MMA\"\n\tinvalidKey    = \"xdIAmQrrNE8k?t6fwO6BQ0MMA\"\n\tvalidSecret   = \"6SIZsEihsjUTkGlUWpdPeLvmplgEYaI3FvKJ5o0cMijLlGaokd\"\n\tinvalidSecret = \"6SIZsEihsjUTkG?UWpdPeLvmplgEYaI3FvKJ5o0cMijLlGaokd\"\n\tkeyword       = \"twitterconsumerkey\"\n)\n\nfunc TestTwitterConsumerKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword twitterconsumerkey\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validSecret),\n\t\t\twant:  []string{validKey + validSecret},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidSecret),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tyntec/tyntec.go",
    "content": "package tyntec\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"tyntec\"}) + `\\b([a-zA-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"tyntec\"}\n}\n\n// FromData will find and optionally verify Tyntec secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Tyntec,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.tyntec.com/2fa/v1/application\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"apiKey\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Tyntec\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/tyntec/tyntec_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage tyntec\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTyntec_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TYNTEC\")\n\tinactiveSecret := testSecrets.MustGetField(\"TYNTEC_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a tyntec secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tyntec,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Tyntec,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Tyntec.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Tyntec.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/tyntec/tyntec_test.go",
    "content": "package tyntec\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"3AZo8XEgSEofwdIlSjdlvPfuzTpPIarb\"\n\tinvalidPattern = \"3AZo8XEg?EofwdIlSjdlvPfuzTpPIarb\"\n\tkeyword        = \"tyntec\"\n)\n\nfunc TestTyntec_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword tyntec\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/typeform/v1/typeform.go",
    "content": "package typeform\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"typeform\"}) + `\\b([0-9A-Za-z]{44})\\b`)\n)\n\nfunc (s Scanner) Version() int { return 1 }\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"typeform\"}\n}\n\n// FromData will find and optionally verify Typeform secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Typeform,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyTypeForm(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Typeform\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Typeform is a service for creating forms and surveys. Typeform API keys can be used to access and manage forms and responses.\"\n}\n\nfunc verifyTypeForm(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.typeform.com/me\", nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/typeform/v1/typeform_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage typeform\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTypeform_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TYPEFORM\")\n\tinactiveSecret := testSecrets.MustGetField(\"TYPEFORM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a typeform secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Typeform,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Typeform,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Typeform.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Typeform.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/typeform/v1/typeform_test.go",
    "content": "package typeform\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"tfpsOcxPYaEVD4SNQ5f7HptyomeOpE1SaEuEbkusbBLR\"\n\tinvalidPattern = \"tf?sOcxPYaEVD4SNQ5f7HptyomeOpE1SaEuEbkusbBLR\"\n\tkeyword        = \"typeform\"\n)\n\nfunc TestTypeform_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword typeform\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/typeform/v2/typeform.go",
    "content": "package typeform\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\tkeyPat = regexp.MustCompile(`\\btfp_[a-zA-Z0-9_]{40,59}\\b`)\n)\n\nfunc (s Scanner) getClient() *http.Client {\n\tif s.client != nil {\n\t\treturn s.client\n\t}\n\n\treturn client\n}\n\nfunc (s Scanner) Version() int { return 2 }\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"tfp_\"}\n}\n\ntype TypeFormResponse struct {\n\tUserID   string `json:\"user_id,omitempty\"`\n\tEmail    string `json:\"email,omitempty\"`\n\tAlias    string `json:\"alias,omitempty\"`\n\tLanguage string `json:\"language,omitempty\"`\n}\n\n// FromData will find and optionally verify Typeform secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllString(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Typeform,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tverified, typeformResponse, requestErr := verifyMatch(ctx, s.getClient(), match)\n\t\t\ts1.Verified = verified\n\t\t\ts1.SetVerificationError(requestErr)\n\n\t\t\tif typeformResponse != nil {\n\t\t\t\ts1.ExtraData = map[string]string{\n\t\t\t\t\t\"UserId\":   typeformResponse.UserID,\n\t\t\t\t\t\"Email\":    typeformResponse.Email,\n\t\t\t\t\t\"Alias\":    typeformResponse.Alias,\n\t\t\t\t\t\"Language\": typeformResponse.Language,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tresults = append(results, s1)\n\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, secret string) (bool, *TypeFormResponse, error) {\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.typeform.com/me\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", secret))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tif res.StatusCode == 200 {\n\t\tvar response *TypeFormResponse\n\t\tif err = json.NewDecoder(res.Body).Decode(&response); err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\n\t\treturn true, response, nil\n\t} else if res.StatusCode == 401 || res.StatusCode == 403 {\n\t\treturn false, nil, nil\n\t} else {\n\t\treturn false, nil, fmt.Errorf(\"unexpected status code %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Typeform\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Typeform is a service for creating forms and surveys. Typeform API keys can be used to access and manage forms and responses.\"\n}\n"
  },
  {
    "path": "pkg/detectors/typeform/v2/typeform_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage typeform\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTypeform_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TYPEFORM_V2\")\n\tinactiveSecret := testSecrets.MustGetField(\"TYPEFORM_V2_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a typeform secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Typeform,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"Alias\":    \"TruffleSecurity Detectors\",\n\t\t\t\t\t\t\"Email\":    \"detectors@trufflesec.com\",\n\t\t\t\t\t\t\"Language\": \"en\",\n\t\t\t\t\t\t\"UserId\":   \"01JEX5WZZGGEC89F5E4DKW4144\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Typeform,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"TypeForm.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"TypeForm.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/typeform/v2/typeform_test.go",
    "content": "package typeform\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestTypeformV2_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern (v2)\",\n\t\t\tinput: \"typeform_token = 'tfp_ABCDEfghijKLMNOPqrstuvWXYZ0123456789ABCDEFGH_ijK12340qqqBBB'\",\n\t\t\twant:  []string{\"tfp_ABCDEfghijKLMNOPqrstuvWXYZ0123456789ABCDEFGH_ijK12340qqqBBB\"},\n\t\t},\n\t\t{\n\t\t\tname: \"finds all matches (v2)\",\n\t\t\tinput: `typeform_token1 = 'tfp_ABCDEfghijKLMNOPqrstuvWXYZ0123456789ABCDEFGH_ijK12340qqqBBB'\ntypeform_token2 = 'tfp_943af478d3ff3d4d760020c11af102b79c440513'`,\n\t\t\twant: []string{\"tfp_ABCDEfghijKLMNOPqrstuvWXYZ0123456789ABCDEFGH_ijK12340qqqBBB\", \"tfp_943af478d3ff3d4d760020c11af102b79c440513\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: \"typeform_token = 'tfp_1'\",\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/typetalk/typetalk.go",
    "content": "package typetalk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"typetalk\"}) + `\\b([0-9a-zA-Z]{64})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"typetalk\"}) + `\\b([0-9a-zA-Z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"typetalk\"}\n}\n\n// FromData will find and optionally verify Typetalk secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idMatch := range idMatches {\n\n\t\t\tresIdMatch := strings.TrimSpace(idMatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Typetalk,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, 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)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Typetalk\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Typetalk is a Japanese chat app. API keys can be used to read potentially sensitive chat messages.\"\n}\n"
  },
  {
    "path": "pkg/detectors/typetalk/typetalk_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage typetalk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestTypetalk_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"TYPETALK\")\n\tid := testSecrets.MustGetField(\"TYPETALK_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"TYPETALK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a typetalk secret %s within typetalk %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Typetalk,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Typetalk,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Typetalk.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Typetalk.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/typetalk/typetalk_test.go",
    "content": "package typetalk\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey   = \"MHxch0NrViQ41psiK4ypAwrUxzbKC3YeEuL1CPRedPiVouXflbdW4nKcaJ8Iidua\"\n    invalidKey = \"MHxch0NrViQ41psiK4ypAwrUxzbKC3Ye?uL1CPRedPiVouXflbdW4nKcaJ8Iidua\"\n    validId    = \"ScH0wbXrV7gBPgTZgjpNy2mtcHbelKGh\"\n    invalidId  = \"ScH0wbXrV7?BPgTZgjpNy2mtcHbelKGh\"\n    keyword    = \"typetalk\"\n)\n\nfunc TestTypetalk_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword typetalk\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ubidots/ubidots.go",
    "content": "package ubidots\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(BBFF-[0-9a-zA-Z]{30})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"BBFF-\"}\n}\n\n// FromData will find and optionally verify Ubidots secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Ubidots,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://industrial.api.ubidots.com/api/v1.6/variables/\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"X-Auth-Token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Ubidots\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/ubidots/ubidots_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage ubidots\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestUbidots_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"UBIDOTS_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"UBIDOTS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a ubidots secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ubidots,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Ubidots,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Ubidots.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Ubidots.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/ubidots/ubidots_test.go",
    "content": "package ubidots\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"BBFF-ZgyYQIXWnkYcnga3ShOXhm4DlMJpSE\"\n\tinvalidPattern = \"BBFF-ZgyYQIXW?kYcnga3ShOXhm4DlMJpSE\"\n\tkeyword        = \"ubidots\"\n)\n\nfunc TestUbidots_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword ubidots\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/uclassify/uclassify.go",
    "content": "package uclassify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"uclassify\"}) + `\\b([a-z0-9A-Z]{12})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"uclassify\"}\n}\n\n// FromData will find and optionally verify Uclassify secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Uclassify,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\"texts\":[\"I am so happy today\"]}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.uclassify.com/v1/uClassify/Sentiment/classify\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Token %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Uclassify\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Uclassify is a text classification service. Uclassify API keys can be used to classify text into various categories such as sentiment, topic, etc.\"\n}\n"
  },
  {
    "path": "pkg/detectors/uclassify/uclassify_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage uclassify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestUclassify_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"UCLASSIFY\")\n\tinactiveSecret := testSecrets.MustGetField(\"UCLASSIFY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a uclassify secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Uclassify,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Uclassify,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Uclassify.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Uclassify.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/uclassify/uclassify_test.go",
    "content": "package uclassify\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"xM98lZh20vZs\"\n\tinvalidPattern = \"xM98lZ?20vZs\"\n\tkeyword        = \"uclassify\"\n)\n\nfunc TestUclassify_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword uclassify\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/unifyid/unifyid.go",
    "content": "package unifyid\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"unify\"}) + `\\b([0-9A-Za-z_=-]{44})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"unify\"}\n}\n\n// FromData will find and optionally verify Unifyid secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_UnifyID,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\"token\": \"sample\"}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.unify.id/v1/humandetect/verify\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"X-API-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\t\t\t\tif (res.StatusCode >= 200 && res.StatusCode < 300) || (res.StatusCode == 400 && strings.Contains(body, \"invalid token\")) {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_UnifyID\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"UnifyID provides human detection services. These API keys can be used to verify human presence.\"\n}\n"
  },
  {
    "path": "pkg/detectors/unifyid/unifyid_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage unifyid\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestUnifyid_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"UNIFY\")\n\tinactiveSecret := testSecrets.MustGetField(\"UNIFY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a unifyid secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_UnifyID,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_UnifyID,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Unifyid.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Unifyid.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/unifyid/unifyid_test.go",
    "content": "package unifyid\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"AstGk-tlQZmhHAzRpyMO841oOydxYadtwiixD_IHuVEy\"\n\tinvalidPattern = \"AstGk-tlQZmhHAzRpyMO84?oOydxYadtwiixD_IHuVEy\"\n\tkeyword        = \"unify\"\n)\n\nfunc TestUnifyid_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword unify\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/unplugg/unplugg.go",
    "content": "package unplugg\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"unplu\"}) + `\\b([a-z0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"unplu\"}\n}\n\n// FromData will find and optionally verify Unplugg secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Unplugg,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tpayload := strings.NewReader(`{\"data\":[{\"timestamp\":1466640000,\"value\":0.27589941523037853},{\"timestamp\":1466640900,\"value\":0.4059699097648263}],\"forecast_to\":1458136800,\"callback\":\"https://yourdomain.com/samplecallback\"}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.unplu.gg/forecast\", payload)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"x-access-token\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Unplugg\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Unplugg is a service for forecasting and data analysis. Unplugg API keys can be used to access and manipulate forecast data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/unplugg/unplugg_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage unplugg\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestUnplugg_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"UNPLUGG\")\n\tinactiveSecret := testSecrets.MustGetField(\"UNPLUGG_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a unplugg secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Unplugg,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Unplugg,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Unplugg.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Unplugg.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/unplugg/unplugg_test.go",
    "content": "package unplugg\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"jk0kl5thu7l6ny1zzxjbml6762ipy9wc6zlcnxyakwnisxbj5m2svwaz0wxdbgco\"\n\tinvalidPattern = \"jk0kl5thu7l?ny1zzxjbml6762ipy9wc6zlcnxyakwnisxbj5m2svwaz0wxdbgco\"\n\tkeyword        = \"unplugg\"\n)\n\nfunc TestUnplugg_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword unplugg\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/unsplash/unsplash.go",
    "content": "package unsplash\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"unsplash\"}) + `\\b([0-9A-Za-z_]{43})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"unsplash\"}\n}\n\n// FromData will find and optionally verify Unsplash secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Unsplash,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.unsplash.com/photos/?client_id=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Unsplash\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/unsplash/unsplash_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage unsplash\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestUnsplash_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"UNSPLASH\")\n\tinactiveSecret := testSecrets.MustGetField(\"UNSPLASH_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a unsplash secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Unsplash,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Unsplash,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Unsplash.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Unsplash.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/unsplash/unsplash_test.go",
    "content": "package unsplash\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"xpDbiR6oablsvuTJoG182EhKSesjV96AvReWRcKHZ3p\"\n\tinvalidPattern = \"xpDbiR6oablsvuTJoG182?hKSesjV96AvReWRcKHZ3p\"\n\tkeyword        = \"unsplash\"\n)\n\nfunc TestUnsplash_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword unsplash\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/upcdatabase/upcdatabase.go",
    "content": "package upcdatabase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"upcdatabase\"}) + `\\b([A-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"upcdatabase\"}\n}\n\n// FromData will find and optionally verify UPCDatabase secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_UPCDatabase,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.upcdatabase.org/product/0111222333446?apikey=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `added_time`)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_UPCDatabase\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/upcdatabase/upcdatabase_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage upcdatabase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestUPCDatabase_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"UPCDATABASE\")\n\tinactiveSecret := testSecrets.MustGetField(\"UPCDATABASE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a upcdatabase secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_UPCDatabase,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_UPCDatabase,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"UPCDatabase.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"UPCDatabase.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/upcdatabase/upcdatabase_test.go",
    "content": "package upcdatabase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"CMJC3HG2BC4MSDUVGGO4ZO4VWDRHB4SO\"\n\tinvalidPattern = \"CMJC3H?2BC4MSDUVGGO4ZO4VWDRHB4SO\"\n\tkeyword        = \"upcdatabase\"\n)\n\nfunc TestUPCDatabase_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword upcdatabase\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/uplead/uplead.go",
    "content": "package uplead\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"uplead\"}) + `\\b([a-z0-9-]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"uplead\"}\n}\n\n// FromData will find and optionally verify Uplead secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Uplead,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.uplead.com/v2/credits\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Uplead\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Uplead is a B2B contact data provider. Uplead API keys can be used to access and manage contact data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/uplead/uplead_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage uplead\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestUplead_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"UPLEAD\")\n\tinactiveSecret := testSecrets.MustGetField(\"UPLEAD_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a uplead secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Uplead,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Uplead,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Uplead.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatal(\"no raw secret present\")\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Uplead.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/uplead/uplead_test.go",
    "content": "package uplead\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"b1xnxn56l6jzvq3j9m2o0oio-aga9bzq\"\n\tinvalidPattern = \"b1xnxn56l6jzvq3j?m2o0oio-aga9bzq\"\n\tkeyword        = \"uplead\"\n)\n\nfunc TestUplead_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword uplead\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/uploadcare/uploadcare.go",
    "content": "package uploadcare\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat       = regexp.MustCompile(detectors.PrefixRegex([]string{\"uploadcare\"}) + `\\b([a-z0-9]{20})\\b`)\n\tpublicKeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"uploadcare\"}) + `\\b([a-z0-9]{20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"uploadcare\"}\n}\n\n// FromData will find and optionally verify UploadCare secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tpublicMatches := publicKeyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, publicMatch := range publicMatches {\n\t\t\tpublicKeyMatch := strings.TrimSpace(publicMatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_UploadCare,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.uploadcare.com/files/\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Accept\", \"application/vnd.uploadcare-v0.5+json\")\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Uploadcare.Simple %s:%s\", publicKeyMatch, resMatch))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_UploadCare\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"UploadCare is a service for handling file uploads and transformations. UploadCare keys can be used to manage and access files within the UploadCare system.\"\n}\n"
  },
  {
    "path": "pkg/detectors/uploadcare/uploadcare_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage uploadcare\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestUploadCare_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"UPLOADCARE\")\n\tpublicKey := testSecrets.MustGetField(\"UPLOADCARE_PUBLIC\")\n\tinactiveSecret := testSecrets.MustGetField(\"UPLOADCARE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a uploadcare secret %s within uploadcare %s\", secret, publicKey)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_UploadCare,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_UploadCare,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"UploadCare.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"UploadCare.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/uploadcare/uploadcare_test.go",
    "content": "package uploadcare\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey         = \"ktoicf994nsq4ht02f1k\"\n\tinvalidKey       = \"ktoicf994n?q4ht02f1k\"\n\tvalidPublicKey   = \"vq2fk1qxwn4bker4ukk1\"\n\tinvalidPublicKey = \"vq2fk1qxwn?bker4ukk1\"\n\tkeyword          = \"uploadcare\"\n)\n\nfunc TestUploadCare_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword uploadcare\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validPublicKey),\n\t\t\twant:  []string{validKey, validPublicKey, validPublicKey, validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidPublicKey),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/uptimerobot/uptimerobot.go",
    "content": "package uptimerobot\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"uptimerobot\"}) + `\\b([a-zA-Z0-9]{9}-[a-zA-Z0-9]{24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"uptimerobot\"}\n}\n\n// FromData will find and optionally verify UptimeRobot secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_UptimeRobot,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.uptimerobot.com/v2/getMonitors?api_key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\tvalidResponse := strings.Contains(bodyString, `\"ok\"`)\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\tif validResponse {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_UptimeRobot\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"UptimeRobot provides website monitoring services. The API keys can be used to manage and monitor these services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/uptimerobot/uptimerobot_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage uptimerobot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestUptimeRobot_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"UPTIMEROBOT\")\n\tinactiveSecret := testSecrets.MustGetField(\"UPTIMEROBOT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a uptimerobot secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_UptimeRobot,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_UptimeRobot,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"UptimeRobot.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"UptimeRobot.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/uptimerobot/uptimerobot_test.go",
    "content": "package uptimerobot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"S0CUf2rZs-SyEiUoQb9g2p6qdwcypE8jjY\"\n\tinvalidPattern = \"S0CUf2rZs-SyEiUoQ?9g2p6qdwcypE8jjY\"\n\tkeyword        = \"uptimerobot\"\n)\n\nfunc TestUptimeRobot_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword uptimerobot\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/upwave/upwave.go",
    "content": "package upwave\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"upwave\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"upwave\"}\n}\n\n// FromData will find and optionally verify Upwave secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Upwave,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.upwave.io/workspaces/\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Token %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} \n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Upwave\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Upwave is a project management tool. Upwave API keys can be used to access and manage project data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/upwave/upwave_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage upwave\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestUpwave_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"UPWAVE\")\n\tinactiveSecret := testSecrets.MustGetField(\"UPWAVE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a upwave secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Upwave,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Upwave,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Upwave.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Upwave.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/upwave/upwave_test.go",
    "content": "package upwave\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"mgepiy2lx8tttu5h45efviri2ngz83y0\"\n\tinvalidPattern = \"mgepiy2lx8tttu5h?5efviri2ngz83y0\"\n\tkeyword        = \"upwave\"\n)\n\nfunc TestUpwave_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword upwave\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/uri/uri.go",
    "content": "package uri\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tallowKnownTestSites bool\n\tclient              *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ interface {\n\tdetectors.Detector\n\tdetectors.CustomFalsePositiveChecker\n} = (*Scanner)(nil)\n\nvar (\n\tkeyPat = regexp.MustCompile(`\\bhttps?:\\/\\/[\\w!#$%&()*+,\\-./;<=>?@[\\\\\\]^_{|}~]{0,50}:([\\w!#$%&()*+,\\-./:;<=>?[\\\\\\]^_{|}~]{3,50})@[a-zA-Z0-9.-]+(?:\\.[a-zA-Z]{2,})?(?::\\d{1,5})?[\\w/]+\\b`)\n\n\t// TODO: make local addr opt-out\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\thostNotFoundCache = simple.NewCache[struct{}]()\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"http://\", \"https://\"}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_URI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"This detector identifies URLs with embedded credentials, which can be used to access web resources without explicit user interaction.\"\n}\n\n// FromData will find and optionally verify URI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tlogger := logContext.AddLogger(ctx).Logger().WithName(\"uri\")\n\tdataStr := string(data)\n\n\turiMatches := make(map[string]string)\n\tfor _, matches := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\turiMatch := matches[0]\n\t\tif !s.allowKnownTestSites {\n\t\t\tif strings.Contains(uriMatch, \"httpbin.org\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif strings.Contains(uriMatch, \"httpwatch.com\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tpassword := matches[1]\n\t\t// Skip findings where the password only has \"*\" characters, this is a redacted password\n\t\t// Also include the url encoded \"*\" characters: \"%2A\"\n\t\tif strings.Trim(password, \"*\") == \"\" || strings.Trim(password, \"%2A\") == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\turiMatches[uriMatch] = password\n\t}\n\n\tfor uri, password := range uriMatches {\n\t\tparsedURL, err := url.Parse(uri)\n\t\tif err != nil {\n\t\t\t// URL is invalid.\n\t\t\tcontinue\n\t\t}\n\t\t// URL does not contain a password.\n\t\tif _, ok := parsedURL.User.Password(); !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Safe, I think? (https://github.com/golang/go/issues/38351)\n\t\trawUrl := *parsedURL\n\t\trawUrlWithPath := rawUrl.String()\n\t\t// Removing the path causes possible deduplication issues if some paths have basic auth and some do not.\n\t\trawUrl.Path = \"\"\n\t\trawUrlWithoutPath := rawUrl.String()\n\n\t\tr := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_URI,\n\t\t\tRaw:          []byte(rawUrlWithoutPath),\n\t\t\tRawV2:        []byte(rawUrlWithPath),\n\t\t\tRedacted:     detectors.RedactURL(*parsedURL),\n\t\t}\n\n\t\tif verify {\n\t\t\thostname := parsedURL.Hostname()\n\t\t\tif hostNotFoundCache.Exists(hostname) {\n\t\t\t\tlogger.V(3).Info(\"Skipping uri: no such host\", \"host\", hostname)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif s.client == nil {\n\t\t\t\ts.client = defaultClient\n\t\t\t}\n\t\t\tisVerified, vErr := verifyURL(ctx, s.client, parsedURL)\n\t\t\tr.Verified = isVerified\n\t\t\tif vErr != nil {\n\t\t\t\tvar dnsErr *net.DNSError\n\t\t\t\tif errors.As(vErr, &dnsErr) && dnsErr.IsNotFound {\n\t\t\t\t\thostNotFoundCache.Set(hostname, struct{}{})\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(vErr, password)\n\t\t\t}\n\t\t}\n\n\t\tif !r.Verified {\n\t\t\t// Skip unverified findings where the password starts with a `$` - it's almost certainly a variable.\n\t\t\tif strings.HasPrefix(password, \"$\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, r)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) IsFalsePositive(_ detectors.Result) (bool, string) {\n\treturn false, \"\"\n}\n\nfunc verifyURL(ctx context.Context, client *http.Client, u *url.URL) (bool, error) {\n\t// defuse most SSRF payloads\n\tu.Path = strings.TrimSuffix(u.Path, \"/\")\n\tu.RawQuery = \"\"\n\tu.Fragment = \"\"\n\n\tcredentialedURL := u.String()\n\n\tu.User = nil\n\tnonCredentialedURL := u.String()\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, credentialedURL, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tcredentialedRes, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, credentialedRes.Body)\n\t\t_ = credentialedRes.Body.Close()\n\t}()\n\n\t// If the credentialed URL returns a non 2XX code, we can assume it's a false positive.\n\tif credentialedRes.StatusCode < 200 || credentialedRes.StatusCode > 299 {\n\t\treturn false, nil\n\t}\n\n\ttime.Sleep(time.Millisecond * 10)\n\n\treq, err = http.NewRequestWithContext(ctx, http.MethodGet, nonCredentialedURL, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tnonCredentialedRes, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, nonCredentialedRes.Body)\n\t\t_ = nonCredentialedRes.Body.Close()\n\t}()\n\n\t// If the non-credentialed URL returns a non 400-428 code and basic auth header, we can assume it's verified now.\n\tif nonCredentialedRes.StatusCode >= 400 && nonCredentialedRes.StatusCode < 429 {\n\t\tif nonCredentialedRes.Header.Get(\"WWW-Authenticate\") != \"\" {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\n\treturn false, nil\n}\n"
  },
  {
    "path": "pkg/detectors/uri/uri_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage uri\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestURI_FromChunk(t *testing.T) {\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, unverified, wrong username\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a uri secret %s within\", \"https://user:pass@httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx\")),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_URI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"https://user:********@httpwatch.com\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a uri secret %s within\", \"https://httpwatch:pass@www.httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx\")),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_URI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     \"https://httpwatch:********@www.httpwatch.com\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified, defused\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a uri secret %s within\", \"https://httpwatch:pass@www.httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx?foo=bar\")),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_URI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     \"https://httpwatch:********@www.httpwatch.com\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a uri secret %s within\", \"https://httpwatch:pass@www.httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx\")),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: func() []detectors.Result {\n\t\t\t\tr := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_URI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     \"https://httpwatch:********@www.httpwatch.com\",\n\t\t\t\t}\n\t\t\t\tr.SetVerificationError(fmt.Errorf(\"context deadline exceeded\"))\n\t\t\t\treturn []detectors.Result{r}\n\t\t\t}(),\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"bad scheme\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"file://user:pass@foo.com:123/wh/at/ever\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"nothing found, password was redacted two different ways\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\")),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    []detectors.Result{},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttt.s.allowKnownTestSites = true\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"URI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// if os.Getenv(\"FORCE_PASS_DIFF\") == \"true\" {\n\t\t\t// \treturn\n\t\t\t// }\n\t\t\tfor i := range got {\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Errorf(\"URI.FromData() error = %v, wantVerificationErr %v\", got[i].VerificationError(), tt.want[i])\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t\tgot[i].RawV2 = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"URI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/uri/uri_test.go",
    "content": "package uri\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"https://kaNydBSAodo87dsm9asuiSAFtsd7.com:1234@qYY3SylY7fHP\"\n\tvalidPattern2  = `<p><a href=\"http://username:password@127.0.0.1\">http://username:password@127.0.0.1</a></p>`\n\tinvalidPattern = \"https://kaNydBSAodo87dsm9asuiSAFtsd7.com.1234@qYY3SylY7fHP\"\n\tkeyword        = \"uri\"\n)\n\nfunc TestURI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword uri\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - do not process duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern2),\n\t\t\twant:  []string{\"http://username:password@127.0.0.1\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/urlscan/urlscan.go",
    "content": "package urlscan\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"urlscan\"}) + `\\b([a-z0-9-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"urlscan\"}\n}\n\n// FromData will find and optionally verify Urlscan secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Urlscan,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://urlscan.io/user/quotas\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"API-Key\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Urlscan\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Urlscan is a service for scanning and analyzing websites. Urlscan API keys can be used to interact with the Urlscan service programmatically.\"\n}\n"
  },
  {
    "path": "pkg/detectors/urlscan/urlscan_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage urlscan\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestUrlscan_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"URLSCAN\")\n\tinactiveSecret := testSecrets.MustGetField(\"URLSCAN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a urlscan secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Urlscan,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Urlscan,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Urlscan.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Urlscan.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/urlscan/urlscan_test.go",
    "content": "package urlscan\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"0l8zbe7ucnhdermmblvndjb2p22abcs7y7j8\"\n\tinvalidPattern = \"0l8zbe7ucnhdermmbl?ndjb2p22abcs7y7j8\"\n\tkeyword        = \"urlscan\"\n)\n\nfunc TestUrlscan_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword urlscan\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/user/user.go",
    "content": "package user\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"user\"}) + `\\b([a-zA-Z0-9-._+=]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"user\"}\n}\n\n// FromData will find and optionally verify User secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_User,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://secretscanner.user.com/api/public/users/\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Token %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_User\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"User credentials can be used to authenticate and authorize actions within the User service, potentially allowing access to sensitive data and operations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/user/user_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage user\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestUser_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"USER\")\n\tinactiveSecret := testSecrets.MustGetField(\"USER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a user secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_User,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_User,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"User.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"User.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/user/user_test.go",
    "content": "package user\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"OmFxWjhZvCpOeMsgTJdZMas+dlUpr=fa7+.QOKKYvi7RKWyBeHtaLa7_rzMhLrRd\"\n\tinvalidPattern = \"OmFxWjhZvCpOeMsgTJdZMas+dlUpr=fa?+.QOKKYvi7RKWyBeHtaLa7_rzMhLrRd\"\n\tkeyword        = \"user\"\n)\n\nfunc TestUser_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword user\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/userflow/userflow.go",
    "content": "package userflow\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"userflow\"}) + `\\b([0-9a-z_]{29})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"userflow\"}\n}\n\n// FromData will find and optionally verify Userflow secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Userflow,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.userflow.com/users\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.userflow+json; version=3\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Userflow\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Userflow is a service for creating user onboarding experiences. Userflow API keys can be used to access and modify user onboarding data and workflows.\"\n}\n"
  },
  {
    "path": "pkg/detectors/userflow/userflow_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage userflow\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestUserflow_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"USERFLOW\")\n\tinactiveSecret := testSecrets.MustGetField(\"USERFLOW_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a userflow secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Userflow,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Userflow,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Userflow.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Userflow.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/userflow/userflow_test.go",
    "content": "package userflow\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"503ulfqkdfay0penkz0zkq1glayt6\"\n\tinvalidPattern = \"503ulfqkdfay0p?nkz0zkq1glayt6\"\n\tkeyword        = \"userflow\"\n)\n\nfunc TestUserflow_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword userflow\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/userstack/userstack.go",
    "content": "package userstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"userstack\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"userstack\"}\n}\n\n// FromData will find and optionally verify UserStack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_UserStack,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.userstack.com/detect?access_key=%s&ua=Mozilla/5.0\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalid := strings.Contains(bodyString, `is_mobile_device`) || strings.Contains(bodyString, `\"info\":\"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption.\"`)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif valid {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_UserStack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/userstack/userstack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage userstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestUserStack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"USERSTACK\")\n\tinactiveSecret := testSecrets.MustGetField(\"USERSTACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a userstack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_UserStack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_UserStack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"UserStack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"UserStack.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/userstack/userstack_test.go",
    "content": "package userstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"fwtfmlat0nmosek0lgvl9o8y0o1xvazt\"\n\tinvalidPattern = \"fwtfm?at0nmosek0lgvl9o8y0o1xvazt\"\n\tkeyword        = \"userstack\"\n)\n\nfunc TestUserStack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword userstack\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/vagrantcloudpersonaltoken/vagrantcloudpersonaltoken.go",
    "content": "package vagrantcloudpersonaltoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"vagrant\"}) + `\\b([A-Za-z0-9]{14}.atlasv1.[A-Za-z0-9]{67})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"vagrant\"}\n}\n\n// FromData will find and optionally verify Vagrantcloudpersonaltoken secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_VagrantCloudPersonalToken,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.vagrantup.com/api/v2/authenticate\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_VagrantCloudPersonalToken\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/vagrantcloudpersonaltoken/vagrantcloudpersonaltoken_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage vagrantcloudpersonaltoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestVagrantcloudpersonaltoken_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VAGRANTCLOUDPERSONALTOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"VAGRANTCLOUDPERSONALTOKEN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a vagrantcloudpersonaltoken secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_VagrantCloudPersonalToken,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_VagrantCloudPersonalToken,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Vagrantcloudpersonaltoken.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Vagrantcloudpersonaltoken.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/vagrantcloudpersonaltoken/vagrantcloudpersonaltoken_test.go",
    "content": "package vagrantcloudpersonaltoken\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"3xPXvIQhdxEXQGwatlasv1x1zK093TwRXADtoR720b1kCixmaWWgo8Ivyir5FsMFVUoCQwWYtGXxLChctCi8CRoSk2\"\n\tinvalidPattern = \"3xPXvIQhdxEXQGwatlasv1x1zK093TwRXADtoR720b1kC?xmaWWgo8Ivyir5FsMFVUoCQwWYtGXxLChctCi8CRoSk2\"\n\tkeyword        = \"vagrantcloudpersonaltoken\"\n)\n\nfunc TestVagrantcloudpersonaltoken_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword vagrantcloudpersonaltoken\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/vatlayer/vatlayer.go",
    "content": "package vatlayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"vatlayer\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"vatlayer\"}\n}\n\n// FromData will find and optionally verify VatLayer secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_VatLayer,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://www.apilayer.net/api/validate?access_key=%s&vat_number=LU26375245&format=1\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `vat_number`) || strings.Contains(bodyString, `\"info\":\"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption.\"`)\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_VatLayer\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/vatlayer/vatlayer_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage vatlayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestVatLayer_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VATLAYER\")\n\tinactiveSecret := testSecrets.MustGetField(\"VATLAYER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a vatlayer secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_VatLayer,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_VatLayer,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"VatLayer.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"VatLayer.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/vatlayer/vatlayer_test.go",
    "content": "package vatlayer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"uj05sglnibecnutz4ky665kpjcqgyara\"\n\tinvalidPattern = \"uj05sglnibecnutz?ky665kpjcqgyara\"\n\tkeyword        = \"vatlayer\"\n)\n\nfunc TestVatLayer_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword vatlayer\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/vbout/vbout.go",
    "content": "package vbout\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"vbout\"}) + `\\b([0-9]{25})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"vbout\"}\n}\n\n// FromData will find and optionally verify Vbout secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Vbout,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.vbout.com/1/app/me.json?key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} \n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Vbout\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Vbout is a marketing automation platform. Vbout API keys can be used to access and manage marketing data and campaigns.\"\n}\n"
  },
  {
    "path": "pkg/detectors/vbout/vbout_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage vbout\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestVbout_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VBOUT\")\n\tinactiveSecret := testSecrets.MustGetField(\"VBOUT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a vbout secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Vbout,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Vbout,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Vbout.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Vbout.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/vbout/vbout_test.go",
    "content": "package vbout\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"9734741337193905126710251\"\n\tinvalidPattern = \"97?4741337193905126710251\"\n\tkeyword        = \"vbout\"\n)\n\nfunc TestVbout_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword vbout\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/vercel/vercel.go",
    "content": "package vercel\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"vercel\"}) + `\\b([a-zA-Z0-9]{24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"vercel\"}\n}\n\n// FromData will find and optionally verify Vercel secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Vercel,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.vercel.com/www/user\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Vercel\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/vercel/vercel_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage vercel\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestVercel_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VERCEL_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"VERCEL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a vercel secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Vercel,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Vercel,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Vercel.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Vercel.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/vercel/vercel_test.go",
    "content": "package vercel\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"gGP4GJRR3TOpU7wzTY9UiSTQ\"\n\tinvalidPattern = \"gGP4GJRR3TOp?7wzTY9UiSTQ\"\n\tkeyword        = \"vercel\"\n)\n\nfunc TestVercel_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword vercel\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/verifier/verifier.go",
    "content": "package verifier\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"verifier\"}) + `\\b([a-z0-9]{96})\\b`)\n\temailPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"verifier\"}) + common.EmailPattern)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"verifier\"}\n}\n\n// FromData will find and optionally verify Verifier secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tuniqueEmailMatches := make(map[string]struct{})\n\tfor _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor emailMatch := range uniqueEmailMatches {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Verifier,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://verifier.meetchopra.com/verify/%s?token=%s\", emailMatch, resMatch), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Verifier\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Verifier is a service used to verify the authenticity of a credential. The tokens can be used to validate user identities.\"\n}\n"
  },
  {
    "path": "pkg/detectors/verifier/verifier_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage verifier\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestVerifier_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VERIFIER\")\n\tuser := testSecrets.MustGetField(\"ACCOUNT_USER\")\n\tinactiveSecret := testSecrets.MustGetField(\"VERIFIER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a verifier secret %s within verifier %s\", secret, user)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Verifier,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Verifier,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Verifier.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Verifier.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/verifier/verifier_test.go",
    "content": "package verifier\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\tverifier_key = abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890 \n\t\tverifier_email = verifiertest@example.com \n\t`\n\tinvalidPattern = \"abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890\"\n)\n\nfunc TestVerifier_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant:  []string{\"abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"verifier: %s\", invalidPattern),\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 && test.want != nil {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"expected %d results, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/verimail/verimail.go",
    "content": "package verimail\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"verimail\"}) + `\\b([A-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"verimail\"}\n}\n\n// FromData will find and optionally verify Verimail secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Verimail,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.verimail.io/v3/verify?key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Verimail\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/verimail/verimail_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage verimail\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestVerimail_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VERIMAIL_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"VERIMAIL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a verimail secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Verimail,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Verimail,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Verimail.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Verimail.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/verimail/verimail_test.go",
    "content": "package verimail\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"G0PW4TG34CP100EJD73BGE79Y2WK1CTJ\"\n\tinvalidPattern = \"G0PW4TG34CP100EJ?73BGE79Y2WK1CTJ\"\n\tkeyword        = \"verimail\"\n)\n\nfunc TestVerimail_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword verimail\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/veriphone/veriphone.go",
    "content": "package veriphone\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"veriphone\"}) + `\\b([0-9A-Z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"veriphone\"}\n}\n\n// FromData will find and optionally verify Veriphone secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Veriphone,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.veriphone.io/v2/verify?phone=%252B49-15123577723&key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Veriphone\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Veriphone is a service that provides phone number validation. Veriphone API keys can be used to access the phone validation service.\"\n}\n"
  },
  {
    "path": "pkg/detectors/veriphone/veriphone_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage veriphone\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestVeriphone_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VERIPHONE\")\n\tinactiveSecret := testSecrets.MustGetField(\"VERIPHONE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a veriphone secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Veriphone,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Veriphone,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Veriphone.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Veriphone.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/veriphone/veriphone_test.go",
    "content": "package veriphone\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"T7OWX9ATDTVE1ITA1HON9MAASLBDWNZA\"\n\tinvalidPattern = \"T7OWX9ATDTVE?ITA1HON9MAASLBDWNZA\"\n\tkeyword        = \"veriphone\"\n)\n\nfunc TestVeriphone_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword veriphone\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/versioneye/versioneye.go",
    "content": "package versioneye\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"versioneye\"}) + `\\b([a-zA-Z0-9-]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"versioneye\"}\n}\n\n// FromData will find and optionally verify VersionEye secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_VersionEye,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.versioneye.com/api/v1/scans\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"apiKey\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_VersionEye\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/versioneye/versioneye_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage versioneye\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestVersionEye_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VERSIONEYE\")\n\tinactiveSecret := testSecrets.MustGetField(\"VERSIONEYE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a versioneye secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_VersionEye,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_VersionEye,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"VersionEye.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"VersionEye.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/versioneye/versioneye_test.go",
    "content": "package versioneye\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"pSbrVZlhasYd50mEJEc2n7lm3UTut3ZOqBVk-uZe\"\n\tinvalidPattern = \"pSbrVZlhasYd50mEJEc2?7lm3UTut3ZOqBVk-uZe\"\n\tkeyword        = \"versioneye\"\n)\n\nfunc TestVersionEye_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword versioneye\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/viewneo/viewneo.go",
    "content": "package viewneo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"viewneo\"}\n}\n\nconst maxKeySize = 1500\n\n// MaxSecretSize returns the maximum size of a secret that this detector can find.\nfunc (s Scanner) MaxSecretSize() int64 { return maxKeySize }\n\n// FromData will find and optionally verify Viewneo secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Viewneo,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://cloud.viewneo.com/api/v1.0/playlist\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Viewneo\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Viewneo is a digital signage platform. Viewneo API keys can be used to access and manage digital signage content and settings.\"\n}\n"
  },
  {
    "path": "pkg/detectors/viewneo/viewneo_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage viewneo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestViewneo_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VIEWNEO\")\n\tinactiveSecret := testSecrets.MustGetField(\"VIEWNEO_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a viewneo secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Viewneo,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Viewneo,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Viewneo.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Viewneo.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/viewneo/viewneo_test.go",
    "content": "package viewneo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"cZZ32f1UDFCqsSP6uUlFyPLrXnGhgunvxc3jRsTO1mygukFtXxmk3sV0Q4PiGW8TstPPagjg8o3N0Jp25RKDJUOssch6rbSBVPc3R5vsRSa7y00CKKuu6T6N.2oV6hZlvOR7Jm3L6PzRLMbPRburA2FUfRlRLktcCbt2nBX1iyAfdMv8JvCjAhUJHT52PhiAT3ca7FNd5q5ZXAkn87LnuQhc5UHyuwD8gcWstOghUHZ20tcz7SjVuKyWZFgODlW2WczXqHKxaNhWYz4.839QXG7zlPYdYNhfbvQZe1zHr6bbbjIQYUs2q5whTtUWm8tCMWaOtm_DEZ_xKO5RUrajoClRedRiGK0fFAMshDSyAROOa7NXcE4WM_AuURDSif51QmcWY5HRITdd7y639Zc2Sz1kkUz-Ks_Aqe7xy0VUOlA8m4w2A7IfQ2iDtUeAlWIz1vsOihDxWeNqvTj5D5JOQcyRCiCfTfDWptrJCkKsMWMcDNRE773ypzQVn3r6VSVC63UqdT5Et5jpS5C1wFMuJDei5w7t4vPBTbodepVLtEkn4HcuyTEt0m-Rh_LIxMShlL56AeC7bVBNvvRpNMi_YT3wTozsXvAXEDS1bdOcD_MLk7-g8L1FfeBZxTnRfLR81idE4qR7ecTeNgfVvuiddb-IGrIAefADZ_Vzl49E3amY7twA7EqX04lBZiVfZsO1R0BlzsCLqQ10fsleLl-S00R01G1Fn2e2gkEkRkwOfxbA7BdTYJwz3s1m7rC2HmQLyT_-h8qE30fGzWkoq7INPSTmJ0EJOPDRY3TZi7axUSDEjZbF8TwXcD3jFDmaAYD3D4E5NSKnILnacXC-kfGZQcP4bcrPbHa4BoNN3kyt\"\n\tinvalidPattern = \"c?Z32f1UDFCqsSP6uUlFyPLrXnGhgunvxc3jRsTO1mygukFtXxmk3sV0Q4PiGW8TstPPagjg8o3N0Jp25RKDJUOssch6rbSBVPc3R5vsRSa7y00CKKuu6T6N.2oV6hZlvOR7Jm3L6PzRLMbPRburA2FUfRlRLktcCbt2nBX1iyAfdMv8JvCjAhUJHT52PhiAT3ca7FNd5q5ZXAkn87LnuQhc5UHyuwD8gcWstOghUHZ20tcz7SjVuKyWZFgODlW2WczXqHKxaNhWYz4.839QXG7zlPYdYNhfbvQZe1zHr6bbbjIQYUs2q5whTtUWm8tCMWaOtm_DEZ_xKO5RUrajoClRedRiGK0fFAMshDSyAROOa7NXcE4WM_AuURDSif51QmcWY5HRITdd7y639Zc2Sz1kkUz-Ks_Aqe7xy0VUOlA8m4w2A7IfQ2iDtUeAlWIz1vsOihDxWeNqvTj5D5JOQcyRCiCfTfDWptrJCkKsMWMcDNRE773ypzQVn3r6VSVC63UqdT5Et5jpS5C1wFMuJDei5w7t4vPBTbodepVLtEkn4HcuyTEt0m-Rh_LIxMShlL56AeC7bVBNvvRpNMi_YT3wTozsXvAXEDS1bdOcD_MLk7-g8L1FfeBZxTnRfLR81idE4qR7ecTeNgfVvuiddb-IGrIAefADZ_Vzl49E3amY7twA7EqX04lBZiVfZsO1R0BlzsCLqQ10fsleLl-S00R01G1Fn2e2gkEkRkwOfxbA7BdTYJwz3s1m7rC2HmQLyT_-h8qE30fGzWkoq7INPSTmJ0EJOPDRY3TZi7axUSDEjZbF8TwXcD3jFDmaAYD3D4E5NSKnILnacXC-kfGZQcP4bcrPbHa4BoNN3kyt\"\n\tkeyword        = \"viewneo\"\n)\n\nfunc TestViewneo_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword viewneo\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/virustotal/virustotal.go",
    "content": "package virustotal\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"virustotal\"}) + `\\b([a-f0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"virustotal\"}\n}\n\n// FromData will find and optionally verify VirusTotal secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_VirusTotal,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\ts1.Verified = verifyToken(ctx, client, resMatch)\n\t\t}\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyToken(ctx context.Context, client *http.Client, token string) bool {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://www.virustotal.com/api/v3/metadata\", nil)\n\tif err != nil {\n\t\treturn false\n\t}\n\treq.Header.Add(\"x-apikey\", token)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false\n\t}\n\tdefer res.Body.Close()\n\n\tif res.StatusCode < 200 || res.StatusCode >= 300 {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_VirusTotal\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/virustotal/virustotal_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage virustotal\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestVirusTotal_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VIRUSTOTAL\")\n\tinactiveSecret := testSecrets.MustGetField(\"VIRUSTOTAL_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a virustotal secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_VirusTotal,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_VirusTotal,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"VirusTotal.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"VirusTotal.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/virustotal/virustotal_test.go",
    "content": "package virustotal\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"67d334aba3419b4d090ebeecac11b8830cafa388f9b6e3bfa97d03f4d25dbf46\"\n\tinvalidPattern = \"67d334aba3419b4d?90ebeecac11b8830cafa388f9b6e3bfa97d03f4d25dbf46\"\n\tkeyword        = \"virustotal\"\n)\n\nfunc TestVirusTotal_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword virustotal\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/visualcrossing/visualcrossing.go",
    "content": "package visualcrossing\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"visualcrossing\"}) + `\\b([0-9A-Z]{25})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"visualcrossing\"}\n}\n\n// FromData will find and optionally verify Visualcrossing secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_VisualCrossing,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/LA?key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_VisualCrossing\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Visual Crossing provides weather data services. Visual Crossing API keys can be used to access weather data and services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/visualcrossing/visualcrossing_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage visualcrossing\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestVisualcrossing_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VISUALCROSSING\")\n\tinactiveSecret := testSecrets.MustGetField(\"VISUALCROSSING_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a visualcrossing secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_VisualCrossing,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_VisualCrossing,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Visualcrossing.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Visualcrossing.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/visualcrossing/visualcrossing_test.go",
    "content": "package visualcrossing\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"9D39V79EZ6V3RO0YFV3XUOGLJ\"\n\tinvalidPattern = \"9D39V79EZ6V3?O0YFV3XUOGLJ\"\n\tkeyword        = \"visualcrossing\"\n)\n\nfunc TestVisualcrossing_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword visualcrossing\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/voiceflow/voiceflow.go",
    "content": "package voiceflow\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Reference: https://developer.voiceflow.com/reference/project#dialog-manager-api-keys\n\t//\n\t// TODO: This includes Workspace and Legacy Workspace API keys; I haven't validated whether these actually work.\n\t// https://github.com/voiceflow/general-runtime/blob/master/tests/runtime/lib/DataAPI/utils.unit.ts\n\tkeyPat = regexp.MustCompile(`\\b(VF\\.(?:(?:DM|WS)\\.)?[a-fA-F0-9]{24}\\.[a-zA-Z0-9]{16})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"vf\", \"dm\"}\n}\n\n// FromData will find and optionally verify Voiceflow secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Voiceflow,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\t// Fetch the state for a random user.\n\t\t\tpayload := []byte(`{\"question\": \"why is the sky blue?\"}`)\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://general-runtime.voiceflow.com/knowledge-base/query\", bytes.NewBuffer(payload))\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Set(\"Accept\", \"application/json\")\n\t\t\treq.Header.Set(\"Authorization\", resMatch)\n\t\t\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tif res.StatusCode == http.StatusOK {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == http.StatusUnauthorized {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\tvar buf bytes.Buffer\n\t\t\t\t\tvar bodyString string\n\t\t\t\t\t_, err = io.Copy(&buf, res.Body)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tbodyString = buf.String()\n\t\t\t\t\t}\n\t\t\t\t\tverificationErr := fmt.Errorf(\"unexpected HTTP response [status=%d, body=%s]\", res.StatusCode, bodyString)\n\t\t\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t\t\t}\n\t\t\t\t_ = res.Body.Close()\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Voiceflow\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Voiceflow is an AI service designed to transact with customers. API keys may be used to access customer data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/voiceflow/voiceflow_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage voiceflow\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestVoiceflow_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VOICEFLOW\")\n\tinactiveSecret := testSecrets.MustGetField(\"VOICEFLOW_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a voiceflow secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Voiceflow,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Voiceflow,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a voiceflow secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Voiceflow,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a voiceflow secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Voiceflow,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Voiceflow.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Voiceflow.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/voiceflow/voiceflow_test.go",
    "content": "package voiceflow\n\nimport (\n\t\"context\"\n\t\"testing\"\n)\n\nfunc TestVoiceflow_Pattern(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tdata        string\n\t\tshouldMatch bool\n\t\tmatch       string\n\t}{\n\t\t// True positives\n\t\t// https://github.com/funDAOmental/endlessquest/blob/5c008f7c6a7e58c45a88b72fef4b965c258d665c/Voiceflow/agent-api/index.js#L6\n\t\t{\n\t\t\tname: `valid_result1`,\n\t\t\tdata: `// z0MG IT'S NOT A SECRET (but we'll delete it)\nconst API_KEY = \"VF.DM.6469b4e5909a470007b96250.k4ip0SMy84jWlCsF\"; // it should look like this: VF.DM.XXXXXXX.XXXXXX... keep this a secret!`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       `VF.DM.6469b4e5909a470007b96250.k4ip0SMy84jWlCsF`,\n\t\t},\n\t\t// https://github.com/sherifButt/ll-site/blob/b98b268214324da42a84e996e4c03c242e122680/src/components/Chatbot.jsx#L14\n\t\t{\n\t\t\tname: `valid_result2`,\n\t\t\tdata: `  const runtime = useRuntime({\n    verify: { authorization: 'VF.DM.652da078cde70b0008e1c5df.zsIo23VTxNXKfb9f' },\n    session: { userID: 'user_123' },\n  });`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       `VF.DM.652da078cde70b0008e1c5df.zsIo23VTxNXKfb9f`,\n\t\t},\n\t\t// https://github.com/the-vv/Voiceflow-chatbot/blob/324db17693dd46387ea7a020e92c4e79b94306c6/src/app/chat/chat.component.ts#L27\n\t\t{\n\t\t\tname: `valid_result3`,\n\t\t\tdata: `    this.http.delete('https://general-runtime.voiceflow.com/state/user/TEST_USER', {\n      headers: {\n        Authorization: \"VF.DM.652ecc210267ec00078fc726.ZFPdEwvU0d1jiIMq\"\n      }\n    }).subscribe(res => {\n      this.loading = false;\n      this.doPrompt('', { action: { type: 'launch' } });\n    })`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       `VF.DM.652ecc210267ec00078fc726.ZFPdEwvU0d1jiIMq`,\n\t\t},\n\t\t// https://github.com/legionX7/Graduation-Project-API/blob/451431771d3fba1d8c634b8855274b414d7aed6d/mainAPI.py#L547\n\t\t{\n\t\t\tname: `valid_result4`,\n\t\t\tdata: `\nAPI_KEY = 'VF.DM.646388eb1419c80007bbbaa4.XHOqETFO3cvTxlGl'\nVERSION_ID = '646bc'`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       `VF.DM.646388eb1419c80007bbbaa4.XHOqETFO3cvTxlGl`,\n\t\t},\n\t\t// https://github.com/voiceflow/general-runtime/blob/master/tests/runtime/lib/DataAPI/utils.unit.ts\n\t\t{\n\t\t\tname: `valid_result5`,\n\t\t\tdata: ` it('extracts ID from a Dialog Manager API key', () => {\n      // eslint-disable-next-line no-secrets/no-secrets\n      const key = 'VF.DM.628d5d92faf688001bda7907.dmC8KKO1oX8JO5ai';\n      const result = utils.extractAPIKeyID(key);\n\n      expect(result).to.equal('628d5d92faf688001bda7907');\n    });`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       `VF.DM.628d5d92faf688001bda7907.dmC8KKO1oX8JO5ai`,\n\t\t},\n\t\t{\n\t\t\tname: `valid_result6_legacy`,\n\t\t\tdata: `    it('extracts ID from a Workspace API key', () => {\n      // eslint-disable-next-line no-secrets/no-secrets\n      const key = 'VF.WS.62bcb0cca5184300066f5ac7.egnKyyzZksiS5iGa';\n      const result = utils.extractAPIKeyID(key);\n\n      expect(result).to.equal('62bcb0cca5184300066f5ac7');\n    });\n`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       `VF.WS.62bcb0cca5184300066f5ac7.egnKyyzZksiS5iGa`,\n\t\t},\n\t\t{\n\t\t\tname: `valid_result7_legacy`,\n\t\t\tdata: `    it('extracts ID from a Legacy Workspace API key', () => {\n      // eslint-disable-next-line no-secrets/no-secrets\n      const key = 'VF.62bcb0cca5184300066f5ac7.dmC8KKO1oX8JO5az';\n      const result = utils.extractAPIKeyID(key);\n\n      expect(result).to.equal('62bcb0cca5184300066f5ac7');\n    });`,\n\t\t\tshouldMatch: true,\n\t\t\tmatch:       `VF.62bcb0cca5184300066f5ac7.dmC8KKO1oX8JO5az`,\n\t\t},\n\n\t\t// False positives\n\t\t// https://github.com/ImperialCollegeLondon/voiceflow-integration-whatsapp/blob/0f3d6a5638b9acb4989d5bf8e77081cc78e9b976/README.md?plain=1#L155\n\t\t{\n\t\t\tname:        `invalid_result1`,\n\t\t\tdata:        \"Now, paste it in your .env file for the **VF_PROJECT_API** variable<br>\\n```VF_PROJECT_API='VF.DM.62xxxxxxxxxxxxxxxxxxxxxxx'```\",\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t// https://github.com/voiceflow/api-examples/blob/c3d8ba9ee8eced7ec8d241973b1eb0284aaec212/rust/src/main.rs#L5\n\t\t{\n\t\t\tname:        `invalid_result2`,\n\t\t\tdata:        `const API_KEY: &str = \"YOUR_API_KEY_HERE\"; // it should look like this: VF.DM.XXXXXXX.XXXXXX... keep this a secret!`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\n\t\t\tresults, err := s.FromData(context.Background(), false, []byte(test.data))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Voiceflow.FromData() error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif test.shouldMatch {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"%s: did not receive a match for '%v' when one was expected\", test.name, test.data)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\texpected := test.data\n\t\t\t\tif test.match != \"\" {\n\t\t\t\t\texpected = test.match\n\t\t\t\t}\n\t\t\t\tresult := results[0]\n\t\t\t\tresultData := string(result.Raw)\n\t\t\t\tif resultData != expected {\n\t\t\t\t\tt.Errorf(\"%s: did not receive expected match.\\n\\texpected: '%s'\\n\\t  actual: '%s'\", test.name, expected, resultData)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif len(results) > 0 {\n\t\t\t\t\tt.Errorf(\"%s: received a match for '%v' when one wasn't wanted\", test.name, test.data)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/voicegain/voicegain.go",
    "content": "package voicegain\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"voicegain\"}\n}\n\n// FromData will find and optionally verify Voicegain secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Voicegain,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.voicegain.ai/v1/sa/config\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Voicegain\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Voicegain is a speech recognition and natural language processing service. Voicegain API keys can be used to access and manage Voicegain services and data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/voicegain/voicegain_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage voicegain\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestVoicegain_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VOICEGAIN\")\n\tinactiveSecret := testSecrets.MustGetField(\"VOICEGAIN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a voicegain secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Voicegain,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Voicegain,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Voicegain.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Voicegain.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/voicegain/voicegain_test.go",
    "content": "package voicegain\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"ey7nVWxLrVTPaolyqAYxmedMyueqpSQ6eIfX.ey582H2Ghcpty-_E0Wn4syTEdwAdXRF6eYyKgHveC26fBGQ4BiZIS30BcPrr1JG2xiFV4-bEOzVAMcWEQkh-THo0Z3RT6Os45ZGD28dlaiicZH.i4G5a88NkP82UpJoh3vW-sHOBx-uX1tYwyGcxykJO55\"\n\tinvalidPattern = \"ay7nVWxLrVTPaolyqAYxmedMyueqpSQ6eIfX.ey582H2Ghcpty-_E0Wn4syTEdwAdXRF6eYyKgHveC26fBGQ4BiZIS30BcPrr1JG2xiFV4-bEOzVAMcWEQkh-THo0Z3RT6Os45ZGD28dlaiicZH.i4G5a88NkP82UpJoh3vW-sHOBx-uX1tYwyGcxykJO55\"\n\tkeyword        = \"voicegain\"\n)\n\nfunc TestVoicegain_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword voicegain\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/voodoosms/voodoosms.go",
    "content": "package voodoosms\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"voodoosms\"}) + `\\b([0-9a-zA-Z]{46})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"voodoosms\"}\n}\n\n// FromData will find and optionally verify VoodooSMS secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_VoodooSMS,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.voodoosms.com/credits\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type'\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_VoodooSMS\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"VoodooSMS is a service for sending SMS messages. VoodooSMS API keys can be used to send SMS messages and check account balance.\"\n}\n"
  },
  {
    "path": "pkg/detectors/voodoosms/voodoosms_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage voodoosms\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestVoodooSMS_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VOODOOSMS\")\n\tinactiveSecret := testSecrets.MustGetField(\"VOODOOSMS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a voodoosms secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_VoodooSMS,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_VoodooSMS,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"VoodooSMS.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"VoodooSMS.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/voodoosms/voodoosms_test.go",
    "content": "package voodoosms\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"qpbVNj3Fdxc38gIixFqXAndxfSvoafOWztBMIzsl0Y262i\"\n\tinvalidPattern = \"qpbVNj3Fd?c38gIixFqXAndxfSvoafOWztBMIzsl0Y262i\"\n\tkeyword        = \"voodoosms\"\n)\n\nfunc TestVoodooSMS_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword voodoosms\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/vouchery/vouchery.go",
    "content": "package vouchery\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"vouchery\"}) + `\\b([a-z0-9-]{36})\\b`)\n\tsubPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"vouchery\"}) + `\\b([a-zA-Z0-9-\\S]{2,20})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"vouchery\"}\n}\n\n// FromData will find and optionally verify Vouchery secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tsubMatches := subPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, subMatch := range subMatches {\n\n\t\t\tsubMatch := strings.TrimSpace(subMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Vouchery,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t\tRawV2:        []byte(resMatch + subMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://%s.vouchery.io/api/v2.0/users\", subMatch), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Vouchery\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/vouchery/vouchery_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage vouchery\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestVouchery_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VOUCHERY\")\n\tsubdomain := testSecrets.MustGetField(\"VOUCHERY_SUBDOMAIN\")\n\tinactiveSecret := testSecrets.MustGetField(\"VOUCHERY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a vouchery secret %s within vouchery %s\", secret, subdomain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Vouchery,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(secret + \"secret\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Vouchery,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRawV2:        []byte(secret + subdomain),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Vouchery,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(inactiveSecret + \"secret\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Vouchery,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRawV2:        []byte(inactiveSecret + subdomain),\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Vouchery.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Vouchery.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/vouchery/vouchery_test.go",
    "content": "package vouchery\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey   = \"7ts1czbnd621chpqufnon62o32w0z2iuf15x\"\n\tinvalidKey = \"7ts1czb?d621chpqufnon62o32w0z2iuf15x\"\n\tvalidSub   = \"u;Yb0#E$0\"\n\tinvalidSub = \"u;Yb?#E$0\"\n\tkeyword    = \"vouchery\"\n)\n\nfunc TestVouchery_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword vouchery\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s'\\n\\n%s '%s'\\n\", keyword, validKey, keyword, validSub),\n\t\t\twant:  []string{validKey + validSub},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s'\\n\\n%s '%s'\\n\", keyword, invalidKey, keyword, invalidSub),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/vpnapi/vpnapi.go",
    "content": "package vpnapi\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"vpnapi\"}) + `\\b([a-z0-9A-Z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"vpnapi\"}\n}\n\n// FromData will find and optionally verify Vpnapi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Vpnapi,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://vpnapi.io/api/8.8.8.8?key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Vpnapi\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Vpnapi provides information about IP addresses, including geolocation and VPN detection. Vpnapi keys can be used to access this information.\"\n}\n"
  },
  {
    "path": "pkg/detectors/vpnapi/vpnapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage vpnapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestVpnapi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VPNAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"VPNAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a vpnapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Vpnapi,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Vpnapi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Vpnapi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Vpnapi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/vpnapi/vpnapi_test.go",
    "content": "package vpnapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"LAqmsNMbdWkEmZW053axrvWTUKVRbcKN\"\n\tinvalidPattern = \"LAqmsNMbdWkEmZW0?3axrvWTUKVRbcKN\"\n\tkeyword        = \"vpnapi\"\n)\n\nfunc TestVpnapi_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword vpnapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/vultrapikey/vultrapikey.go",
    "content": "package vultrapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"vultr\"}) + `\\b([A-Z0-9]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"vultr\"}\n}\n\n// FromData will find and optionally verify VultrApiKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_VultrApiKey,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.vultr.com/v2/account\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_VultrApiKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/vultrapikey/vultrapikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage vultrapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestVultrApiKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VULTR_API_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"VULTR_API_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a vultrapikey secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_VultrApiKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_VultrApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"VultrApiKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"VultrApiKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/vultrapikey/vultrapikey_test.go",
    "content": "package vultrapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"F7CTEQ0FK6XSPCSG24XMBULREVNG4AL3RTQK\"\n\tinvalidPattern = \"F7CTEQ0FK6?SPCSG24XMBULREVNG4AL3RTQK\"\n\tkeyword        = \"vultrapikey\"\n)\n\nfunc TestVultrApiKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword vultrapikey\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/vyte/vyte.go",
    "content": "package vyte\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"vyte\"}) + `\\b([0-9a-z]{50})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"vyte\"}\n}\n\n// FromData will find and optionally verify Vyte secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Vyte,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.vyte.in/v2/events\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/vnd.vyte+json; version=3\")\n\t\t\treq.Header.Add(\"Authorization\", resMatch)\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Vyte\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/vyte/vyte_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage vyte\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestVyte_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"VYTE\")\n\tinactiveSecret := testSecrets.MustGetField(\"VYTE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a vyte secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Vyte,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Vyte,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Vyte.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Vyte.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/vyte/vyte_test.go",
    "content": "package vyte\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"ii86dga95chsw6qe6dt0oh7nqn5udhqsk3fhaizktnv770zk81\"\n\tinvalidPattern = \"ii86dga95chsw6qe6dt0oh7?qn5udhqsk3fhaizktnv770zk81\"\n\tkeyword        = \"vyte\"\n)\n\nfunc TestVyte_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword vyte\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/walkscore/walkscore.go",
    "content": "package walkscore\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"walkscore\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"walkscore\"}\n}\n\n// FromData will find and optionally verify Walkscore secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_WalkScore,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://transit.walkscore.com/transit/search/stops/?lat=47.6101359&lon=-122.3420567&wsapikey=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\t\t\t\tif (res.StatusCode >= 200 && res.StatusCode < 300) && strings.Contains(body, `distance`) {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_WalkScore\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Walkscore API keys can be used to access Walkscore's services for retrieving walkability scores and related data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/walkscore/walkscore_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage walkscore\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWalkscore_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WALKSCORE\")\n\tinactiveSecret := testSecrets.MustGetField(\"WALKSCORE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a walkscore secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WalkScore,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WalkScore,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Walkscore.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Walkscore.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/walkscore/walkscore_test.go",
    "content": "package walkscore\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"tmexev6bvpc43sk0g0fk3dpft50ss56r\"\n\tinvalidPattern = \"tmexev6bvpc43sk0?0fk3dpft50ss56r\"\n\tkeyword        = \"walkscore\"\n)\n\nfunc TestWalkscore_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword walkscore\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/weatherbit/weatherbit.go",
    "content": "package weatherbit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"weatherbit\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"weatherbit\"}\n}\n\n// FromData will find and optionally verify Weatherbit secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_WeatherBit,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, 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)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_WeatherBit\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Weatherbit is a weather data service provider. Weatherbit API keys can be used to access weather data and related services.\"\n}\n"
  },
  {
    "path": "pkg/detectors/weatherbit/weatherbit_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage weatherbit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWeatherbit_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WEATHERBIT\")\n\tinactiveSecret := testSecrets.MustGetField(\"WEATHERBIT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a weatherbit secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WeatherBit,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WeatherBit,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Weatherbit.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Weatherbit.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/weatherbit/weatherbit_test.go",
    "content": "package weatherbit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"zqjxo96wmdi1rdcvohnpu0v3bkypbuti\"\n\tinvalidPattern = \"zqjx?96wmdi1rdcvohnpu0v3bkypbuti\"\n\tkeyword        = \"weatherbit\"\n)\n\nfunc TestWeatherbit_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword weatherbit\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/weatherstack/weatherstack.go",
    "content": "package weatherstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"weatherstack\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"weatherstack\"}\n}\n\n// FromData will find and optionally verify Weatherstack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_WeatherStack,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.weatherstack.com/current?access_key=%s&query=LA\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\t\t\t\tvalidResponse := strings.Contains(body, `location`) || strings.Contains(body, `\"info\":\"Access Restricted - Your current Subscription Plan does not support HTTPS Encryption.\"`)\n\t\t\t\tif (res.StatusCode >= 200 && res.StatusCode < 300) && validResponse {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_WeatherStack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Weatherstack is a weather service that provides real-time weather information. Weatherstack API keys can be used to access weather data from their API.\"\n}\n"
  },
  {
    "path": "pkg/detectors/weatherstack/weatherstack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage weatherstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWeatherstack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WEATHERSTACK\")\n\tinactiveSecret := testSecrets.MustGetField(\"WEATHERSTACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a weatherstack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WeatherStack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WeatherStack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Weatherstack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Weatherstack.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/weatherstack/weatherstack_test.go",
    "content": "package weatherstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"w3m1ri2dev8tqm96o8wwxxjh1tutvqvw\"\n\tinvalidPattern = \"w3m1ri2dev8tqm96?8wwxxjh1tutvqvw\"\n\tkeyword        = \"weatherstack\"\n)\n\nfunc TestWeatherstack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword weatherstack\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/web3storage/web3storage.go",
    "content": "package web3storage\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"web3\"}) + `\\b(eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\\.eyJ[A-Za-z0-9-_]{100,300}\\.[A-Za-z0-9-_]{25,100})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"web3\"}\n}\n\n// FromData will find and optionally verify Web3storage secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Web3Storage,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.web3.storage/user/uploads\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 401 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\terr := fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Web3Storage\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Web3.storage is a service offering decentralized storage solutions. Web3.storage API keys can be used to access and manage stored data.\"\n}\n"
  },
  {
    "path": "pkg/detectors/web3storage/web3storage_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage web3storage\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWeb3Storage_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WEB3STORAGE\")\n\tinactiveSecret := testSecrets.MustGetField(\"WEB3STORAGE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a web3storage secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Web3Storage,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Web3Storage,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a web3storage secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Web3Storage,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a web3storage secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Web3Storage,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Web3Storage.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Web3Storage.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/web3storage/web3storage_test.go",
    "content": "package web3storage\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ5nzGXjcjJ2CaJVf8LF1cNVd07A4XSYHvHK1MQMuum_OZw3ibxmzrTUFYWMvbzzSlJB1jg4E5aT4s6wmpP8OstDNWeHaHuhRBtJss.DuF8uu3Z49HQtAQRG_3r_wCqQ_S-YnFcKrL7vPU1xGtPKxzhK1NbPkZCjXVjGBxseuirozDquv05HxJBScNaQzn\"\n\tinvalidPattern = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ5nzGXjcjJ2CaJVf8LF1cNVd07A?XSYHvHK1MQMuum_OZw3ibxmzrTUFYWMvbzzSlJB1jg4E5aT4s6wmpP8OstDNWeHaHuhRBtJss.DuF8uu3Z49HQtAQRG_3r_wCqQ_S-YnFcKrL7vPU1xGtPKxzhK1NbPkZCjXVjGBxseuirozDquv05HxJBScNaQzn\"\n\tkeyword        = \"web3storage\"\n)\n\nfunc TestWeb3Storage_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword web3storage\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/webex/webex.go",
    "content": "package webex\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"webex\"}) + `\\b([a-f0-9]{64})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"webex\"}) + `\\b(C[a-f0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"webex\"}\n}\n\n// FromData will find and optionally verify Webex secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidMatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\tfor _, idMatch := range idMatches {\n\t\t\tid := strings.TrimSpace(idMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Webex,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tpayload := 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\")\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://webexapis.com/v1/access_token\", payload)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\t\tclient := common.SaneHttpClient()\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbody, err := io.ReadAll(res.Body)\n\t\t\t\t\tres.Body.Close()\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tvar message struct {\n\t\t\t\t\t\t\tMessage string `json:\"message\"`\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif err := json.Unmarshal(body, &message); err == nil {\n\t\t\t\t\t\t\tvar getError = regexp.MustCompile(detectors.PrefixRegex([]string{\"error\"}) + `(redirect_uri_mismatch)`)\n\t\t\t\t\t\t\tresult := getError.FindAllStringSubmatch(message.Message, -1)\n\t\t\t\t\t\t\tif len(result) > 0 {\n\t\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Webex\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/webex/webex_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage webex\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWebex_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WEBEX_CLIENT_SECRET\")\n\tinactiveId := testSecrets.MustGetField(\"WEBEX_CLIENT_ID_INACTIVE\")\n\tid := testSecrets.MustGetField(\"WEBEX_CLIENT_ID\")\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a webex secret %s within webex secret %s\", id, secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Webex,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Webex,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Webex.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Webex.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/webex/webex_test.go",
    "content": "package webex\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey        = \"b631516f9ef65883d1fa8124a09bfd51209cfb38081eb8e1e9054a37210fdb5a\"\n    invalidKey      = \"b631516f9ef65883d1fa81?4a09bfd51209cfb38081eb8e1e9054a37210fdb5a\"\n    validId         = \"Cce4c920e27143ee7bef6d57381f468d8dca7ac861f3d1e3f09205ee7a0927b0b\"\n    invalidId       = \"Cce4c920e27143ee7bef6d573?1f468d8dca7ac861f3d1e3f09205ee7a0927b0b\"\n    validGetError   = \"redirect_uri_mismatch\"\n    invalidGetError = \"redirect_url_mismatch\"\n    keyword         = \"webex\"\n)\n\nfunc TestWebex_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword webex\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId, keyword, validGetError),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId, keyword, invalidGetError),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/webexbot/webexbot.go",
    "content": "package webexbot\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\tkeyPat        = 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})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"spark\", \"webex\"}\n}\n\n// FromData will find and optionally verify Webexbot secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tif len(match) > 1 {\n\t\t\tuniqueMatches[match[1]] = struct{}{}\n\t\t}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_WebexBot,\n\t\t\tRaw:          []byte(match),\n\t\t\tRedacted:     match[:5] + \"...\",\n\t\t\tExtraData:    map[string]string{},\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\ntype response struct {\n\tType        string   `json:\"type\"`\n\tID          string   `json:\"id\"`\n\tDisplayName string   `json:\"displayName\"`\n\tNickName    string   `json:\"nickName\"`\n\tUserName    string   `json:\"username\"`\n\tEmails      []string `json:\"emails\"`\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://webexapis.com/v1/people/me\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Accept\", \"application/json\")\n\treq.Header.Add(\"Authorization\", \"Bearer \"+token)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\t// parse the response body to json\n\t\tvar resp response\n\t\textraData := make(map[string]string)\n\t\tif err := json.NewDecoder(res.Body).Decode(&resp); err != nil {\n\t\t\treturn true, nil, fmt.Errorf(\"failed to decode response: %w\", err)\n\t\t}\n\n\t\textraData[\"type\"] = resp.Type\n\t\textraData[\"username\"] = resp.UserName\n\t\treturn true, extraData, nil\n\tcase http.StatusUnauthorized:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_WebexBot\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Webex bot allows applications to interact with users in Webex spaces.\"\n}\n"
  },
  {
    "path": "pkg/detectors/webexbot/webexbot_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage webexbot\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWebexbot_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors6\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WEBEXBOT\")\n\tinactiveSecret := testSecrets.MustGetField(\"WEBEXBOT_INACTIVE\")\n\n\tredacted := secret[:5] + \"...\"\n\tinactiveRedacted := inactiveSecret[:5] + \"...\"\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a webexbot secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WebexBot,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tRedacted:     redacted,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WebexBot,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     inactiveRedacted,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a webexbot secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WebexBot,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     redacted,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a webexbot secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WebexBot,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tRedacted:     redacted,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Webexbot.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"primarySecret\", \"ExtraData\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Webexbot.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/webexbot/webexbot_test.go",
    "content": "package webexbot\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestWebexbot_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: \"webexbot_token = 'ajlksdjasda9090sadsa9dsad0saasdkl0asd90asdcasc90asdajij2n2njkjn3_asdf_asdkqw34-qwer-vbnm-asdf-qwertyuiopkl'\",\n\t\t\twant:  []string{\"ajlksdjasda9090sadsa9dsad0saasdkl0asd90asdcasc90asdajij2n2njkjn3_asdf_asdkqw34-qwer-vbnm-asdf-qwertyuiopkl\"},\n\t\t},\n\t\t{\n\t\t\tname: \"finds multiple Webex Bot tokens\",\n\t\t\tinput: `\n\t\t\twebex_bot_token = 'ajlksdjasda9090sadsa9dsad0saasdkl0asd90asdcasc90asdajij2n2njkjn3_qwer_zxcv1234-asdf-ghjk-tyui-mnbvcxzqwert'\n\t\t\twebexbot = \"ajlksdjasda9090sadsa9dsad0saasdkl0asd90asdcasc90asdajij2n2njkjn3_asdf_qwer4567-qwer-vbnm-asdf-xcvbnmasdfgh\"\n\t\t`,\n\t\t\twant: []string{\n\t\t\t\t\"ajlksdjasda9090sadsa9dsad0saasdkl0asd90asdcasc90asdajij2n2njkjn3_qwer_zxcv1234-asdf-ghjk-tyui-mnbvcxzqwert\",\n\t\t\t\t\"ajlksdjasda9090sadsa9dsad0saasdkl0asd90asdcasc90asdajij2n2njkjn3_asdf_qwer4567-qwer-vbnm-asdf-xcvbnmasdfgh\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"does not match invalid token name\",\n\t\t\tinput: \"webex = 'asdf1234qwer5678zxcv9012lkjh_asdf_qwer3456-qwer-vbnm-asdf-zxcvbnmasdfgh'\",\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/webflow/webflow.go",
    "content": "package webflow\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"webflow\"}) + `\\b([a-zA0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"webflow\"}\n}\n\n// FromData will find and optionally verify Webflow secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Webflow,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.webflow.com/info\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"accept-version\", \"1.0.0\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Webflow\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/webflow/webflow_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage webflow\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWebflow_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WEBFLOW_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"WEBFLOW_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a webflow secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Webflow,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Webflow,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Webflow.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Webflow.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/webflow/webflow_test.go",
    "content": "package webflow\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"nwA9d0mv4juqgv8sqprzlz5yqcfkpv6o3jzlfuxlwugcikyhqpyn3h2ip6ffjbas\"\n\tinvalidPattern = \"nwA9d0mv4juqgv8sqprzlz5yqcfkpv6o?jzlfuxlwugcikyhqpyn3h2ip6ffjbas\"\n\tkeyword        = \"webflow\"\n)\n\nfunc TestWebflow_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword webflow\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/webscraper/webscraper.go",
    "content": "package webscraper\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"webscraper\"}) + `\\b([a-zA-Z0-9]{60})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"webscraper\"}\n}\n\n// FromData will find and optionally verify WebScraper secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_WebScraper,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.webscraper.io/api/v1/sitemaps?api_token=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_WebScraper\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/webscraper/webscraper_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage webscraper\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWebScraper_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WEBSCRAPER\")\n\tinactiveSecret := testSecrets.MustGetField(\"WEBSCRAPER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a webscraper secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WebScraper,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WebScraper,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"WebScraper.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"WebScraper.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/webscraper/webscraper_test.go",
    "content": "package webscraper\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"XEOnTTxcYkZqSEpTs8DVKLahmlp7YtnSfkoCmwI3Wbn5AmlM5TPhaVWxV2Ql\"\n\tinvalidPattern = \"XEO?TTxcYkZqSEpTs8DVKLahmlp7YtnSfkoCmwI3Wbn5AmlM5TPhaVWxV2Ql\"\n\tkeyword        = \"webscraper\"\n)\n\nfunc TestWebScraper_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword webscraper\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/webscraping/webscraping.go",
    "content": "package webscraping\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"webscraping\"}) + `\\b([0-9A-Za-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"webscraping\"}\n}\n\n// FromData will find and optionally verify Webscraping secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Webscraping,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", `https://api.webscrapingapi.com/v1?api_key=`+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\n\t\t\t\tif strings.Contains(body, \"key `url` required.\") {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Webscraping\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Webscraping API keys can be used to access web scraping services to extract data from websites.\"\n}\n"
  },
  {
    "path": "pkg/detectors/webscraping/webscraping_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage webscraping\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWebscraping_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WEBSCRAPING\")\n\tinactiveSecret := testSecrets.MustGetField(\"WEBSCRAPING_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a webscraping secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Webscraping,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Webscraping,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Webscraping.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Webscraping.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/webscraping/webscraping_test.go",
    "content": "package webscraping\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"lCBYsml2jnyw2gO7lsLj8j6DwIyUXbkE\"\n\tinvalidPattern = \"?CBYsml2jnyw2gO7lsLj8j6DwIyUXbkE\"\n\tkeyword        = \"webscraping\"\n)\n\nfunc TestWebscraping_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword webscraping\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/websitepulse/websitepulse.go",
    "content": "package websitepulse\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"websitepulse\"}) + `\\b([0-9a-f]{32})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"websitepulse\"}) + `\\b([0-9a-zA-Z._]{4,22})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"websitepulse\"}\n}\n\n// FromData will find and optionally verify Websitepulse secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdMatch := strings.TrimSpace(idmatch[1])\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Websitepulse,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.websitepulse.com/textserver.php?method=GetContacts&username=%s&key=%s\", resIdMatch, resMatch), nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tbody := string(bodyBytes)\n\n\t\t\t\t\tif strings.Contains(body, \"Active\") {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Websitepulse\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Websitepulse is a web-based service that monitors websites and servers. The keys and IDs can be used to access and manage monitoring configurations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/websitepulse/websitepulse_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage websitepulse\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWebsitepulse_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors4\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WEBSITEPULSE\")\n\tinactiveSecret := testSecrets.MustGetField(\"WEBSITEPULSE_INACTIVE\")\n\tid := testSecrets.MustGetField(\"WEBSITEPULSE_ID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a websitepulse secret %s within websitepulse %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Websitepulse,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Websitepulse,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Websitepulse,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Websitepulse,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Websitepulse.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Websitepulse.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/websitepulse/websitepulse_test.go",
    "content": "package websitepulse\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey   = \"3bdaacfbb85ee2febeb2b82f37a11d09\"\n\tinvalidKey = \"3?daacfbb85ee2febeb2b82f37a11d09\"\n\tvalidId    = \"E7Yr.IG0P6W2vtOya\"\n\tinvalidId  = \"?7Yr.IG0P6W2vtOy?\"\n\tkeyword    = \"websitepulse\"\n)\n\nfunc TestWebsitepulse_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword websitepulse\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s'\\n%s '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s '%s'\\n%s '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/weightsandbiases/weightsandbiases.go",
    "content": "package weightsandbiases\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{ client *http.Client }\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"wandb\"}) + `\\b([0-9a-f]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string { return []string{\"wandb\"} }\n\n// FromData will find and optionally verify Weightsandbiases secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_WeightsAndBiases,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\ntype viewerResponse struct {\n\tData struct {\n\t\tViewer struct {\n\t\t\tID       string `json:\"id\"`\n\t\t\tUsername string `json:\"username\"`\n\t\t\tEmail    string `json:\"email\"`\n\t\t\tAdmin    bool   `json:\"admin\"`\n\t\t} `json:\"viewer\"`\n\t} `json:\"data\"`\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, map[string]string, error) {\n\tquery := `{\"query\": \"query Viewer { viewer { id username email admin } }\"}`\n\n\tconst baseURL = \"https://api.wandb.ai/graphql\"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL, bytes.NewBufferString(query))\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\tauthHeader := base64.StdEncoding.EncodeToString([]byte(\"api:\" + token))\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Basic \"+authHeader)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tvar viewerResp viewerResponse\n\t\tif err := json.NewDecoder(res.Body).Decode(&viewerResp); err != nil {\n\t\t\treturn false, nil, err\n\t\t}\n\n\t\t// Only consider it verified if we got back a username.\n\t\tif viewerResp.Data.Viewer.Username == \"\" {\n\t\t\treturn false, nil, nil\n\t\t}\n\n\t\textraData := map[string]string{\n\t\t\t\"username\": viewerResp.Data.Viewer.Username,\n\t\t\t\"email\":    viewerResp.Data.Viewer.Email,\n\t\t\t\"admin\":    strconv.FormatBool(viewerResp.Data.Viewer.Admin),\n\t\t}\n\t\treturn true, extraData, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Weights & Biases is a Machine Learning Operations (MLOps) platform that helps track experiments, version datasets, evaluate model performance, and collaborate with team members\"\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_WeightsAndBiases\n}\n"
  },
  {
    "path": "pkg/detectors/weightsandbiases/weightsandbiases_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage weightsandbiases\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWeightsandbiases_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WEIGHTSANDBIASES\")\n\tinactiveSecret := testSecrets.MustGetField(\"WEIGHTSANDBIASES_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a weightsandbiases secret wandb %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WeightsAndBiases,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"admin\":    \"false\",\n\t\t\t\t\t\t\"email\":    \"source-integrations@trufflesec.com\",\n\t\t\t\t\t\t\"username\": \"source-integrations\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WeightsAndBiases,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a weightsandbiases secret wandb %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WeightsAndBiases,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a weightsandbiases secret wandb %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WeightsAndBiases,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Weightsandbiases.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Weightsandbiases.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/weightsandbiases/weightsandbiases_test.go",
    "content": "package weightsandbiases\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nfunc TestWeightsandbiases_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: \"WANDB_API_KEY = 'eedf1c984f6b995ec40ecc6658356044847ffb31'\",\n\t\t\twant:  []string{\"eedf1c984f6b995ec40ecc6658356044847ffb31\"},\n\t\t},\n\t\t{\n\t\t\tname: \"finds all matches\",\n\t\t\tinput: `WANDB_API_KEY = 'eedf1c984f6b995ec40ecc6658356044847ffb31'\nWANDB_API_KEY= 'eedf1c984f6b995ec40ecc6658356044847ffb32'`,\n\t\t\twant: []string{\"eedf1c984f6b995ec40ecc6658356044847ffb31\", \"eedf1c984f6b995ec40ecc6658356044847ffb32\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: \"WANDB_API_KEY = 'e84f6b995ec40ecc6658356044847ffb31'\",\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/wepay/wepay.go",
    "content": "package wepay\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\n\tappIDPat = regexp.MustCompile(`\\b(\\d{6})\\b`)\n\tkeyPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"wepay\"}) + `\\b([a-zA-Z0-9_?]{62})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"wepay\"}\n}\n\n// FromData will find and optionally verify WePay secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tappIDmatches := appIDPat.FindAllStringSubmatch(dataStr, -1)\n\n\tresAppIDMatch := \"\"\n\tfor _, appIDMatch := range appIDmatches {\n\t\tresAppIDMatch = strings.TrimSpace(appIDMatch[1])\n\t}\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_WePay,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://stage-api.wepay.com/payments?type=credit_card&credit_card=4003830171874018\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"App-Token\", resMatch)\n\t\t\treq.Header.Add(\"App-Id\", resAppIDMatch)\n\t\t\treq.Header.Add(\"Api-Version\", \"3.0\")\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\treq.Header.Add(\"Unique-Key\", \"Unique-Key0\")\n\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_WePay\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"WePay is an online payment service provider. WePay API keys can be used to process payments and access account information.\"\n}\n"
  },
  {
    "path": "pkg/detectors/wepay/wepay_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage wepay\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWePay_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WEPAY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"WEPAY_INACTIVE\")\n\tappID := testSecrets.MustGetField(\"WEPAY_APPID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a wepay token %s and app ID %s within \", secret, appID)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WePay,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WePay,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"WePay.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"WePay.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/wepay/wepay_test.go",
    "content": "package wepay\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidAppID   = \"135827\"\n\tinvalidAppID = \"135?27\"\n\tvalidKey     = \"JFdESNcC4meVkWFygHV5O5FmhycSCsQnlmmXWwTKSQOJG3ERd?stTjE8m98hJU\"\n\tinvalidKey   = \"JFdESNcC4meVkWFygHV5O5FmhycSCsQ!lmmXWwTKSQOJG3ERd?stTjE8m98hJU\"\n\tkeyword      = \"wepay\"\n)\n\nfunc TestWePay_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword wepay\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validAppID, keyword, validKey),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidAppID, keyword, invalidKey),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/whoxy/whoxy.go",
    "content": "package whoxy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"whoxy\"}) + `\\b([0-9a-z]{33})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"whoxy\"}\n}\n\n// FromData will find and optionally verify Whoxy secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Whoxy,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.whoxy.com/?key=%s&account=balance\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tbody := string(bodyBytes)\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 && strings.Contains(body, `\"status\": 1`) {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Whoxy\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Whoxy is a service used to retrieve WHOIS data for domain names. Whoxy API keys can be used to query WHOIS information.\"\n}\n"
  },
  {
    "path": "pkg/detectors/whoxy/whoxy_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage whoxy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWhoxy_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WHOXY\")\n\tinactiveSecret := testSecrets.MustGetField(\"WHOXY_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a whoxy secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Whoxy,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Whoxy,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Whoxy.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Whoxy.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/whoxy/whoxy_test.go",
    "content": "package whoxy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"ev1avuvxo6rnn2dlof3r1vesz4xyvawit\"\n\tinvalidPattern = \"ev1avuvx?6rnn2dlof3r1vesz4xyvawit\"\n\tkeyword        = \"whoxy\"\n)\n\nfunc TestWhoxy_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword whoxy\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/wistia/wistia.go",
    "content": "package wistia\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"wistia\"}) + `\\b([0-9a-z]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"wistia\"}\n}\n\n// FromData will find and optionally verify Wistia secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Wistia,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.wistia.com/v1/stats/account.json?access_token=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} \n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Wistia\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Wistia is a video hosting platform designed for businesses. Wistia API keys can be used to access and manage video content and account settings.\"\n}\n"
  },
  {
    "path": "pkg/detectors/wistia/wistia_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage wistia\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWistia_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WISTIA\")\n\tinactiveSecret := testSecrets.MustGetField(\"WISTIA_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a wistia secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Wistia,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Wistia,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Wistia.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Wistia.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/wistia/wistia_test.go",
    "content": "package wistia\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"epg6wik0b273hrk8ubc3xk8e00yg0o22c9l1prumz0ag4dz6ht4zgj99uq0kuor8\"\n\tinvalidPattern = \"epg6wik0b273hrk8ub?3xk8e00yg0o22c9l1prumz0ag4dz6ht4zgj99uq0kuor8\"\n\tkeyword        = \"wistia\"\n)\n\nfunc TestWistia_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword wistia\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/wit/wit.go",
    "content": "package wit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"wit\"}) + `\\b([A-Z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"wit\"}\n}\n\n// FromData will find and optionally verify Wit secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Wit,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.wit.ai/message?q=saascndncdcdksCHDKSCVSDCasdasdVCSDVCSDAVHKCDCVHKSADVCKDVKCDSVHCSACVHJDSCVJHSADCVJHSAJ\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} \n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Wit\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/wit/wit_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage wit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWit_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WIT\")\n\tinactiveSecret := testSecrets.MustGetField(\"WIT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a wit secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Wit,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Wit,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Wit.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Wit.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/wit/wit_test.go",
    "content": "package wit\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"1ZLU5OXGEYX56K3IOL1Z88Y5YU5CC1KK\"\n\tinvalidPattern = \"1ZLU5?XGEYX56K3IOL1Z88Y5YU5CC1KK\"\n\tkeyword        = \"wit\"\n)\n\nfunc TestWit_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword wit\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/wiz/wiz.go",
    "content": "package wiz\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tidPat     = regexp.MustCompile(detectors.PrefixRegex([]string{\"wiz\"}) + `\\b([a-zA-Z0-9]{53})\\b`)\n\tsecretPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"wiz\"}) + `\\b([a-zA-Z0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"wiz\"}\n}\n\n// FromData will find and optionally verify Wiz secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tidMatches := make(map[string]struct{})\n\tfor _, match := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tidMatches[match[1]] = struct{}{}\n\t}\n\n\tsecretMatches := make(map[string]struct{})\n\tfor _, match := range secretPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tsecretMatches[match[1]] = struct{}{}\n\t}\n\n\tfor idMatch := range idMatches {\n\t\tfor secretMatch := range secretMatches {\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_Wiz,\n\t\t\t\tRaw:          []byte(idMatch),\n\t\t\t\tRawV2:        []byte(idMatch + secretMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tclient := s.client\n\t\t\t\tif client == nil {\n\t\t\t\t\tclient = defaultClient\n\t\t\t\t}\n\n\t\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, idMatch, secretMatch)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.ExtraData = extraData\n\t\t\t\ts1.SetVerificationError(verificationErr, idMatch, secretMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\n\t\t\t// 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.\n\t\t\tif s1.Verified {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, clientID, clientSecret string) (bool, map[string]string, error) {\n\tauthData := url.Values{}\n\tauthData.Set(\"grant_type\", \"client_credentials\")\n\tauthData.Set(\"audience\", \"wiz-api\")\n\tauthData.Set(\"client_id\", clientID)\n\tauthData.Set(\"client_secret\", clientSecret)\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://auth.app.wiz.io/oauth/token\", strings.NewReader(authData.Encode()))\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Add(\"Encoding\", \"UTF-8\")\n\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t// If the endpoint returns useful information, we can return it as a map.\n\t\treturn true, nil, nil\n\t} else if res.StatusCode == 401 {\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\t} else {\n\t\terr = fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t\treturn false, nil, err\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Wiz\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Wiz is a cloud security platform. Wiz credentials can be used to access and manage cloud security configurations.\"\n}\n"
  },
  {
    "path": "pkg/detectors/wiz/wiz_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage wiz\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWiz_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WIZ\")\n\tinactiveSecret := testSecrets.MustGetField(\"WIZ_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a wiz secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Wiz,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Wiz,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a wiz secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Wiz,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a wiz secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Wiz,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Wiz.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Wiz.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/wiz/wiz_test.go",
    "content": "package wiz\n\nimport (\n\t\"context\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"testing\"\n)\n\nfunc TestWiz_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname: \"typical pattern\",\n\t\t\tinput: `\nwiz_client_id = 'lmSlx1fe6yCfwAbDa8pMp9sJDM9rZzDblmSlx1fe6yCfwAbDa8pMp'\nwiz_client_secret = 'lmSlx1fe6yCfwAbDa8pMp9sJDM9rZzDblmSlx1fe6yCfwAbDa8pMp9sJDM9rZzDb'\n`,\n\t\t\twant: []string{\n\t\t\t\t\"lmSlx1fe6yCfwAbDa8pMp9sJDM9rZzDblmSlx1fe6yCfwAbDa8pMp\" +\n\t\t\t\t\t\"lmSlx1fe6yCfwAbDa8pMp9sJDM9rZzDblmSlx1fe6yCfwAbDa8pMp9sJDM9rZzDb\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/worksnaps/worksnaps.go",
    "content": "package worksnaps\n\nimport (\n\t\"context\"\n\tb64 \"encoding/base64\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"worksnaps\"}) + `\\b([0-9A-Za-z]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"worksnaps\"}\n}\n\n// FromData will find and optionally verify Worksnaps secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Worksnaps,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tdata := fmt.Sprintf(\"%s:ignored\", resMatch)\n\t\t\tsEnc := b64.StdEncoding.EncodeToString([]byte(data))\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.worksnaps.com/api/projects.xml\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Basic %s\", sEnc))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Worksnaps\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/worksnaps/worksnaps_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage worksnaps\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWorksnaps_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WORKSNAPS\")\n\tinactiveSecret := testSecrets.MustGetField(\"WORKSNAPS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a worksnaps secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Worksnaps,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Worksnaps,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Worksnaps.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Worksnaps.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/worksnaps/worksnaps_test.go",
    "content": "package worksnaps\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"YrJteqAWhy6iB8sds3g8LOWopzyUqiyoglEaP9zD\"\n\tinvalidPattern = \"YrJteqAWhy6iB8sds3g8?OWopzyUqiyoglEaP9zD\"\n\tkeyword        = \"worksnaps\"\n)\n\nfunc TestWorksnaps_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword worksnaps\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/workstack/workstack.go",
    "content": "package workstack\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"workstack\"}) + `\\b([0-9Aa-zA-Z]{60})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"workstack\"}\n}\n\n// FromData will find and optionally verify Workstack secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Workstack,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app.workstack.io/api/team?api_token=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Accept\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Workstack\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Workstack is a project management tool. Workstack API keys can be used to access and modify project data and manage tasks.\"\n}\n"
  },
  {
    "path": "pkg/detectors/workstack/workstack_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage workstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWorkstack_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WORKSTACK\")\n\tinactiveSecret := testSecrets.MustGetField(\"WORKSTACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a workstack secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Workstack,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Workstack,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Workstack.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Workstack.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/workstack/workstack_test.go",
    "content": "package workstack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"X6JXXwZhJEOwpIdSAlTZm71XkZFjoLbagkybF4A8U3CiKjPsiIih0xAvQbYB\"\n\tinvalidPattern = \"X6JXXwZhJEOwpIdSAlTZm71XkZFjoL?agkybF4A8U3CiKjPsiIih0xAvQbYB\"\n\tkeyword        = \"workstack\"\n)\n\nfunc TestWorkstack_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword workstack\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/worldcoinindex/worldcoinindex.go",
    "content": "package worldcoinindex\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"worldcoinindex\"}) + `\\b([a-zA-Z0-9]{35})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"worldcoinindex\"}\n}\n\n// FromData will find and optionally verify WorldCoinIndex secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_WorldCoinIndex,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://www.worldcoinindex.com/apiservice/ticker?key=%s&label=ethbtc-ltcbtc&fiat=btc\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\t\t\tif err == nil {\n\t\t\t\t\tbodyString := string(bodyBytes)\n\t\t\t\t\tvalidResponse := strings.Contains(bodyString, `\"Markets\"`)\n\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\tif validResponse {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\ts1.Verified = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_WorldCoinIndex\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"WorldCoinIndex is a cryptocurrency market data provider. API keys from WorldCoinIndex can be used to access market data and other information.\"\n}\n"
  },
  {
    "path": "pkg/detectors/worldcoinindex/worldcoinindex_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage worldcoinindex\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWorldCoinIndex_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WORLDCOININDEX\")\n\tinactiveSecret := testSecrets.MustGetField(\"WORLDCOININDEX_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a worldcoinindex secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WorldCoinIndex,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WorldCoinIndex,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"WorldCoinIndex.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"WorldCoinIndex.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/worldcoinindex/worldcoinindex_test.go",
    "content": "package worldcoinindex\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"CQ4tKlZUi74jEckzgck4tTdXlLodOqtHdos\"\n\tinvalidPattern = \"CQ4tKlZUi74jE?kzgck4tTdXlLodOqtHdos\"\n\tkeyword        = \"worldcoinindex\"\n)\n\nfunc TestWorldCoinIndex_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword worldcoinindex\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/worldweather/worldweather.go",
    "content": "package worldweather\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"worldweather\"}) + `\\b([0-9a-z]{31})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"worldweather\"}\n}\n\n// FromData will find and optionally verify Worldweather secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_WorldWeather,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"https://api.worldweatheronline.com/premium/v1/search.ashx?query=LA&key=%s\", resMatch), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_WorldWeather\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Worldweather provides weather data services through its API. The keys can be used to access various weather data endpoints.\"\n}\n"
  },
  {
    "path": "pkg/detectors/worldweather/worldweather_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage worldweather\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWorldweather_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WORLDWEATHER\")\n\tinactiveSecret := testSecrets.MustGetField(\"WORLDWEATHER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a worldweather secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WorldWeather,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_WorldWeather,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Worldweather.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Worldweather.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/worldweather/worldweather_test.go",
    "content": "package worldweather\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"ld9oqdefulr8099jt5tdejksjz7aivh\"\n\tinvalidPattern = \"ld9oqdefulr8099?t5tdejksjz7aivh\"\n\tkeyword        = \"worldweather\"\n)\n\nfunc TestWorldweather_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword worldweather\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/wrike/wrike.go",
    "content": "package wrike\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"wrike\"}) + `\\b(ey[a-zA-Z0-9-._]{333})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"wrike\"}\n}\n\n// FromData will find and optionally verify Wrike secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Wrike,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://app-us2.wrike.com/api/v4/contacts?me=true\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Wrike\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/wrike/wrike_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage wrike\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestWrike_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"WRIKE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"WRIKE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a wrike secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Wrike,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Wrike,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Wrike.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Wrike.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/wrike/wrike_test.go",
    "content": "package wrike\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"eyHrUMIkHqmEhqDBTWcuk5kL.tRiP-uQD9ZZSHfYYzdqRYHgkfiPEplgL1INSyA_c9VgbpVIzvFhVx0vzWzhxv9CdyLTr6FCvmDDrxM5BU0gQdyDGgXosinkyl4Z_7OIbHY08sImeavc-fxQ1OzVo6mqb-bDmxwuVWIE5WXb2bcRRnKotFAa4.pCkZAYDxZd66Yt6fK7HM33G6Rr.ALuDQg2mFdemMy1_WIOh9-0l6b6iP2anAp0CbxpcXlrecGHaMgrgzla41NrfpzWjAKlOwq.hbIKdPLLK0bOvg2m6ETkKdGhfRRHkYAqyYO.EhONITs48Eb21kDH0gc\"\n\tinvalidPattern = \"ey?rUMIkHqmEhqDBTWcuk5kL.tRiP-uQD9ZZSHfYYzdqRYHgkfiPEplgL1INSyA_c9VgbpVIzvFhVx0vzWzhxv9CdyLTr6FCvmDDrxM5BU0gQdyDGgXosinkyl4Z_7OIbHY08sImeavc-fxQ1OzVo6mqb-bDmxwuVWIE5WXb2bcRRnKotFAa4.pCkZAYDxZd66Yt6fK7HM33G6Rr.ALuDQg2mFdemMy1_WIOh9-0l6b6iP2anAp0CbxpcXlrecGHaMgrgzla41NrfpzWjAKlOwq.hbIKdPLLK0bOvg2m6ETkKdGhfRRHkYAqyYO.EhONITs48Eb21kDH0gc\"\n\tkeyword        = \"wrike\"\n)\n\nfunc TestWrike_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword wrike\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/xai/xai.go",
    "content": "package xai\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(xai-[0-9a-zA-Z_]{80})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"xai-\"}\n}\n\n// FromData will find and optionally verify Xai secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatches := make(map[string]struct{})\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tkeyMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range keyMatches {\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_XAI,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, extraData, verificationErr := verifyMatch(ctx, client, match)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.ExtraData = extraData\n\t\t\ts1.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn\n}\n\nfunc verifyMatch(ctx context.Context, client *http.Client, apiKey string) (bool, map[string]string, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.x.ai/v1/api-key\", nil)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.Header.Add(\"Authorization\", \"Bearer \"+apiKey)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\t// Parse the API response for useful information like name and ACLs\n\t\tvar data struct {\n\t\t\tName string   `json:\"name\"`\n\t\t\tAcls []string `json:\"acls\"`\n\t\t}\n\t\tif err := json.NewDecoder(res.Body).Decode(&data); err != nil {\n\t\t\t// The API Key is still verified, but there are parsing errors.\n\t\t\t// Hence, return true for verified along with error.\n\t\t\treturn true, nil, fmt.Errorf(\"failed to decode response: %w\", err)\n\t\t}\n\n\t\taclsStr := strings.Join(data.Acls, \",\")\n\n\t\t// Convert the relevant fields into a map\n\t\tresult := map[string]string{\n\t\t\t\"name\": data.Name,\n\t\t\t\"acls\": aclsStr,\n\t\t}\n\n\t\treturn true, result, nil\n\tcase http.StatusBadRequest, http.StatusUnauthorized:\n\t\t// The secret is determinately not verified (nothing to do)\n\t\treturn false, nil, nil\n\tdefault:\n\t\treturn false, nil, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_XAI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"xAI is an AI company with the mission of advancing scientific discovery and gaining a deeper understanding of our universe.\"\n}\n"
  },
  {
    "path": "pkg/detectors/xai/xai_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage xai\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestXai_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"XAI\")\n\tinactiveSecret := testSecrets.MustGetField(\"XAI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a xai secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_XAI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_XAI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a xai secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_XAI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a xai secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_XAI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Xai.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"ExtraData\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Xai.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/xai/xai_test.go",
    "content": "package xai\n\nimport (\n\t\"context\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"testing\"\n)\n\nfunc TestXai_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern\",\n\t\t\tinput: \"xai_token = 'xai-W5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42Ab'\",\n\t\t\twant:  []string{\"xai-W5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42Ab\"},\n\t\t},\n\t\t{\n\t\t\tname: \"finds all matches\",\n\t\t\tinput: `grok_token1 = 'xai-W5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42Ab'\nxai_token2 = 'xai-W5zbfUkzlXedo7qD42AbBLlRSsyJr1W5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42Ab'`,\n\t\t\twant: []string{\"xai-W5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42Ab\", \"xai-W5zbfUkzlXedo7qD42AbBLlRSsyJr1W5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXedo7qD42Ab\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: \"xai_token = 'xai-W5zbfUkzlXedo7qD42AbBLlRSsyJrOW5zbfUkzlXe'\",\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/yandex/yandex.go",
    "content": "package yandex\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"yandex\"}) + `\\b([a-z0-9A-Z.]{83})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"yandex\"}\n}\n\n// FromData will find and optionally verify Yandex secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Yandex,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://dictionary.yandex.net/api/v1/dicservice.json/getLangs?key=\"+resMatch, nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Yandex\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/yandex/yandex_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage yandex\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestYandex_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"YANDEX\")\n\tinactiveSecret := testSecrets.MustGetField(\"YANDEX_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a yandex secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Yandex,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Yandex,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Yandex.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Yandex.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/yandex/yandex_test.go",
    "content": "package yandex\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"oNKE6BzKsFuO8qvuau.kqvuAuEkFUS5hoEo.rpoD0siIHlzm305uKw6kFDbzT8p8KSHZGs5iVQ0TPDin4Xt\"\n\tinvalidPattern = \"oNKE6BzK?FuO8qvuau.kqvuAuEkFUS5hoEo.rpoD0siIHlzm305uKw6kFDbzT8p8KSHZGs5iVQ0TPDin4Xt\"\n\tkeyword        = \"yandex\"\n)\n\nfunc TestYandex_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword yandex\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/yelp/yelp.go",
    "content": "package yelp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"yelp\"}) + `\\b([a-zA-Z0-9_\\\\=\\.\\-]{128})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"yelp\"}\n}\n\n// FromData will find and optionally verify Yelp secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Yelp,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.yelp.com/v3/businesses/search?term=delis&latitude=37.786882&longitude=-122.399972\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else if res.StatusCode == 401 || res.StatusCode == 403 {\n\t\t\t\t\t// The secret is determinately not verified (nothing to do)\n\t\t\t\t} else {\n\t\t\t\t\ts1.SetVerificationError(fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode), resMatch)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts1.SetVerificationError(err, resMatch)\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Yelp\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/yelp/yelp_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage yelp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestYelp_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"YELP\")\n\tinactiveSecret := testSecrets.MustGetField(\"YELP_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a yelp secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Yelp,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Yelp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a yelp secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Yelp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a yelp secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Yelp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := tt.s\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Yelp.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"SlackWebhook.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/yelp/yelp_test.go",
    "content": "package yelp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"YPtzmBZBThgFVginAOiy_.PirbsD-U5rf=L9HUXS-ZdKhyqACHEig8n0raz9ucrqyXp=afbP5FczH3TtSp7XxyxqVA3LVMIGRaxdFw-yIBbsJYMRDchqHpg1=F=irXh1\"\n\tinvalidPattern = \"Y?tzmBZBThgFVginAOiy_.PirbsD-U5rf=L9HUXS-ZdKhyqACHEig8n0raz9ucrqyXp=afbP5FczH3TtSp7XxyxqVA3LVMIGRaxdFw-yIBbsJYMRDchqHpg1=F=irXh1\"\n\tkeyword        = \"yelp\"\n)\n\nfunc TestYelp_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword yelp\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/youneedabudget/youneedabudget.go",
    "content": "package youneedabudget\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"youneedabudget\"}) + `\\b([0-9a-f]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"youneedabudget\"}\n}\n\n// FromData will find and optionally verify YouNeedABudget secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_YouNeedABudget,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://api.youneedabudget.com/v1/user\", nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_YouNeedABudget\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/youneedabudget/youneedabudget_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage youneedabudget\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestYouNeedABudget_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"YOUNEEDABUDGET\")\n\tinactiveSecret := testSecrets.MustGetField(\"YOUNEEDABUDGET_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a youneedabudget secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_YouNeedABudget,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_YouNeedABudget,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"YouNeedABudget.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"YouNeedABudget.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/youneedabudget/youneedabudget_test.go",
    "content": "package youneedabudget\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"1f80bdfa73f8e9e50445de3a5a52fbe585fafe0e26d6fccf090b11153775c43d\"\n\tinvalidPattern = \"1f?0bdfa73f8e9e50445de3a5a52fbe585fafe0e26d6fccf090b11153775c43d\"\n\tkeyword        = \"youneedabudget\"\n)\n\nfunc TestYouNeedABudget_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword youneedabudget\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/yousign/yousign.go",
    "content": "package yousign\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// docs: https://dev.yousign.com/#api-v3-documentation-new\nconst PROD_URL = \"https://api.yousign.com\"\nconst STAGING_URL = \"https://staging-api.yousign.com\"\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"yousign\"}) + `\\b([0-9a-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"yousign\"}\n}\n\n// FromData will find and optionally verify Yousign secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_YouSign,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"%s/users\", PROD_URL), nil)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\tres, err := client.Do(req)\n\t\t\tif err == nil {\n\t\t\t\tdefer res.Body.Close()\n\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\ts1.Verified = true\n\t\t\t\t} else {\n\t\t\t\t\treq, err = http.NewRequestWithContext(ctx, \"GET\", fmt.Sprintf(\"%s/users\", STAGING_URL), nil)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\treq.Header.Add(\"Content-Type\", \"application/json\")\n\t\t\t\t\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", resMatch))\n\t\t\t\t\tres, err = client.Do(req)\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_YouSign\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/yousign/yousign_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage yousign\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestYousign_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"YOUSIGN\")\n\tinactiveSecret := testSecrets.MustGetField(\"YOUSIGN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a yousign secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_YouSign,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_YouSign,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Yousign.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Yousign.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/yousign/yousign_test.go",
    "content": "package yousign\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"1opov9oy6ycsi675cyj2gp3jkjt8pw7d\"\n\tinvalidPattern = \"1opov9oy6y?si675cyj2gp3jkjt8pw7d\"\n\tkeyword        = \"yousign\"\n)\n\nfunc TestYousign_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword yousign\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/youtubeapikey/youtubeapikey.go",
    "content": "package youtubeapikey\n\nimport (\n\t\"context\"\n\tregexp \"github.com/wasilibs/go-re2\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"youtube\"}) + `\\b([a-zA-Z-0-9_]{39})\\b`)\n\tidPat  = regexp.MustCompile(detectors.PrefixRegex([]string{\"youtube\"}) + `\\b([a-zA-Z-0-9]{24})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"youtube\"}\n}\n\n// FromData will find and optionally verify YoutubeApiKey secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tidmatches := idPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\tfor _, idmatch := range idmatches {\n\t\t\tresIdmatch := strings.TrimSpace(idmatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_YoutubeApiKey,\n\t\t\t\tRaw:          []byte(resMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, \"GET\", \"https://www.googleapis.com/youtube/v3/channelSections?key=\"+resMatch+\"&channelId=\"+resIdmatch, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tres, err := client.Do(req)\n\t\t\t\tif err == nil {\n\t\t\t\t\tdefer res.Body.Close()\n\t\t\t\t\tif res.StatusCode >= 200 && res.StatusCode < 300 {\n\t\t\t\t\t\ts1.Verified = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_YoutubeApiKey\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"YouTube API Keys allow access to various functionalities of the YouTube Data API, enabling operations such as retrieving video details and managing playlists.\"\n}\n"
  },
  {
    "path": "pkg/detectors/youtubeapikey/youtubeapikey_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage youtubeapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestYoutubeApiKey_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"YOUTUBEAPIKEY_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"YOUTUBEAPIKEY_INACTIVE\")\n\tid := testSecrets.MustGetField(\"YOUTUBEAPIKEY_CHANNELID\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a youtube secret %s within youtube %s\", secret, id)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_YoutubeApiKey,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_YoutubeApiKey,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"YoutubeApiKey.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"YoutubeApiKey.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/youtubeapikey/youtubeapikey_test.go",
    "content": "package youtubeapikey\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validKey   = \"8SN8OtkzJ6z2tcFtv93Gl63o97LGGBvYAyJviDg\"\n    invalidKey = \"8SN8OtkzJ6z2tcFtv93?l63o97LGGBvYAyJviDg\"\n    validId    = \"0ifNmkGT6biPToj9TDGYqyFP\"\n    invalidId  = \"0ifNmkG?6biPToj9TDGYqyFP\"\n    keyword    = \"youtubeapikey\"\n)\n\nfunc TestYoutubeApiKey_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword youtubeapikey\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId),\n\t\t\twant:  []string{validKey},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zapierwebhook/zapierwebhook.go",
    "content": "package zapierwebhook\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`(https:\\/\\/hooks\\.zapier\\.com\\/hooks\\/catch\\/[A-Za-z0-9\\/]{16})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"hooks.zapier.com/hooks/catch/\"}\n}\n\n// FromData will find and optionally verify ZapierWebhook secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ZapierWebhook,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyZapierWebhook(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ZapierWebhook\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc verifyZapierWebhook(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://hooks.zapier.com/hooks/catch/\"+key, http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zapierwebhook/zapierwebhook_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage zapierwebhook\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestZapierWebhook_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ZAPIERWEBHOOK_TOKEN\")\n\t// inactiveSecret := testSecrets.MustGetField(\"ZAPIERWEBHOOK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zapierwebhook secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZapierWebhook,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t// {\n\t\t// \tname: \"found, unverified\",\n\t\t// \ts:    Scanner{},\n\t\t// \targs: args{\n\t\t// \t\tctx:    context.Background(),\n\t\t// \t\tdata:   []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\n\t\t// \t\tverify: true,\n\t\t// \t},\n\t\t// \twant: []detectors.Result{\n\t\t// \t\t{\n\t\t// \t\t\tDetectorType: detectorspb.DetectorType_ZapierWebhook,\n\t\t// \t\t\tVerified:   false,\n\t\t// \t\t},\n\t\t// \t},\n\t\t// \twantErr: false,\n\t\t// },\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ZapierWebhook.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ZapierWebhook.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zapierwebhook/zapierwebhook_test.go",
    "content": "package zapierwebhook\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"https://hooks.zapier.com/hooks/catch/bJdWL5uSLdrqauDu\"\n\tinvalidPattern = \"https://hooks.zapier.com/hooks/catch/b=?WL5uSLdrqauDu\"\n\tkeyword        = \"zapierwebhook\"\n)\n\nfunc TestZapierWebhook_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword zapierwebhook\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zendeskapi/zendeskapi.go",
    "content": "package zendeskapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\ttoken  = regexp.MustCompile(detectors.PrefixRegex([]string{\"zendesk\"}) + `([A-Za-z0-9_-]{40})`)\n\temail  = 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`)\n\tdomain = regexp.MustCompile(`\\b([a-zA-Z-0-9]{3,25}\\.zendesk\\.com)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"zendesk\"}\n}\n\n// FromData will find and optionally verify ZendeskApi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tvar uniqueEmails, uniqueTokens, uniqueDomains = make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{})\n\n\tfor _, match := range email.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueEmails[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range token.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueTokens[match[1]] = struct{}{}\n\t}\n\n\tfor _, match := range domain.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueDomains[match[1]] = struct{}{}\n\t}\n\n\tfor token := range uniqueTokens {\n\t\tfor email := range uniqueEmails {\n\t\t\tfor domain := range uniqueDomains {\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZendeskApi,\n\t\t\t\t\tRaw:          []byte(token),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tisVerified, verificationErr := verifyZendesk(ctx, client, email, token, domain)\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\ts1.SetVerificationError(verificationErr, token)\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\n\t\t\t}\n\t\t}\n\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ZendeskApi\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Zendesk is a customer service platform. Zendesk API tokens can be used to access and modify customer service data.\"\n}\n\nfunc verifyZendesk(ctx context.Context, client *http.Client, email, token, domain string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://\"+domain+\"/api/v2/users.json\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// docs: https://developer.zendesk.com/api-reference/introduction/security-and-auth/\n\treq.SetBasicAuth(email+\"/token\", token)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusNotFound:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zendeskapi/zendeskapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage zendeskapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestZendeskApi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\ttoken := testSecrets.MustGetField(\"ZENDESKAPI_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"ZENDESKAPI_INACTIVE\")\n\temail := testSecrets.MustGetField(\"ZENDESK_EMAIL\")\n\tdomain := testSecrets.MustGetField(\"ZENDESK_DOMAIN\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zendesk secret %s within zendesk %s with zendesk %s\", token, email, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZendeskApi,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZendeskApi,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ZendeskApi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ZendeskApi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zendeskapi/zendeskapi_test.go",
    "content": "package zendeskapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n    validToken    = \"9kXe6PUOsazCNF48SK8pzNluINpVQzuuqTArJC9X\"\n    invalidToken  = \"9kXe6PUOsa?CNF48SK8pzNluINpVQzuuqTArJC9X\"\n    validEmail    = \"qT0pYx@yV8H7EmhFDbvH4j.com\"\n    invalidEmail  = \"qT0pYx@yV8H7Em?FDbvH4j.com\"\n    validDomain   = \"yF5j0x7.zendesk.com\"\n    invalidDomain = \"yF5j0x7.z?ndesk.com\"\n    keyword       = \"zendeskapi\"\n)\n\nfunc TestZendeskApi_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword zendeskapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, validToken, keyword, validEmail, keyword, validDomain),\n\t\t\twant:  []string{validToken},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidToken, keyword, invalidEmail, keyword, invalidDomain),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zenkitapi/zenkitapi.go",
    "content": "package zenkitapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"zenkit\"}) + `\\b([0-9a-z]{8}\\-[0-9A-Za-z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"zenkit\"}\n}\n\n// FromData will find and optionally verify ZenkitAPI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ZenkitAPI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyZenkitKey(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ZenkitAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc verifyZenkitKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://base.zenkit.com/api/v1/users/me\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Zenkit-API-Key\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zenkitapi/zenkitapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage zenkitapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestZenkitAPI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ZENKITAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"ZENKITAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zenkit secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZenkitAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZenkitAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ZenkitAPI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ZenkitAPI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\ts.FromData(ctx, false, data)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zenkitapi/zenkitapi_test.go",
    "content": "package zenkitapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"6fcehxlk-zTFk2oYJb7YS6Dx2RnU0UIyXHwQ922Ln\"\n\tinvalidPattern = \"6fcehxlk-zTFk2oYJb7Y?6Dx2RnU0UIyXHwQ922Ln\"\n\tkeyword        = \"zenkitapi\"\n)\n\nfunc TestZenkitAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword zenkitapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zenrows/zenrows.go",
    "content": "package zenrows\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"zenrows\"}) + `\\b([0-9a-f]{40})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"zenrows\"}\n}\n\n// FromData will find and optionally verify ZenRows secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ZenRows,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyZenrowsKey(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ZenRows\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc verifyZenrowsKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"https://api.zenrows.com/v1/?apikey=%s&url=https://httpbin.org/anything\", key), http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusPaymentRequired: // docs: https://docs.zenrows.com/api-error-codes#402-payment-required\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zenrows/zenrows_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage zenrows\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestZenRows_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ZENROWS\")\n\tinactiveSecret := testSecrets.MustGetField(\"ZENROWS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zenrows secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZenRows,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZenRows,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ZenRows.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ZenRows.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zenrows/zenrows_test.go",
    "content": "package zenrows\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"1122be45ae1bdd11f40d1ed29a25cbdcc70920c7\"\n\tinvalidPattern = \"?122be45ae1bdd11f40d1ed29a25cbdcc70920c7\"\n\tkeyword        = \"zenrows\"\n)\n\nfunc TestZenRows_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword zenrows\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zenscrape/zenscrape.go",
    "content": "package zenscrape\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"zenscrape\"}\n}\n\n// FromData will find and optionally verify Zenscrape secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Zenscrape,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyZenScrapeKey(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Zenscrape\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc verifyZenScrapeKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://app.zenscrape.com/api/v1/status\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"apikey\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK, http.StatusTooManyRequests:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zenscrape/zenscrape_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage zenscrape\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestZenscrape_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ZENSCRAPE\")\n\tinactiveSecret := testSecrets.MustGetField(\"ZENSCRAPE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zenscrape secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Zenscrape,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Zenscrape,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Zenscrape.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Zenscrape.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zenscrape/zenscrape_test.go",
    "content": "package zenscrape\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"3e687efb-1561-ecee-3d5a-e989224e7276\"\n\tinvalidPattern = \"3e687efb?1561-ecee-3d5a-e989224e7276\"\n\tkeyword        = \"zenscrape\"\n)\n\nfunc TestZenscrape_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword zenscrape\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zenserp/zenserp.go",
    "content": "package zenserp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClientTimeOut(5 * time.Second)\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"zenserp\"}) + `\\b([0-9a-z-]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"zenserp\"}\n}\n\n// FromData will find and optionally verify Zenserp secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Zenserp,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyZenSerpKey(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Zenserp\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Zenserp is a service that provides SERP (Search Engine Results Page) API. Zenserp API keys can be used to access search engine data programmatically.\"\n}\n\nfunc verifyZenSerpKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, `https://app.zenserp.com/api/v2/search?q=\"test\"&apikey=`+key, http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tbodyBytes, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tbody := string(bodyBytes)\n\t\tif strings.Contains(body, \"query\") {\n\t\t\treturn true, nil\n\t\t}\n\n\t\treturn false, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zenserp/zenserp_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage zenserp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestZenserp_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ZENSERP\")\n\tinactiveSecret := testSecrets.MustGetField(\"ZENSERP_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zenserp secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Zenserp,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Zenserp,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Zenserp.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Zenserp.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zenserp/zenserp_test.go",
    "content": "package zenserp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"nleq2hhp9tdp-x8qfucd1t028118t5mk0qge\"\n\tinvalidPattern = \"nleq2hhp9tdp-x8qfu?d1t028118t5mk0qge\"\n\tkeyword        = \"zenserp\"\n)\n\nfunc TestZenserp_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword zenserp\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zeplin/zeplin.go",
    "content": "package zeplin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"zeplin\"}) + `\\b([a-zA-Z0-9-.]{350,400})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"zeplin\"}\n}\n\n// FromData will find and optionally verify Zeplin secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Zeplin,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyZeplinKey(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Zeplin\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Zeplin is a collaboration app for UI designers and front-end developers. Zeplin API keys can be used to access and modify project data.\"\n}\n\nfunc verifyZeplinKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.zeplin.dev/v1/users/me\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Bearer %s\", key))\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zeplin/zeplin_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage zeplin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestZeplin_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ZEPLIN_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"ZEPLIN_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zeplin secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Zeplin,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Zeplin,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Zeplin.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Zeplin.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zeplin/zeplin_test.go",
    "content": "package zeplin\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"bv9qqMUYcE-wGLV4SGIFrlQ2OKRWMC.w18-Mpt3ZfQ3We.RrE1Cpx9-o50H5DFyPlLNhIBni20i6uelit3U5zJf-kxOY2UZjEOw3Nn1I..gsJbav9MhwiwQqITI8Z3vrOKpYLZ6vwib4bfU.NFMhmyxUUsRuOR4whWubCyV9-K7NPQwhRbKgaQJ7iWIx1eJPTD7Hzanr92YpeexiglRULihOhEQWAhbSqt87i5YQBwfQ-s06KsGMmRUgx0DyRbcoo6vzlM7FV.fS7jzWDoSxas1rMBUtp7s9p3gNY77wBdvVm6HrTCy3t85PMzjSZkN6cck44XXFo8KZ3ItVTWcvIxF.7qLTnL\"\n\tinvalidPattern = \"b 9qqMUYcE-wGLV4SGIFrlQ2OKRWMC.w18-Mpt3ZfQ3We.RrE1Cpx9-o50H5DFyPlLNhIBni20i6uelit3U5zJf-kxOY2UZjEOw3Nn1I..gsJbav9MhwiwQqITI8Z3vrOKpYLZ6vwib4bfU.NFMhmyxUUsRuOR4whWubCyV9-K7NPQwhRbKgaQJ7iWIx1eJPTD7Hzanr92YpeexiglRULihOhEQWAhbSqt87i5YQBwfQ-s06KsGMmRUgx0DyRbcoo6vzlM7FV.fS7jzWDoSxas1rMBUtp7s9p3gNY77wBdvVm6HrTCy3t85PMzjSZkN6cck44XXFo8KZ3ItVTWcvIxF.7qLTnL\"\n\tkeyword        = \"zeplin\"\n)\n\nfunc TestZeplin_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword zeplin\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zerobounce/zerobounce.go",
    "content": "package zerobounce\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"zerobounce\"}) + `\\b([a-z0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"zerobounce\"}\n}\n\n// FromData will find and optionally verify Zerobounce secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Zerobounce,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyZeroBounceKey(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Zerobounce\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"Zerobounce is an email validation and verification service. Zerobounce API keys can be used to access and utilize this service.\"\n}\n\nfunc verifyZeroBounceKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.zerobounce.net/v2/activity?email=testemail@email.com&api_key=\"+key, http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tbodyBytes, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tif strings.Contains(string(bodyBytes), \"found\") {\n\t\t\treturn true, nil\n\t\t}\n\n\t\treturn false, nil\n\tcase http.StatusUnauthorized, http.StatusNotFound:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zerobounce/zerobounce_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage zerobounce\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestZerobounce_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ZEROBOUNCE_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"ZEROBOUNCE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zerobounce secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Zerobounce,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Zerobounce,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Zerobounce.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Zerobounce.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zerobounce/zerobounce_test.go",
    "content": "package zerobounce\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"dclyjj1w13g1hwkny2qz47ba6zq10fpn\"\n\tinvalidPattern = \"dcl?jj1w13g1hwkny2qz47ba6zq10fpn\"\n\tkeyword        = \"zerobounce\"\n)\n\nfunc TestZerobounce_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword zerobounce\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zerotier/zerotier.go",
    "content": "package zerotier\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"zerotier\"}) + `\\b([0-9a-zA-Z]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"zerotier\"}\n}\n\n// FromData will find and optionally verify Zerotier secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ZeroTier,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyZerotierKey(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ZeroTier\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\n// API docs: https://docs.zerotier.com/central/v1/\nfunc verifyZerotierKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://api.zerotier.com/api/v1/network\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"token %s\", key))\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zerotier/zerotier_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage zerotier\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestZerotier_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ZEROTIER\")\n\tinactiveSecret := testSecrets.MustGetField(\"ZEROTIER_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zerotier secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZeroTier,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZeroTier,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Zerotier.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\", \"primarySecret\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Zerotier.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zerotier/zerotier_test.go",
    "content": "package zerotier\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"AiO4lTwWm7vdj2D0Zf3xdQbQNN3tSyJB\"\n\tinvalidPattern = \"AiO4lTwWm7vdj2D0?f3xdQbQNN3tSyJB\"\n\tkeyword        = \"zerotier\"\n)\n\nfunc TestZerotier_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword zerotier\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zipapi/zipapi.go",
    "content": "package zipapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat   = regexp.MustCompile(detectors.PrefixRegex([]string{\"zipapi\"}) + `\\b([A-Z0-9a-z]{32})\\b`)\n\temailPat = regexp.MustCompile(common.EmailPattern)\n\tpwordPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"zipapi\"}) + `\\b([a-zA-Z0-9!=@#$%^]{7,})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"zipapi\"}\n}\n\n// FromData will find and optionally verify Zipapi secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tuniqueEmailMatches, uniqueKeyMatches, uniquePassMatches := make(map[string]struct{}), make(map[string]struct{}), make(map[string]struct{})\n\tfor _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueKeyMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor _, match := range pwordPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniquePassMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor keyMatch := range uniqueKeyMatches {\n\t\tfor emailMatch := range uniqueEmailMatches {\n\t\t\tfor passMatch := range uniquePassMatches {\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZipAPI,\n\t\t\t\t\tRaw:          []byte(keyMatch),\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tisVerified, verificationErr := verifyZipAPI(ctx, client, emailMatch, keyMatch, passMatch)\n\t\t\t\t\ts1.Verified = isVerified\n\t\t\t\t\ts1.SetVerificationError(verificationErr, keyMatch)\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ZipAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ZipAPI is a service used to retrieve ZIP code information. ZipAPI keys can be used to access and retrieve this information from their API.\"\n}\n\nfunc verifyZipAPI(ctx context.Context, client *http.Client, email, key, password string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"https://service.zipapi.us/zipcode/90210/?X-API-KEY=%s\", key), http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\treq.SetBasicAuth(email, password)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zipapi/zipapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage zipapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestZipapi_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ZIPAPI\")\n\temail := testSecrets.MustGetField(\"SCANNERS_EMAIL\")\n\tpword := testSecrets.MustGetField(\"SCANNERS_PASS\")\n\tinactiveSecret := testSecrets.MustGetField(\"ZIPAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zipapi secret %s within zipapi email %s and zipapi password %s\", secret, email, pword)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZipAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZipAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Zipapi.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Zipapi.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zipapi/zipapi_test.go",
    "content": "package zipapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern = `\n\t\tzipapi_key = zipapiabcdef1234567890abcdef1234 \n\t\tzipapi_email = zipapi_user@example.com\n\t\tzipapi_pass = zipapiSecurePass123!\n\t`\n\tinvalidPattern = \"abcde/testing@go\"\n)\n\nfunc TestZipapi_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern\",\n\t\t\tinput: validPattern,\n\t\t\twant: []string{\n\t\t\t\t\"zipapiabcdef1234567890abcdef1234\",\n\t\t\t\t\"zipapiabcdef1234567890abcdef1234\",\n\t\t\t\t\"zipapiabcdef1234567890abcdef1234\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"zipapi: %s\", invalidPattern),\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 && test.want != nil {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"expected %d results, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zipbooks/zipbooks.go",
    "content": "package zipbooks\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\temailPat = regexp.MustCompile(common.EmailPattern)\n\tpwordPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"zipbooks\", \"password\"}) + `\\b([a-zA-Z0-9!=@#$%^]{8,})`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"zipbooks\"}\n}\n\n// FromData will find and optionally verify Zipbooks secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tpwordMatches := pwordPat.FindAllStringSubmatch(dataStr, -1)\n\n\tuniqueEmailMatches := make(map[string]struct{})\n\tfor _, match := range emailPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueEmailMatches[strings.TrimSpace(match[1])] = struct{}{}\n\t}\n\n\tfor emailMatch := range uniqueEmailMatches {\n\t\tfor _, pwordMatch := range pwordMatches {\n\t\t\tresPword := strings.TrimSpace(pwordMatch[1])\n\n\t\t\ts1 := detectors.Result{\n\t\t\t\tDetectorType: detectorspb.DetectorType_ZipBooks,\n\t\t\t\tRaw:          []byte(emailMatch),\n\t\t\t}\n\n\t\t\tif verify {\n\t\t\t\tisVerified, verificationErr := verifyZipBooksCredentials(ctx, client, emailMatch, resPword)\n\t\t\t\ts1.Verified = isVerified\n\t\t\t\ts1.SetVerificationError(verificationErr, emailMatch)\n\t\t\t}\n\n\t\t\tresults = append(results, s1)\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ZipBooks\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc verifyZipBooksCredentials(ctx context.Context, client *http.Client, email, password string) (bool, error) {\n\tpayload := strings.NewReader(fmt.Sprintf(`{\"email\": \"%s\", \"password\": \"%s\"}`, email, password))\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, \"https://api.zipbooks.com/v2/auth/login\", payload)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusNotFound: // username or password not found\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zipbooks/zipbooks_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage zipbooks\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestZipbooks_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors2\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\temail := testSecrets.MustGetField(\"SCANNERS_EMAIL\")\n\tpword := testSecrets.MustGetField(\"SCANNERS_PASS\")\n\tinactivePass := testSecrets.MustGetField(\"SCANNERS_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zipbooks email %s within zipbooks password %s\", email, pword)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZipBooks,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZipBooks,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Zipbooks.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Zipbooks.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zipbooks/zipbooks_test.go",
    "content": "package zipbooks\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"SecureP@ss123 / admin@secure.com\"\n\tinvalidPattern = \"abcde/testing@go\"\n)\n\nfunc TestZipBooks_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - zipbooks keyword\",\n\t\t\tinput: fmt.Sprintf(\"zipbooks = %s\", validPattern),\n\t\t\twant:  []string{\"admin@secure.com\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - password keyword\",\n\t\t\tinput: fmt.Sprintf(\"zipbooks-password: %s\", validPattern),\n\t\t\twant:  []string{\"admin@secure.com\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"zipbooks: %s\", invalidPattern),\n\t\t\twant:  nil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 && test.want != nil {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tt.Errorf(\"expected %d results, got %d\", len(test.want), len(results))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zipcodeapi/zipcodeapi.go",
    "content": "package zipcodeapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"zipcodeapi\"}) + `\\b([a-zA-Z0-9]{64})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"zipcodeapi\"}\n}\n\n// FromData will find and optionally verify ZipCodeAPI secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ZipCodeAPI,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyZipCodeAPIKey(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ZipCodeAPI\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ZipCodeAPI provides a service for retrieving zip code information and calculating distances between zip codes. ZipCodeAPI keys can be used to access these services.\"\n}\n\nfunc verifyZipCodeAPIKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"https://www.zipcodeapi.com/rest/%s/distance.json/71601/72959/mile\", key), http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zipcodeapi/zipcodeapi_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage zipcodeapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestZipCodeAPI_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors1\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ZIPCODEAPI\")\n\tinactiveSecret := testSecrets.MustGetField(\"ZIPCODEAPI_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zipcodeapi secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZipCodeAPI,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZipCodeAPI,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ZipCodeAPI.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ZipCodeAPI.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zipcodeapi/zipcodeapi_test.go",
    "content": "package zipcodeapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"8SOdOO0FIJrmO5iNYhyInMT0Dm81a5MHO9ZKwGlkdX04dBwLmrVZtm4JLaGm2Ulq\"\n\tinvalidPattern = \"8S?dOO0FIJrmO5iNYhyInMT0Dm81a5MHO9ZKwGlkdX04dBwLmrVZtm4JLaGm2Ulq\"\n\tkeyword        = \"zipcodeapi\"\n)\n\nfunc TestZipCodeAPI_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword zipcodeapi\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zipcodebase/zipcodebase.go",
    "content": "package zipcodebase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives\n\tkeyPat = 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`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"zipcodebase\"}\n}\n\n// FromData will find and optionally verify Zipcodebase secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_Zipcodebase,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyZipCodeBaseKey(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_Zipcodebase\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n\nfunc verifyZipCodeBaseKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, \"https://app.zipcodebase.com/api/v1/search?codes=10005,10006\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Accept\", \"application/vnd.zipcodebase+json; version=3\")\n\treq.Header.Add(\"apikey\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized, http.StatusForbidden:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zipcodebase/zipcodebase_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage zipcodebase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestZipcodebase_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ZIPCODEBASE\")\n\tinactiveSecret := testSecrets.MustGetField(\"ZIPCODEBASE_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zipcodebase secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Zipcodebase,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_Zipcodebase,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Zipcodebase.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"Zipcodebase.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zipcodebase/zipcodebase_test.go",
    "content": "package zipcodebase\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"aef47d5e-b057-711f-066c-3a45682a3dd7\"\n\tinvalidPattern = \"aef47d5e?b057-711f-066c-3a45682a3dd7\"\n\tkeyword        = \"zipcodebase\"\n)\n\nfunc TestZipcodebase_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword zipcodebase\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zohocrm/zohocrm.go",
    "content": "package zohocrm\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n}\n\ntype UnauthorizedResponseBody struct {\n\tCode string `json:\"code\"`\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = common.SaneHttpClient()\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(`\\b(1000\\.[a-f0-9]{32}\\.[a-f0-9]{32})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"1000.\"}\n}\n\n// FromData will find and optionally verify Zoho CRM secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\tuniqueMatches := make(map[string]struct{})\n\n\tfor _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tuniqueMatches[match[1]] = struct{}{}\n\t}\n\n\tfor match := range uniqueMatches {\n\t\tresult := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ZohoCRM,\n\t\t\tRaw:          []byte(match),\n\t\t}\n\n\t\tif verify {\n\t\t\tclient := s.client\n\t\t\tif client == nil {\n\t\t\t\tclient = defaultClient\n\t\t\t}\n\n\t\t\tisVerified, verificationErr := verifyMatch(ctx, client, match)\n\t\t\tresult.Verified = isVerified\n\t\t\tresult.SetVerificationError(verificationErr, match)\n\t\t}\n\n\t\tresults = append(results, result)\n\t}\n\n\treturn\n}\n\n// Verifies the Zoho CRM API access token by making a GET request to the Zoho CRM API.\nfunc verifyMatch(ctx context.Context, client *http.Client, token string) (bool, error) {\n\tendpoint := \"https://www.zohoapis.com/crm/v7/Leads?fields=Email&per_page=1\"\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Add(\"Authorization\", fmt.Sprintf(\"Zoho-oauthtoken %s\", token))\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\tbodyBytes, err := io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\treturn false, fmt.Errorf(\"failed to read response body: %v\", err)\n\t\t}\n\n\t\tvar responseBody UnauthorizedResponseBody\n\t\terr = json.Unmarshal(bodyBytes, &responseBody)\n\t\tif err != nil {\n\t\t\treturn false, fmt.Errorf(\"failed to parse JSON response: %v\", err)\n\t\t}\n\n\t\tswitch responseBody.Code {\n\t\tcase \"OAUTH_SCOPE_MISMATCH\":\n\t\t\treturn true, nil\n\t\tcase \"INVALID_TOKEN\":\n\t\t\treturn false, nil\n\t\tdefault:\n\t\t\treturn false, fmt.Errorf(\"unexpected error code: %s\", responseBody.Code)\n\t\t}\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ZohoCRM\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/zohocrm/zohocrm_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage zohocrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\n// TestZohocrm_FromChunk verifies the validity of a ZohoCRM access token\n// Note: The token validity test relies on an access token stored in the GCP secret manager.\n// Since Zoho CRM tokens expire after 60 minutes, this test will eventually fail once the token becomes invalid.\n// The official guide linked below can be followed in order to generate a new valid access token:\n// https://www.zoho.com/accounts/protocol/oauth/self-client/authorization-code-flow.html\n\nfunc TestZohocrm_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors5\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ZOHOCRM\")\n\tinactiveSecret := testSecrets.MustGetField(\"ZOHOCRM_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zohocrm secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZohoCRM,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZohoCRM,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zohocrm secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZohoCRM,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zohocrm secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZohoCRM,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Zohocrm.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Zohocrm.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zohocrm/zohocrm_test.go",
    "content": "package zohocrm\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"1000.1fa6966eafbb115624baa4103269e50e.e57d155232227b4e41fa7dd2b88dd4d4\"\n\tinvalidPattern = \"1000.24baa4103269e50e.41fa7dd2b88dd4d4\"\n)\n\nfunc TestZohocrm_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"typical pattern - with keyword zoho crm\",\n\t\t\tinput: fmt.Sprintf(\"zoho crm token = '%s'\", validPattern),\n\t\t\twant:  []string{\"1000.1fa6966eafbb115624baa4103269e50e.e57d155232227b4e41fa7dd2b88dd4d4\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"typical pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"zoho crm token = '%s' | '%s'\", validPattern, validPattern),\n\t\t\twant:  []string{\"1000.1fa6966eafbb115624baa4103269e50e.e57d155232227b4e41fa7dd2b88dd4d4\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"zoho crm = '%s'\", invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zonkafeedback/zonkafeedback.go",
    "content": "package zonkafeedback\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct{}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tclient = common.SaneHttpClient()\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{\"zonka\"}) + `\\b([A-Za-z0-9]{36})\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"zonka\"}\n}\n\n// FromData will find and optionally verify ZonkaFeedback secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tmatches := keyPat.FindAllStringSubmatch(dataStr, -1)\n\n\tfor _, match := range matches {\n\t\tresMatch := strings.TrimSpace(match[1])\n\n\t\ts1 := detectors.Result{\n\t\t\tDetectorType: detectorspb.DetectorType_ZonkaFeedback,\n\t\t\tRaw:          []byte(resMatch),\n\t\t}\n\n\t\tif verify {\n\t\t\tisVerified, verificationErr := verifyZonkaFeedbackKey(ctx, client, resMatch)\n\t\t\ts1.Verified = isVerified\n\t\t\ts1.SetVerificationError(verificationErr, resMatch)\n\t\t}\n\n\t\tresults = append(results, s1)\n\t}\n\n\treturn results, nil\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ZonkaFeedback\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"ZonkaFeedback is a platform for collecting and analyzing customer feedback. The API token can be used to access and manage feedback data.\"\n}\n\nfunc verifyZonkaFeedbackKey(ctx context.Context, client *http.Client, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet,\n\t\t\"https://app-us1.zonkafeedback.com/responses?page=1&limit=25&startDate=2020-05-02&endDate=2020-05-09\", http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Add(\"Z-API-TOKEN\", key)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zonkafeedback/zonkafeedback_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage zonkafeedback\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestZonkaFeedback_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ZONKAFEEDBACK_TOKEN\")\n\tinactiveSecret := testSecrets.MustGetField(\"ZONKAFEEDBACK_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\ts       Scanner\n\t\targs    args\n\t\twant    []detectors.Result\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zonkafeedback secret %s within\", secret)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZonkaFeedback,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZonkaFeedback,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Scanner{}\n\t\t\tgot, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ZonkaFeedback.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tgot[i].Raw = nil\n\t\t\t}\n\t\t\tif diff := pretty.Compare(got, tt.want); diff != \"\" {\n\t\t\t\tt.Errorf(\"ZonkaFeedback.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zonkafeedback/zonkafeedback_test.go",
    "content": "package zonkafeedback\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidPattern   = \"WwpY1WKIBFnxr41nJLLk8nWnCpJ2hFz58Eoa\"\n\tinvalidPattern = \"WwpY1WKIBFnxr41nJ?Lk8nWnCpJ2hFz58Eoa\"\n\tkeyword        = \"zonkafeedback\"\n)\n\nfunc TestZonkaFeedback_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword zonkafeedback\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - ignore duplicate\",\n\t\t\tinput: fmt.Sprintf(\"%s token = '%s' | '%s'\", keyword, validPattern, validPattern),\n\t\t\twant:  []string{validPattern},\n\t\t},\n\t\t{\n\t\t\tname:  \"valid pattern - key out of prefix range\",\n\t\t\tinput: fmt.Sprintf(\"%s keyword is not close to the real key in the data\\n = '%s'\", keyword, validPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s = '%s'\", keyword, invalidPattern),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zulipchat/zulipchat.go",
    "content": "package zulipchat\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\tregexp \"github.com/wasilibs/go-re2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\ntype Scanner struct {\n\tclient *http.Client\n\tdetectors.DefaultMultiPartCredentialProvider\n}\n\n// Ensure the Scanner satisfies the interface at compile time.\nvar _ detectors.Detector = (*Scanner)(nil)\n\nvar (\n\tdefaultClient = detectors.DetectorHttpClientWithNoLocalAddresses\n\n\t// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.\n\tkeyPat    = regexp.MustCompile(common.BuildRegex(common.AlphaNumPattern, \"\", 32))\n\tidPat     = regexp.MustCompile(`\\b([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})\\b`)\n\tdomainPat = regexp.MustCompile(`(?i)\\b([a-z0-9-]+\\.zulip(?:chat)?\\.com|chat\\.zulip\\.org)\\b`)\n)\n\n// Keywords are used for efficiently pre-filtering chunks.\n// Use identifiers in the secret preferably, or the provider name.\nfunc (s Scanner) Keywords() []string {\n\treturn []string{\"zulip\"}\n}\n\n// FromData will find and optionally verify ZulipChat secrets in a given set of bytes.\nfunc (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {\n\tdataStr := string(data)\n\n\tkeyMatches := make(map[string]struct{})\n\tfor _, m := range keyPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tkeyMatches[m[1]] = struct{}{}\n\t}\n\tidMatches := make(map[string]struct{})\n\tfor _, m := range idPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tidMatches[m[1]] = struct{}{}\n\t}\n\tdomainMatches := make(map[string]struct{})\n\tfor _, m := range domainPat.FindAllStringSubmatch(dataStr, -1) {\n\t\tdomainMatches[m[1]] = struct{}{}\n\t}\n\n\tfor key := range keyMatches {\n\t\tfor id := range idMatches {\n\t\t\tfor domain := range domainMatches {\n\t\t\t\ts1 := detectors.Result{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZulipChat,\n\t\t\t\t\tRaw:          []byte(key),\n\t\t\t\t\tRawV2:        []byte(fmt.Sprintf(\"%s:%s:%s\", key, id, domain)),\n\t\t\t\t\tExtraData: map[string]string{\n\t\t\t\t\t\t\"Domain\": domain,\n\t\t\t\t\t\t\"Id\":     id,\n\t\t\t\t\t},\n\t\t\t\t}\n\n\t\t\t\tif verify {\n\t\t\t\t\tclient := s.client\n\t\t\t\t\tif client == nil {\n\t\t\t\t\t\tclient = defaultClient\n\t\t\t\t\t}\n\t\t\t\t\tverified, verificationErr := verifyResult(ctx, client, domain, id, key)\n\t\t\t\t\ts1.Verified = verified\n\t\t\t\t\ts1.SetVerificationError(verificationErr)\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, s1)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\nfunc verifyResult(ctx context.Context, client *http.Client, domain, id, key string) (bool, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"https://%s/api/v1/users\", domain), http.NoBody)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treq.Header.Set(\"Accept\", \"application/json\")\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.SetBasicAuth(id, key)\n\n\tres, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, res.Body)\n\t\t_ = res.Body.Close()\n\t}()\n\n\tswitch res.StatusCode {\n\tcase http.StatusOK:\n\t\tvar users usersResponse\n\t\tif err := json.NewDecoder(res.Body).Decode(&users); err != nil {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn true, nil\n\tcase http.StatusUnauthorized:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, fmt.Errorf(\"unexpected HTTP response status %d\", res.StatusCode)\n\t}\n}\n\ntype usersResponse struct {\n\tResult  string   `json:\"result\"`\n\tMembers []member `json:\"members\"`\n}\n\ntype member struct {\n\tFullName string `json:\"full_name\"`\n\tEmail    string `json:\"email\"`\n}\n\nfunc (s Scanner) Type() detectorspb.DetectorType {\n\treturn detectorspb.DetectorType_ZulipChat\n}\n\nfunc (s Scanner) Description() string {\n\treturn \"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.\"\n}\n"
  },
  {
    "path": "pkg/detectors/zulipchat/zulipchat_integration_test.go",
    "content": "//go:build detectors\n// +build detectors\n\npackage zulipchat\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestZulipChat_FromChunk(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\ttestSecrets, err := common.GetSecret(ctx, \"trufflehog-testing\", \"detectors3\")\n\tif err != nil {\n\t\tt.Fatalf(\"could not get test secrets from GCP: %s\", err)\n\t}\n\tsecret := testSecrets.MustGetField(\"ZULIPCHAT\")\n\tdomain := testSecrets.MustGetField(\"ZULIPCHAT_DOMAINV2\")\n\tid := testSecrets.MustGetField(\"ZULIPCHAT_ID\")\n\tinactiveSecret := testSecrets.MustGetField(\"ZULIPCHAT_INACTIVE\")\n\n\ttype args struct {\n\t\tctx    context.Context\n\t\tdata   []byte\n\t\tverify bool\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\ts                   Scanner\n\t\targs                args\n\t\twant                []detectors.Result\n\t\twantErr             bool\n\t\twantVerificationErr bool\n\t}{\n\t\t{\n\t\t\tname: \"found, verified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zulipchat secret %s within zulipchat %s and zulipchat %s\", secret, id, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZulipChat,\n\t\t\t\t\tVerified:     true,\n\t\t\t\t\tExtraData:    map[string]string{\"Domain\": \"secretscanner.zulipchat.com\", \"Id\": \"knightmoverchan@gmail.com\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, unverified\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []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\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZulipChat,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData:    map[string]string{\"Domain\": \"secretscanner.zulipchat.com\", \"Id\": \"knightmoverchan@gmail.com\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\ts:    Scanner{},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(\"You cannot find the secret within\"),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant:                nil,\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"found, would be verified if not for timeout\",\n\t\t\ts:    Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zulipchat secret %s within zulipchat %s and zulipchat %s\", secret, id, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZulipChat,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData:    map[string]string{\"Domain\": \"secretscanner.zulipchat.com\", \"Id\": \"knightmoverchan@gmail.com\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"found, verified but unexpected api surface\",\n\t\t\ts:    Scanner{client: common.ConstantResponseHttpClient(404, \"\")},\n\t\t\targs: args{\n\t\t\t\tctx:    context.Background(),\n\t\t\t\tdata:   []byte(fmt.Sprintf(\"You can find a zulipchat secret %s within zulipchat %s and zulipchat %s\", secret, id, domain)),\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twant: []detectors.Result{\n\t\t\t\t{\n\t\t\t\t\tDetectorType: detectorspb.DetectorType_ZulipChat,\n\t\t\t\t\tVerified:     false,\n\t\t\t\t\tExtraData:    map[string]string{\"Domain\": \"secretscanner.zulipchat.com\", \"Id\": \"knightmoverchan@gmail.com\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\twantVerificationErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Zulipchat.FromData() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor i := range got {\n\t\t\t\tif len(got[i].Raw) == 0 {\n\t\t\t\t\tt.Fatalf(\"no raw secret present: \\n %+v\", got[i])\n\t\t\t\t}\n\t\t\t\tif (got[i].VerificationError() != nil) != tt.wantVerificationErr {\n\t\t\t\t\tt.Fatalf(\"wantVerificationError = %v, verification error = %v\", tt.wantVerificationErr, got[i].VerificationError())\n\t\t\t\t}\n\t\t\t}\n\t\t\tignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, \"Raw\", \"RawV2\", \"verificationError\")\n\t\t\tif diff := cmp.Diff(got, tt.want, ignoreOpts); diff != \"\" {\n\t\t\t\tt.Errorf(\"Zulipchat.FromData() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkFromData(benchmark *testing.B) {\n\tctx := context.Background()\n\ts := Scanner{}\n\tfor name, data := range detectors.MustGetBenchmarkData() {\n\t\tbenchmark.Run(name, func(b *testing.B) {\n\t\t\tb.ResetTimer()\n\t\t\tfor n := 0; n < b.N; n++ {\n\t\t\t\t_, err := s.FromData(ctx, false, data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tb.Fatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/detectors/zulipchat/zulipchat_test.go",
    "content": "package zulipchat\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n)\n\nvar (\n\tvalidKey      = \"zwoltpBbfT4QjuNst0Gms4TXnK4rMMKX\"\n\tinvalidKey    = \"z?oltpBbfT4QjuNst0Gms4TXnK4rMMKX\"\n\tvalidId       = \"aE5f.L1CIrn4WwIq_YB1DsB-E11p_8azu7x@aujstMgjEY5.com\"\n\tinvalidId     = \"aE5f.L1?Irn4WwIq_YB1DsB-E11p_8azu7x@aujstMgjEY5.com\"\n\tvalidDomain   = \"dwj7s0gr-uedt0sg-ll.zulipchat.com\"\n\tinvalidDomain = \"d?j7s0gr-uedt0sg-ll.zulipchat.com\"\n\tkeyword       = \"zulipchat\"\n)\n\nfunc TestZulipChat_Pattern(t *testing.T) {\n\td := Scanner{}\n\tahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  []string\n\t}{\n\t\t{\n\t\t\tname:  \"valid pattern - with keyword zulipchat\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, validKey, keyword, validId, keyword, validDomain),\n\t\t\twant:  []string{validKey + \":\" + validId + \":\" + validDomain},\n\t\t},\n\t\t{\n\t\t\tname:  \"invalid pattern\",\n\t\t\tinput: fmt.Sprintf(\"%s token - '%s'\\n%s token - '%s'\\n%s token - '%s'\\n\", keyword, invalidKey, keyword, invalidId, keyword, invalidDomain),\n\t\t\twant:  []string{},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tmatchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))\n\t\t\tif len(matchedDetectors) == 0 {\n\t\t\t\tt.Errorf(\"keywords '%v' not matched by: %s\", d.Keywords(), test.input)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tresults, err := d.FromData(context.Background(), false, []byte(test.input))\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif len(results) != len(test.want) {\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tt.Errorf(\"did not receive result\")\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"expected %d results, only received %d\", len(test.want), len(results))\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tactual := make(map[string]struct{}, len(results))\n\t\t\tfor _, r := range results {\n\t\t\t\tif len(r.RawV2) > 0 {\n\t\t\t\t\tactual[string(r.RawV2)] = struct{}{}\n\t\t\t\t} else {\n\t\t\t\t\tactual[string(r.Raw)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\texpected := make(map[string]struct{}, len(test.want))\n\t\t\tfor _, v := range test.want {\n\t\t\t\texpected[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"%s diff: (-want +got)\\n%s\", test.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/ahocorasick/ahocorasickcore.go",
    "content": "package ahocorasick\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\n\tahocorasick \"github.com/BobuSumisu/aho-corasick\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/custom_detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\n// DetectorKey is used to identify a detector in the keywordsToDetectors map.\n// Multiple detectors can have the same detector type but different versions.\n// This allows us to identify a detector by its type and version. An\n// additional (optional) field is provided to disambiguate multiple custom\n// detectors. This type is exported even though none of its fields are so\n// that the AhoCorasickCore can populate passed-in maps keyed on this type\n// without exposing any of its internals to consumers.\ntype DetectorKey struct {\n\tdetectorType       detectorspb.DetectorType\n\tversion            int\n\tcustomDetectorName string\n}\n\nfunc (k DetectorKey) Loggable() map[string]any {\n\tres := map[string]any{\"type\": k.detectorType.String()}\n\tif k.version > 0 {\n\t\tres[\"version\"] = k.version\n\t}\n\tif k.customDetectorName != \"\" {\n\t\tres[\"name\"] = k.customDetectorName\n\t}\n\treturn res\n}\n\n// Type returns the detector type of the key.\nfunc (k DetectorKey) Type() detectorspb.DetectorType { return k.detectorType }\n\n// spanCalculator is an interface that defines a method for calculating a match span\n// in the chunk data. This allows for different strategies to be used without changing the core logic.\ntype spanCalculator interface {\n\tcalculateSpan(params spanCalculationParams) matchSpan\n}\n\n// spanCalculationParams provides the necessary context for calculating match spans,\n// including the keyword index in the chunk, the chunk data itself, and the detector being used.\ntype spanCalculationParams struct {\n\tkeywordIdx int64 // Index of the keyword in the chunk data\n\tchunkData  []byte\n\tdetector   detectors.Detector\n}\n\n// EntireChunkSpanCalculator is a strategy that calculates the match span to use the entire chunk data.\n// This is used when we want to match against the full length of the provided chunk.\ntype EntireChunkSpanCalculator struct{}\n\n// calculateSpan returns the match span as the length of the chunk data,\n// effectively using the entire chunk for matching.\nfunc (e *EntireChunkSpanCalculator) calculateSpan(params spanCalculationParams) matchSpan {\n\treturn matchSpan{startOffset: 0, endOffset: int64(len(params.chunkData))}\n}\n\n// adjustableSpanCalculator is a strategy that calculates match spans. It uses a default offset magnitude\n// or values provided by specific detectors to adjust the start and end indices of the span, allowing\n// for more granular control over the match.\ntype adjustableSpanCalculator struct{ offsetMagnitude int64 }\n\n// newAdjustableSpanCalculator creates a new instance of adjustableSpanCalculator with the\n// specified offset magnitude.\nfunc newAdjustableSpanCalculator(offsetRadius int64) *adjustableSpanCalculator {\n\treturn &adjustableSpanCalculator{offsetMagnitude: offsetRadius}\n}\n\n// calculateSpan computes the match span based on the keyword index and the offset magnitude.\n// If the detector provides an override value, it uses that instead of the default offset magnitude to\n// calculate the maximum size of the span.\n// The start index of the span is also adjusted if the detector provides a start offset.\nfunc (m *adjustableSpanCalculator) calculateSpan(params spanCalculationParams) matchSpan {\n\tkeywordIdx := params.keywordIdx\n\n\tmaxSize := keywordIdx + m.offsetMagnitude\n\tstartOffset := keywordIdx - m.offsetMagnitude\n\n\t// Check if the detector implements each interface and update values accordingly.\n\t// This CAN'T be done in a switch statement because a detector can implement multiple interfaces.\n\tif provider, ok := params.detector.(detectors.MultiPartCredentialProvider); ok {\n\t\tmaxSize = provider.MaxCredentialSpan() + keywordIdx\n\t\tstartOffset = keywordIdx - provider.MaxCredentialSpan()\n\t}\n\tif provider, ok := params.detector.(detectors.MaxSecretSizeProvider); ok {\n\t\tmaxSize = provider.MaxSecretSize() + keywordIdx\n\t}\n\tif provider, ok := params.detector.(detectors.StartOffsetProvider); ok {\n\t\tstartOffset = keywordIdx - provider.StartOffset()\n\t}\n\n\tstartIdx := max(startOffset, 0)\n\tendIdx := min(maxSize, int64(len(params.chunkData)))\n\n\t// Ensure the start index is not greater than the end index to prevent invalid spans.\n\t// In rare cases where the calculated start index exceeds the end index (possibly due to\n\t// detector-provided offsets), we reset the start index to 0 to maintain a valid span range\n\t// and avoid runtime panics. This is a temporary fix until the root cause is identified.\n\tif startIdx >= endIdx {\n\t\tstartIdx = 0\n\t}\n\n\treturn matchSpan{startOffset: startIdx, endOffset: endIdx}\n}\n\n// CoreOption is a functional option type for configuring an AhoCorasickCore instance.\ntype CoreOption func(*Core)\n\n// WithSpanCalculator sets the span calculator for AhoCorasickCore.\nfunc WithSpanCalculator(spanCalculator spanCalculator) CoreOption {\n\treturn func(ac *Core) { ac.spanCalculator = spanCalculator }\n}\n\n// Core encapsulates the operations and data structures used for keyword matching via the\n// Aho-Corasick algorithm. It is responsible for constructing and managing the trie for efficient\n// substring searches, as well as mapping keywords to their associated detectors for rapid lookups.\ntype Core struct {\n\t// prefilter is a ahocorasick struct used for doing efficient string\n\t// matching given a set of words. (keywords from the rules in the config)\n\tprefilter ahocorasick.Trie\n\t// Maps for efficient lookups during detection.\n\t// (This implementation maps in two layers: from keywords to detector\n\t// type and then again from detector type to detector. We could\n\t// go straight from keywords to detectors but doing it this way makes\n\t// some consuming code a little cleaner.)\n\tkeywordsToDetectors map[string][]DetectorKey\n\tdetectorsByKey      map[DetectorKey]detectors.Detector\n\tspanCalculator      spanCalculator // Strategy for calculating match spans\n}\n\n// NewAhoCorasickCore allocates and initializes a new instance of AhoCorasickCore. It uses the\n// provided detector slice to create a map from keywords to detectors and build the Aho-Corasick\n// prefilter trie.\nfunc NewAhoCorasickCore(allDetectors []detectors.Detector, opts ...CoreOption) *Core {\n\tkeywordsToDetectors := make(map[string][]DetectorKey)\n\tdetectorsByKey := make(map[DetectorKey]detectors.Detector, len(allDetectors))\n\tvar keywords []string\n\tfor _, d := range allDetectors {\n\t\tkey := CreateDetectorKey(d)\n\t\tdetectorsByKey[key] = d\n\t\tfor _, kw := range d.Keywords() {\n\t\t\tkwLower := strings.ToLower(kw)\n\t\t\tkeywords = append(keywords, kwLower)\n\t\t\tkeywordsToDetectors[kwLower] = append(keywordsToDetectors[kwLower], key)\n\t\t}\n\t}\n\n\tconst defaultOffsetRadius int64 = 512\n\tcore := &Core{\n\t\tkeywordsToDetectors: keywordsToDetectors,\n\t\tdetectorsByKey:      detectorsByKey,\n\t\tprefilter:           *ahocorasick.NewTrieBuilder().AddStrings(keywords).Build(),\n\t\tspanCalculator:      newAdjustableSpanCalculator(defaultOffsetRadius), // Default span calculator\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(core)\n\t}\n\n\treturn core\n}\n\n// DetectorMatch represents a detected pattern's metadata in a data chunk.\n// It encapsulates the key identifying a specific detector, the detector instance itself,\n// the start and end offsets of the matched keyword in the chunk, and the matched portions of the chunk data.\ntype DetectorMatch struct {\n\tKey DetectorKey\n\tdetectors.Detector\n\tmatchSpans []matchSpan\n\n\t// matches is a slice of byte slices, each representing a matched portion of the chunk data.\n\tmatches [][]byte\n}\n\n// MatchSpan represents a single occurrence of a matched keyword in the chunk.\n// It contains the start and end byte offsets of the matched keyword within the chunk.\ntype matchSpan struct {\n\tstartOffset int64\n\tendOffset   int64\n}\n\n// addMatchSpan adds a match span to the DetectorMatch instance.\nfunc (d *DetectorMatch) addMatchSpan(spans ...matchSpan) {\n\td.matchSpans = append(d.matchSpans, spans...)\n}\n\n// mergeMatches merges overlapping or adjacent matchSpans into a single matchSpan.\n// It updates the matchSpans field with the merged spans.\nfunc (d *DetectorMatch) mergeMatches() {\n\tif len(d.matchSpans) <= 1 {\n\t\treturn\n\t}\n\n\tmerged := make([]matchSpan, 0, len(d.matchSpans))\n\tcurrent := d.matchSpans[0]\n\n\tfor i := 1; i < len(d.matchSpans); i++ {\n\t\tif d.matchSpans[i].startOffset <= current.endOffset {\n\t\t\tif d.matchSpans[i].endOffset > current.endOffset {\n\t\t\t\tcurrent.endOffset = d.matchSpans[i].endOffset\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tmerged = append(merged, current)\n\t\tcurrent = d.matchSpans[i]\n\t}\n\n\tmerged = append(merged, current)\n\td.matchSpans = merged\n}\n\n// extractMatches extracts the matched portions from the chunk data and stores them in the matches field.\nfunc (d *DetectorMatch) extractMatches(chunkData []byte) {\n\td.matches = make([][]byte, len(d.matchSpans))\n\tfor i, m := range d.matchSpans {\n\t\td.matches[i] = chunkData[m.startOffset:m.endOffset]\n\t}\n}\n\n// Matches returns a slice of byte slices, each representing a matched portion of the chunk data.\nfunc (d *DetectorMatch) Matches() [][]byte { return d.matches }\n\n// FindDetectorMatches finds the matching detectors for a given chunk of data using the Aho-Corasick algorithm.\n// It returns a slice of DetectorMatch instances, each containing the detector key, detector,\n// a slice of matchSpans, and the corresponding matched portions of the chunk data.\n//\n// Each matchSpan represents a position in the chunk data where a keyword was found,\n// along with the corresponding span (start and end positions).\n// The span is determined based on the configured spanCalculator strategy.\n// Adjacent or overlapping matches are merged to avoid duplicating or overlapping the matched\n// portions of the chunk data.\n//\n// The matches field contains the actual byte slices of the matched portions from the chunk data.\nfunc (ac *Core) FindDetectorMatches(chunkData []byte) []*DetectorMatch {\n\tmatches := ac.prefilter.Match(bytes.ToLower(chunkData))\n\n\tmatchCount := len(matches)\n\tif matchCount == 0 {\n\t\treturn nil\n\t}\n\n\tdetectorMatches := make(map[DetectorKey]*DetectorMatch)\n\n\tfor _, m := range matches {\n\t\tfor _, k := range ac.keywordsToDetectors[m.MatchString()] {\n\t\t\tif _, exists := detectorMatches[k]; !exists {\n\t\t\t\tdetector := ac.detectorsByKey[k]\n\t\t\t\tdetectorMatches[k] = &DetectorMatch{\n\t\t\t\t\tKey:        k,\n\t\t\t\t\tDetector:   detector,\n\t\t\t\t\tmatchSpans: make([]matchSpan, 0),\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdetectorMatch := detectorMatches[k]\n\t\t\tstartIdx := m.Pos()\n\t\t\tspan := ac.spanCalculator.calculateSpan(\n\t\t\t\tspanCalculationParams{\n\t\t\t\t\tkeywordIdx: startIdx,\n\t\t\t\t\tchunkData:  chunkData,\n\t\t\t\t\tdetector:   detectorMatch.Detector,\n\t\t\t\t},\n\t\t\t)\n\t\t\tdetectorMatch.addMatchSpan(span)\n\t\t}\n\t}\n\n\tuniqueDetectors := make([]*DetectorMatch, 0, len(detectorMatches))\n\tfor _, detectorMatch := range detectorMatches {\n\t\t// Merge overlapping or adjacent match spans.\n\t\tdetectorMatch.mergeMatches()\n\t\tdetectorMatch.extractMatches(chunkData)\n\n\t\tuniqueDetectors = append(uniqueDetectors, detectorMatch)\n\t}\n\n\treturn uniqueDetectors\n}\n\n// CreateDetectorKey creates a unique key for each detector from its type, version, and, for\n// custom regex detectors, its name.\nfunc CreateDetectorKey(d detectors.Detector) DetectorKey {\n\tdetectorType := d.Type()\n\tvar version int\n\tif v, ok := d.(detectors.Versioner); ok {\n\t\tversion = v.Version()\n\t}\n\tvar customDetectorName string\n\tif r, ok := d.(*custom_detectors.CustomRegexWebhook); ok {\n\t\tcustomDetectorName = r.GetName()\n\t}\n\treturn DetectorKey{detectorType: detectorType, version: version, customDetectorName: customDetectorName}\n}\n\nfunc (ac *Core) KeywordsToDetectors() map[string][]DetectorKey {\n\treturn ac.keywordsToDetectors\n}\n"
  },
  {
    "path": "pkg/engine/ahocorasick/ahocorasickcore_test.go",
    "content": "package ahocorasick\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/custom_detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/custom_detectorspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nconst TestDetectorType = -1\n\ntype testDetectorV1 struct {\n}\n\nfunc (testDetectorV1) FromData(ctx context.Context, verify bool, data []byte) ([]detectors.Result, error) {\n\treturn make([]detectors.Result, 0), nil\n}\n\nfunc (testDetectorV1) Keywords() []string { return []string{\"a\", \"b\"} }\n\nfunc (testDetectorV1) Type() detectorspb.DetectorType {\n\treturn TestDetectorType\n}\n\nfunc (testDetectorV1) Version() int { return 1 }\n\nfunc (testDetectorV1) Description() string { return \"\" }\n\ntype testDetectorV2 struct {\n}\n\nfunc (testDetectorV2) FromData(ctx context.Context, verify bool, data []byte) ([]detectors.Result, error) {\n\treturn make([]detectors.Result, 0), nil\n}\n\nfunc (testDetectorV2) Keywords() []string {\n\treturn []string{\"a\"}\n}\n\nfunc (testDetectorV2) Type() detectorspb.DetectorType {\n\treturn TestDetectorType\n}\n\nfunc (testDetectorV2) Version() int { return 2 }\n\nfunc (testDetectorV2) Description() string { return \"\" }\n\ntype testDetectorV3 struct {\n}\n\nfunc (testDetectorV3) FromData(ctx context.Context, verify bool, data []byte) ([]detectors.Result, error) {\n\treturn make([]detectors.Result, 0), nil\n}\n\nfunc (testDetectorV3) Keywords() []string {\n\treturn []string{\"truffle\"}\n}\n\nfunc (testDetectorV3) Type() detectorspb.DetectorType {\n\treturn TestDetectorType\n}\n\nfunc (testDetectorV3) Version() int { return 1 }\n\nfunc (testDetectorV3) Description() string { return \"\" }\n\nvar _ detectors.Detector = (*testDetectorV4)(nil)\nvar _ detectors.MultiPartCredentialProvider = (*testDetectorV4)(nil)\nvar _ detectors.StartOffsetProvider = (*testDetectorV4)(nil)\n\ntype testDetectorV4 struct{}\n\nfunc (testDetectorV4) FromData(context.Context, bool, []byte) ([]detectors.Result, error) {\n\treturn make([]detectors.Result, 0), nil\n}\n\nfunc (testDetectorV4) Keywords() []string { return []string{\"password\"} }\n\nfunc (testDetectorV4) Type() detectorspb.DetectorType { return TestDetectorType }\n\nfunc (testDetectorV4) Version() int { return 1 }\n\nfunc (testDetectorV4) Description() string { return \"\" }\n\nfunc (testDetectorV4) MaxCredentialSpan() int64 { return 15 }\n\nfunc (testDetectorV4) StartOffset() int64 { return 5 }\n\nvar _ detectors.Detector = (*testDetectorV5)(nil)\nvar _ detectors.MaxSecretSizeProvider = (*testDetectorV5)(nil)\nvar _ detectors.StartOffsetProvider = (*testDetectorV5)(nil)\n\ntype testDetectorV5 struct{}\n\nfunc (testDetectorV5) FromData(context.Context, bool, []byte) ([]detectors.Result, error) {\n\treturn make([]detectors.Result, 0), nil\n}\n\nfunc (testDetectorV5) Keywords() []string { return []string{\"password\"} }\n\nfunc (testDetectorV5) Type() detectorspb.DetectorType { return TestDetectorType }\n\nfunc (testDetectorV5) Version() int { return 1 }\n\nfunc (testDetectorV5) Description() string { return \"\" }\n\nfunc (testDetectorV5) MaxSecretSize() int64 { return 10 }\n\nfunc (testDetectorV5) StartOffset() int64 { return 3 }\n\nvar _ detectors.Detector = (*testDetectorV6)(nil)\nvar _ detectors.Detector = (*testDetectorV6)(nil)\nvar _ detectors.StartOffsetProvider = (*testDetectorV6)(nil)\n\ntype testDetectorV6 struct{}\n\nfunc (testDetectorV6) FromData(context.Context, bool, []byte) ([]detectors.Result, error) {\n\treturn make([]detectors.Result, 0), nil\n}\n\nfunc (testDetectorV6) Keywords() []string { return []string{\"password\"} }\n\nfunc (testDetectorV6) Type() detectorspb.DetectorType { return TestDetectorType }\n\nfunc (testDetectorV6) Version() int { return 1 }\n\nfunc (testDetectorV6) Description() string { return \"\" }\n\nfunc (testDetectorV6) StartOffset() int64 { return 1 }\n\nvar _ detectors.Detector = (*testDetectorV1)(nil)\nvar _ detectors.Detector = (*testDetectorV2)(nil)\nvar _ detectors.Versioner = (*testDetectorV1)(nil)\nvar _ detectors.Versioner = (*testDetectorV2)(nil)\nvar _ detectors.Versioner = (*testDetectorV3)(nil)\n\nfunc TestAhoCorasickCore_MultipleCustomDetectorsMatchable(t *testing.T) {\n\tcustomDetector1, err := custom_detectors.NewWebhookCustomRegex(&custom_detectorspb.CustomRegex{\n\t\tName:     \"custom detector 1\",\n\t\tKeywords: []string{\"a\"},\n\t\tRegex:    map[string]string{\"\": \"\"},\n\t})\n\tassert.Nil(t, err)\n\n\tcustomDetector2, err := custom_detectors.NewWebhookCustomRegex(&custom_detectorspb.CustomRegex{\n\t\tName:     \"custom detector 2\",\n\t\tKeywords: []string{\"a\"},\n\t\tRegex:    map[string]string{\"\": \"\"},\n\t})\n\tassert.Nil(t, err)\n\n\tallDetectors := []detectors.Detector{customDetector1, customDetector2}\n\n\tac := NewAhoCorasickCore(allDetectors)\n\n\tdts := ac.FindDetectorMatches([]byte(\"a\"))\n\tmatchingDetectors := make([]detectors.Detector, 0, 2)\n\tfor _, d := range dts {\n\t\tmatchingDetectors = append(matchingDetectors, d.Detector)\n\t}\n\tassert.ElementsMatch(t, allDetectors, matchingDetectors)\n}\n\nfunc TestAhoCorasickCore_MultipleDetectorVersionsMatchable(t *testing.T) {\n\tv1 := testDetectorV1{}\n\tv2 := testDetectorV2{}\n\tallDetectors := []detectors.Detector{v1, v2}\n\n\tac := NewAhoCorasickCore(allDetectors)\n\n\tdts := ac.FindDetectorMatches([]byte(\"a\"))\n\tmatchingDetectors := make([]detectors.Detector, 0, 2)\n\tfor _, d := range dts {\n\t\tmatchingDetectors = append(matchingDetectors, d.Detector)\n\t}\n\tassert.ElementsMatch(t, allDetectors, matchingDetectors)\n}\n\nfunc TestAhoCorasickCore_NoDuplicateDetectorsMatched(t *testing.T) {\n\td := testDetectorV1{}\n\tallDetectors := []detectors.Detector{d}\n\n\tac := NewAhoCorasickCore(allDetectors)\n\n\tdts := ac.FindDetectorMatches([]byte(\"a a b b\"))\n\tmatchingDetectors := make([]detectors.Detector, 0, 2)\n\tfor _, d := range dts {\n\t\tmatchingDetectors = append(matchingDetectors, d.Detector)\n\t}\n\tassert.ElementsMatch(t, allDetectors, matchingDetectors)\n}\n\nfunc TestFindDetectorMatches(t *testing.T) {\n\ttestCases := []struct {\n\t\tname           string\n\t\topts           []CoreOption\n\t\tdetectors      []detectors.Detector\n\t\tsampleData     string\n\t\texpectedResult map[DetectorKey][][]int64\n\t}{\n\n\t\t{\n\t\t\tname: \"single matchSpan\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\ttestDetectorV3{},\n\t\t\t},\n\t\t\tsampleData: \"This is a sample data containing keyword truffle\",\n\t\t\texpectedResult: map[DetectorKey][][]int64{\n\t\t\t\tCreateDetectorKey(testDetectorV3{}): {{0, 48}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Multiple matches overlapping\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\ttestDetectorV1{},\n\t\t\t},\n\t\t\tsampleData: \"This is a sample data containing keyword a\",\n\t\t\texpectedResult: map[DetectorKey][][]int64{\n\t\t\t\tCreateDetectorKey(testDetectorV1{}): {{0, 42}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Multiple matches\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\ttestDetectorV2{},\n\t\t\t},\n\t\t\tsampleData: `This is the first occurrence of the letter a.\n                 Lorem ipsum dolor sit met, consectetur dipiscing elit. Sed uctor,\n                 mgn bibendum bibendum, ugue ugue tincidunt ugue,\n                 eget ultricies ugue ugue id ugue. Meens liquet libero\n                 c libero molestie, nec mlesud ugue ugue eget. Donec\n                 sed ugue. Sed euismod, ugue sit met liqum lcini,\n                 ugue ugue tincidunt ugue, eget ultricies ugue ugue id\n                 ugue. Meens liquet libero c libero molestie, nec\n                 mlesud ugue ugue eget. Donec sed ugue. Sed euismod,\n                 ugue sit met liqum lcini, ugue ugue tincidunt ugue,\n                 eget ultricies ugue ugue id ugue. Meens liquet libero\n                 c libero molestie, nec mlesud ugue ugue eget. This is the second occurrence of the letter a.`,\n\t\t\texpectedResult: map[DetectorKey][][]int64{\n\t\t\t\tCreateDetectorKey(testDetectorV2{}): {{0, 856}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"single matchSpan; entireSpanChunkCalculator\",\n\t\t\topts: []CoreOption{WithSpanCalculator(&EntireChunkSpanCalculator{})},\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\ttestDetectorV3{},\n\t\t\t},\n\t\t\tsampleData: \"This is a sample data containing keyword truffle\",\n\t\t\texpectedResult: map[DetectorKey][][]int64{\n\t\t\t\tCreateDetectorKey(testDetectorV3{}): {{0, 48}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Multiple matches overlapping; entireSpanChunkCalculator\",\n\t\t\topts: []CoreOption{WithSpanCalculator(&EntireChunkSpanCalculator{})},\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\ttestDetectorV1{},\n\t\t\t},\n\t\t\tsampleData: \"This is a sample data containing keyword a\",\n\t\t\texpectedResult: map[DetectorKey][][]int64{\n\t\t\t\tCreateDetectorKey(testDetectorV1{}): {{0, 42}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Multiple matches; entireSpanChunkCalculator\",\n\t\t\topts: []CoreOption{WithSpanCalculator(&EntireChunkSpanCalculator{})},\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\ttestDetectorV2{},\n\t\t\t},\n\t\t\tsampleData: `This is the first occurrence of the letter a.\n                 Lorem ipsum dolor sit met, consectetur dipiscing elit. Sed uctor,\n                 mgn bibendum bibendum, ugue ugue tincidunt ugue,\n                 eget ultricies ugue ugue id ugue. Meens liquet libero\n                 c libero molestie, nec mlesud ugue ugue eget. Donec\n                 sed ugue. Sed euismod, ugue sit met liqum lcini,\n                 ugue ugue tincidunt ugue, eget ultricies ugue ugue id\n                 ugue. Meens liquet libero c libero molestie, nec\n                 mlesud ugue ugue eget. Donec sed ugue. Sed euismod,\n                 ugue sit met liqum lcini, ugue ugue tincidunt ugue,\n                 eget ultricies ugue ugue id ugue. Meens liquet libero\n                 c libero molestie, nec mlesud ugue ugue eget. This is the second occurrence of the letter a.`,\n\t\t\texpectedResult: map[DetectorKey][][]int64{\n\t\t\t\tCreateDetectorKey(testDetectorV2{}): {{0, 856}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"keyword in the middle of the credential; MultiPartCredentialProvider, StartOffsetProvider\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\ttestDetectorV4{},\n\t\t\t},\n\t\t\tsampleData: \"This is a password in the middle of some data\",\n\t\t\texpectedResult: map[DetectorKey][][]int64{\n\t\t\t\tCreateDetectorKey(testDetectorV4{}): {{5, 25}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"keyword at the end of the credential; MultiPartCredentialProvider, StartOffsetProvider\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\ttestDetectorV4{},\n\t\t\t},\n\t\t\tsampleData: \"This data ends with a password\",\n\t\t\texpectedResult: map[DetectorKey][][]int64{\n\t\t\t\tCreateDetectorKey(testDetectorV4{}): {{17, 30}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"keyword near the start of the data; MultiPartCredentialProvider, StartOffsetProvider\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\ttestDetectorV4{},\n\t\t\t},\n\t\t\tsampleData: \"a password at the start\",\n\t\t\texpectedResult: map[DetectorKey][][]int64{\n\t\t\t\tCreateDetectorKey(testDetectorV4{}): {{0, 17}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"keyword in the middle of the credential; MaxSecretSizeProvider, StartOffsetProvider\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\ttestDetectorV5{},\n\t\t\t},\n\t\t\tsampleData: \"This is a password in the middle of some data\",\n\t\t\texpectedResult: map[DetectorKey][][]int64{\n\t\t\t\tCreateDetectorKey(testDetectorV5{}): {{7, 20}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"keyword at the end of the credential; MaxSecretSizeProvider, StartOffsetProvider\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\ttestDetectorV5{},\n\t\t\t},\n\t\t\tsampleData: \"This data ends with a password\",\n\t\t\texpectedResult: map[DetectorKey][][]int64{\n\t\t\t\tCreateDetectorKey(testDetectorV5{}): {{19, 30}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"keyword near the start of the data; MaxSecretSizeProvider, StartOffsetProvider\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\ttestDetectorV5{},\n\t\t\t},\n\t\t\tsampleData: \"a password at the start\",\n\t\t\texpectedResult: map[DetectorKey][][]int64{\n\t\t\t\tCreateDetectorKey(testDetectorV5{}): {{0, 12}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"keyword in the middle of the credential; StartOffsetProvider\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\ttestDetectorV6{},\n\t\t\t},\n\t\t\tsampleData: \"This is a password in the middle of some data\",\n\t\t\texpectedResult: map[DetectorKey][][]int64{\n\t\t\t\tCreateDetectorKey(testDetectorV6{}): {{9, 45}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"keyword at the end of the credential; StartOffsetProvider\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\ttestDetectorV6{},\n\t\t\t},\n\t\t\tsampleData: \"This data ends with a password\",\n\t\t\texpectedResult: map[DetectorKey][][]int64{\n\t\t\t\tCreateDetectorKey(testDetectorV6{}): {{21, 30}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"keyword near the start of the data; StartOffsetProvider\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\ttestDetectorV6{},\n\t\t\t},\n\t\t\tsampleData: \"a password at the start\",\n\t\t\texpectedResult: map[DetectorKey][][]int64{\n\t\t\t\tCreateDetectorKey(testDetectorV6{}): {{1, 23}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple keyword in the middle of the credential; StartOffsetProvider\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\ttestDetectorV6{},\n\t\t\t},\n\t\t\tsampleData: \"This is a password in the middle of some data, and another password at the end!\",\n\t\t\texpectedResult: map[DetectorKey][][]int64{\n\t\t\t\tCreateDetectorKey(testDetectorV6{}): {{9, 79}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"No matches\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\ttestDetectorV1{},\n\t\t\t\ttestDetectorV2{},\n\t\t\t},\n\t\t\tsampleData:     \"xxy yzz lnnope\",\n\t\t\texpectedResult: map[DetectorKey][][]int64{},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tac := NewAhoCorasickCore(tc.detectors, tc.opts...)\n\t\t\tdetectorMatches := ac.FindDetectorMatches([]byte(tc.sampleData))\n\n\t\t\t// Verify that all matching detectors and their matches are returned.\n\t\t\tfor _, detectorMatch := range detectorMatches {\n\t\t\t\tassert.Contains(t, tc.expectedResult, detectorMatch.Key, \"Expected detector key to be present\")\n\n\t\t\t\texpectedMatches := tc.expectedResult[detectorMatch.Key]\n\t\t\t\tactualMatches := make([][]int64, len(detectorMatch.matchSpans))\n\t\t\t\tfor i, match := range detectorMatch.matchSpans {\n\t\t\t\t\tactualMatches[i] = []int64{match.startOffset, match.endOffset}\n\t\t\t\t}\n\n\t\t\t\tassert.ElementsMatch(t, expectedMatches, actualMatches, \"Expected matches to be returned for the detector\")\n\t\t\t}\n\n\t\t\t// Verify that all expected matches are returned for each detector.\n\t\t\tfor key, expectedMatches := range tc.expectedResult {\n\t\t\t\tvar actualMatches [][]int64\n\t\t\t\tfor _, detectorMatch := range detectorMatches {\n\t\t\t\t\tif detectorMatch.Key == key {\n\t\t\t\t\t\tfor _, match := range detectorMatch.matchSpans {\n\t\t\t\t\t\t\tactualMatches = append(actualMatches, []int64{match.startOffset, match.endOffset})\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tassert.ElementsMatch(t, expectedMatches, actualMatches, \"Expected all matches to be returned for the detector\")\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/circleci.go",
    "content": "package engine\n\nimport (\n\t\"runtime\"\n\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/circleci\"\n)\n\n// ScanCircleCI scans CircleCI logs.\nfunc (e *Engine) ScanCircleCI(ctx context.Context, token string) (sources.JobProgressRef, error) {\n\tconnection := &sourcespb.CircleCI{\n\t\tCredential: &sourcespb.CircleCI_Token{\n\t\t\tToken: token,\n\t\t},\n\t}\n\n\tvar conn anypb.Any\n\terr := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"failed to marshal Circle CI connection\")\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\n\tsourceName := \"trufflehog - Circle CI\"\n\tsourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, circleci.SourceType)\n\n\tcircleSource := &circleci.Source{}\n\tif err := circleSource.Init(ctx, \"trufflehog - Circle CI\", jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\treturn e.sourceManager.EnumerateAndScan(ctx, sourceName, circleSource)\n}\n"
  },
  {
    "path": "pkg/engine/defaults/defaults.go",
    "content": "package defaults\n\nimport (\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/abuseipdb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/abyssale\"\n\taccuweatherv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/accuweather/v1\"\n\taccuweatherv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/accuweather/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/adafruitio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/adzuna\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aeroworkflow\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/agora\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aha\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/airbrakeprojectkey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/airbrakeuserkey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/airship\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/airtableoauth\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/airtablepersonalaccesstoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/airvisual\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aiven\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/alchemy\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/alegra\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aletheiaapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/algoliaadminkey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/alibaba\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/alienvault\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/allsports\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/amadeus\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ambee\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/amplitudeapikey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/anthropic\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/anypoint\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/anypointoauth2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apacta\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/api2cart\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apideck\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apiflash\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apifonica\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apify\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apilayer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apimatic\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apimetrics\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apitemplate\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/appcues\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/appfollow\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/appointedd\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/appoptics\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/appsynergy\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/apptivo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/artifactory\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/artifactoryreferencetoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/artsy\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/asanaoauth\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/asanapersonalaccesstoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/assemblyai\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/atera\"\n\tatlassianv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/atlassian/v1\"\n\tatlassianv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/atlassian/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/audd\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/auth0managementapitoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/auth0oauth\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/autodesk\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/autoklose\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/autopilot\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/avazapersonalaccesstoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aviationstack\"\n\taws_access_keys \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aws/access_keys\"\n\taws_session_keys \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aws/session_keys\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/axonaut\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/aylien\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ayrshare\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_batch\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_cosmosdb\"\n\tazure_entra_refreshtoken \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra/refreshtoken\"\n\tazure_entra_serviceprincipal_v1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra/serviceprincipal/v1\"\n\tazure_entra_serviceprincipal_v2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_entra/serviceprincipal/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_openai\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azure_storage\"\n\tazurerepositorykey \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azureapimanagement/repositorykey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azureapimanagementsubscriptionkey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azureappconfigconnectionstring\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azurecontainerregistry\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azuredevopspersonalaccesstoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azuredirectmanagementkey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azuresastoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azuresearchadminkey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/azuresearchquerykey\"\n\tbannerbearv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bannerbear/v1\"\n\tbannerbearv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bannerbear/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/baremetrics\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/beamer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/beebole\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/besttime\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/betterstack\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/billomat\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bingsubscriptionkey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitbar\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitbucketapppassword\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitcoinaverage\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitfinex\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitlyaccesstoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bitmex\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/blazemeter\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/blitapp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/blogger\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bombbomb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/boostnote\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/borgbase\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/box\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/boxoauth\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/braintreepayments\"\n\tbrandfetchv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/brandfetch/v1\"\n\tbrandfetchv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/brandfetch/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/browserstack\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/browshot\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bscscan\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/buddyns\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/budibase\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bugherd\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bugsnag\"\n\tbuildKitev1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/buildkite/v1\"\n\tbuildKitev2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/buildkite/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bulbul\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/bulksms\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/buttercms\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/caflou\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/calendarific\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/calendlyapikey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/calorieninja\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/campayn\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cannyio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/capsulecrm\"\n\tcaptainDataV1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/captaindata/v1\"\n\tcaptainDataV2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/captaindata/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/carboninterface\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cashboard\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/caspio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/censys\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/centralstationcrm\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cexio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/chartmogul\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/chatbot\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/chatfule\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/checio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/checklyhq\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/checkout\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/checkvist\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cicero\"\n\tcircleciV1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/circleci/v1\"\n\tcircleciV2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/circleci/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clarifai\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clearbit\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clickhelp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clicksendsms\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clickuppersonaltoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cliengo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clientary\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clinchpad\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clockify\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clockworksms\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/closecrm\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudconvert\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudelements\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudflareapitoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudflarecakey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudflareglobalapikey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudimage\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudmersive\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudplan\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloudsmith\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloverly\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cloze\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/clustdoc\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/coda\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/codacy\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/codeclimate\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/codemagic\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/codequiry\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/coinapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/coinbase\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/coinlayer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/coinlib\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/collect2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/column\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/commercejs\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/commodities\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/companyhub\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/confluent\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/contentfulpersonalaccesstoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/conversiontools\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/convertapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/convertkit\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/convier\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/copper\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/couchbase\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/countrylayer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/courier\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/coveralls\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/craftmypdf\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/crowdin\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/cryptocompare\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/currencycloud\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/currencyfreaks\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/currencylayer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/currencyscoop\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/currentsapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/customerguru\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/customerio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/d7network\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dandelion\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dareboost\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/databox\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/databrickstoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/datadogtoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/datagov\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/deepai\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/deepgram\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/deepseek\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/delighted\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/demio\"\n\tdenodeploy \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/deno\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/deputy\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/detectify\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/detectlanguage\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dfuse\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/diffbot\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/diggernaut\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/digitaloceantoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/digitaloceanv2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/discordbottoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/discordwebhook\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/disqus\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ditto\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dnscheck\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/docker\"\n\tdockerhubv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dockerhub/v1\"\n\tdockerhubv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dockerhub/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/docparser\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/documo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/docusign\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/doppler\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dotdigital\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dovico\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dronahq\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/droneci\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dropbox\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/duply\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dwolla\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dynalist\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/dyspatch\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/eagleeyenetworks\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/easyinsight\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ecostruxureit\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/edamam\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/edenai\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/eightxeight\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/elasticemail\"\n\televenlabsv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/elevenlabs/v1\"\n\televenlabsv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/elevenlabs/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/enablex\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/endorlabs\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/enigma\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/envoyapikey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/eraser\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/etherscan\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ethplorer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/eventbrite\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/everhour\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/exchangerateapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/exchangeratesapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/exportsdk\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/extractorapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/facebookoauth\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/faceplusplus\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fastforex\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fastlypersonaltoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/feedier\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fetchrss\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fibery\"\n\tfigmapersonalaccesstokenv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/figmapersonalaccesstoken/v1\"\n\tfigmapersonalaccesstokenv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/figmapersonalaccesstoken/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fileio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/finage\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/financialmodelingprep\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/findl\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/finnhub\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fixerio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flatio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fleetbase\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flexport\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flickr\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flightapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flightlabs\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flightstats\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/float\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flowflu\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flutterwave\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/flyio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fmfw\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/formbucket\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/formcraft\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/formio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/formsite\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/foursquare\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/frameio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/freshbooks\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/freshdesk\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/front\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ftp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fulcrum\"\n\tfullstoryv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fullstory/v1\"\n\tfullstoryv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fullstory/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/fxmarket\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gcp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gcpapplicationdefaultcredentials\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/geckoboard\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gemini\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gengo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/geoapify\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/geocode\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/geocodify\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/geocodio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/geoipifi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/getgeoapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/getgist\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/getresponse\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/getsandbox\"\n\tgithubv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/github/v1\"\n\tgithubv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/github/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/github_oauth2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/githubapp\"\n\tgitlabv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gitlab/v1\"\n\tgitlabv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gitlab/v2\"\n\tgitlabv3 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gitlab/v3\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gitter\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/glassnode\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gocanvas\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gocardless\"\n\tgodaddyv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/godaddy/v1\"\n\tgodaddyv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/godaddy/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/goodday\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/googlegemini\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/googleoauth2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/grafana\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/grafanaserviceaccount\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/graphcms\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/graphhopper\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/groovehq\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/groq\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gtmetrix\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/guardianapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gumroad\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gyazo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/happyscribe\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/harness\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/harvest\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hashicorpvaultauth\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hasura\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hellosign\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/helpcrunch\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/helpscout\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hereapi\"\n\theroku_v1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/heroku/v1\"\n\theroku_v2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/heroku/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hiveage\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/holidayapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/holistic\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/honeycomb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/host\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/html2pdf\"\n\thubspot_apikey_v1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hubspot_apikey/v1\"\n\thubspot_apikey_v2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hubspot_apikey/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/huggingface\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/humanity\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hunter\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hybiscus\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hypertrack\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/iconfinder\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/iexapis\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/iexcloud\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/imagekit\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/imagga\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/impala\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/infura\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/insightly\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/instabot\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/instamojo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/intercom\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/interseller\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/intra42\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/intrinio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/invoiceocean\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ip2location\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ipapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ipgeolocation\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ipinfodb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ipquality\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ipstack\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jdbc\"\n\tjiratokenv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jiratoken/v1\"\n\tjiratokenv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jiratoken/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jotform\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jumpcloud\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jupiterone\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/juro\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/jwt\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kanban\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kanbantool\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/karmacrm\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/keenio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kickbox\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/klaviyo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/klipfolio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/knapsackpro\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kontent\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kraken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kucoin\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/kylas\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/langfuse\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/langsmith\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/languagelayer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/larksuite\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/larksuiteapikey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/launchdarkly\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ldap\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/leadfeeder\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/lemlist\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/lemonsqueezy\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/lendflow\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/lessannoyingcrm\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/lexigram\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/linearapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/linenotify\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/linkpreview\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/liveagent\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/livestorm\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/loadmill\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/locationiq\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/loggly\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/loginradius\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/logzio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/lokalisetoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/loyverse\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/lunchmoney\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/luno\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/madkudu\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/magicbell\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mailboxlayer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mailchimp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mailerlite\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mailgun\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mailjetbasicauth\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mailjetsms\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mailmodo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mailsac\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mandrill\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mapbox\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mapquest\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/marketstack\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mattermostpersonaltoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mavenlink\"\n\tmaxmindlicensev1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/maxmindlicense/v1\"\n\tmaxmindlicensev2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/maxmindlicense/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/meaningcloud\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mediastack\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/meistertask\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/meraki\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mesibo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/messagebird\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/metaapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/metabase\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/metrilo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/microsoftteamswebhook\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mindmeister\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/miro\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mite\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mixmax\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mockaroo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/moderation\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/monday\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mongodb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/monkeylearn\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/moonclerk\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/moosend\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/moralis\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mrticktock\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/mux\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/myfreshworks\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/myintervals\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nethunt\"\n\tnetlifyv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/netlify/v1\"\n\tnetlifyv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/netlify/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/netsuite\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/neutrinoapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/newrelicpersonalapikey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/newsapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/newscatcher\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nexmoapikey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nftport\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ngc\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ngrok\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nicereply\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nightfall\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nimble\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/noticeable\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/notion\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nozbeteams\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/npmtoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/npmtokenv2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nugetapikey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/numverify\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nutritionix\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nvapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/nylas\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/oanda\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/okta\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/omnisend\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/onedesk\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/onelogin\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/onepagecrm\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/onesignal\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/onfleet\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/oopspam\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/openai\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/openaiadmin\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/opencagedata\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/openuv\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/openvpn\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/openweather\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/opsgenie\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/optimizely\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/overloop\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/owlbot\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/packagecloud\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pagarme\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pagerdutyapikey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pandadoc\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pandascore\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/paperform\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/paralleldots\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/parsehub\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/parsers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/parseur\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/partnerstack\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pastebin\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/paydirtapp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/paymoapp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/paymongo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/paypaloauth\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/paystack\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pdflayer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pdfshift\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/peopledatalabs\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pepipost\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/percy\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/photoroom\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/phraseaccesstoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pinata\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pipedream\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pipedrive\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pivotaltracker\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pixabay\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/plaidkey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/planetscale\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/planetscaledb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/planviewleankit\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/planyo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/plivo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/podio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pollsapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/poloniex\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/polygon\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/portainer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/portainertoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/positionstack\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/postageapp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/postbacks\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/postgres\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/posthog\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/postman\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/postmark\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/powrbot\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/prefect\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/privacy\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/privatekey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/prodpad\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/prospectcrm\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/protocolsio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/proxycrawl\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pubnubpublishkey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pubnubsubscriptionkey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pulumi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/purestake\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pushbulletapikey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pusherchannelkey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/pypi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/qase\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/qualaroo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/qubole\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rabbitmq\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/railwayapp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ramp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rapidapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rawg\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/razorpay\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/reachmail\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/readme\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/reallysimplesystems\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rebrandly\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rechargepayments\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/redis\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/refiner\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rentman\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/repairshopr\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/replicate\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/replyio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/requestfinance\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/restpackhtmltopdfapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/restpackscreenshotapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/revampcrm\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ringcentral\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ritekit\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/roaring\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/robinhoodcrypto\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rocketreach\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rootly\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/route4me\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rownd\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rubygems\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/runrunit\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/saladcloudapikey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/salesblink\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/salescookie\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/salesflare\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/salesforce\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/salesforceoauth2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/salesforcerefreshtoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/salesmate\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sanity\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/satismeterprojectkey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/satismeterwritekey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/saucelabs\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scalewaykey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scalr\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrapeowl\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scraperapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scraperbox\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrapestack\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrapfly\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrapingant\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrapingbee\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/screenshotapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/screenshotlayer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/scrutinizerci\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/securitytrails\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/segmentapikey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/selectpdf\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/semaphore\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sendbird\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sendbirdorganizationapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sendgrid\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sendinbluev2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sentryorgtoken\"\n\tsentrytokenv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sentrytoken/v1\"\n\tsentrytokenv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sentrytoken/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/serphouse\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/serpstack\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sheety\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sherpadesk\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/shipday\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/shodankey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/shopify\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/shortcut\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/shotstack\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/shutterstock\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/shutterstockoauth\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/signable\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/signalwire\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/signaturit\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/signupgenius\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sigopt\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/simfin\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/simplesat\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/simplynoted\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/simvoly\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sinchmessage\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sirv\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/siteleaf\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/skrappio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/skybiometry\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/slack\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/slackwebhook\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/smartsheets\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/smartystreets\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/smooch\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/snipcart\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/snowflake\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/snykkey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sonarcloud\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sourcegraph\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sourcegraphcody\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/speechtextai\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/splunkobservabilitytoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/spoonacular\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sportsmonk\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sqlserver\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/square\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/squareapp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/squarespace\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/squareup\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sslmate\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/statuscake\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/statuspage\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/statuspal\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/stitchdata\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/stockdata\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/storecove\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/stormboard\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/stormglass\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/storyblok\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/storyblokpersonalaccesstoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/storychief\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/strava\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/streak\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/stripe\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/stripepaymentintent\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/stripo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/stytch\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sugester\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sumologickey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/supabasetoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/supernotesapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/surveyanyplace\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/surveybot\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/surveysparrow\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/survicate\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/swell\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/swiftype\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tableau\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tailscale\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tallyfy\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tatumio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/taxjar\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/teamgate\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/teamworkcrm\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/teamworkdesk\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/teamworkspaces\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/technicalanalysisapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tefter\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/telegrambottoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/teletype\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/telnyx\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/terraformcloudpersonaltoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/testingbot\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/textmagic\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/theoddsapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/thinkific\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/thousandeyes\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ticketmaster\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tickettailor\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tiingo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/timecamp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/timezoneapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tineswebhook\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tmetric\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/todoist\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tokeet\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tomorrowio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tomtom\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tradier\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/transferwise\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/travelpayouts\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/travisci\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/trelloapikey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/trufflehogenterprise\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twelvedata\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twilio\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twilioapikey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twist\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twitch\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twitchaccesstoken\"\n\ttwitterv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twitter/v1\"\n\ttwitterv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twitter/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/twitterconsumerkey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tyntec\"\n\ttypeformv1 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/typeform/v1\"\n\ttypeformv2 \"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/typeform/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/typetalk\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ubidots\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/uclassify\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/unifyid\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/unplugg\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/unsplash\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/upcdatabase\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/uplead\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/uploadcare\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/uptimerobot\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/upwave\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/uri\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/urlscan\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/userflow\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/userstack\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/vagrantcloudpersonaltoken\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/vatlayer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/vbout\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/vercel\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/verifier\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/verimail\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/veriphone\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/versioneye\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/viewneo\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/virustotal\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/visualcrossing\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/voiceflow\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/voicegain\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/voodoosms\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/vouchery\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/vpnapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/vultrapikey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/vyte\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/walkscore\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/weatherbit\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/weatherstack\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/web3storage\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/webex\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/webexbot\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/webflow\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/webscraper\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/webscraping\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/websitepulse\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/weightsandbiases\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/whoxy\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/wistia\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/wiz\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/worksnaps\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/workstack\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/worldcoinindex\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/worldweather\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/wrike\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/xai\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/yandex\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/yelp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/youneedabudget\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/yousign\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/youtubeapikey\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zendeskapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zenkitapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zenrows\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zenscrape\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zenserp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zeplin\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zerobounce\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zerotier\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zipapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zipbooks\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zipcodeapi\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zipcodebase\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zohocrm\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zonkafeedback\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/zulipchat\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc buildDetectorList() []detectors.Detector {\n\treturn []detectors.Detector{\n\t\t&abyssale.Scanner{},\n\t\t// &abstract.Scanner{},\n\t\t&abuseipdb.Scanner{},\n\t\t&accuweatherv1.Scanner{},\n\t\t&accuweatherv2.Scanner{},\n\t\t&adafruitio.Scanner{},\n\t\t// &adobeio.Scanner{},\n\t\t&adzuna.Scanner{},\n\t\t&aeroworkflow.Scanner{},\n\t\t&agora.Scanner{},\n\t\t&aha.Scanner{},\n\t\t&airbrakeprojectkey.Scanner{},\n\t\t&airbrakeuserkey.Scanner{},\n\t\t&airship.Scanner{},\n\t\t&airtableoauth.Scanner{},\n\t\t&airtablepersonalaccesstoken.Scanner{},\n\t\t&airvisual.Scanner{},\n\t\t&aiven.Scanner{},\n\t\t&alchemy.Scanner{},\n\t\t// The service currently has blocked requests with a \"TruffleHog\" UserAgent.\n\t\t// &alconost.Scanner{},\n\t\t&alegra.Scanner{},\n\t\t&aletheiaapi.Scanner{},\n\t\t&algoliaadminkey.Scanner{},\n\t\t&alibaba.Scanner{},\n\t\t&alienvault.Scanner{},\n\t\t&allsports.Scanner{},\n\t\t&amadeus.Scanner{},\n\t\t&ambee.Scanner{},\n\t\t&amplitudeapikey.Scanner{},\n\t\t&anthropic.Scanner{},\n\t\t&anypoint.Scanner{},\n\t\t&anypointoauth2.Scanner{},\n\t\t&apacta.Scanner{},\n\t\t&api2cart.Scanner{},\n\t\t&apideck.Scanner{},\n\t\t&apiflash.Scanner{},\n\t\t&apifonica.Scanner{},\n\t\t&apify.Scanner{},\n\t\t&apilayer.Scanner{},\n\t\t&apimatic.Scanner{},\n\t\t&apimetrics.Scanner{},\n\t\t&apitemplate.Scanner{},\n\t\t// &apollo.Scanner{},\n\t\t&appcues.Scanner{},\n\t\t&appfollow.Scanner{},\n\t\t&appointedd.Scanner{},\n\t\t&appoptics.Scanner{},\n\t\t&appsynergy.Scanner{},\n\t\t&apptivo.Scanner{},\n\t\t&artifactory.Scanner{},\n\t\t&artifactoryreferencetoken.Scanner{},\n\t\t&artsy.Scanner{},\n\t\t&asanaoauth.Scanner{},\n\t\t&asanapersonalaccesstoken.Scanner{},\n\t\t&assemblyai.Scanner{},\n\t\t&atera.Scanner{},\n\t\t&atlassianv1.Scanner{},\n\t\t&atlassianv2.Scanner{},\n\t\t&audd.Scanner{},\n\t\t&auth0managementapitoken.Scanner{},\n\t\t&auth0oauth.Scanner{},\n\t\t&autodesk.Scanner{},\n\t\t&autoklose.Scanner{},\n\t\t&autopilot.Scanner{},\n\t\t&avazapersonalaccesstoken.Scanner{},\n\t\t&aviationstack.Scanner{},\n\t\taws_access_keys.New(),\n\t\taws_session_keys.New(),\n\t\t&axonaut.Scanner{},\n\t\t&aylien.Scanner{},\n\t\t&ayrshare.Scanner{},\n\t\t&azureapimanagementsubscriptionkey.Scanner{},\n\t\t&azure_entra_refreshtoken.Scanner{},\n\t\t&azure_entra_serviceprincipal_v1.Scanner{},\n\t\t&azure_entra_serviceprincipal_v2.Scanner{},\n\t\t&azure_batch.Scanner{},\n\t\t&azureappconfigconnectionstring.Scanner{},\n\t\t&azure_cosmosdb.Scanner{},\n\t\t&azurecontainerregistry.Scanner{},\n\t\t&azuredevopspersonalaccesstoken.Scanner{},\n\t\t&azuredirectmanagementkey.Scanner{},\n\t\t// &azurefunctionkey.Scanner{}, // detector is throwing some FPs\n\t\t&azure_openai.Scanner{},\n\t\t&azuresastoken.Scanner{},\n\t\t&azuresearchadminkey.Scanner{},\n\t\t&azuresearchquerykey.Scanner{},\n\t\t&azure_storage.Scanner{},\n\t\t&azurerepositorykey.Scanner{},\n\t\t&bannerbearv1.Scanner{},\n\t\t&bannerbearv2.Scanner{},\n\t\t&baremetrics.Scanner{},\n\t\t&beamer.Scanner{},\n\t\t&beebole.Scanner{},\n\t\t// Besnappy appears to be abandoned. The domain has expired. API returns 200 OK for all secrets causing FPs\n\t\t// &besnappy.Scanner{},\n\t\t&besttime.Scanner{},\n\t\t&betterstack.Scanner{},\n\t\t&billomat.Scanner{},\n\t\t&bingsubscriptionkey.Scanner{},\n\t\t&bitbar.Scanner{},\n\t\t&bitbucketapppassword.Scanner{},\n\t\t&bitcoinaverage.Scanner{},\n\t\t&bitfinex.Scanner{},\n\t\t&bitlyaccesstoken.Scanner{},\n\t\t&bitmex.Scanner{},\n\t\t&blazemeter.Scanner{},\n\t\t&blitapp.Scanner{},\n\t\t// &blocknative.Scanner{}, // temporary disabled due to API issue\n\t\t&blogger.Scanner{},\n\t\t&bombbomb.Scanner{},\n\t\t&boostnote.Scanner{},\n\t\t&borgbase.Scanner{},\n\t\t&box.Scanner{},\n\t\t&boxoauth.Scanner{},\n\t\t&braintreepayments.Scanner{},\n\t\t&brandfetchv1.Scanner{},\n\t\t&brandfetchv2.Scanner{},\n\t\t&browserstack.Scanner{},\n\t\t&browshot.Scanner{},\n\t\t&bscscan.Scanner{},\n\t\t&buddyns.Scanner{},\n\t\t&budibase.Scanner{},\n\t\t&bugherd.Scanner{},\n\t\t&bugsnag.Scanner{},\n\t\t&buildKitev1.Scanner{},\n\t\t&buildKitev2.Scanner{},\n\t\t&bulbul.Scanner{},\n\t\t&bulksms.Scanner{},\n\t\t&buttercms.Scanner{},\n\t\t&caflou.Scanner{},\n\t\t&calendarific.Scanner{},\n\t\t&calendlyapikey.Scanner{},\n\t\t&calorieninja.Scanner{},\n\t\t&campayn.Scanner{},\n\t\t&cannyio.Scanner{},\n\t\t&capsulecrm.Scanner{},\n\t\t&captainDataV1.Scanner{},\n\t\t&captainDataV2.Scanner{},\n\t\t&carboninterface.Scanner{},\n\t\t&cashboard.Scanner{},\n\t\t&caspio.Scanner{},\n\t\t&censys.Scanner{},\n\t\t&centralstationcrm.Scanner{},\n\t\t&cexio.Scanner{},\n\t\t&chartmogul.Scanner{},\n\t\t&chatbot.Scanner{},\n\t\t&chatfule.Scanner{},\n\t\t&checio.Scanner{},\n\t\t&checklyhq.Scanner{},\n\t\t&checkout.Scanner{},\n\t\t&checkvist.Scanner{},\n\t\t&cicero.Scanner{},\n\t\t&circleciV1.Scanner{},\n\t\t&circleciV2.Scanner{},\n\t\t&clarifai.Scanner{},\n\t\t&clearbit.Scanner{},\n\t\t&clickhelp.Scanner{},\n\t\t&clicksendsms.Scanner{},\n\t\t&clickuppersonaltoken.Scanner{},\n\t\t&cliengo.Scanner{},\n\t\t&clientary.Scanner{},\n\t\t&clinchpad.Scanner{},\n\t\t&clockify.Scanner{},\n\t\t&clockworksms.Scanner{},\n\t\t&closecrm.Scanner{},\n\t\t&cloudconvert.Scanner{},\n\t\t&cloudelements.Scanner{},\n\t\t&cloudflareapitoken.Scanner{},\n\t\t&cloudflarecakey.Scanner{},\n\t\t&cloudflareglobalapikey.Scanner{},\n\t\t&cloudimage.Scanner{},\n\t\t&cloudmersive.Scanner{},\n\t\t&cloudplan.Scanner{},\n\t\t&cloudsmith.Scanner{},\n\t\t&cloverly.Scanner{},\n\t\t&cloze.Scanner{},\n\t\t&clustdoc.Scanner{},\n\t\t&coda.Scanner{},\n\t\t&codacy.Scanner{},\n\t\t&codeclimate.Scanner{},\n\t\t&codemagic.Scanner{},\n\t\t&codequiry.Scanner{},\n\t\t&coinapi.Scanner{},\n\t\t&coinbase.Scanner{},\n\t\t&coinlayer.Scanner{},\n\t\t&coinlib.Scanner{},\n\t\t&collect2.Scanner{},\n\t\t&column.Scanner{},\n\t\t&commercejs.Scanner{},\n\t\t&commodities.Scanner{},\n\t\t&companyhub.Scanner{},\n\t\t&confluent.Scanner{},\n\t\t&contentfulpersonalaccesstoken.Scanner{},\n\t\t&conversiontools.Scanner{},\n\t\t&convertapi.Scanner{},\n\t\t&convertkit.Scanner{},\n\t\t&convier.Scanner{},\n\t\t&copper.Scanner{},\n\t\t&couchbase.Scanner{},\n\t\t&countrylayer.Scanner{},\n\t\t&courier.Scanner{},\n\t\t&coveralls.Scanner{},\n\t\t&craftmypdf.Scanner{},\n\t\t&crowdin.Scanner{},\n\t\t&cryptocompare.Scanner{},\n\t\t&currencycloud.Scanner{},\n\t\t&currencyfreaks.Scanner{},\n\t\t&currencylayer.Scanner{},\n\t\t&currencyscoop.Scanner{},\n\t\t&currentsapi.Scanner{},\n\t\t&customerguru.Scanner{},\n\t\t&customerio.Scanner{},\n\t\t&d7network.Scanner{},\n\t\t// &dailyco.Scanner{},\n\t\t&dandelion.Scanner{},\n\t\t&dareboost.Scanner{},\n\t\t&databox.Scanner{},\n\t\t&databrickstoken.Scanner{},\n\t\t&datadogtoken.Scanner{},\n\t\t&datagov.Scanner{},\n\t\t// &debounce.Scanner{},\n\t\t&deepai.Scanner{},\n\t\t&deepgram.Scanner{},\n\t\t&deepseek.Scanner{},\n\t\t&delighted.Scanner{},\n\t\t&demio.Scanner{},\n\t\t&denodeploy.Scanner{},\n\t\t&deputy.Scanner{},\n\t\t&detectify.Scanner{},\n\t\t&detectlanguage.Scanner{},\n\t\t&dfuse.Scanner{},\n\t\t&diffbot.Scanner{},\n\t\t&diggernaut.Scanner{},\n\t\t&digitaloceantoken.Scanner{},\n\t\t&digitaloceanv2.Scanner{},\n\t\t&discordbottoken.Scanner{},\n\t\t&discordwebhook.Scanner{},\n\t\t&disqus.Scanner{},\n\t\t&ditto.Scanner{},\n\t\t&dnscheck.Scanner{},\n\t\t&docker.Scanner{},\n\t\t&dockerhubv1.Scanner{},\n\t\t&dockerhubv2.Scanner{},\n\t\t&docparser.Scanner{},\n\t\t&documo.Scanner{},\n\t\t&docusign.Scanner{},\n\t\t&doppler.Scanner{},\n\t\t&dotdigital.Scanner{},\n\t\t&dovico.Scanner{},\n\t\t&dronahq.Scanner{},\n\t\t&droneci.Scanner{},\n\t\t&dropbox.Scanner{},\n\t\t&duply.Scanner{},\n\t\t&dwolla.Scanner{},\n\t\t&dynalist.Scanner{},\n\t\t&dyspatch.Scanner{},\n\t\t&eagleeyenetworks.Scanner{},\n\t\t&easyinsight.Scanner{},\n\t\t&ecostruxureit.Scanner{},\n\t\t&edamam.Scanner{},\n\t\t&edenai.Scanner{},\n\t\t&eightxeight.Scanner{},\n\t\t&elasticemail.Scanner{},\n\t\t&elevenlabsv1.Scanner{},\n\t\t&elevenlabsv2.Scanner{},\n\t\t&enablex.Scanner{},\n\t\t&endorlabs.Scanner{},\n\t\t&enigma.Scanner{},\n\t\t&envoyapikey.Scanner{},\n\t\t&eraser.Scanner{},\n\t\t&etherscan.Scanner{},\n\t\t&ethplorer.Scanner{},\n\t\t&eventbrite.Scanner{},\n\t\t&everhour.Scanner{},\n\t\t&exchangerateapi.Scanner{},\n\t\t&exchangeratesapi.Scanner{},\n\t\t&exportsdk.Scanner{},\n\t\t&extractorapi.Scanner{},\n\t\t&facebookoauth.Scanner{},\n\t\t&faceplusplus.Scanner{},\n\t\t&fastforex.Scanner{},\n\t\t&fastlypersonaltoken.Scanner{},\n\t\t&feedier.Scanner{},\n\t\t&fetchrss.Scanner{},\n\t\t&fibery.Scanner{},\n\t\t&figmapersonalaccesstokenv1.Scanner{},\n\t\t&figmapersonalaccesstokenv2.Scanner{},\n\t\t&fileio.Scanner{},\n\t\t&finage.Scanner{},\n\t\t&financialmodelingprep.Scanner{},\n\t\t&findl.Scanner{},\n\t\t&finnhub.Scanner{},\n\t\t&fixerio.Scanner{},\n\t\t&flatio.Scanner{},\n\t\t&fleetbase.Scanner{},\n\t\t&flexport.Scanner{},\n\t\t&flickr.Scanner{},\n\t\t&flightapi.Scanner{},\n\t\t&flightlabs.Scanner{},\n\t\t&flightstats.Scanner{},\n\t\t&float.Scanner{},\n\t\t&flowflu.Scanner{},\n\t\t&flutterwave.Scanner{},\n\t\t&flyio.Scanner{},\n\t\t&fmfw.Scanner{},\n\t\t&formbucket.Scanner{},\n\t\t&formcraft.Scanner{},\n\t\t&formio.Scanner{},\n\t\t&formsite.Scanner{},\n\t\t&foursquare.Scanner{},\n\t\t&frameio.Scanner{},\n\t\t&freshbooks.Scanner{},\n\t\t&freshdesk.Scanner{},\n\t\t&front.Scanner{},\n\t\t&ftp.Scanner{},\n\t\t&fulcrum.Scanner{},\n\t\t&fullstoryv1.Scanner{},\n\t\t&fullstoryv2.Scanner{},\n\t\t&fxmarket.Scanner{},\n\t\t&gcp.Scanner{},\n\t\t&gcpapplicationdefaultcredentials.Scanner{},\n\t\t&geckoboard.Scanner{},\n\t\t&gemini.Scanner{},\n\t\t// &generic.Scanner{},\n\t\t&gengo.Scanner{},\n\t\t&geoapify.Scanner{},\n\t\t&geocode.Scanner{},\n\t\t&geocodify.Scanner{},\n\t\t&geocodio.Scanner{},\n\t\t&geoipifi.Scanner{},\n\t\t// &getemail.Scanner{},\n\t\t// &getemails.Scanner{},\n\t\t&getgeoapi.Scanner{},\n\t\t&getgist.Scanner{},\n\t\t&getresponse.Scanner{},\n\t\t&getsandbox.Scanner{},\n\t\t&github_oauth2.Scanner{},\n\t\t&githubapp.Scanner{},\n\t\t&githubv1.Scanner{},\n\t\t&githubv2.Scanner{},\n\t\t&gitlabv1.Scanner{},\n\t\t&gitlabv2.Scanner{},\n\t\t&gitlabv3.Scanner{},\n\t\t&gitter.Scanner{},\n\t\t&glassnode.Scanner{},\n\t\t&gocanvas.Scanner{},\n\t\t&gocardless.Scanner{},\n\t\t&godaddyv1.Scanner{},\n\t\t&godaddyv2.Scanner{},\n\t\t&goodday.Scanner{},\n\t\t&googlegemini.Scanner{},\n\t\t&googleoauth2.Scanner{},\n\t\t&grafana.Scanner{},\n\t\t&grafanaserviceaccount.Scanner{},\n\t\t&graphcms.Scanner{},\n\t\t&graphhopper.Scanner{},\n\t\t&groovehq.Scanner{},\n\t\t&groq.Scanner{},\n\t\t&gtmetrix.Scanner{},\n\t\t&guardianapi.Scanner{},\n\t\t&gumroad.Scanner{},\n\t\t&gyazo.Scanner{},\n\t\t&happyscribe.Scanner{},\n\t\t&harness.Scanner{},\n\t\t&harvest.Scanner{},\n\t\t&hashicorpvaultauth.Scanner{},\n\t\t&hasura.Scanner{},\n\t\t&hellosign.Scanner{},\n\t\t&helpcrunch.Scanner{},\n\t\t&helpscout.Scanner{},\n\t\t&hereapi.Scanner{},\n\t\t&heroku_v1.Scanner{},\n\t\t&heroku_v2.Scanner{},\n\t\t// &hive.Scanner{},\n\t\t&hiveage.Scanner{},\n\t\t&holidayapi.Scanner{},\n\t\t&holistic.Scanner{},\n\t\t&honeycomb.Scanner{},\n\t\t&host.Scanner{},\n\t\t&html2pdf.Scanner{},\n\t\t&hubspot_apikey_v1.Scanner{},\n\t\t&hubspot_apikey_v2.Scanner{},\n\t\t&huggingface.Scanner{},\n\t\t&humanity.Scanner{},\n\t\t&hunter.Scanner{},\n\t\t&hybiscus.Scanner{},\n\t\t&hypertrack.Scanner{},\n\t\t// &ibmclouduserkey.Scanner{},\n\t\t&iconfinder.Scanner{},\n\t\t&iexapis.Scanner{},\n\t\t&iexcloud.Scanner{},\n\t\t&imagekit.Scanner{},\n\t\t&imagga.Scanner{},\n\t\t&impala.Scanner{},\n\t\t&infura.Scanner{},\n\t\t&insightly.Scanner{},\n\t\t&instabot.Scanner{},\n\t\t&instamojo.Scanner{},\n\t\t&intercom.Scanner{},\n\t\t&interseller.Scanner{},\n\t\t&intra42.Scanner{},\n\t\t&intrinio.Scanner{},\n\t\t&invoiceocean.Scanner{},\n\t\t&ip2location.Scanner{},\n\t\t&ipapi.Scanner{},\n\t\t&ipgeolocation.Scanner{},\n\t\t&ipinfodb.Scanner{},\n\t\t&ipquality.Scanner{},\n\t\t&ipstack.Scanner{},\n\t\t&jdbc.Scanner{},\n\t\t&jiratokenv1.Scanner{},\n\t\t&jiratokenv2.Scanner{},\n\t\t&jotform.Scanner{},\n\t\t&jumpcloud.Scanner{},\n\t\t&jupiterone.Scanner{},\n\t\t&juro.Scanner{},\n\t\t&jwt.Scanner{},\n\t\t&kanban.Scanner{},\n\t\t&kanbantool.Scanner{},\n\t\t&karmacrm.Scanner{},\n\t\t&keenio.Scanner{},\n\t\t&kickbox.Scanner{},\n\t\t&klaviyo.Scanner{},\n\t\t&klipfolio.Scanner{},\n\t\t&knapsackpro.Scanner{},\n\t\t&kontent.Scanner{},\n\t\t&kraken.Scanner{},\n\t\t&kucoin.Scanner{},\n\t\t&kylas.Scanner{},\n\t\t&langfuse.Scanner{},\n\t\t&langsmith.Scanner{},\n\t\t&languagelayer.Scanner{},\n\t\t&larksuite.Scanner{},\n\t\t&larksuiteapikey.Scanner{},\n\t\t&launchdarkly.Scanner{},\n\t\t&ldap.Scanner{},\n\t\t&leadfeeder.Scanner{},\n\t\t&lemlist.Scanner{},\n\t\t&lemonsqueezy.Scanner{},\n\t\t&lendflow.Scanner{},\n\t\t&lessannoyingcrm.Scanner{},\n\t\t&lexigram.Scanner{},\n\t\t&linearapi.Scanner{},\n\t\t// &linemessaging.Scanner{},\n\t\t&linenotify.Scanner{},\n\t\t&linkpreview.Scanner{},\n\t\t&liveagent.Scanner{},\n\t\t&livestorm.Scanner{},\n\t\t&loadmill.Scanner{},\n\t\t&locationiq.Scanner{},\n\t\t&loggly.Scanner{},\n\t\t&loginradius.Scanner{},\n\t\t&logzio.Scanner{},\n\t\t&lokalisetoken.Scanner{},\n\t\t&loyverse.Scanner{},\n\t\t&lunchmoney.Scanner{},\n\t\t&luno.Scanner{},\n\t\t// &m3o.Scanner{},\n\t\t&madkudu.Scanner{},\n\t\t&magicbell.Scanner{},\n\t\t// &magnetic.Scanner{},\n\t\t&mailboxlayer.Scanner{},\n\t\t&mailchimp.Scanner{},\n\t\t&mailerlite.Scanner{},\n\t\t&mailgun.Scanner{},\n\t\t&mailjetbasicauth.Scanner{},\n\t\t&mailjetsms.Scanner{},\n\t\t&mailmodo.Scanner{},\n\t\t&mailsac.Scanner{},\n\t\t&mandrill.Scanner{},\n\t\t// &manifest.Scanner{},\n\t\t&mapbox.Scanner{},\n\t\t&mapquest.Scanner{},\n\t\t&marketstack.Scanner{},\n\t\t&mattermostpersonaltoken.Scanner{},\n\t\t&mavenlink.Scanner{},\n\t\t&maxmindlicensev1.Scanner{},\n\t\t&maxmindlicensev2.Scanner{},\n\t\t&meaningcloud.Scanner{},\n\t\t&mediastack.Scanner{},\n\t\t&meistertask.Scanner{},\n\t\t&meraki.Scanner{},\n\t\t&mesibo.Scanner{},\n\t\t&messagebird.Scanner{},\n\t\t&metaapi.Scanner{},\n\t\t&metabase.Scanner{},\n\t\t&metrilo.Scanner{},\n\t\t&microsoftteamswebhook.Scanner{},\n\t\t&mindmeister.Scanner{},\n\t\t&miro.Scanner{},\n\t\t&mite.Scanner{},\n\t\t&mixmax.Scanner{},\n\t\t// &mixpanel.Scanner{},\n\t\t&mockaroo.Scanner{},\n\t\t&moderation.Scanner{},\n\t\t&monday.Scanner{},\n\t\t&mongodb.Scanner{},\n\t\t&monkeylearn.Scanner{},\n\t\t&moonclerk.Scanner{},\n\t\t&moosend.Scanner{},\n\t\t&moralis.Scanner{},\n\t\t&mrticktock.Scanner{},\n\t\t&mux.Scanner{},\n\t\t&myfreshworks.Scanner{},\n\t\t&myintervals.Scanner{},\n\t\t// &nasdaqdatalink.Scanner{},\n\t\t&nethunt.Scanner{},\n\t\t&netlifyv1.Scanner{},\n\t\t&netlifyv2.Scanner{},\n\t\t&netsuite.Scanner{},\n\t\t&neutrinoapi.Scanner{},\n\t\t&newrelicpersonalapikey.Scanner{},\n\t\t&newsapi.Scanner{},\n\t\t&newscatcher.Scanner{},\n\t\t&nexmoapikey.Scanner{},\n\t\t&nftport.Scanner{},\n\t\t&ngc.Scanner{},\n\t\t&ngrok.Scanner{},\n\t\t&nicereply.Scanner{},\n\t\t&nightfall.Scanner{},\n\t\t&nimble.Scanner{},\n\t\t&noticeable.Scanner{},\n\t\t&notion.Scanner{},\n\t\t&nozbeteams.Scanner{},\n\t\t&npmtoken.Scanner{},\n\t\t&npmtokenv2.Scanner{},\n\t\t&nugetapikey.Scanner{},\n\t\t&numverify.Scanner{},\n\t\t&nutritionix.Scanner{},\n\t\t&nvapi.Scanner{},\n\t\t&nylas.Scanner{},\n\t\t&oanda.Scanner{},\n\t\t&okta.Scanner{},\n\t\t&omnisend.Scanner{},\n\t\t&onedesk.Scanner{},\n\t\t&onelogin.Scanner{},\n\t\t&onepagecrm.Scanner{},\n\t\t&onesignal.Scanner{},\n\t\t&onfleet.Scanner{},\n\t\t&oopspam.Scanner{},\n\t\t&openai.Scanner{},\n\t\t&openaiadmin.Scanner{},\n\t\t&opencagedata.Scanner{},\n\t\t&openuv.Scanner{},\n\t\t&openvpn.Scanner{},\n\t\t&openweather.Scanner{},\n\t\t&opsgenie.Scanner{},\n\t\t&optimizely.Scanner{},\n\t\t&overloop.Scanner{},\n\t\t&owlbot.Scanner{},\n\t\t&packagecloud.Scanner{},\n\t\t&pagarme.Scanner{},\n\t\t&pagerdutyapikey.Scanner{},\n\t\t&pandadoc.Scanner{},\n\t\t&pandascore.Scanner{},\n\t\t&paperform.Scanner{},\n\t\t&paralleldots.Scanner{},\n\t\t&parsehub.Scanner{},\n\t\t&parsers.Scanner{},\n\t\t&parseur.Scanner{},\n\t\t&partnerstack.Scanner{},\n\t\t&pastebin.Scanner{},\n\t\t&paydirtapp.Scanner{},\n\t\t&paymoapp.Scanner{},\n\t\t&paymongo.Scanner{},\n\t\t&paypaloauth.Scanner{},\n\t\t&paystack.Scanner{},\n\t\t&pdflayer.Scanner{},\n\t\t&pdfshift.Scanner{},\n\t\t&peopledatalabs.Scanner{},\n\t\t&pepipost.Scanner{},\n\t\t&percy.Scanner{},\n\t\t&photoroom.Scanner{},\n\t\t&phraseaccesstoken.Scanner{},\n\t\t&pinata.Scanner{},\n\t\t&pipedream.Scanner{},\n\t\t&pipedrive.Scanner{},\n\t\t&pivotaltracker.Scanner{},\n\t\t&pixabay.Scanner{},\n\t\t&plaidkey.Scanner{},\n\t\t&planetscale.Scanner{},\n\t\t&planetscaledb.Scanner{},\n\t\t&planviewleankit.Scanner{},\n\t\t&planyo.Scanner{},\n\t\t&plivo.Scanner{},\n\t\t&podio.Scanner{},\n\t\t&pollsapi.Scanner{},\n\t\t&poloniex.Scanner{},\n\t\t&polygon.Scanner{},\n\t\t&portainer.Scanner{},\n\t\t&portainertoken.Scanner{},\n\t\t&positionstack.Scanner{},\n\t\t&postageapp.Scanner{},\n\t\t&postbacks.Scanner{},\n\t\t&postgres.Scanner{},\n\t\t&posthog.Scanner{},\n\t\t&postman.Scanner{},\n\t\t&postmark.Scanner{},\n\t\t&powrbot.Scanner{},\n\t\t&prefect.Scanner{},\n\t\t&privacy.Scanner{},\n\t\t&privatekey.Scanner{},\n\t\t&prodpad.Scanner{},\n\t\t&prospectcrm.Scanner{},\n\t\t&protocolsio.Scanner{},\n\t\t&proxycrawl.Scanner{},\n\t\t&pubnubpublishkey.Scanner{},\n\t\t&pubnubsubscriptionkey.Scanner{},\n\t\t&pulumi.Scanner{},\n\t\t&purestake.Scanner{},\n\t\t&pushbulletapikey.Scanner{},\n\t\t&pusherchannelkey.Scanner{},\n\t\t&pypi.Scanner{},\n\t\t&qase.Scanner{},\n\t\t&qualaroo.Scanner{},\n\t\t&qubole.Scanner{},\n\t\t&rabbitmq.Scanner{},\n\t\t&railwayapp.Scanner{},\n\t\t&ramp.Scanner{},\n\t\t&rapidapi.Scanner{},\n\t\t// &raven.Scanner{},\n\t\t&rawg.Scanner{},\n\t\t&razorpay.Scanner{},\n\t\t&reachmail.Scanner{},\n\t\t&readme.Scanner{},\n\t\t&reallysimplesystems.Scanner{},\n\t\t&rebrandly.Scanner{},\n\t\t&rechargepayments.Scanner{},\n\t\t&redis.Scanner{},\n\t\t&refiner.Scanner{},\n\t\t&rentman.Scanner{},\n\t\t&repairshopr.Scanner{},\n\t\t&replicate.Scanner{},\n\t\t&replyio.Scanner{},\n\t\t&requestfinance.Scanner{},\n\t\t// &restpack.Scanner{},\n\t\t&restpackhtmltopdfapi.Scanner{},\n\t\t&restpackscreenshotapi.Scanner{},\n\t\t&revampcrm.Scanner{},\n\t\t&ringcentral.Scanner{},\n\t\t&ritekit.Scanner{},\n\t\t&roaring.Scanner{},\n\t\t&robinhoodcrypto.Scanner{},\n\t\t&rocketreach.Scanner{},\n\t\t// &rockset.Scanner{},\n\t\t&rootly.Scanner{},\n\t\t&route4me.Scanner{},\n\t\t&rownd.Scanner{},\n\t\t&rubygems.Scanner{},\n\t\t&runrunit.Scanner{},\n\t\t&saladcloudapikey.Scanner{},\n\t\t&salesblink.Scanner{},\n\t\t&salescookie.Scanner{},\n\t\t&salesflare.Scanner{},\n\t\t&salesforce.Scanner{},\n\t\t&salesforceoauth2.Scanner{},\n\t\t&salesforcerefreshtoken.Scanner{},\n\t\t&salesmate.Scanner{},\n\t\t&sanity.Scanner{},\n\t\t&satismeterprojectkey.Scanner{},\n\t\t&satismeterwritekey.Scanner{},\n\t\t&saucelabs.Scanner{},\n\t\t&scalewaykey.Scanner{},\n\t\t&scalr.Scanner{},\n\t\t&scrapeowl.Scanner{},\n\t\t&scraperapi.Scanner{},\n\t\t&scraperbox.Scanner{},\n\t\t&scrapestack.Scanner{},\n\t\t&scrapfly.Scanner{},\n\t\t&scrapingant.Scanner{},\n\t\t&scrapingbee.Scanner{},\n\t\t&screenshotapi.Scanner{},\n\t\t&screenshotlayer.Scanner{},\n\t\t&scrutinizerci.Scanner{},\n\t\t&securitytrails.Scanner{},\n\t\t&segmentapikey.Scanner{},\n\t\t&selectpdf.Scanner{},\n\t\t&semaphore.Scanner{},\n\t\t&sendbird.Scanner{},\n\t\t&sendbirdorganizationapi.Scanner{},\n\t\t&sendgrid.Scanner{},\n\t\t&sendinbluev2.Scanner{},\n\t\t&sentrytokenv1.Scanner{},\n\t\t&sentrytokenv2.Scanner{},\n\t\t&sentryorgtoken.Scanner{},\n\t\t&serphouse.Scanner{},\n\t\t&serpstack.Scanner{},\n\t\t&sheety.Scanner{},\n\t\t&sherpadesk.Scanner{},\n\t\t&shipday.Scanner{},\n\t\t&shodankey.Scanner{},\n\t\t&shopify.Scanner{},\n\t\t&shortcut.Scanner{},\n\t\t&shotstack.Scanner{},\n\t\t&shutterstock.Scanner{},\n\t\t&shutterstockoauth.Scanner{},\n\t\t&signable.Scanner{},\n\t\t&signalwire.Scanner{},\n\t\t&signaturit.Scanner{},\n\t\t&signupgenius.Scanner{},\n\t\t&sigopt.Scanner{},\n\t\t&simfin.Scanner{},\n\t\t&simplesat.Scanner{},\n\t\t&simplynoted.Scanner{},\n\t\t&simvoly.Scanner{},\n\t\t&sinchmessage.Scanner{},\n\t\t&sirv.Scanner{},\n\t\t&siteleaf.Scanner{},\n\t\t&skrappio.Scanner{},\n\t\t&skybiometry.Scanner{},\n\t\t&slack.Scanner{}, // has 4 secret types\n\t\t&slackwebhook.Scanner{},\n\t\t&smartsheets.Scanner{},\n\t\t&smartystreets.Scanner{},\n\t\t&smooch.Scanner{},\n\t\t&snipcart.Scanner{},\n\t\t&snowflake.Scanner{},\n\t\t&snykkey.Scanner{},\n\t\t&sonarcloud.Scanner{},\n\t\t&sourcegraph.Scanner{},\n\t\t&sourcegraphcody.Scanner{},\n\t\t// &sparkpost.Scanner{},\n\t\t&speechtextai.Scanner{},\n\t\t&splunkobservabilitytoken.Scanner{},\n\t\t&spoonacular.Scanner{},\n\t\t&sportsmonk.Scanner{},\n\t\t// &spotifykey.Scanner{},\n\t\t&sqlserver.Scanner{},\n\t\t&square.Scanner{},\n\t\t&squareapp.Scanner{},\n\t\t&squarespace.Scanner{},\n\t\t&squareup.Scanner{},\n\t\t&sslmate.Scanner{},\n\t\t&statuscake.Scanner{},\n\t\t&statuspage.Scanner{},\n\t\t&statuspal.Scanner{},\n\t\t&stitchdata.Scanner{},\n\t\t&stockdata.Scanner{},\n\t\t&storecove.Scanner{},\n\t\t&stormboard.Scanner{},\n\t\t&stormglass.Scanner{},\n\t\t&storyblok.Scanner{},\n\t\t&storyblokpersonalaccesstoken.Scanner{},\n\t\t&storychief.Scanner{},\n\t\t&strava.Scanner{},\n\t\t&streak.Scanner{},\n\t\t&stripe.Scanner{},\n\t\t&stripepaymentintent.Scanner{},\n\t\t&stripo.Scanner{},\n\t\t&stytch.Scanner{},\n\t\t&sugester.Scanner{},\n\t\t&sumologickey.Scanner{},\n\t\t&supabasetoken.Scanner{},\n\t\t&supernotesapi.Scanner{},\n\t\t&surveyanyplace.Scanner{},\n\t\t&surveybot.Scanner{},\n\t\t&surveysparrow.Scanner{},\n\t\t&survicate.Scanner{},\n\t\t&swell.Scanner{},\n\t\t&swiftype.Scanner{},\n\t\t&tableau.Scanner{},\n\t\t&tailscale.Scanner{},\n\t\t&tallyfy.Scanner{},\n\t\t&tatumio.Scanner{},\n\t\t&taxjar.Scanner{},\n\t\t&teamgate.Scanner{},\n\t\t&teamworkcrm.Scanner{},\n\t\t&teamworkdesk.Scanner{},\n\t\t&teamworkspaces.Scanner{},\n\t\t&technicalanalysisapi.Scanner{},\n\t\t&tefter.Scanner{},\n\t\t&telegrambottoken.Scanner{},\n\t\t&teletype.Scanner{},\n\t\t&telnyx.Scanner{},\n\t\t&terraformcloudpersonaltoken.Scanner{},\n\t\t&testingbot.Scanner{},\n\t\t&textmagic.Scanner{},\n\t\t&theoddsapi.Scanner{},\n\t\t&thinkific.Scanner{},\n\t\t&thousandeyes.Scanner{},\n\t\t&ticketmaster.Scanner{},\n\t\t&tickettailor.Scanner{},\n\t\t&tiingo.Scanner{},\n\t\t&timecamp.Scanner{},\n\t\t&timezoneapi.Scanner{},\n\t\t&tineswebhook.Scanner{},\n\t\t&tmetric.Scanner{},\n\t\t&todoist.Scanner{},\n\t\t// &toggltrack.Scanner{},\n\t\t&tokeet.Scanner{},\n\t\t&tomorrowio.Scanner{},\n\t\t&tomtom.Scanner{},\n\t\t&tradier.Scanner{},\n\t\t&transferwise.Scanner{},\n\t\t&travelpayouts.Scanner{},\n\t\t&travisci.Scanner{},\n\t\t&trelloapikey.Scanner{},\n\t\t&trufflehogenterprise.Scanner{},\n\t\t&twelvedata.Scanner{},\n\t\t&twilio.Scanner{},\n\t\t&twilioapikey.Scanner{},\n\t\t&twist.Scanner{},\n\t\t&twitch.Scanner{},\n\t\t&twitchaccesstoken.Scanner{},\n\t\t&twitterconsumerkey.Scanner{},\n\t\t&twitterv1.Scanner{},\n\t\t&twitterv2.Scanner{},\n\t\t&tyntec.Scanner{},\n\t\t&typeformv1.Scanner{},\n\t\t&typeformv2.Scanner{},\n\t\t&typetalk.Scanner{},\n\t\t&ubidots.Scanner{},\n\t\t&uclassify.Scanner{},\n\t\t&unifyid.Scanner{},\n\t\t&unplugg.Scanner{},\n\t\t&unsplash.Scanner{},\n\t\t&upcdatabase.Scanner{},\n\t\t&uplead.Scanner{},\n\t\t&uploadcare.Scanner{},\n\t\t&uptimerobot.Scanner{},\n\t\t&upwave.Scanner{},\n\t\t&uri.Scanner{},\n\t\t&urlscan.Scanner{},\n\t\t&userflow.Scanner{},\n\t\t&userstack.Scanner{},\n\t\t&vagrantcloudpersonaltoken.Scanner{},\n\t\t&vatlayer.Scanner{},\n\t\t&vbout.Scanner{},\n\t\t&vercel.Scanner{},\n\t\t&verifier.Scanner{},\n\t\t&verimail.Scanner{},\n\t\t&veriphone.Scanner{},\n\t\t&versioneye.Scanner{},\n\t\t&viewneo.Scanner{},\n\t\t&virustotal.Scanner{},\n\t\t&visualcrossing.Scanner{},\n\t\t&voiceflow.Scanner{},\n\t\t&voicegain.Scanner{},\n\t\t&voodoosms.Scanner{},\n\t\t&vouchery.Scanner{},\n\t\t&vpnapi.Scanner{},\n\t\t&vultrapikey.Scanner{},\n\t\t&vyte.Scanner{},\n\t\t&walkscore.Scanner{},\n\t\t&weatherbit.Scanner{},\n\t\t&weatherstack.Scanner{},\n\t\t&web3storage.Scanner{},\n\t\t&webex.Scanner{},\n\t\t&webexbot.Scanner{},\n\t\t&webflow.Scanner{},\n\t\t&webscraper.Scanner{},\n\t\t&webscraping.Scanner{},\n\t\t&websitepulse.Scanner{},\n\t\t&weightsandbiases.Scanner{},\n\t\t// &wepay.Scanner{},\n\t\t&whoxy.Scanner{},\n\t\t&wistia.Scanner{},\n\t\t&wiz.Scanner{},\n\t\t&worksnaps.Scanner{},\n\t\t&workstack.Scanner{},\n\t\t&worldcoinindex.Scanner{},\n\t\t&worldweather.Scanner{},\n\t\t&xai.Scanner{},\n\t\t&wrike.Scanner{},\n\t\t&yandex.Scanner{},\n\t\t&yelp.Scanner{},\n\t\t&youneedabudget.Scanner{},\n\t\t&yousign.Scanner{},\n\t\t&youtubeapikey.Scanner{},\n\t\t// &zapierwebhook.Scanner{},\n\t\t&zendeskapi.Scanner{},\n\t\t&zenkitapi.Scanner{},\n\t\t&zenrows.Scanner{},\n\t\t&zenscrape.Scanner{},\n\t\t&zenserp.Scanner{},\n\t\t&zeplin.Scanner{},\n\t\t&zerobounce.Scanner{},\n\t\t&zerotier.Scanner{},\n\t\t&zipapi.Scanner{},\n\t\t&zipbooks.Scanner{},\n\t\t&zipcodeapi.Scanner{},\n\t\t&zipcodebase.Scanner{},\n\t\t&zohocrm.Scanner{},\n\t\t&zonkafeedback.Scanner{},\n\t\t&zulipchat.Scanner{},\n\t}\n}\n\nfunc DefaultDetectors() []detectors.Detector {\n\tdetectorList := buildDetectorList()\n\n\t// Automatically initialize all detectors that implement\n\t// EndpointCustomizer and/or CloudProvider interfaces.\n\tfor _, d := range detectorList {\n\t\tcustomizer, ok := d.(detectors.EndpointCustomizer)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\t// Default to always use the cloud endpoints (if available) and the found endpoints.\n\t\tcustomizer.UseFoundEndpoints(true)\n\t\tcustomizer.UseCloudEndpoint(true)\n\t\tif cloudProvider, ok := d.(detectors.CloudProvider); ok {\n\t\t\tcustomizer.SetCloudEndpoint(cloudProvider.CloudEndpoint())\n\t\t}\n\t}\n\n\treturn detectorList\n}\n\nfunc DefaultDetectorTypesImplementing[T any]() map[detectorspb.DetectorType]struct{} {\n\tout := make(map[detectorspb.DetectorType]struct{})\n\tfor _, detector := range DefaultDetectors() {\n\t\tif _, ok := detector.(T); ok {\n\t\t\tout[detector.Type()] = struct{}{}\n\t\t}\n\t}\n\treturn out\n}\n"
  },
  {
    "path": "pkg/engine/defaults/defaults_test.go",
    "content": "package defaults\n\nimport (\n\t\"testing\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nfunc TestDefaultDetectorsHaveUniqueVersions(t *testing.T) {\n\tdetectorTypeToVersions := make(map[detectorspb.DetectorType]map[int]struct{})\n\taddVersion := func(versions map[int]struct{}, version int) map[int]struct{} {\n\t\tif versions == nil {\n\t\t\tversions = make(map[int]struct{})\n\t\t}\n\t\tversions[version] = struct{}{}\n\t\treturn versions\n\t}\n\t// Loop through all our default detectors and find the ones that\n\t// implement Versioner. Of those, check each version number is unique.\n\tfor _, detector := range DefaultDetectors() {\n\t\tv, ok := detector.(detectors.Versioner)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tversion := v.Version()\n\t\tkey := detector.Type()\n\t\tif set, ok := detectorTypeToVersions[key]; ok && set != nil {\n\t\t\tif _, ok := set[version]; ok {\n\t\t\t\tt.Errorf(\"detector %q has duplicate version: %d\", detectorspb.DetectorType_name[int32(key)], version)\n\t\t\t}\n\t\t}\n\t\tdetectorTypeToVersions[key] = addVersion(detectorTypeToVersions[key], version)\n\t}\n}\n\nfunc TestDefaultDetectorTypesImplementing(t *testing.T) {\n\tisVersioner := DefaultDetectorTypesImplementing[detectors.Versioner]()\n\tfor _, detector := range DefaultDetectors() {\n\t\t_, expectedOk := detector.(detectors.Versioner)\n\t\t_, gotOk := isVersioner[detector.Type()]\n\t\tif expectedOk == gotOk {\n\t\t\tcontinue\n\t\t}\n\t\tt.Errorf(\n\t\t\t\"detector %q doesn't match expected\",\n\t\t\tdetectorspb.DetectorType_name[int32(detector.Type())],\n\t\t)\n\t}\n}\n\nfunc TestDefaultVersionerDetectorsHaveNonZeroVersions(t *testing.T) {\n\t// Loop through all our default detectors and find the ones that\n\t// implement Versioner. Of those, check each version is not zero.\n\t// This is required due to an implementation detail of filtering detectors.\n\t// See: https://github.com/trufflesecurity/trufflehog/blob/v3.63.7/main.go#L624-L638\n\tfor _, detector := range DefaultDetectors() {\n\t\tv, ok := detector.(detectors.Versioner)\n\t\tif !ok || v.Version() != 0 {\n\t\t\tcontinue\n\t\t}\n\t\tt.Errorf(\n\t\t\t\"detector %q implements Versioner that returns a zero version\",\n\t\t\tdetectorspb.DetectorType_name[int32(detector.Type())],\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/docker.go",
    "content": "package engine\n\nimport (\n\t\"runtime\"\n\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/docker\"\n)\n\n// ScanDocker scans a given docker connection.\nfunc (e *Engine) ScanDocker(ctx context.Context, c sources.DockerConfig) (sources.JobProgressRef, error) {\n\tconnection := &sourcespb.Docker{\n\t\tImages:        c.Images,\n\t\tExcludePaths:  c.ExcludePaths,\n\t\tNamespace:     c.Namespace,\n\t\tRegistryToken: c.RegistryToken,\n\t}\n\n\tswitch {\n\tcase c.UseDockerKeychain:\n\t\tconnection.Credential = &sourcespb.Docker_DockerKeychain{DockerKeychain: true}\n\tcase len(c.BearerToken) > 0:\n\t\tconnection.Credential = &sourcespb.Docker_BearerToken{BearerToken: c.BearerToken}\n\tdefault:\n\t\tconnection.Credential = &sourcespb.Docker_Unauthenticated{}\n\t}\n\n\tvar conn anypb.Any\n\terr := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"failed to marshal gitlab connection\")\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\n\tsourceName := \"trufflehog - docker\"\n\tsourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, docker.SourceType)\n\n\tdockerSource := &docker.Source{}\n\tif err := dockerSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\treturn e.sourceManager.EnumerateAndScan(ctx, sourceName, dockerSource)\n}\n"
  },
  {
    "path": "pkg/engine/elasticsearch.go",
    "content": "package engine\n\nimport (\n\t\"runtime\"\n\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/elasticsearch\"\n)\n\n// ScanElasticsearch scans a Elasticsearch installation.\nfunc (e *Engine) ScanElasticsearch(ctx context.Context, c sources.ElasticsearchConfig) (sources.JobProgressRef, error) {\n\tconnection := &sourcespb.Elasticsearch{\n\t\tNodes:          c.Nodes,\n\t\tUsername:       c.Username,\n\t\tPassword:       c.Password,\n\t\tCloudId:        c.CloudID,\n\t\tApiKey:         c.APIKey,\n\t\tServiceToken:   c.ServiceToken,\n\t\tIndexPattern:   c.IndexPattern,\n\t\tQueryJson:      c.QueryJSON,\n\t\tSinceTimestamp: c.SinceTimestamp,\n\t\tBestEffortScan: c.BestEffortScan,\n\t}\n\n\tvar conn anypb.Any\n\terr := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"failed to marshal Elasticsearch connection\")\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\n\tsourceName := \"trufflehog - Elasticsearch\"\n\tsourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, elasticsearch.SourceType)\n\n\telasticsearchSource := &elasticsearch.Source{}\n\tif err := elasticsearchSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\treturn e.sourceManager.EnumerateAndScan(ctx, sourceName, elasticsearchSource)\n}\n"
  },
  {
    "path": "pkg/engine/engine.go",
    "content": "// Check the [process flow](docs/process_flow.md) and [concurrency](docs/concurrency.md) docs for\n// something of a structural overview\n\npackage engine\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/adrg/strutil\"\n\t\"github.com/adrg/strutil/metrics\"\n\tlru \"github.com/hashicorp/golang-lru/v2\"\n\t\"google.golang.org/protobuf/proto\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/decoders\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/defaults\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/giturl\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/output\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/verificationcache\"\n)\n\nvar detectionTimeout = detectors.DefaultResponseTimeout\n\nvar errOverlap = errors.New(\n\t\"More than one detector has found this result. For your safety, verification has been disabled.\" +\n\t\t\"You can override this behavior by using the --allow-verification-overlap flag.\",\n)\n\n// Metrics for the scan engine for external consumption.\ntype Metrics struct {\n\tBytesScanned           uint64\n\tChunksScanned          uint64\n\tVerifiedSecretsFound   uint64\n\tUnverifiedSecretsFound uint64\n\tAvgDetectorTime        map[string]time.Duration\n\n\tscanStartTime time.Time\n\tScanDuration  time.Duration\n}\n\n// runtimeMetrics for the scan engine for internal use by the engine.\ntype runtimeMetrics struct {\n\tmu sync.RWMutex\n\tMetrics\n\tdetectorAvgTime sync.Map\n}\n\n// getScanDuration returns the duration of the scan.\n// If the scan is still running, it returns the time since the scan started.\nfunc (m *Metrics) getScanDuration() time.Duration {\n\tif m.ScanDuration == 0 {\n\t\treturn time.Since(m.scanStartTime)\n\t}\n\n\treturn m.ScanDuration\n}\n\n// ResultsDispatcher is an interface for dispatching findings of detected results.\n// Implementations can vary from printing results to the console to sending results to an external system.\ntype ResultsDispatcher interface {\n\tDispatch(ctx context.Context, result detectors.ResultWithMetadata) error\n}\n\n// Printer is used to format found results and output them to the user. Ex JSON, plain text, etc.\n// Please note printer implementations SHOULD BE thread safe.\ntype Printer interface {\n\tPrint(ctx context.Context, r *detectors.ResultWithMetadata) error\n}\n\n// PrinterDispatcher wraps an existing Printer implementation and adapts it to the ResultsDispatcher interface.\ntype PrinterDispatcher struct{ printer Printer }\n\n// NewPrinterDispatcher creates a new PrinterDispatcher instance with the provided Printer.\nfunc NewPrinterDispatcher(printer Printer) *PrinterDispatcher { return &PrinterDispatcher{printer} }\n\n// Dispatch sends the result to the printer.\nfunc (p *PrinterDispatcher) Dispatch(ctx context.Context, result detectors.ResultWithMetadata) error {\n\treturn p.printer.Print(ctx, &result)\n}\n\n// Config used to configure the engine.\ntype Config struct {\n\t// Number of concurrent scanner workers,\n\t// also serves as a multiplier for other worker types (e.g., detector workers, notifier workers)\n\tConcurrency int\n\n\tConfiguredSources             []sources.ConfiguredSource\n\tDecoders                      []decoders.Decoder\n\tDetectors                     []detectors.Detector\n\tDetectorVerificationOverrides map[config.DetectorID]bool\n\tIncludeDetectors              string\n\tExcludeDetectors              string\n\tCustomVerifiersOnly           bool\n\tVerifierEndpoints             map[string]string\n\n\t// Verify determines whether the scanner will verify candidate secrets.\n\tVerify bool\n\n\t// Defines which results will be notified by the engine\n\t// (e.g., verified, unverified, unknown)\n\tResults               map[string]struct{}\n\tLogFilteredUnverified bool\n\n\t// FilterEntropy filters out unverified results using Shannon entropy.\n\tFilterEntropy float64\n\t// FilterUnverified sets the filterUnverified flag on the engine. If set to\n\t// true, the engine will only return the first unverified result for a chunk for a detector.\n\tFilterUnverified      bool\n\tShouldScanEntireChunk bool\n\n\tDispatcher ResultsDispatcher\n\n\t// SourceManager is used to manage the sources and units.\n\t// TODO (ahrav): Update this comment, i'm dumb and don't really know what else it does.\n\tSourceManager *sources.SourceManager\n\n\t// PrintAvgDetectorTime sets the printAvgDetectorTime flag on the engine. If set to\n\t// true, the engine will print the average time taken by each detector.\n\t// This option allows us to measure the time taken for each detector ONLY if\n\t// the engine is configured to print the results.\n\t// Calculating the average time taken by each detector is an expensive operation\n\t// and should be avoided unless specified by the user.\n\tPrintAvgDetectorTime bool\n\n\t// VerificationOverlap determines whether the scanner will attempt to verify candidate secrets\n\t// that have been detected by multiple detectors.\n\t// By default, it is set to true.\n\tVerificationOverlap bool\n\n\t// DetectorWorkerMultiplier is used to determine the number of detector workers to spawn.\n\tDetectorWorkerMultiplier int\n\n\t// NotificationWorkerMultiplier is used to determine the number of notification workers to spawn.\n\tNotificationWorkerMultiplier int\n\n\t// VerificationOverlapWorkerMultiplier is used to determine the number of verification overlap workers to spawn.\n\tVerificationOverlapWorkerMultiplier int\n\n\tVerificationResultCache  verificationcache.ResultCache\n\tVerificationCacheMetrics verificationcache.MetricsReporter\n\n\t// MaxDecodeDepth is the maximum number of iterative decoding passes per chunk.\n\t// When a decoder transforms data, all decoders are re-run on the output up to this limit.\n\t// 1 = single pass (no chaining), 2+ = chained (e.g., base64 inside UTF-16).\n\t// Default: 5.\n\tMaxDecodeDepth int\n}\n\n// Engine represents the core scanning engine responsible for detecting secrets in input data.\n// It manages the lifecycle of the scanning process, including initialization, worker management,\n// and result notification. The engine is designed to be flexible and configurable, allowing for\n// customization through various options and configurations.\ntype Engine struct {\n\t// CLI flags.\n\tconcurrency       int\n\tdecoders          []decoders.Decoder\n\tdetectors         []detectors.Detector\n\tverificationCache *verificationcache.VerificationCache\n\t// Any detectors configured to override sources' verification flags\n\tdetectorVerificationOverrides map[config.DetectorID]bool\n\n\t// filterUnverified is used to reduce the number of unverified results.\n\t// If there are multiple unverified results for the same chunk for the same detector,\n\t// only the first one will be kept.\n\tfilterUnverified bool\n\t// entropyFilter is used to filter out unverified results using Shannon entropy.\n\tfilterEntropy           float64\n\tnotifyVerifiedResults   bool\n\tnotifyUnverifiedResults bool\n\tnotifyUnknownResults    bool\n\tretainFalsePositives    bool\n\tverificationOverlap     bool\n\tprintAvgDetectorTime    bool\n\t// By default, the engine will only scan a subset of the chunk if a detector matches the chunk.\n\t// If this flag is set to true, the engine will scan the entire chunk.\n\tscanEntireChunk bool\n\n\t// ahoCorasickHandler manages the Aho-Corasick trie and related keyword lookups.\n\tAhoCorasickCore *ahocorasick.Core\n\n\t// Engine synchronization primitives.\n\tsourceManager                 *sources.SourceManager\n\tresults                       chan detectors.ResultWithMetadata\n\tdetectableChunksChan          chan detectableChunk\n\tverificationOverlapChunksChan chan verificationOverlapChunk\n\tworkersWg                     sync.WaitGroup\n\tverificationOverlapWg         sync.WaitGroup\n\twgDetectorWorkers             sync.WaitGroup\n\tWgNotifier                    sync.WaitGroup\n\n\t// Runtime information.\n\tmetrics runtimeMetrics\n\t// numFoundResults is used to keep track of the number of results found.\n\tnumFoundResults uint32\n\n\t// ResultsDispatcher is used to send results.\n\tdispatcher ResultsDispatcher\n\n\t// dedupeCache is used to deduplicate results by comparing the\n\t// detector type, raw result, and source metadata\n\tdedupeCache *lru.Cache[string, detectorspb.DecoderType]\n\n\t// verify determines whether the scanner will attempt to verify candidate secrets.\n\tverify bool\n\n\t// Note: bad hack only used for testing.\n\tverificationOverlapTracker *atomic.Int32\n\n\t// detectorWorkerMultiplier is used to calculate the number of detector workers.\n\tdetectorWorkerMultiplier int\n\t// notificationWorkerMultiplier is used to calculate the number of notification workers.\n\tnotificationWorkerMultiplier int\n\t// verificationOverlapWorkerMultiplier is used to calculate the number of verification overlap workers.\n\tverificationOverlapWorkerMultiplier int\n\n\tmaxDecodeDepth int\n}\n\n// NewEngine creates a new Engine instance with the provided configuration.\nfunc NewEngine(ctx context.Context, cfg *Config) (*Engine, error) {\n\tverificationCache := verificationcache.New(cfg.VerificationResultCache, cfg.VerificationCacheMetrics)\n\n\tengine := &Engine{\n\t\tconcurrency:                         cfg.Concurrency,\n\t\tdecoders:                            cfg.Decoders,\n\t\tdetectors:                           cfg.Detectors,\n\t\tverificationCache:                   verificationCache,\n\t\tdispatcher:                          cfg.Dispatcher,\n\t\tverify:                              cfg.Verify,\n\t\tfilterUnverified:                    cfg.FilterUnverified,\n\t\tfilterEntropy:                       cfg.FilterEntropy,\n\t\tprintAvgDetectorTime:                cfg.PrintAvgDetectorTime,\n\t\tretainFalsePositives:                cfg.LogFilteredUnverified,\n\t\tverificationOverlap:                 cfg.VerificationOverlap,\n\t\tsourceManager:                       cfg.SourceManager,\n\t\tscanEntireChunk:                     cfg.ShouldScanEntireChunk,\n\t\tdetectorVerificationOverrides:       cfg.DetectorVerificationOverrides,\n\t\tdetectorWorkerMultiplier:            cfg.DetectorWorkerMultiplier,\n\t\tnotificationWorkerMultiplier:        cfg.NotificationWorkerMultiplier,\n\t\tverificationOverlapWorkerMultiplier: cfg.VerificationOverlapWorkerMultiplier,\n\t\tmaxDecodeDepth:                      cfg.MaxDecodeDepth,\n\t}\n\tif engine.sourceManager == nil {\n\t\treturn nil, fmt.Errorf(\"source manager is required\")\n\t}\n\n\tengine.setDefaults(ctx)\n\n\t// Build include and exclude detector sets for filtering on engine initialization.\n\tincludeDetectorSet, excludeDetectorSet, err := buildDetectorSets(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Apply include/exclude filters.\n\tvar filters []func(detectors.Detector) bool\n\n\tif len(includeDetectorSet) > 0 {\n\t\tfilters = append(filters, func(d detectors.Detector) bool {\n\t\t\t_, ok := getWithDetectorID(d, includeDetectorSet)\n\t\t\treturn ok\n\t\t})\n\t}\n\n\tif len(excludeDetectorSet) > 0 {\n\t\tfilters = append(filters, func(d detectors.Detector) bool {\n\t\t\t_, ok := getWithDetectorID(d, excludeDetectorSet)\n\t\t\treturn !ok\n\t\t})\n\t}\n\n\t// Apply custom verifier endpoints to detectors that support it.\n\tdetectorsWithCustomVerifierEndpoints, err := parseCustomVerifierEndpoints(cfg.VerifierEndpoints)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(detectorsWithCustomVerifierEndpoints) > 0 {\n\t\tfilters = append(filters, func(d detectors.Detector) bool {\n\t\t\turls, ok := getWithDetectorID(d, detectorsWithCustomVerifierEndpoints)\n\t\t\tif !ok {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tcustomizer, ok := d.(detectors.EndpointCustomizer)\n\t\t\tif !ok {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tif cfg.CustomVerifiersOnly && len(urls) > 0 {\n\t\t\t\tcustomizer.UseCloudEndpoint(false)\n\t\t\t\tcustomizer.UseFoundEndpoints(false)\n\t\t\t}\n\n\t\t\tif err := customizer.SetConfiguredEndpoints(urls...); err != nil {\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\treturn true\n\t\t})\n\t}\n\tengine.applyFilters(filters...)\n\n\tif results := cfg.Results; len(results) > 0 {\n\t\t_, ok := results[\"verified\"]\n\t\tengine.notifyVerifiedResults = ok\n\n\t\t_, ok = results[\"unknown\"]\n\t\tengine.notifyUnknownResults = ok\n\n\t\t_, ok = results[\"unverified\"]\n\t\tengine.notifyUnverifiedResults = ok\n\n\t\tif _, ok = results[\"filtered_unverified\"]; ok {\n\t\t\tengine.retainFalsePositives = ok\n\t\t\tengine.notifyUnverifiedResults = ok\n\t\t}\n\t}\n\n\tif err := engine.initialize(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn engine, nil\n}\n\n// SetDetectorTimeout sets the maximum timeout for each detector to scan a chunk.\nfunc SetDetectorTimeout(timeout time.Duration) { detectionTimeout = timeout }\n\n// setDefaults ensures that if specific engine properties aren't provided,\n// they're set to reasonable default values. It makes the engine robust to\n// incomplete configuration.\nfunc (e *Engine) setDefaults(ctx context.Context) {\n\tif e.concurrency == 0 {\n\t\tnumCPU := runtime.NumCPU()\n\t\tctx.Logger().Info(\"No concurrency specified, defaulting to max\", \"cpu\", numCPU)\n\t\te.concurrency = numCPU\n\t}\n\n\tif e.detectorWorkerMultiplier < 1 {\n\t\t// bound by net i/o so it's higher than other workers\n\t\te.detectorWorkerMultiplier = 8\n\t}\n\n\tif e.notificationWorkerMultiplier < 1 {\n\t\te.notificationWorkerMultiplier = 1\n\t}\n\n\tif e.verificationOverlapWorkerMultiplier < 1 {\n\t\te.verificationOverlapWorkerMultiplier = 1\n\t}\n\n\tif e.maxDecodeDepth < 1 {\n\t\te.maxDecodeDepth = 1\n\t}\n\n\t// Default decoders handle common encoding formats.\n\tif len(e.decoders) == 0 {\n\t\te.decoders = decoders.DefaultDecoders()\n\t}\n\n\t// Only use the default detectors if none are provided.\n\tif len(e.detectors) == 0 {\n\t\te.detectors = defaults.DefaultDetectors()\n\t}\n\n\tif e.dispatcher == nil {\n\t\te.dispatcher = NewPrinterDispatcher(new(output.PlainPrinter))\n\t}\n\te.notifyVerifiedResults = true\n\te.notifyUnverifiedResults = true\n\te.notifyUnknownResults = true\n\n\tctx.Logger().V(4).Info(\"default engine options set\")\n}\n\nfunc buildDetectorSets(cfg *Config) (map[config.DetectorID]struct{}, map[config.DetectorID]struct{}, error) {\n\tincludeList, err := config.ParseDetectors(cfg.IncludeDetectors)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"invalid include list detector configuration: %w\", err)\n\t}\n\texcludeList, err := config.ParseDetectors(cfg.ExcludeDetectors)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"invalid exclude list detector configuration: %w\", err)\n\t}\n\n\tincludeDetectorSet := detectorTypeToSet(includeList)\n\texcludeDetectorSet := detectorTypeToSet(excludeList)\n\n\t// Verify that all the user-provided detectors support the optional\n\t// detector features.\n\tif id, err := verifyDetectorsAreVersioner(includeDetectorSet); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"invalid include list detector configuration id %v: %w\", id, err)\n\t}\n\n\tif id, err := verifyDetectorsAreVersioner(excludeDetectorSet); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"invalid exclude list detector configuration id %v: %w\", id, err)\n\t}\n\n\treturn includeDetectorSet, excludeDetectorSet, nil\n}\n\nfunc parseCustomVerifierEndpoints(endpoints map[string]string) (map[config.DetectorID][]string, error) {\n\tif len(endpoints) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tcustomVerifierEndpoints, err := config.ParseVerifierEndpoints(endpoints)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid verifier detector configuration: %w\", err)\n\t}\n\n\tif id, err := verifyDetectorsAreVersioner(customVerifierEndpoints); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid verifier detector configuration id %v: %w\", id, err)\n\t}\n\t// Extra check for endpoint customization.\n\tisEndpointCustomizer := defaults.DefaultDetectorTypesImplementing[detectors.EndpointCustomizer]()\n\tfor id := range customVerifierEndpoints {\n\t\tif _, ok := isEndpointCustomizer[id.ID]; !ok {\n\t\t\treturn nil, fmt.Errorf(\"endpoint provided but detector does not support endpoint customization: %w\", err)\n\t\t}\n\t}\n\treturn customVerifierEndpoints, nil\n}\n\n// detectorTypeToSet is a helper function to convert a slice of detector IDs into a set.\nfunc detectorTypeToSet(detectors []config.DetectorID) map[config.DetectorID]struct{} {\n\tout := make(map[config.DetectorID]struct{}, len(detectors))\n\tfor _, d := range detectors {\n\t\tout[d] = struct{}{}\n\t}\n\treturn out\n}\n\n// getWithDetectorID is a helper function to get a value from a map using a\n// detector's ID. This function behaves like a normal map lookup, with an extra\n// step of checking for the non-specific version of a detector.\nfunc getWithDetectorID[T any](d detectors.Detector, data map[config.DetectorID]T) (T, bool) {\n\tkey := config.GetDetectorID(d)\n\t// Check if the specific ID is provided.\n\tif t, ok := data[key]; ok || key.Version == 0 {\n\t\treturn t, ok\n\t}\n\t// Check if the generic type is provided without a version.\n\t// This means \"all\" versions of a type.\n\tkey.Version = 0\n\tt, ok := data[key]\n\treturn t, ok\n}\n\n// verifyDetectorsAreVersioner checks all keys in a provided map to verify the\n// provided type is actually a Versioner.\nfunc verifyDetectorsAreVersioner[T any](data map[config.DetectorID]T) (config.DetectorID, error) {\n\tisVersioner := defaults.DefaultDetectorTypesImplementing[detectors.Versioner]()\n\tfor id := range data {\n\t\tif id.Version == 0 {\n\t\t\t// Version not provided.\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := isVersioner[id.ID]; ok {\n\t\t\t// Version provided for a Versioner detector.\n\t\t\tcontinue\n\t\t}\n\t\t// Version provided on a non-Versioner detector.\n\t\treturn id, fmt.Errorf(\"version provided but detector does not have a version\")\n\t}\n\treturn config.DetectorID{}, nil\n}\n\n// applyFilters applies a variable number of filters to the detectors.\nfunc (e *Engine) applyFilters(filters ...func(detectors.Detector) bool) {\n\tfor _, filter := range filters {\n\t\te.detectors = filterDetectors(filter, e.detectors)\n\t}\n}\n\nfunc filterDetectors(filterFunc func(detectors.Detector) bool, input []detectors.Detector) []detectors.Detector {\n\tvar out []detectors.Detector\n\tfor _, detector := range input {\n\t\tif filterFunc(detector) {\n\t\t\tout = append(out, detector)\n\t\t}\n\t}\n\treturn out\n}\n\n// initialize prepares the engine's internal structures. The LRU cache optimizes\n// deduplication efforts, allowing the engine to quickly check if a chunk has\n// been processed before, thereby saving computational overhead.\nfunc (e *Engine) initialize(ctx context.Context) error {\n\t// TODO (ahrav): Determine the optimal cache size.\n\tconst cacheSize = 512 // number of entries in the LRU cache\n\n\tcache, err := lru.New[string, detectorspb.DecoderType](cacheSize)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to initialize LRU cache: %w\", err)\n\t}\n\tconst (\n\t\t// detectableChunksChanMultiplier is set to accommodate a high number of concurrent worker goroutines.\n\t\t// This multiplier ensures that the detectableChunksChan channel has sufficient buffer capacity\n\t\t// to hold messages from multiple worker groups (detector workers/ verificationOverlap workers) without blocking.\n\t\t// A large buffer helps accommodate for the fact workers are producing data at a faster rate\n\t\t// than it can be consumed.\n\t\tdetectableChunksChanMultiplier = 50\n\t\t// verificationOverlapChunksChanMultiplier uses a smaller buffer compared to detectableChunksChanMultiplier.\n\t\t// This reflects the anticipated lower volume of data that needs re-verification.\n\t\t// The buffer size is a trade-off between memory usage and the need to prevent blocking.\n\t\tverificationOverlapChunksChanMultiplier = 25\n\t\tresultsChanMultiplier                   = detectableChunksChanMultiplier\n\t)\n\n\t// Channels are used for communication between different parts of the engine,\n\t// ensuring that data flows smoothly without race conditions.\n\t// The buffer sizes for these channels are set to multiples of defaultChannelBuffer,\n\t// considering the expected concurrency and workload in the system.\n\te.detectableChunksChan = make(chan detectableChunk, defaultChannelBuffer*detectableChunksChanMultiplier)\n\te.verificationOverlapChunksChan = make(\n\t\tchan verificationOverlapChunk, defaultChannelBuffer*verificationOverlapChunksChanMultiplier,\n\t)\n\te.results = make(chan detectors.ResultWithMetadata, defaultChannelBuffer*resultsChanMultiplier)\n\te.dedupeCache = cache\n\tctx.Logger().V(4).Info(\"engine initialized\")\n\n\t// Configure the EntireChunkSpanCalculator if the engine is set to scan the entire chunk.\n\tvar ahoCOptions []ahocorasick.CoreOption\n\tif e.scanEntireChunk {\n\t\tahoCOptions = append(ahoCOptions, ahocorasick.WithSpanCalculator(new(ahocorasick.EntireChunkSpanCalculator)))\n\t}\n\n\tctx.Logger().V(4).Info(\"setting up aho-corasick core\")\n\te.AhoCorasickCore = ahocorasick.NewAhoCorasickCore(e.detectors, ahoCOptions...)\n\tctx.Logger().V(4).Info(\"set up aho-corasick core\")\n\n\treturn nil\n}\n\nconst ignoreTag = \"trufflehog:ignore\"\n\n// AhoCorasickCoreKeywords returns a set of keywords that the engine's\n// AhoCorasickCore is using.\nfunc (e *Engine) AhoCorasickCoreKeywords() map[string]struct{} {\n\t// Turn AhoCorasick keywordsToDetectors into a map of keywords\n\tkeywords := make(map[string]struct{})\n\tfor key := range e.AhoCorasickCore.KeywordsToDetectors() {\n\t\tkeywords[key] = struct{}{}\n\t}\n\treturn keywords\n}\n\n// HasFoundResults returns true if any results are found.\nfunc (e *Engine) HasFoundResults() bool {\n\treturn atomic.LoadUint32(&e.numFoundResults) > 0\n}\n\n// GetMetrics returns a copy of Metrics.\n// It's safe for concurrent use, and the caller can't modify the original data.\nfunc (e *Engine) GetMetrics() Metrics {\n\te.metrics.mu.RLock()\n\tdefer e.metrics.mu.RUnlock()\n\n\tresult := e.metrics.Metrics\n\tresult.AvgDetectorTime = make(map[string]time.Duration, len(e.metrics.AvgDetectorTime))\n\n\tfor detectorName, durations := range e.DetectorAvgTime() {\n\t\tvar total time.Duration\n\t\tfor _, d := range durations {\n\t\t\ttotal += d\n\t\t}\n\t\tavgDuration := total / time.Duration(len(durations))\n\t\tresult.AvgDetectorTime[detectorName] = avgDuration\n\t}\n\n\tresult.ScanDuration = e.metrics.getScanDuration()\n\n\treturn result\n}\n\n// GetDetectorsMetrics returns a copy of the average time taken by each detector.\nfunc (e *Engine) GetDetectorsMetrics() map[string]time.Duration {\n\te.metrics.mu.RLock()\n\tdefer e.metrics.mu.RUnlock()\n\n\tresult := make(map[string]time.Duration, len(defaults.DefaultDetectors()))\n\tfor detectorName, durations := range e.DetectorAvgTime() {\n\t\tvar total time.Duration\n\t\tfor _, d := range durations {\n\t\t\ttotal += d\n\t\t}\n\t\tavgDuration := total / time.Duration(len(durations))\n\t\tresult[detectorName] = avgDuration\n\t}\n\n\treturn result\n}\n\n// DetectorAvgTime returns the average time taken by each detector.\nfunc (e *Engine) DetectorAvgTime() map[string][]time.Duration {\n\tlogger := context.Background().Logger()\n\tavgTime := map[string][]time.Duration{}\n\te.metrics.detectorAvgTime.Range(func(k, v any) bool {\n\t\tkey, ok := k.(string)\n\t\tif !ok {\n\t\t\tlogger.Info(\"expected detectorAvgTime key to be a string\")\n\t\t\treturn true\n\t\t}\n\n\t\tvalue, ok := v.([]time.Duration)\n\t\tif !ok {\n\t\t\tlogger.Info(\"expected detectorAvgTime value to be []time.Duration\")\n\t\t\treturn true\n\t\t}\n\t\tavgTime[key] = value\n\t\treturn true\n\t})\n\treturn avgTime\n}\n\n// Start initializes and activates the engine's processing pipeline.\n// It sets up various default configurations, prepares lookup structures for\n// detectors, and kickstarts all necessary workers. Once started, the engine\n// begins processing input data to identify secrets.\nfunc (e *Engine) Start(ctx context.Context) {\n\te.metrics = runtimeMetrics{Metrics: Metrics{scanStartTime: time.Now()}}\n\te.sanityChecks(ctx)\n\te.startWorkers(ctx)\n}\n\nvar defaultChannelBuffer = runtime.NumCPU()\n\n// Sanity check detectors for duplicate configuration. Only log in case\n// a detector has been configured in a way that isn't represented by\n// the DetectorID (type and version).\nfunc (e *Engine) sanityChecks(ctx context.Context) {\n\tseenDetectors := make(map[config.DetectorID]struct{}, len(e.detectors))\n\tfor _, det := range e.detectors {\n\t\tid := config.GetDetectorID(det)\n\t\tif _, ok := seenDetectors[id]; ok && id.ID != detectorspb.DetectorType_CustomRegex {\n\t\t\tctx.Logger().Info(\"possible duplicate detector configured\", \"detector\", id)\n\t\t}\n\t\tseenDetectors[id] = struct{}{}\n\t}\n}\n\n// startWorkers initiates all necessary workers. Workers handle processing of\n// chunks concurrently. Separating the initialization of different types of\n// workers helps in scalability and makes it easier to diagnose issues.\nfunc (e *Engine) startWorkers(ctx context.Context) {\n\t// Scanner workers process input data and extract chunks for detectors.\n\te.startScannerWorkers(ctx)\n\n\t// Detector workers apply keyword matching, regexes and API calls to detect secrets in chunks.\n\te.startDetectorWorkers(ctx)\n\n\t// verificationOverlap workers handle verification of chunks that have been detected by multiple detectors.\n\t// They ensure that verification is disabled for any secrets that have been detected by multiple detectors.\n\te.startVerificationOverlapWorkers(ctx)\n\n\t// ResultsDispatcher workers communicate detected issues to the user or any downstream systems.\n\t// We want 1/4th of the notifier workers as the number of scanner workers.\n\te.startNotifierWorkers(ctx)\n}\n\nfunc (e *Engine) startScannerWorkers(ctx context.Context) {\n\tctx.Logger().V(2).Info(\"starting scanner workers\", \"count\", e.concurrency)\n\tfor worker := uint64(0); worker < uint64(e.concurrency); worker++ {\n\t\te.workersWg.Add(1)\n\t\tgo func() {\n\t\t\tctx := context.WithValue(ctx, \"scanner_worker_id\", common.RandomID(5))\n\t\t\tdefer common.Recover(ctx)\n\t\t\tdefer e.workersWg.Done()\n\t\t\te.scannerWorker(ctx)\n\t\t}()\n\t}\n}\n\nfunc (e *Engine) startDetectorWorkers(ctx context.Context) {\n\tnumWorkers := e.concurrency * e.detectorWorkerMultiplier\n\n\tctx.Logger().V(2).Info(\"starting detector workers\", \"count\", numWorkers)\n\tfor worker := 0; worker < numWorkers; worker++ {\n\t\te.wgDetectorWorkers.Add(1)\n\t\tgo func() {\n\t\t\tctx := context.WithValue(ctx, \"detector_worker_id\", common.RandomID(5))\n\t\t\tdefer common.Recover(ctx)\n\t\t\tdefer e.wgDetectorWorkers.Done()\n\t\t\te.detectorWorker(ctx)\n\t\t}()\n\t}\n}\n\nfunc (e *Engine) startVerificationOverlapWorkers(ctx context.Context) {\n\tnumWorkers := e.concurrency * e.verificationOverlapWorkerMultiplier\n\n\tctx.Logger().V(2).Info(\"starting verificationOverlap workers\", \"count\", numWorkers)\n\tfor worker := 0; worker < numWorkers; worker++ {\n\t\te.verificationOverlapWg.Add(1)\n\t\tgo func() {\n\t\t\tctx := context.WithValue(ctx, \"verification_overlap_worker_id\", common.RandomID(5))\n\t\t\tdefer common.Recover(ctx)\n\t\t\tdefer e.verificationOverlapWg.Done()\n\t\t\te.verificationOverlapWorker(ctx)\n\t\t}()\n\t}\n}\n\nfunc (e *Engine) startNotifierWorkers(ctx context.Context) {\n\tnumWorkers := e.notificationWorkerMultiplier * e.concurrency\n\n\tctx.Logger().V(2).Info(\"starting notifier workers\", \"count\", numWorkers)\n\tfor worker := 0; worker < numWorkers; worker++ {\n\t\te.WgNotifier.Add(1)\n\t\tgo func() {\n\t\t\tctx := context.WithValue(ctx, \"notifier_worker_id\", common.RandomID(5))\n\t\t\tdefer common.Recover(ctx)\n\t\t\tdefer e.WgNotifier.Done()\n\t\t\te.notifierWorker(ctx)\n\t\t}()\n\t}\n}\n\n// Finish waits for running sources to complete and workers to finish scanning\n// chunks before closing their respective channels. Once Finish is called, no\n// more sources may be scanned by the engine.\nfunc (e *Engine) Finish(ctx context.Context) error {\n\tdefer common.RecoverWithExit(ctx)\n\t// Wait for the sources to finish putting chunks onto the chunks channel.\n\terr := e.sourceManager.Wait()\n\n\te.workersWg.Wait() // Wait for the workers to finish scanning chunks.\n\n\tclose(e.verificationOverlapChunksChan)\n\te.verificationOverlapWg.Wait()\n\n\tclose(e.detectableChunksChan)\n\te.wgDetectorWorkers.Wait() // Wait for the detector workers to finish detecting chunks.\n\n\tclose(e.results)    // Detector workers are done, close the results channel and call it a day.\n\te.WgNotifier.Wait() // Wait for the notifier workers to finish notifying results.\n\n\te.metrics.ScanDuration = time.Since(e.metrics.scanStartTime)\n\n\treturn err\n}\n\nfunc (e *Engine) ChunksChan() <-chan *sources.Chunk {\n\treturn e.sourceManager.Chunks()\n}\n\nfunc (e *Engine) ResultsChan() chan detectors.ResultWithMetadata {\n\treturn e.results\n}\n\n// ScanChunk injects a chunk into the output stream of chunks to be scanned.\n// This method should rarely be used. TODO(THOG-1577): Remove when dependencies\n// no longer rely on this functionality.\nfunc (e *Engine) ScanChunk(chunk *sources.Chunk) {\n\te.sourceManager.ScanChunk(chunk)\n}\n\n// detectableChunk is a decoded chunk that is ready to be scanned by its detector.\ntype detectableChunk struct {\n\tdetector *ahocorasick.DetectorMatch\n\tchunk    sources.Chunk\n\tdecoder  detectorspb.DecoderType\n\twgDoneFn func()\n\tverify   bool\n}\n\n// verificationOverlapChunk is a decoded chunk that has multiple detectors that match it.\n// It will be initially processed with verification disabled, and then reprocessed with verification\n// enabled if the same secret was not found by multiple detectors.\ntype verificationOverlapChunk struct {\n\tchunk                       sources.Chunk\n\tdecoder                     detectorspb.DecoderType\n\tdetectors                   []*ahocorasick.DetectorMatch\n\tverificationOverlapWgDoneFn func()\n}\n\n// iterativeDecode applies all decoders to data, then re-applies them to any\n// new output, up to maxDepth passes. Each pass skips the PLAIN (UTF-8) decoder\n// because all other decoders already produce valid UTF-8/ASCII output, so\n// re-running PLAIN would only duplicate work without changing the data.\n//\n// The returned chunks include results from every depth level -- intermediate\n// decoded forms are scanned, not just the final one, because a secret may only\n// be recognizable at a particular decoding stage.\nfunc iterativeDecode(chunk *sources.Chunk, allDecoders []decoders.Decoder, maxDepth int) []*decoders.DecodableChunk {\n\tvar results []*decoders.DecodableChunk\n\n\tcurrentInputs := [][]byte{chunk.Data}\n\tvar seen [][]byte\n\n\tfor depth := 0; depth < maxDepth; depth++ {\n\t\tvar nextInputs [][]byte\n\n\t\tfor _, data := range currentInputs {\n\t\t\tfor _, decoder := range allDecoders {\n\t\t\t\t// The PLAIN (UTF-8) decoder always returns non-nil and only transforms\n\t\t\t\t// invalid UTF-8 via extractSubstrings. All other decoders already produce\n\t\t\t\t// valid UTF-8/ASCII output, so re-running PLAIN at depth > 0 would just\n\t\t\t\t// duplicate detector work without ever changing the data.\n\t\t\t\tif depth > 0 && decoder.Type() == detectorspb.DecoderType_PLAIN {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tchunkCopy := *chunk\n\t\t\t\tchunkCopy.Data = data\n\t\t\t\tdecoded := decoder.FromChunk(&chunkCopy)\n\t\t\t\tif decoded == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tresults = append(results, decoded)\n\n\t\t\t\tif depth+1 < maxDepth &&\n\t\t\t\t\tdecoder.Type() != detectorspb.DecoderType_PLAIN &&\n\t\t\t\t\t!bytes.Equal(decoded.Chunk.Data, data) &&\n\t\t\t\t\t!slices.ContainsFunc(seen, func(s []byte) bool { return bytes.Equal(s, decoded.Chunk.Data) }) {\n\n\t\t\t\t\tseen = append(seen, decoded.Chunk.Data)\n\t\t\t\t\tnextInputs = append(nextInputs, decoded.Chunk.Data)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(nextInputs) == 0 {\n\t\t\tbreak\n\t\t}\n\t\tcurrentInputs = nextInputs\n\t}\n\n\treturn results\n}\n\nfunc (e *Engine) scannerWorker(ctx context.Context) {\n\tvar wgDetect sync.WaitGroup\n\tvar wgVerificationOverlap sync.WaitGroup\n\n\tfor chunk := range e.ChunksChan() {\n\t\tstartTime := time.Now()\n\t\tsourceVerify := chunk.SourceVerify\n\n\t\tchunk.OriginalData = chunk.Data\n\t\tdecoded := iterativeDecode(chunk, e.decoders, e.maxDecodeDepth)\n\n\t\tfor _, d := range decoded {\n\t\t\tmatchingDetectors := e.AhoCorasickCore.FindDetectorMatches(d.Chunk.Data)\n\t\t\tif len(matchingDetectors) > 1 && !e.verificationOverlap {\n\t\t\t\twgVerificationOverlap.Add(1)\n\t\t\t\te.verificationOverlapChunksChan <- verificationOverlapChunk{\n\t\t\t\t\tchunk:                       *d.Chunk,\n\t\t\t\t\tdetectors:                   matchingDetectors,\n\t\t\t\t\tdecoder:                     d.DecoderType,\n\t\t\t\t\tverificationOverlapWgDoneFn: wgVerificationOverlap.Done,\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfor _, detector := range matchingDetectors {\n\t\t\t\twgDetect.Add(1)\n\t\t\t\te.detectableChunksChan <- detectableChunk{\n\t\t\t\t\tchunk:    *d.Chunk,\n\t\t\t\t\tdetector: detector,\n\t\t\t\t\tdecoder:  d.DecoderType,\n\t\t\t\t\tverify:   e.shouldVerifyChunk(sourceVerify, detector, e.detectorVerificationOverrides),\n\t\t\t\t\twgDoneFn: wgDetect.Done,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tdataSize := float64(len(chunk.Data))\n\n\t\tscanBytesPerChunk.WithLabelValues(chunk.SourceType.String()).Observe(dataSize)\n\t\tjobBytesScanned.WithLabelValues(\n\t\t\tchunk.SourceType.String(),\n\t\t\tchunk.SourceName,\n\t\t).Add(dataSize)\n\t\tchunksScannedLatency.Observe(float64(time.Since(startTime).Microseconds()))\n\t\tjobChunksScanned.WithLabelValues(\n\t\t\tchunk.SourceType.String(),\n\t\t\tchunk.SourceName,\n\t\t).Inc()\n\n\t\tatomic.AddUint64(&e.metrics.ChunksScanned, 1)\n\t\tatomic.AddUint64(&e.metrics.BytesScanned, uint64(dataSize))\n\t}\n\n\twgVerificationOverlap.Wait()\n\twgDetect.Wait()\n\tctx.Logger().V(4).Info(\"finished scanning chunks\")\n}\n\nfunc (e *Engine) shouldVerifyChunk(\n\tsourceVerify bool,\n\tdetector detectors.Detector,\n\tdetectorVerificationOverrides map[config.DetectorID]bool,\n) bool {\n\t// The verify flag takes precedence over the detector's verification flag.\n\tif !e.verify {\n\t\treturn false\n\t}\n\n\tdetectorId := config.DetectorID{ID: detector.Type(), Version: 0}\n\n\tif v, ok := detector.(detectors.Versioner); ok {\n\t\tdetectorId.Version = v.Version()\n\t}\n\n\tif detectorVerify, ok := detectorVerificationOverrides[detectorId]; ok {\n\t\treturn detectorVerify\n\t}\n\n\t// If the user is running with a detector verification override that does not specify a particular detector version,\n\t// then its override map entry will have version 0. We should check for that too, but if the detector being checked\n\t// doesn't have any version information then its version is 0, so we've already done the check, and we don't need to\n\t// do it a second time.\n\tif detectorId.Version != 0 {\n\t\tdetectorId.Version = 0\n\n\t\tif detectorVerify, ok := detectorVerificationOverrides[detectorId]; ok {\n\t\t\treturn detectorVerify\n\t\t}\n\t}\n\n\treturn sourceVerify\n}\n\n// chunkSecretKey ties secrets to the specific detector that found them. This allows identifying identical\n// credentials extracted by multiple different detectors processing the same chunk. Or duplicates found\n// by the same detector in the chunk. Exact matches on lookup indicate a duplicate secret for a detector\n// in that chunk - which is expected and not malicious. Those intra-detector dupes are still verified.\ntype chunkSecretKey struct {\n\tsecret      string\n\tdetectorKey ahocorasick.DetectorKey\n}\n\nfunc likelyDuplicate(ctx context.Context, val chunkSecretKey, dupes map[chunkSecretKey]struct{}) bool {\n\tconst similarityThreshold = 0.9\n\n\tvalStr := val.secret\n\tfor dupeKey := range dupes {\n\t\tdupe := dupeKey.secret\n\t\t// Avoid comparing strings of vastly different lengths.\n\t\tif len(dupe)*10 < len(valStr)*9 || len(dupe)*10 > len(valStr)*11 {\n\t\t\tcontinue\n\t\t}\n\n\t\t// If the detector type is the same, we don't need to compare the strings.\n\t\t// These are not duplicates, and should be verified.\n\t\tif val.detectorKey.Type() == dupeKey.detectorKey.Type() {\n\t\t\tcontinue\n\t\t}\n\n\t\tif valStr == dupe {\n\t\t\tctx.Logger().V(2).Info(\n\t\t\t\t\"found exact duplicate\",\n\t\t\t)\n\t\t\treturn true\n\t\t}\n\n\t\tsimilarity := strutil.Similarity(valStr, dupe, metrics.NewLevenshtein())\n\n\t\t// close enough\n\t\tif similarity > similarityThreshold {\n\t\t\tctx.Logger().V(2).Info(\n\t\t\t\t\"found similar duplicate\",\n\t\t\t)\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (e *Engine) verificationOverlapWorker(ctx context.Context) {\n\tvar wgDetect sync.WaitGroup\n\n\t// Reuse the same map and slice to avoid allocations.\n\tconst avgSecretsPerDetector = 8\n\tdetectorKeysWithResults := make(map[ahocorasick.DetectorKey]*ahocorasick.DetectorMatch, avgSecretsPerDetector)\n\tchunkSecrets := make(map[chunkSecretKey]struct{}, avgSecretsPerDetector)\n\n\tfor chunk := range e.verificationOverlapChunksChan {\n\t\tfor _, detector := range chunk.detectors {\n\t\t\tisFalsePositive := detectors.GetFalsePositiveCheck(detector.Detector)\n\n\t\t\t// DO NOT VERIFY at this stage of the pipeline.\n\t\t\tmatchedBytes := detector.Matches()\n\t\t\tfor _, match := range matchedBytes {\n\t\t\t\tctx, cancel := context.WithTimeout(ctx, time.Second*2)\n\t\t\t\tresults, err := detector.FromData(ctx, false, match)\n\t\t\t\tcancel()\n\t\t\t\tif err != nil {\n\t\t\t\t\tctx.Logger().Error(\n\t\t\t\t\t\terr, \"error finding results in chunk during verification overlap\",\n\t\t\t\t\t\t\"detector\", detector.Key.Type().String(),\n\t\t\t\t\t)\n\t\t\t\t}\n\n\t\t\t\tif len(results) == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, ok := detectorKeysWithResults[detector.Key]; !ok {\n\t\t\t\t\tdetectorKeysWithResults[detector.Key] = detector\n\t\t\t\t}\n\n\t\t\t\t// If results filtration eliminates a rotated secret, then that rotation will never be reported. This\n\t\t\t\t// problem can theoretically occur for any scan, but we've only actually seen it in practice during\n\t\t\t\t// targeted scans. (The reason for this discrepancy is unclear.) The simplest fix is therefore to\n\t\t\t\t// disable filtration for targeted scans, but if you're here because this problem surfaced for a\n\t\t\t\t// non-targeted scan then we'll have to solve it correctly.\n\t\t\t\tif chunk.chunk.SecretID == 0 {\n\t\t\t\t\tresults = e.filterResults(ctx, detector, results)\n\t\t\t\t}\n\n\t\t\t\tfor _, res := range results {\n\t\t\t\t\tvar val []byte\n\t\t\t\t\tif res.RawV2 != nil {\n\t\t\t\t\t\tval = res.RawV2\n\t\t\t\t\t} else {\n\t\t\t\t\t\tval = res.Raw\n\t\t\t\t\t}\n\n\t\t\t\t\t// Use levenstein distance to determine if the secret is likely the same.\n\t\t\t\t\t// Ex:\n\t\t\t\t\t// - postman api key: PMAK-qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r\n\t\t\t\t\t// - malicious detector \"api key\": qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r\n\t\t\t\t\tkey := chunkSecretKey{secret: string(val), detectorKey: detector.Key}\n\t\t\t\t\tif _, ok := chunkSecrets[key]; ok {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tif likelyDuplicate(ctx, key, chunkSecrets) {\n\t\t\t\t\t\t// This indicates that the same secret was found by multiple detectors.\n\t\t\t\t\t\t// We should NOT VERIFY this chunk's data.\n\t\t\t\t\t\tif e.verificationOverlapTracker != nil {\n\t\t\t\t\t\t\te.verificationOverlapTracker.Add(1)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tres.SetVerificationError(errOverlap)\n\t\t\t\t\t\te.processResult(\n\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\tres,\n\t\t\t\t\t\t\tchunk.chunk,\n\t\t\t\t\t\t\tchunk.decoder,\n\t\t\t\t\t\t\tdetector.Detector.Description(),\n\t\t\t\t\t\t\tisFalsePositive,\n\t\t\t\t\t\t)\n\n\t\t\t\t\t\t// Remove the detector key from the list of detector keys with results.\n\t\t\t\t\t\t// This is to ensure that the chunk is not reprocessed with verification enabled\n\t\t\t\t\t\t// for this detector.\n\t\t\t\t\t\tdelete(detectorKeysWithResults, detector.Key)\n\t\t\t\t\t}\n\t\t\t\t\tchunkSecrets[key] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor _, detector := range detectorKeysWithResults {\n\t\t\twgDetect.Add(1)\n\t\t\te.detectableChunksChan <- detectableChunk{\n\t\t\t\tchunk:    chunk.chunk,\n\t\t\t\tdetector: detector,\n\t\t\t\tdecoder:  chunk.decoder,\n\t\t\t\tverify:   e.shouldVerifyChunk(chunk.chunk.SourceVerify, detector, e.detectorVerificationOverrides),\n\t\t\t\twgDoneFn: wgDetect.Done,\n\t\t\t}\n\t\t}\n\n\t\t// Empty the dupes and detectors slice\n\t\tfor k := range chunkSecrets {\n\t\t\tdelete(chunkSecrets, k)\n\t\t}\n\t\tfor k := range detectorKeysWithResults {\n\t\t\tdelete(detectorKeysWithResults, k)\n\t\t}\n\n\t\tchunk.verificationOverlapWgDoneFn()\n\t}\n\n\twgDetect.Wait()\n}\n\nfunc (e *Engine) detectorWorker(ctx context.Context) {\n\tfor data := range e.detectableChunksChan {\n\t\tstart := time.Now()\n\t\te.detectChunk(ctx, data)\n\t\tchunksDetectedLatency.Observe(float64(time.Since(start).Milliseconds()))\n\t}\n}\n\nfunc (e *Engine) detectChunk(ctx context.Context, data detectableChunk) {\n\tvar start time.Time\n\tif e.printAvgDetectorTime {\n\t\tstart = time.Now()\n\t}\n\tdefer common.Recover(ctx)\n\n\tctx = context.WithValues(ctx,\n\t\t\"detector\", data.detector.Key.Loggable(),\n\t\t\"decoder_type\", data.decoder.String(),\n\t\t\"chunk_source_name\", data.chunk.SourceName,\n\t\t\"chunk_source_id\", data.chunk.SourceID,\n\t\t\"chunk_source_metadata\", data.chunk.SourceMetadata.String())\n\n\tctx.Logger().V(5).Info(\"Starting to detect chunk\")\n\n\tisFalsePositive := detectors.GetFalsePositiveCheck(data.detector.Detector)\n\n\tvar matchCount int\n\t// To reduce the overhead of regex calls in the detector,\n\t// we limit the amount of data passed to each detector.\n\t// The matches field of the DetectorMatch struct contains the\n\t// relevant portions of the chunk data that were matched.\n\t// This avoids the need for additional regex processing on the entire chunk data.\n\tmatches := data.detector.Matches()\n\tfor _, matchBytes := range matches {\n\t\tmatchCount++\n\t\tdetectBytesPerMatch.Observe(float64(len(matchBytes)))\n\n\t\tctx, cancel := context.WithTimeout(ctx, detectionTimeout)\n\t\tt := time.AfterFunc(detectionTimeout+1*time.Second, func() {\n\t\t\tctx.Logger().Error(nil, \"a detector ignored the context timeout\")\n\t\t})\n\t\tresults, err := e.verificationCache.FromData(\n\t\t\tctx,\n\t\t\tdata.detector.Detector,\n\t\t\tdata.verify,\n\t\t\tdata.chunk.SecretID != 0,\n\t\t\tmatchBytes)\n\t\tt.Stop()\n\t\tcancel()\n\t\tif err != nil {\n\t\t\tctx.Logger().Error(err, \"error finding results in chunk\")\n\t\t\tcontinue\n\t\t}\n\n\t\tdetectorExecutionCount.WithLabelValues(\n\t\t\tdata.detector.Type().String(),\n\t\t\tstrconv.Itoa(int(data.chunk.JobID)),\n\t\t\tdata.chunk.SourceName,\n\t\t).Inc()\n\t\tdetectorExecutionDuration.WithLabelValues(\n\t\t\tdata.detector.Type().String(),\n\t\t).Observe(float64(time.Since(start).Milliseconds()))\n\n\t\tif e.printAvgDetectorTime && len(results) > 0 {\n\t\t\telapsed := time.Since(start)\n\t\t\tdetectorName := results[0].DetectorType.String()\n\t\t\tavgTimeI, ok := e.metrics.detectorAvgTime.Load(detectorName)\n\t\t\tvar avgTime []time.Duration\n\t\t\tif ok {\n\t\t\t\tavgTime, ok = avgTimeI.([]time.Duration)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tavgTime = append(avgTime, elapsed)\n\t\t\te.metrics.detectorAvgTime.Store(detectorName, avgTime)\n\t\t}\n\n\t\t// If results filtration eliminates a rotated secret, then that rotation will never be reported. This problem\n\t\t// can theoretically occur for any scan, but we've only actually seen it in practice during targeted scans. (The\n\t\t// reason for this discrepancy is unclear.) The simplest fix is therefore to disable filtration for targeted\n\t\t// scans, but if you're here because this problem surfaced for a non-targeted scan then we'll have to solve it\n\t\t// correctly.\n\t\tif data.chunk.SecretID == 0 {\n\t\t\tresults = e.filterResults(ctx, data.detector, results)\n\t\t}\n\n\t\tfor _, res := range results {\n\t\t\te.processResult(ctx, res, data.chunk, data.decoder, data.detector.Detector.Description(), isFalsePositive)\n\t\t}\n\t}\n\n\tmatchesPerChunk.Observe(float64(matchCount))\n\n\tctx.Logger().V(5).Info(\"Finished detecting chunk\")\n\n\tdata.wgDoneFn()\n}\n\nfunc (e *Engine) filterResults(\n\tctx context.Context,\n\tdetector *ahocorasick.DetectorMatch,\n\tresults []detectors.Result,\n) []detectors.Result {\n\tclean := detectors.CleanResults\n\tignoreConfig := false\n\tif cleaner, ok := detector.Detector.(detectors.CustomResultsCleaner); ok {\n\t\tclean = cleaner.CleanResults\n\t\tignoreConfig = cleaner.ShouldCleanResultsIrrespectiveOfConfiguration()\n\t}\n\tif e.filterUnverified || ignoreConfig {\n\t\tresults = clean(results)\n\t}\n\n\tif e.filterEntropy != 0 {\n\t\tresults = detectors.FilterResultsWithEntropy(ctx, results, e.filterEntropy, e.retainFalsePositives)\n\t}\n\n\treturn results\n}\n\n// processResult generates a detectors.ResultWithMetadata from the provided chunk and result and puts it on the results\n// channel, unless the result exists on a line with an ignore tag, in which case no result is generated.\nfunc (e *Engine) processResult(\n\tctx context.Context,\n\tres detectors.Result,\n\tchunk sources.Chunk,\n\tdecoderType detectorspb.DecoderType,\n\tdetectorDescription string,\n\tisFalsePositive func(detectors.Result) (bool, string),\n) {\n\tignoreLinePresent := false\n\tif SupportsLineNumbers(chunk.SourceType) {\n\t\tcopyChunk := chunk\n\t\tcopyMetaDataClone := proto.Clone(chunk.SourceMetadata)\n\t\tif copyMetaData, ok := copyMetaDataClone.(*source_metadatapb.MetaData); ok {\n\t\t\tcopyChunk.SourceMetadata = copyMetaData\n\t\t}\n\t\tfragStart, mdLine, link := FragmentFirstLineAndLink(&copyChunk)\n\t\tignoreLinePresent = SetResultLineNumber(&copyChunk, &res, fragStart, mdLine)\n\t\tif err := UpdateLink(ctx, copyChunk.SourceMetadata, link, *mdLine); err != nil {\n\t\t\tctx.Logger().Error(err, \"error setting link\")\n\t\t\treturn\n\t\t}\n\t\tchunk = copyChunk\n\t}\n\tif ignoreLinePresent {\n\t\treturn\n\t}\n\n\tsecret := detectors.CopyMetadata(&chunk, res)\n\tsecret.DecoderType = decoderType\n\tsecret.DetectorDescription = detectorDescription\n\n\tif !res.Verified && res.Raw != nil {\n\t\tisFp, _ := isFalsePositive(res)\n\t\tsecret.IsWordlistFalsePositive = isFp\n\t}\n\n\te.results <- secret\n}\n\nfunc (e *Engine) notifierWorker(ctx context.Context) {\n\tfor result := range e.ResultsChan() {\n\t\tstartTime := time.Now()\n\t\t// Filter unwanted results, based on `--results`.\n\t\tif !result.Verified {\n\t\t\tif result.IsWordlistFalsePositive && !e.retainFalsePositives {\n\t\t\t\t// Skip false positives\n\t\t\t\tcontinue\n\t\t\t} else if result.VerificationError() != nil {\n\t\t\t\tif !e.notifyUnknownResults {\n\t\t\t\t\t// Skip results with verification errors.\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t} else if !e.notifyUnverifiedResults {\n\t\t\t\t// Skip unverified results.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t} else if !e.notifyVerifiedResults {\n\t\t\t// Skip verified results.\n\t\t\t// TODO: Is this a legitimate use case?\n\t\t\tcontinue\n\t\t}\n\t\tatomic.AddUint32(&e.numFoundResults, 1)\n\n\t\t// Dedupe results by comparing the detector type, raw result, and source metadata.\n\t\t// We want to avoid duplicate results with different decoder types, but we also\n\t\t// want to include duplicate results with the same decoder type.\n\t\t// Duplicate results with the same decoder type SHOULD have their own entry in the\n\t\t// results list, this would happen if the same secret is found multiple times.\n\t\t// Note: If the source type is postman, we dedupe the results regardless of decoder type.\n\t\tkey := fmt.Sprintf(\"%s%s%s%+v\", result.DetectorType.String(), result.Raw, result.RawV2, result.SourceMetadata)\n\t\tif val, ok := e.dedupeCache.Get(key); ok && (val != result.DecoderType ||\n\t\t\tresult.SourceType == sourcespb.SourceType_SOURCE_TYPE_POSTMAN) {\n\t\t\tcontinue\n\t\t}\n\t\te.dedupeCache.Add(key, result.DecoderType)\n\n\t\tif result.Verified {\n\t\t\tatomic.AddUint64(&e.metrics.VerifiedSecretsFound, 1)\n\t\t} else {\n\t\t\tatomic.AddUint64(&e.metrics.UnverifiedSecretsFound, 1)\n\t\t}\n\n\t\tif err := e.dispatcher.Dispatch(ctx, result); err != nil {\n\t\t\tctx.Logger().Error(err, \"error notifying result\")\n\t\t}\n\n\t\tchunksNotifiedLatency.Observe(float64(time.Since(startTime).Milliseconds()))\n\t}\n}\n\n// SupportsLineNumbers determines if a line number can be found for a source type.\nfunc SupportsLineNumbers(sourceType sourcespb.SourceType) bool {\n\tswitch sourceType {\n\tcase sourcespb.SourceType_SOURCE_TYPE_GIT,\n\t\tsourcespb.SourceType_SOURCE_TYPE_GITHUB,\n\t\tsourcespb.SourceType_SOURCE_TYPE_GITHUB_REALTIME,\n\t\tsourcespb.SourceType_SOURCE_TYPE_GITLAB,\n\t\tsourcespb.SourceType_SOURCE_TYPE_BITBUCKET,\n\t\tsourcespb.SourceType_SOURCE_TYPE_GERRIT,\n\t\tsourcespb.SourceType_SOURCE_TYPE_GITHUB_UNAUTHENTICATED_ORG,\n\t\tsourcespb.SourceType_SOURCE_TYPE_PUBLIC_GIT,\n\t\tsourcespb.SourceType_SOURCE_TYPE_FILESYSTEM,\n\t\tsourcespb.SourceType_SOURCE_TYPE_AZURE_REPOS:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// FragmentLineOffset sets the line number for a provided source chunk with a given detector result.\nfunc FragmentLineOffset(chunk *sources.Chunk, result *detectors.Result) (int64, bool) {\n\t// get the primary secret value from the result if set\n\tsecret := result.GetPrimarySecretValue()\n\tif secret == \"\" {\n\t\tsecret = string(result.Raw)\n\t}\n\n\tbefore, after, found := bytes.Cut(chunk.Data, []byte(secret))\n\tif !found {\n\t\treturn 0, false\n\t}\n\tlineNumber := int64(bytes.Count(before, []byte(\"\\n\")))\n\tresult.SetPrimarySecretLine(lineNumber)\n\t// If the line contains the ignore tag, we should ignore the result.\n\tendLine := bytes.Index(after, []byte(\"\\n\"))\n\tif endLine == -1 {\n\t\tendLine = len(after)\n\t}\n\tif bytes.Contains(after[:endLine], []byte(ignoreTag)) {\n\t\treturn lineNumber, true\n\t}\n\treturn lineNumber, false\n}\n\n// FragmentFirstLineAndLink extracts the first line number and the link from the chunk metadata.\n// It returns:\n//   - The first line number of the fragment.\n//   - A pointer to the line number, facilitating direct updates.\n//   - The link associated with the fragment. This link may be updated in the chunk metadata\n//     if there's a change in the line number.\nfunc FragmentFirstLineAndLink(chunk *sources.Chunk) (int64, *int64, string) {\n\tif chunk.SourceMetadata == nil {\n\t\treturn 0, nil, \"\"\n\t}\n\n\tvar (\n\t\tfragmentStart *int64\n\t\tlink          string\n\t)\n\tswitch metadata := chunk.SourceMetadata.GetData().(type) {\n\tcase *source_metadatapb.MetaData_Git:\n\t\tfragmentStart = &metadata.Git.Line\n\tcase *source_metadatapb.MetaData_Github:\n\t\tfragmentStart = &metadata.Github.Line\n\t\tlink = metadata.Github.Link\n\tcase *source_metadatapb.MetaData_Gitlab:\n\t\tfragmentStart = &metadata.Gitlab.Line\n\t\tlink = metadata.Gitlab.Link\n\tcase *source_metadatapb.MetaData_Bitbucket:\n\t\tfragmentStart = &metadata.Bitbucket.Line\n\t\tlink = metadata.Bitbucket.Link\n\tcase *source_metadatapb.MetaData_Gerrit:\n\t\tfragmentStart = &metadata.Gerrit.Line\n\tcase *source_metadatapb.MetaData_Filesystem:\n\t\tfragmentStart = &metadata.Filesystem.Line\n\t\tlink = metadata.Filesystem.Link\n\tcase *source_metadatapb.MetaData_AzureRepos:\n\t\tfragmentStart = &metadata.AzureRepos.Line\n\t\tlink = metadata.AzureRepos.Link\n\tdefault:\n\t\treturn 1, nil, \"\"\n\t}\n\n\t// Ensure we maintain 1-based line indexing if fragmentStart is not set or is 0.\n\tif *fragmentStart == 0 {\n\t\t*fragmentStart = 1\n\t}\n\n\treturn *fragmentStart, fragmentStart, link\n}\n\n// SetResultLineNumber sets the line number in the provided result.\nfunc SetResultLineNumber(chunk *sources.Chunk, result *detectors.Result, fragStart int64, mdLine *int64) bool {\n\toffset, skip := FragmentLineOffset(chunk, result)\n\t*mdLine = fragStart + offset\n\treturn skip\n}\n\n// UpdateLink updates the link of the provided source metadata.\nfunc UpdateLink(ctx context.Context, metadata *source_metadatapb.MetaData, link string, line int64) error {\n\tif metadata == nil {\n\t\treturn fmt.Errorf(\"metadata is nil when setting the link\")\n\t}\n\n\tif link == \"\" {\n\t\tctx.Logger().V(4).Info(\"link is empty, skipping update\")\n\t\treturn nil\n\t}\n\n\tnewLink := giturl.UpdateLinkLineNumber(ctx, link, line)\n\n\tswitch meta := metadata.GetData().(type) {\n\tcase *source_metadatapb.MetaData_Github:\n\t\tmeta.Github.Link = newLink\n\tcase *source_metadatapb.MetaData_Gitlab:\n\t\tmeta.Gitlab.Link = newLink\n\tcase *source_metadatapb.MetaData_Bitbucket:\n\t\tmeta.Bitbucket.Link = newLink\n\tcase *source_metadatapb.MetaData_Filesystem:\n\t\tmeta.Filesystem.Link = newLink\n\tcase *source_metadatapb.MetaData_AzureRepos:\n\t\tmeta.AzureRepos.Link = newLink\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported metadata type\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/engine/engine_test.go",
    "content": "package engine\n\nimport (\n\taCtx \"context\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/protobuf/testing/protocmp\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/config\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/custom_detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/decoders\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/gitlab/v2\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/defaults\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/custom_detectorspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/verificationcache\"\n)\n\nconst fakeDetectorKeyword = \"fakedetector\"\n\ntype fakeDetectorV1 struct{}\ntype fakeDetectorV2 struct{}\n\nvar _ detectors.Detector = (*fakeDetectorV1)(nil)\nvar _ detectors.Versioner = (*fakeDetectorV1)(nil)\nvar _ detectors.Detector = (*fakeDetectorV2)(nil)\nvar _ detectors.Versioner = (*fakeDetectorV2)(nil)\n\nfunc (f fakeDetectorV1) FromData(_ aCtx.Context, _ bool, _ []byte) ([]detectors.Result, error) {\n\treturn []detectors.Result{\n\t\t{\n\t\t\tDetectorType: detectorspb.DetectorType(-1),\n\t\t\tVerified:     true,\n\t\t\tRaw:          []byte(\"fake secret v1\"),\n\t\t},\n\t}, nil\n}\n\nfunc (f fakeDetectorV1) Keywords() []string             { return []string{fakeDetectorKeyword} }\nfunc (f fakeDetectorV1) Type() detectorspb.DetectorType { return detectorspb.DetectorType(-1) }\nfunc (f fakeDetectorV1) Version() int                   { return 1 }\n\nfunc (f fakeDetectorV1) Description() string { return \"fake detector v1\" }\n\nfunc (f fakeDetectorV2) FromData(_ aCtx.Context, _ bool, _ []byte) ([]detectors.Result, error) {\n\treturn []detectors.Result{\n\t\t{\n\t\t\tDetectorType: detectorspb.DetectorType(-1),\n\t\t\tVerified:     true,\n\t\t\tRaw:          []byte(\"fake secret v2\"),\n\t\t},\n\t}, nil\n}\n\nfunc (f fakeDetectorV2) Keywords() []string             { return []string{fakeDetectorKeyword} }\nfunc (f fakeDetectorV2) Type() detectorspb.DetectorType { return detectorspb.DetectorType(-1) }\nfunc (f fakeDetectorV2) Version() int                   { return 2 }\n\nfunc (f fakeDetectorV2) Description() string { return \"fake detector v2\" }\n\nfunc TestFragmentLineOffset(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tchunk        *sources.Chunk\n\t\tresult       *detectors.Result\n\t\texpectedLine int64\n\t\tignore       bool\n\t}{\n\t\t{\n\t\t\tname: \"ignore found on same line\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(\"line1\\nline2\\nsecret here trufflehog:ignore\\nline4\"),\n\t\t\t},\n\t\t\tresult: &detectors.Result{\n\t\t\t\tRaw: []byte(\"secret here\"),\n\t\t\t},\n\t\t\texpectedLine: 2,\n\t\t\tignore:       true,\n\t\t},\n\t\t{\n\t\t\tname: \"no ignore\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(\"line1\\nline2\\nsecret here\\nline4\"),\n\t\t\t},\n\t\t\tresult: &detectors.Result{\n\t\t\t\tRaw: []byte(\"secret here\"),\n\t\t\t},\n\t\t\texpectedLine: 2,\n\t\t\tignore:       false,\n\t\t},\n\t\t{\n\t\t\tname: \"ignore on different line\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(\"line1\\nline2\\ntrufflehog:ignore\\nline4\\nsecret here\\nline6\"),\n\t\t\t},\n\t\t\tresult: &detectors.Result{\n\t\t\t\tRaw: []byte(\"secret here\"),\n\t\t\t},\n\t\t\texpectedLine: 4,\n\t\t\tignore:       false,\n\t\t},\n\t\t{\n\t\t\tname: \"match on consecutive lines\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(\"line1\\nline2\\ntrufflehog:ignore\\nline4\\nsecret\\nhere\\nline6\"),\n\t\t\t},\n\t\t\tresult: &detectors.Result{\n\t\t\t\tRaw: []byte(\"secret\\nhere\"),\n\t\t\t},\n\t\t\texpectedLine: 4,\n\t\t\tignore:       false,\n\t\t},\n\t\t{\n\t\t\tname: \"ignore on last consecutive lines\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(\"line1\\nline2\\nline3\\nsecret\\nhere // trufflehog:ignore\\nline5\"),\n\t\t\t},\n\t\t\tresult: &detectors.Result{\n\t\t\t\tRaw: []byte(\"secret\\nhere\"),\n\t\t\t},\n\t\t\texpectedLine: 3,\n\t\t\tignore:       true,\n\t\t},\n\t\t{\n\t\t\tname: \"ignore on last line\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(\"line1\\nline2\\nline3\\nsecret here // trufflehog:ignore\"),\n\t\t\t},\n\t\t\tresult: &detectors.Result{\n\t\t\t\tRaw: []byte(\"secret here\"),\n\t\t\t},\n\t\t\texpectedLine: 3,\n\t\t\tignore:       true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tlineOffset, isIgnored := FragmentLineOffset(tt.chunk, tt.result)\n\t\t\tif lineOffset != tt.expectedLine {\n\t\t\t\tt.Errorf(\"Expected line offset to be %d, got %d\", tt.expectedLine, lineOffset)\n\t\t\t}\n\t\t\tif isIgnored != tt.ignore {\n\t\t\t\tt.Errorf(\"Expected isIgnored to be %v, got %v\", tt.ignore, isIgnored)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestFragmentLineOffsetWithPrimarySecret(t *testing.T) {\n\tprimarySecretResult1 := &detectors.Result{\n\t\tRaw: []byte(\"id heresecret here\"), // RAW has two secrets merged\n\t}\n\n\tprimarySecretResult1.SetPrimarySecretValue(\"secret here\") // set `secret here` as primary secret value for line number calculation\n\n\tprimarySecretResult2 := &detectors.Result{\n\t\tRaw: []byte(\"idsecret\"), // RAW has two secrets merged\n\t}\n\n\ttests := []struct {\n\t\tname         string\n\t\tchunk        *sources.Chunk\n\t\tresult       *detectors.Result\n\t\texpectedLine int64\n\t\tignore       bool\n\t}{\n\t\t{\n\t\t\tname: \"primary secret line number - correct line number\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(\"line1\\nline2\\nid here\\nsecret here\\nline5\"),\n\t\t\t},\n\t\t\tresult:       primarySecretResult1,\n\t\t\texpectedLine: 3,\n\t\t\tignore:       false,\n\t\t},\n\t\t{\n\t\t\tname: \"no primary secret set - wrong line number\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tData: []byte(\"line1\\nline2\\nid\\nsecret\\nline5\"),\n\t\t\t},\n\t\t\tresult:       primarySecretResult2,\n\t\t\texpectedLine: 0,\n\t\t\tignore:       false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tlineOffset, isIgnored := FragmentLineOffset(tt.chunk, tt.result)\n\t\t\tif lineOffset != tt.expectedLine {\n\t\t\t\tt.Errorf(\"Expected line offset to be %d, got %d\", tt.expectedLine, lineOffset)\n\t\t\t}\n\t\t\tif isIgnored != tt.ignore {\n\t\t\t\tt.Errorf(\"Expected isIgnored to be %v, got %v\", tt.ignore, isIgnored)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestFragmentLineOffsetWithPrimarySecretMultiline(t *testing.T) {\n\tresult := &detectors.Result{\n\t\tRaw: []byte(\"secret here\"),\n\t}\n\tresult.SetPrimarySecretValue(\"secret:\\nsecret here\")\n\n\tchunk := &sources.Chunk{\n\t\tData: []byte(\"line1\\nline2\\nsecret:\\nsecret here\\nline5\"),\n\t}\n\tlineOffset, isIgnored := FragmentLineOffset(chunk, result)\n\tassert.False(t, isIgnored)\n\t// offset 2 means line 3\n\tassert.Equal(t, int64(2), lineOffset)\n}\n\nfunc setupFragmentLineOffsetBench(totalLines, needleLine int) (*sources.Chunk, *detectors.Result) {\n\tdata := make([]byte, 0, 4096)\n\tneedle := []byte(\"needle\")\n\tfor i := 0; i < totalLines; i++ {\n\t\tif i != needleLine {\n\t\t\tdata = append(data, []byte(fmt.Sprintf(\"line%d\\n\", i))...)\n\t\t\tcontinue\n\t\t}\n\t\tdata = append(data, needle...)\n\t\tdata = append(data, '\\n')\n\t}\n\tchunk := &sources.Chunk{Data: data}\n\tresult := &detectors.Result{Raw: needle}\n\treturn chunk, result\n}\n\nfunc BenchmarkFragmentLineOffsetStart(b *testing.B) {\n\tchunk, result := setupFragmentLineOffsetBench(512, 2)\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = FragmentLineOffset(chunk, result)\n\t}\n}\n\nfunc BenchmarkFragmentLineOffsetMiddle(b *testing.B) {\n\tchunk, result := setupFragmentLineOffsetBench(512, 256)\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = FragmentLineOffset(chunk, result)\n\t}\n}\n\nfunc BenchmarkFragmentLineOffsetEnd(b *testing.B) {\n\tchunk, result := setupFragmentLineOffsetBench(512, 510)\n\tfor i := 0; i < b.N; i++ {\n\t\t_, _ = FragmentLineOffset(chunk, result)\n\t}\n}\n\n// Test to make sure that DefaultDecoders always returns the UTF8 decoder first.\n// Technically a decoder test but we want this to run and fail in CI\nfunc TestDefaultDecoders(t *testing.T) {\n\tds := decoders.DefaultDecoders()\n\tif _, ok := ds[0].(*decoders.UTF8); !ok {\n\t\tt.Errorf(\"DefaultDecoders() = %v, expected UTF8 decoder to be first\", ds)\n\t}\n}\n\nfunc TestSupportsLineNumbers(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tsourceType    sourcespb.SourceType\n\t\texpectedValue bool\n\t}{\n\t\t{\"Git source\", sourcespb.SourceType_SOURCE_TYPE_GIT, true},\n\t\t{\"Github source\", sourcespb.SourceType_SOURCE_TYPE_GITHUB, true},\n\t\t{\"Gitlab source\", sourcespb.SourceType_SOURCE_TYPE_GITLAB, true},\n\t\t{\"Bitbucket source\", sourcespb.SourceType_SOURCE_TYPE_BITBUCKET, true},\n\t\t{\"Gerrit source\", sourcespb.SourceType_SOURCE_TYPE_GERRIT, true},\n\t\t{\"Github unauthenticated org source\", sourcespb.SourceType_SOURCE_TYPE_GITHUB_UNAUTHENTICATED_ORG, true},\n\t\t{\"Public Git source\", sourcespb.SourceType_SOURCE_TYPE_PUBLIC_GIT, true},\n\t\t{\"Filesystem source\", sourcespb.SourceType_SOURCE_TYPE_FILESYSTEM, true},\n\t\t{\"Azure Repos source\", sourcespb.SourceType_SOURCE_TYPE_AZURE_REPOS, true},\n\t\t{\"Unsupported type\", sourcespb.SourceType_SOURCE_TYPE_BUILDKITE, false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := SupportsLineNumbers(tt.sourceType)\n\t\t\tassert.Equal(t, tt.expectedValue, result)\n\t\t})\n\t}\n}\n\nfunc BenchmarkSupportsLineNumbersLoop(b *testing.B) {\n\tsourceType := sourcespb.SourceType_SOURCE_TYPE_GITHUB\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = SupportsLineNumbers(sourceType)\n\t}\n}\n\n// TestEngine_DuplicateSecrets is a test that detects ALL duplicate secrets with the same decoder.\nfunc TestEngine_DuplicateSecrets(t *testing.T) {\n\tctx := context.Background()\n\n\tabsPath, err := filepath.Abs(\"./testdata/secrets.txt\")\n\tassert.Nil(t, err)\n\n\tctx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\tdefer cancel()\n\n\tconst defaultOutputBufferSize = 64\n\topts := []func(*sources.SourceManager){\n\t\tsources.WithSourceUnits(),\n\t\tsources.WithBufferedOutput(defaultOutputBufferSize),\n\t}\n\n\tsourceManager := sources.NewManager(opts...)\n\n\tconf := Config{\n\t\tConcurrency:   1,\n\t\tDecoders:      decoders.DefaultDecoders(),\n\t\tDetectors:     defaults.DefaultDetectors(),\n\t\tVerify:        false,\n\t\tSourceManager: sourceManager,\n\t\tDispatcher:    NewPrinterDispatcher(new(discardPrinter)),\n\t}\n\n\te, err := NewEngine(ctx, &conf)\n\tassert.NoError(t, err)\n\n\te.Start(ctx)\n\n\tcfg := sources.FilesystemConfig{Paths: []string{absPath}}\n\tif _, err := e.ScanFileSystem(ctx, cfg); err != nil {\n\t\treturn\n\t}\n\n\t// Wait for all the chunks to be processed.\n\tassert.Nil(t, e.Finish(ctx))\n\twant := uint64(2)\n\tassert.Equal(t, want, e.GetMetrics().UnverifiedSecretsFound)\n}\n\n// lineCaptureDispatcher is a test dispatcher that captures the line number\n// of detected secrets. It implements the Dispatcher interface and is used\n// to verify that the Engine correctly identifies and reports the line numbers\n// where secrets are found in the source code.\ntype lineCaptureDispatcher struct{ line int64 }\n\nfunc (d *lineCaptureDispatcher) Dispatch(_ context.Context, result detectors.ResultWithMetadata) error {\n\td.line = result.SourceMetadata.GetFilesystem().GetLine()\n\treturn nil\n}\n\nfunc TestEngineLineVariations(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tcontent      string\n\t\texpectedLine int64\n\t}{\n\t\t{\n\t\t\tname: \"secret on first line\",\n\t\t\tcontent: `AKIA2OGYBAH6STMMNXNN\naws_secret_access_key = 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`,\n\t\t\texpectedLine: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"secret after multiple newlines\",\n\t\t\tcontent: `\n\n\nAKIA2OGYBAH6STMMNXNN\naws_secret_access_key = 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`,\n\t\t\texpectedLine: 4,\n\t\t},\n\t\t{\n\t\t\tname: \"secret with mixed whitespace before\",\n\t\t\tcontent: `first line\n\n\nAKIA2OGYBAH6STMMNXNN\naws_secret_access_key = 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`,\n\t\t\texpectedLine: 4,\n\t\t},\n\t\t{\n\t\t\tname: \"secret with content after\",\n\t\t\tcontent: `[default]\nregion = us-east-1\nAKIA2OGYBAH6STMMNXNN\naws_secret_access_key = 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\nmore content\neven more`,\n\t\t\texpectedLine: 3,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\t\t\tdefer cancel()\n\n\t\t\ttmpFile, err := os.CreateTemp(\"\", \"test_aws_credentials\")\n\t\t\tassert.NoError(t, err)\n\t\t\tdefer os.Remove(tmpFile.Name())\n\n\t\t\terr = os.WriteFile(tmpFile.Name(), []byte(tt.content), os.ModeAppend)\n\t\t\tassert.NoError(t, err)\n\n\t\t\tconst defaultOutputBufferSize = 64\n\t\t\topts := []func(*sources.SourceManager){\n\t\t\t\tsources.WithSourceUnits(),\n\t\t\t\tsources.WithBufferedOutput(defaultOutputBufferSize),\n\t\t\t}\n\n\t\t\tsourceManager := sources.NewManager(opts...)\n\t\t\tlineCapturer := new(lineCaptureDispatcher)\n\n\t\t\tconf := Config{\n\t\t\t\tConcurrency:   1,\n\t\t\t\tDecoders:      decoders.DefaultDecoders(),\n\t\t\t\tDetectors:     defaults.DefaultDetectors(),\n\t\t\t\tVerify:        false,\n\t\t\t\tSourceManager: sourceManager,\n\t\t\t\tDispatcher:    lineCapturer,\n\t\t\t}\n\n\t\t\teng, err := NewEngine(ctx, &conf)\n\t\t\tassert.NoError(t, err)\n\n\t\t\teng.Start(ctx)\n\n\t\t\tcfg := sources.FilesystemConfig{Paths: []string{tmpFile.Name()}}\n\t\t\t_, err = eng.ScanFileSystem(ctx, cfg)\n\t\t\tassert.NoError(t, err)\n\n\t\t\tassert.NoError(t, eng.Finish(ctx))\n\t\t\twant := uint64(1)\n\t\t\tassert.Equal(t, want, eng.GetMetrics().UnverifiedSecretsFound)\n\t\t\tassert.Equal(t, tt.expectedLine, lineCapturer.line)\n\t\t})\n\t}\n}\n\n// TestEngine_VersionedDetectorsVerifiedSecrets is a test that detects ALL verified secrets across\n// versioned detectors.\nfunc TestEngine_VersionedDetectorsVerifiedSecrets(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*10)\n\tdefer cancel()\n\n\ttmpFile, err := os.CreateTemp(\"\", \"testfile\")\n\tassert.Nil(t, err)\n\tdefer tmpFile.Close()\n\tdefer os.Remove(tmpFile.Name())\n\n\t_, err = tmpFile.WriteString(fmt.Sprintf(\"test data using keyword %s\", fakeDetectorKeyword))\n\tassert.NoError(t, err)\n\n\tconst defaultOutputBufferSize = 64\n\topts := []func(*sources.SourceManager){\n\t\tsources.WithSourceUnits(),\n\t\tsources.WithBufferedOutput(defaultOutputBufferSize),\n\t}\n\n\tsourceManager := sources.NewManager(opts...)\n\n\tconf := Config{\n\t\tConcurrency:   1,\n\t\tDecoders:      decoders.DefaultDecoders(),\n\t\tDetectors:     []detectors.Detector{new(fakeDetectorV1), new(fakeDetectorV2)},\n\t\tVerify:        true,\n\t\tSourceManager: sourceManager,\n\t\tDispatcher:    NewPrinterDispatcher(new(discardPrinter)),\n\t}\n\n\te, err := NewEngine(ctx, &conf)\n\tassert.NoError(t, err)\n\n\te.Start(ctx)\n\n\tcfg := sources.FilesystemConfig{Paths: []string{tmpFile.Name()}}\n\tif _, err := e.ScanFileSystem(ctx, cfg); err != nil {\n\t\treturn\n\t}\n\n\tassert.NoError(t, e.Finish(ctx))\n\twant := uint64(2)\n\tassert.Equal(t, want, e.GetMetrics().VerifiedSecretsFound)\n}\n\n// TestEngine_CustomDetectorsDetectorsVerifiedSecrets is a test that covers an edge case where there are\n// multiple detectors with the same type, keywords and regex that match the same secret.\n// This ensures that those secrets get verified.\nfunc TestEngine_CustomDetectorsDetectorsVerifiedSecrets(t *testing.T) {\n\ttmpFile, err := os.CreateTemp(\"\", \"testfile\")\n\tassert.Nil(t, err)\n\tdefer tmpFile.Close()\n\tdefer os.Remove(tmpFile.Name())\n\n\t_, err = tmpFile.WriteString(\"test stuff\")\n\tassert.Nil(t, err)\n\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t}))\n\tdefer ts.Close()\n\n\tcustomDetector1, err := custom_detectors.NewWebhookCustomRegex(&custom_detectorspb.CustomRegex{\n\t\tName:     \"custom detector 1\",\n\t\tKeywords: []string{\"test\"},\n\t\tRegex:    map[string]string{\"test\": \"\\\\w+\"},\n\t\tVerify:   []*custom_detectorspb.VerifierConfig{{Endpoint: ts.URL, Unsafe: true, SuccessRanges: []string{\"200\"}}},\n\t})\n\tassert.Nil(t, err)\n\n\tcustomDetector2, err := custom_detectors.NewWebhookCustomRegex(&custom_detectorspb.CustomRegex{\n\t\tName:     \"custom detector 2\",\n\t\tKeywords: []string{\"test\"},\n\t\tRegex:    map[string]string{\"test\": \"\\\\w+\"},\n\t\tVerify:   []*custom_detectorspb.VerifierConfig{{Endpoint: ts.URL, Unsafe: true, SuccessRanges: []string{\"200\"}}},\n\t})\n\tassert.Nil(t, err)\n\n\tallDetectors := []detectors.Detector{customDetector1, customDetector2}\n\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\n\tconst defaultOutputBufferSize = 64\n\topts := []func(*sources.SourceManager){\n\t\tsources.WithSourceUnits(),\n\t\tsources.WithBufferedOutput(defaultOutputBufferSize),\n\t}\n\n\tsourceManager := sources.NewManager(opts...)\n\n\tconf := Config{\n\t\tConcurrency:   1,\n\t\tDecoders:      decoders.DefaultDecoders(),\n\t\tDetectors:     allDetectors,\n\t\tVerify:        true,\n\t\tSourceManager: sourceManager,\n\t\tDispatcher:    NewPrinterDispatcher(new(discardPrinter)),\n\t}\n\n\te, err := NewEngine(ctx, &conf)\n\tassert.NoError(t, err)\n\n\te.Start(ctx)\n\n\tcfg := sources.FilesystemConfig{Paths: []string{tmpFile.Name()}}\n\tif _, err := e.ScanFileSystem(ctx, cfg); err != nil {\n\t\treturn\n\t}\n\n\tassert.Nil(t, e.Finish(ctx))\n\t// We should have 4 verified secrets, 2 for each custom detector.\n\twant := uint64(4)\n\tassert.Equal(t, want, e.GetMetrics().VerifiedSecretsFound)\n}\n\nfunc TestProcessResult_SourceSupportsLineNumbers_LinkUpdated(t *testing.T) {\n\t// Arrange: Create an engine\n\te := Engine{results: make(chan detectors.ResultWithMetadata, 1)}\n\n\t// Arrange: Create a Chunk\n\tchunk := sources.Chunk{\n\t\tData: []byte(\"abcde\\nswordfish\"),\n\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\tLine: 1,\n\t\t\t\t\tLink: \"https://github.com/org/repo/blob/abcdef/file.txt#L1\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tSourceType: sourcespb.SourceType_SOURCE_TYPE_GIT,\n\t}\n\n\t// Arrange: Create a Result\n\tresult := detectors.Result{\n\t\tRaw:      []byte(\"swordfish\"),\n\t\tVerified: true,\n\t}\n\n\t// Act\n\te.processResult(context.AddLogger(t.Context()), result, chunk, 0, \"\", nil)\n\n\t// Assert that the link has been correctly updated\n\trequire.Len(t, e.results, 1)\n\tr := <-e.results\n\tassert.Equal(t, \"https://github.com/org/repo/blob/abcdef/file.txt#L2\", r.SourceMetadata.GetGithub().GetLink())\n}\n\nfunc TestProcessResult_IgnoreLinePresent_NothingGenerated(t *testing.T) {\n\t// Arrange: Create an engine\n\te := Engine{results: make(chan detectors.ResultWithMetadata, 1)}\n\n\t// Arrange: Create a Chunk\n\tchunk := sources.Chunk{\n\t\tData: []byte(\"swordfish trufflehog:ignore\"),\n\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\tData: &source_metadatapb.MetaData_Git{\n\t\t\t\tGit: &source_metadatapb.Git{\n\t\t\t\t\tLine: 1,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tSourceType: sourcespb.SourceType_SOURCE_TYPE_GIT,\n\t}\n\n\t// Arrange: Create a Result\n\tresult := detectors.Result{\n\t\tRaw:      []byte(\"swordfish\"),\n\t\tVerified: true,\n\t}\n\n\t// Act\n\te.processResult(context.AddLogger(t.Context()), result, chunk, 0, \"\", nil)\n\n\t// Assert that no results were generated\n\tassert.Empty(t, e.results)\n}\n\nfunc TestProcessResult_AllFieldsCopied(t *testing.T) {\n\t// Arrange: Create an engine\n\te := Engine{results: make(chan detectors.ResultWithMetadata, 1)}\n\n\t// Arrange: Create a Chunk\n\tchunk := sources.Chunk{\n\t\tSourceName: \"test source\",\n\t\tSourceID:   1,\n\t\tJobID:      2,\n\t\tSecretID:   3,\n\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\tData: &source_metadatapb.MetaData_Docker{\n\t\t\t\tDocker: &source_metadatapb.Docker{\n\t\t\t\t\tFile:  \"file\",\n\t\t\t\t\tImage: \"image\",\n\t\t\t\t\tLayer: \"layer\",\n\t\t\t\t\tTag:   \"tag\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tSourceType: sourcespb.SourceType_SOURCE_TYPE_DOCKER,\n\t}\n\n\t// Arrange: Create a Result\n\tresult := detectors.Result{\n\t\tDetectorType: detectorspb.DetectorType(-1),\n\t\tExtraData:    map[string]string{\"key\": \"value\"},\n\t\tRaw:          []byte(\"something\"),\n\t\tRawV2:        []byte(\"something:else\"),\n\t\tRedacted:     \"someth***\",\n\t\tVerified:     true,\n\t}\n\n\t// Act\n\te.processResult(context.AddLogger(t.Context()), result, chunk, detectorspb.DecoderType_PLAIN, \"a detector that detects\", nil)\n\n\t// Assert that the single generated result has the correct fields\n\trequire.Len(t, e.results, 1)\n\tr := <-e.results\n\tif diff := cmp.Diff(chunk.SourceMetadata, r.SourceMetadata, protocmp.Transform()); diff != \"\" {\n\t\tt.Errorf(\"metadata mismatch (-want +got):\\n%s\", diff)\n\t}\n\tassert.Equal(t, map[string]string{\"key\": \"value\"}, r.ExtraData)\n\tassert.Equal(t, []byte(\"something\"), r.Raw)\n\tassert.Equal(t, []byte(\"something:else\"), r.RawV2)\n\tassert.Equal(t, \"someth***\", r.Redacted)\n\tassert.True(t, r.Verified)\n\tassert.Equal(t, detectorspb.DetectorType(-1), r.DetectorType)\n\tassert.Equal(t, sources.SourceID(1), r.SourceID)\n\tassert.Equal(t, sources.JobID(2), r.JobID)\n\tassert.Equal(t, int64(3), r.SecretID)\n\tassert.Equal(t, \"test source\", r.SourceName)\n\tassert.Equal(t, sourcespb.SourceType_SOURCE_TYPE_DOCKER, r.SourceType)\n\tassert.Equal(t, detectorspb.DecoderType_PLAIN, r.DecoderType)\n\tassert.Equal(t, \"a detector that detects\", r.DetectorDescription)\n}\n\nfunc TestProcessResult_FalsePositiveFlagSetCorrectly(t *testing.T) {\n\ttestcases := []struct {\n\t\tname                string\n\t\tverified            bool\n\t\tisFalsePositive     bool\n\t\twantIsFalsePositive bool\n\t}{\n\t\t{\n\t\t\tname:                \"unverified/false positive\",\n\t\t\tverified:            false,\n\t\t\tisFalsePositive:     true,\n\t\t\twantIsFalsePositive: true,\n\t\t},\n\t\t{\n\t\t\tname:                \"unverified/not false positive\",\n\t\t\tverified:            false,\n\t\t\tisFalsePositive:     false,\n\t\t\twantIsFalsePositive: false,\n\t\t},\n\t\t{\n\t\t\tname:                \"verified/false positive\",\n\t\t\tverified:            true,\n\t\t\tisFalsePositive:     true,\n\t\t\twantIsFalsePositive: false, // The false positive check should not be run for verified secrets\n\t\t},\n\t\t{\n\t\t\tname:                \"verified/not false positive\",\n\t\t\tverified:            true,\n\t\t\tisFalsePositive:     false,\n\t\t\twantIsFalsePositive: false,\n\t\t},\n\t}\n\n\tfor _, tt := range testcases {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// Arrange: Create an Engine\n\t\t\te := Engine{results: make(chan detectors.ResultWithMetadata, 1)}\n\n\t\t\t// Arrange: Create a Result\n\t\t\tres := detectors.Result{\n\t\t\t\tRaw:      []byte(\"something not nil\"), // The false positive check is not run when Raw is nil\n\t\t\t\tVerified: tt.verified,\n\t\t\t}\n\n\t\t\t// Arrange: Create the false positive check\n\t\t\tisFalsePositive := func(_ detectors.Result) (bool, string) { return tt.isFalsePositive, \"\" }\n\n\t\t\t// Act\n\t\t\te.processResult(context.AddLogger(t.Context()), res, sources.Chunk{}, 0, \"\", isFalsePositive)\n\n\t\t\t// Assert that the single generated result has the correct false positive flag\n\t\t\trequire.Len(t, e.results, 1)\n\t\t\tassert.Equal(t, tt.wantIsFalsePositive, (<-e.results).IsWordlistFalsePositive)\n\t\t})\n\t}\n}\n\nfunc TestVerificationOverlapChunk(t *testing.T) {\n\tctx := context.Background()\n\n\tabsPath, err := filepath.Abs(\"./testdata/verificationoverlap_secrets.txt\")\n\tassert.Nil(t, err)\n\n\tctx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\tdefer cancel()\n\n\tconfPath, err := filepath.Abs(\"./testdata/verificationoverlap_detectors.yaml\")\n\tassert.Nil(t, err)\n\tconf, err := config.Read(confPath)\n\tassert.Nil(t, err)\n\n\tconst defaultOutputBufferSize = 64\n\topts := []func(*sources.SourceManager){\n\t\tsources.WithSourceUnits(),\n\t\tsources.WithBufferedOutput(defaultOutputBufferSize),\n\t}\n\n\tsourceManager := sources.NewManager(opts...)\n\n\tc := Config{\n\t\tConcurrency:      1,\n\t\tDecoders:         decoders.DefaultDecoders(),\n\t\tDetectors:        conf.Detectors,\n\t\tIncludeDetectors: \"904\", // isolate this test to only the custom detectors provided\n\t\tVerify:           false,\n\t\tSourceManager:    sourceManager,\n\t\tDispatcher:       NewPrinterDispatcher(new(discardPrinter)),\n\t}\n\n\te, err := NewEngine(ctx, &c)\n\tassert.NoError(t, err)\n\n\te.verificationOverlapTracker = &atomic.Int32{}\n\n\te.Start(ctx)\n\n\tcfg := sources.FilesystemConfig{Paths: []string{absPath}}\n\tif _, err := e.ScanFileSystem(ctx, cfg); err != nil {\n\t\treturn\n\t}\n\n\t// Wait for all the chunks to be processed.\n\tassert.Nil(t, e.Finish(ctx))\n\t// We want TWO secrets that match both the custom regexes.\n\twant := uint64(2)\n\tassert.Equal(t, want, e.GetMetrics().UnverifiedSecretsFound)\n\n\t// We want 0 because these are custom detectors and verification should still occur.\n\twantDupe := int32(0)\n\tassert.Equal(t, wantDupe, e.verificationOverlapTracker.Load())\n}\n\nfunc TestEngine_FalsePositivesRetainedCorrectly(t *testing.T) {\n\t// Arrange: Generate the absolute path of the file to scan\n\tsecretsPath, err := filepath.Abs(\"./testdata/verificationoverlap_secrets_fp.txt\")\n\trequire.NoError(t, err)\n\n\ttestCases := []struct {\n\t\tname                      string\n\t\tdetectors                 []detectors.Detector\n\t\tretainFalsePositives      bool\n\t\twantUnverifiedSecretCount uint64\n\t}{\n\t\t{\n\t\t\tname: \"no overlap, retain false positives\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\tpassthroughDetector{detectorType: detectorspb.DetectorType(-1), keywords: []string{\"sample\"}},\n\t\t\t},\n\t\t\tretainFalsePositives:      true,\n\t\t\twantUnverifiedSecretCount: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"no overlap, do not retain false positives\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\tpassthroughDetector{detectorType: detectorspb.DetectorType(-1), keywords: []string{\"sample\"}},\n\t\t\t},\n\t\t\tretainFalsePositives:      false,\n\t\t\twantUnverifiedSecretCount: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"overlap, retain false positives\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\tpassthroughDetector{detectorType: detectorspb.DetectorType(-1), keywords: []string{\"sample\"}},\n\t\t\t\tpassthroughDetector{detectorType: detectorspb.DetectorType(-2), keywords: []string{\"ample\"}},\n\t\t\t},\n\t\t\tretainFalsePositives:      true,\n\t\t\twantUnverifiedSecretCount: 2,\n\t\t},\n\t\t{\n\t\t\tname: \"overlap, do not retain false positives\",\n\t\t\tdetectors: []detectors.Detector{\n\t\t\t\tpassthroughDetector{detectorType: detectorspb.DetectorType(-1), keywords: []string{\"sample\"}},\n\t\t\t\tpassthroughDetector{detectorType: detectorspb.DetectorType(-2), keywords: []string{\"ample\"}},\n\t\t\t},\n\t\t\tretainFalsePositives:      false,\n\t\t\twantUnverifiedSecretCount: 0,\n\t\t},\n\t}\n\n\tfor _, tt := range testCases {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tctx := context.AddLogger(t.Context())\n\n\t\t\t// Arrange: Generate a base engine config\n\t\t\tengineConfig := Config{\n\t\t\t\tConcurrency:   1,\n\t\t\t\tDecoders:      decoders.DefaultDecoders(),\n\t\t\t\tDetectors:     tt.detectors,\n\t\t\t\tDispatcher:    NewPrinterDispatcher(new(discardPrinter)),\n\t\t\t\tResults:       map[string]struct{}{\"verified\": {}, \"unverified\": {}, \"unknown\": {}},\n\t\t\t\tSourceManager: sources.NewManager(sources.WithSourceUnits()),\n\t\t\t\tVerify:        false,\n\t\t\t}\n\n\t\t\t// Arrange: Set the appropriate false positive flag\n\t\t\tif tt.retainFalsePositives {\n\t\t\t\tengineConfig.Results[\"filtered_unverified\"] = struct{}{}\n\t\t\t}\n\n\t\t\t// Arrange: Create and start an engine\n\t\t\te, err := NewEngine(ctx, &engineConfig)\n\t\t\trequire.NoError(t, err)\n\t\t\te.Start(ctx)\n\n\t\t\t// Act: Scan the file\n\t\t\tcfg := sources.FilesystemConfig{Paths: []string{secretsPath}}\n\t\t\t_, err = e.ScanFileSystem(ctx, cfg)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NoError(t, e.Finish(ctx))\n\n\t\t\t// Assert that the unverified secret count was expected\n\t\t\tassert.Equal(t, tt.wantUnverifiedSecretCount, e.GetMetrics().UnverifiedSecretsFound)\n\t\t})\n\t}\n}\n\nfunc TestFragmentFirstLineAndLink(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\tchunk        *sources.Chunk\n\t\texpectedLine int64\n\t\texpectedLink string\n\t}{\n\t\t{\n\t\t\tname: \"Test Git Metadata\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Git{\n\t\t\t\t\t\tGit: &source_metadatapb.Git{\n\t\t\t\t\t\t\tLine: 10,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedLine: 10,\n\t\t\texpectedLink: \"\", // Git doesn't support links\n\t\t},\n\t\t{\n\t\t\tname: \"Test Github Metadata\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\t\tLine: 5,\n\t\t\t\t\t\t\tLink: \"https://example.github.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedLine: 5,\n\t\t\texpectedLink: \"https://example.github.com\",\n\t\t},\n\t\t{\n\t\t\tname: \"Test Azure Repos Metadata\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_AzureRepos{\n\t\t\t\t\t\tAzureRepos: &source_metadatapb.AzureRepos{\n\t\t\t\t\t\t\tLine: 5,\n\t\t\t\t\t\t\tLink: \"https://example.azure.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedLine: 5,\n\t\t\texpectedLink: \"https://example.azure.com\",\n\t\t},\n\t\t{\n\t\t\tname: \"Line number not set\",\n\t\t\tchunk: &sources.Chunk{\n\t\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\t\tLink: \"https://example.github.com\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedLine: 1,\n\t\t\texpectedLink: \"https://example.github.com\",\n\t\t},\n\t\t{\n\t\t\tname:         \"Unsupported Type\",\n\t\t\tchunk:        &sources.Chunk{},\n\t\t\texpectedLine: 0,\n\t\t\texpectedLink: \"\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tline, linePtr, link := FragmentFirstLineAndLink(tt.chunk)\n\t\t\tassert.Equal(t, tt.expectedLink, link, \"Mismatch in link\")\n\t\t\tassert.Equal(t, tt.expectedLine, line, \"Mismatch in line\")\n\n\t\t\tif linePtr != nil {\n\t\t\t\tassert.Equal(t, tt.expectedLine, *linePtr, \"Mismatch in linePtr value\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetLink(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tinput    *source_metadatapb.MetaData\n\t\tlink     string\n\t\tline     int64\n\t\twantLink string\n\t\twantErr  bool\n\t}{\n\t\t{\n\t\t\tname: \"Github link set\",\n\t\t\tinput: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\tGithub: &source_metadatapb.Github{},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlink:     \"https://github.com/example\",\n\t\t\tline:     42,\n\t\t\twantLink: \"https://github.com/example#L42\",\n\t\t},\n\t\t{\n\t\t\tname: \"Gitlab link set\",\n\t\t\tinput: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Gitlab{\n\t\t\t\t\tGitlab: &source_metadatapb.Gitlab{},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlink:     \"https://gitlab.com/example\",\n\t\t\tline:     10,\n\t\t\twantLink: \"https://gitlab.com/example#L10\",\n\t\t},\n\t\t{\n\t\t\tname: \"Bitbucket link set\",\n\t\t\tinput: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Bitbucket{\n\t\t\t\t\tBitbucket: &source_metadatapb.Bitbucket{},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlink:     \"https://bitbucket.com/example\",\n\t\t\tline:     8,\n\t\t\twantLink: \"https://bitbucket.com/example#L8\",\n\t\t},\n\t\t{\n\t\t\tname: \"Filesystem link set\",\n\t\t\tinput: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Filesystem{\n\t\t\t\t\tFilesystem: &source_metadatapb.Filesystem{},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlink:     \"file:///path/to/example\",\n\t\t\tline:     3,\n\t\t\twantLink: \"file:///path/to/example#L3\",\n\t\t},\n\t\t{\n\t\t\tname: \"Azure Repos link set\",\n\t\t\tinput: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_AzureRepos{\n\t\t\t\t\tAzureRepos: &source_metadatapb.AzureRepos{},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlink:     \"https://dev.azure.com/example\",\n\t\t\tline:     3,\n\t\t\twantLink: \"https://dev.azure.com/example?line=3&lineEnd=4&lineStartColumn=1\",\n\t\t},\n\t\t{\n\t\t\tname: \"Unsupported metadata type\",\n\t\t\tinput: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Git{\n\t\t\t\t\tGit: &source_metadatapb.Git{},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlink:    \"https://git.example.com/link\",\n\t\t\tline:    5,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"Metadata nil\",\n\t\t\tinput:   nil,\n\t\t\tlink:    \"https://some.link\",\n\t\t\tline:    1,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tctx := context.Background()\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\terr := UpdateLink(ctx, tt.input, tt.link, tt.line)\n\t\t\tif err != nil && !tt.wantErr {\n\t\t\t\tt.Errorf(\"Unexpected error: %v\", err)\n\t\t\t}\n\n\t\t\tif tt.wantErr {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tswitch data := tt.input.GetData().(type) {\n\t\t\tcase *source_metadatapb.MetaData_Github:\n\t\t\t\tassert.Equal(t, tt.wantLink, data.Github.Link, \"Github link mismatch\")\n\t\t\tcase *source_metadatapb.MetaData_Gitlab:\n\t\t\t\tassert.Equal(t, tt.wantLink, data.Gitlab.Link, \"Gitlab link mismatch\")\n\t\t\tcase *source_metadatapb.MetaData_Bitbucket:\n\t\t\t\tassert.Equal(t, tt.wantLink, data.Bitbucket.Link, \"Bitbucket link mismatch\")\n\t\t\tcase *source_metadatapb.MetaData_Filesystem:\n\t\t\t\tassert.Equal(t, tt.wantLink, data.Filesystem.Link, \"Filesystem link mismatch\")\n\t\t\tcase *source_metadatapb.MetaData_AzureRepos:\n\t\t\t\tassert.Equal(t, tt.wantLink, data.AzureRepos.Link, \"Azure Repos link mismatch\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestLikelyDuplicate(t *testing.T) {\n\t// Initialize detectors\n\t// (not actually calling detector FromData or anything, just using detector struct for key creation)\n\tdetectorA := ahocorasick.DetectorMatch{\n\t\tKey:      ahocorasick.CreateDetectorKey(defaults.DefaultDetectors()[0]),\n\t\tDetector: defaults.DefaultDetectors()[0],\n\t}\n\tdetectorB := ahocorasick.DetectorMatch{\n\t\tKey:      ahocorasick.CreateDetectorKey(defaults.DefaultDetectors()[1]),\n\t\tDetector: defaults.DefaultDetectors()[1],\n\t}\n\n\t// Define test cases\n\ttests := []struct {\n\t\tname     string\n\t\tval      chunkSecretKey\n\t\tdupes    map[chunkSecretKey]struct{}\n\t\texpected bool\n\t}{\n\t\t{\n\t\t\tname: \"exact duplicate different detector\",\n\t\t\tval:  chunkSecretKey{\"PMAK-qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r\", detectorA.Key},\n\t\t\tdupes: map[chunkSecretKey]struct{}{\n\t\t\t\t{\"PMAK-qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r\", detectorB.Key}: {},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tname: \"non-duplicate length outside range\",\n\t\t\tval:  chunkSecretKey{\"short\", detectorA.Key},\n\t\t\tdupes: map[chunkSecretKey]struct{}{\n\t\t\t\t{\"muchlongerthanthevalstring\", detectorB.Key}: {},\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tname: \"similar within threshold\",\n\t\t\tval:  chunkSecretKey{\"PMAK-qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r\", detectorA.Key},\n\t\t\tdupes: map[chunkSecretKey]struct{}{\n\t\t\t\t{\"qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r\", detectorB.Key}: {},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tname: \"similar outside threshold\",\n\t\t\tval:  chunkSecretKey{\"anotherkey\", detectorA.Key},\n\t\t\tdupes: map[chunkSecretKey]struct{}{\n\t\t\t\t{\"completelydifferent\", detectorB.Key}: {},\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tname:     \"empty strings\",\n\t\t\tval:      chunkSecretKey{\"\", detectorA.Key},\n\t\t\tdupes:    map[chunkSecretKey]struct{}{{\"\", detectorB.Key}: {}},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tname: \"similar within threshold same detector\",\n\t\t\tval:  chunkSecretKey{\"PMAK-qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r\", detectorA.Key},\n\t\t\tdupes: map[chunkSecretKey]struct{}{\n\t\t\t\t{\"qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r\", detectorA.Key}: {},\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tctx := context.Background()\n\t\t\tresult := likelyDuplicate(ctx, tc.val, tc.dupes)\n\t\t\tif result != tc.expected {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", tc.expected, result)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype customCleaner struct {\n\tignoreConfig bool\n}\n\nvar _ detectors.CustomResultsCleaner = (*customCleaner)(nil)\nvar _ detectors.Detector = (*customCleaner)(nil)\n\nfunc (c customCleaner) FromData(aCtx.Context, bool, []byte) ([]detectors.Result, error) {\n\treturn []detectors.Result{}, nil\n}\n\nfunc (c customCleaner) Keywords() []string             { return []string{} }\nfunc (c customCleaner) Type() detectorspb.DetectorType { return detectorspb.DetectorType(-1) }\n\nfunc (customCleaner) Description() string { return \"\" }\n\nfunc (c customCleaner) CleanResults([]detectors.Result) []detectors.Result {\n\treturn []detectors.Result{}\n}\nfunc (c customCleaner) ShouldCleanResultsIrrespectiveOfConfiguration() bool { return c.ignoreConfig }\n\nfunc TestFilterResults_CustomCleaner(t *testing.T) {\n\ttestCases := []struct {\n\t\tname               string\n\t\tcleaningConfigured bool\n\t\tignoreConfig       bool\n\t\tresultsToClean     []detectors.Result\n\t\twantResults        []detectors.Result\n\t}{\n\t\t{\n\t\t\tname:               \"respect config to clean\",\n\t\t\tcleaningConfigured: true,\n\t\t\tignoreConfig:       false,\n\t\t\tresultsToClean:     []detectors.Result{{}},\n\t\t\twantResults:        []detectors.Result{},\n\t\t},\n\t\t{\n\t\t\tname:               \"respect config to not clean\",\n\t\t\tcleaningConfigured: false,\n\t\t\tignoreConfig:       false,\n\t\t\tresultsToClean:     []detectors.Result{{}},\n\t\t\twantResults:        []detectors.Result{{}},\n\t\t},\n\t\t{\n\t\t\tname:               \"clean irrespective of config\",\n\t\t\tcleaningConfigured: false,\n\t\t\tignoreConfig:       true,\n\t\t\tresultsToClean:     []detectors.Result{{}},\n\t\t\twantResults:        []detectors.Result{},\n\t\t},\n\t}\n\n\tfor _, tt := range testCases {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tmatch := ahocorasick.DetectorMatch{\n\t\t\t\tDetector: customCleaner{\n\t\t\t\t\tignoreConfig: tt.ignoreConfig,\n\t\t\t\t},\n\t\t\t}\n\t\t\tengine := Engine{\n\t\t\t\tfilterUnverified:     tt.cleaningConfigured,\n\t\t\t\tretainFalsePositives: true,\n\t\t\t}\n\n\t\t\tcleaned := engine.filterResults(context.Background(), &match, tt.resultsToClean)\n\n\t\t\tassert.ElementsMatch(t, tt.wantResults, cleaned)\n\t\t})\n\t}\n}\n\nfunc BenchmarkPopulateMatchingDetectors(b *testing.B) {\n\tallDetectors := defaults.DefaultDetectors()\n\tac := ahocorasick.NewAhoCorasickCore(allDetectors)\n\n\t// Generate sample data with keywords from detectors.\n\tdataSize := 1 << 20 // 1 MB\n\tsampleData := generateRandomDataWithKeywords(dataSize, allDetectors)\n\n\tsmallChunk := 1 << 10  // 1 KB\n\tmediumChunk := 1 << 12 // 4 KB\n\tcurrent := sources.TotalChunkSize\n\tlargeChunk := 1 << 14 // 16 KB\n\txlChunk := 1 << 15    // 32 KB\n\txxlChunk := 1 << 16   // 64 KB\n\txxxlChunk := 1 << 18  // 256 KB\n\tchunkSizes := []int{smallChunk, mediumChunk, current, largeChunk, xlChunk, xxlChunk, xxxlChunk}\n\n\tfor _, chunkSize := range chunkSizes {\n\t\tb.Run(fmt.Sprintf(\"ChunkSize_%d\", chunkSize), func(b *testing.B) {\n\t\t\tb.ReportAllocs()\n\t\t\tb.SetBytes(int64(chunkSize))\n\n\t\t\t// Create a single chunk of the desired size.\n\t\t\tchunk := sampleData[:chunkSize]\n\n\t\t\tb.ResetTimer()\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tac.FindDetectorMatches([]byte(chunk)) // Match against the single chunk\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc generateRandomDataWithKeywords(size int, detectors []detectors.Detector) string {\n\tconst charset = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\"\n\tdata := make([]byte, size)\n\n\tr := rand.New(rand.NewSource(42)) // Seed for reproducibility\n\n\tfor i := range data {\n\t\tdata[i] = charset[r.Intn(len(charset))]\n\t}\n\n\ttotalKeywords := 0\n\tfor _, d := range detectors {\n\t\ttotalKeywords += len(d.Keywords())\n\t}\n\n\t// Target keyword density (keywords per character)\n\t// This ensures that the generated data has a reasonable number of keywords and is consistent\n\t// across different data sizes.\n\tkeywordDensity := 0.01\n\n\ttargetKeywords := int(float64(size) * keywordDensity)\n\n\tfor i := 0; i < targetKeywords; i++ {\n\t\tdetectorIndex := r.Intn(len(detectors))\n\t\tkeywordIndex := r.Intn(len(detectors[detectorIndex].Keywords()))\n\t\tkeyword := detectors[detectorIndex].Keywords()[keywordIndex]\n\n\t\tinsertPosition := r.Intn(size - len(keyword))\n\t\tcopy(data[insertPosition:], keyword)\n\t}\n\n\treturn string(data)\n}\n\nfunc TestEngine_ShouldVerifyChunk(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tdetector    detectors.Detector\n\t\toverrideKey config.DetectorID\n\t\twant        func(sourceVerify, detectorVerify bool) bool\n\t}{\n\t\t{\n\t\t\tname:        \"detector override by exact version\",\n\t\t\tdetector:    &gitlab.Scanner{},\n\t\t\toverrideKey: config.DetectorID{ID: detectorspb.DetectorType_Gitlab, Version: 2},\n\t\t\twant:        func(sourceVerify, detectorVerify bool) bool { return detectorVerify },\n\t\t},\n\t\t{\n\t\t\tname:        \"detector override by versionless config\",\n\t\t\tdetector:    &gitlab.Scanner{},\n\t\t\toverrideKey: config.DetectorID{ID: detectorspb.DetectorType_Gitlab, Version: 0},\n\t\t\twant:        func(sourceVerify, detectorVerify bool) bool { return detectorVerify },\n\t\t},\n\t\t{\n\t\t\tname:        \"no detector override because of detector type mismatch\",\n\t\t\tdetector:    &gitlab.Scanner{},\n\t\t\toverrideKey: config.DetectorID{ID: detectorspb.DetectorType_NpmToken, Version: 2},\n\t\t\twant:        func(sourceVerify, detectorVerify bool) bool { return sourceVerify },\n\t\t},\n\t\t{\n\t\t\tname:        \"no detector override because of detector version mismatch\",\n\t\t\tdetector:    &gitlab.Scanner{},\n\t\t\toverrideKey: config.DetectorID{ID: detectorspb.DetectorType_Gitlab, Version: 1},\n\t\t\twant:        func(sourceVerify, detectorVerify bool) bool { return sourceVerify },\n\t\t},\n\t}\n\n\tbooleanChoices := [2]bool{true, false}\n\n\tengine := &Engine{verify: true}\n\n\tfor _, tt := range tests {\n\t\tfor _, sourceVerify := range booleanChoices {\n\t\t\tfor _, detectorVerify := range booleanChoices {\n\n\t\t\t\tt.Run(fmt.Sprintf(\"%s (source verify = %v, detector verify = %v)\", tt.name, sourceVerify, detectorVerify), func(t *testing.T) {\n\t\t\t\t\toverrides := map[config.DetectorID]bool{\n\t\t\t\t\t\ttt.overrideKey: detectorVerify,\n\t\t\t\t\t}\n\n\t\t\t\t\twant := tt.want(sourceVerify, detectorVerify)\n\n\t\t\t\t\tgot := engine.shouldVerifyChunk(sourceVerify, tt.detector, overrides)\n\n\t\t\t\t\tassert.Equal(t, want, got)\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestEngineInitializesCloudProviderDetectors(t *testing.T) {\n\tctx := context.Background()\n\tconf := Config{\n\t\tConcurrency:   1,\n\t\tDetectors:     defaults.DefaultDetectors(),\n\t\tVerify:        false,\n\t\tSourceManager: sources.NewManager(),\n\t\tDispatcher:    NewPrinterDispatcher(new(discardPrinter)),\n\t}\n\n\te, err := NewEngine(ctx, &conf)\n\tassert.NoError(t, err)\n\n\tvar count int\n\tnoCloudEndpointDetectors := map[detectorspb.DetectorType]struct{}{\n\t\tdetectorspb.DetectorType_ArtifactoryAccessToken:     {},\n\t\tdetectorspb.DetectorType_ArtifactoryReferenceToken:  {},\n\t\tdetectorspb.DetectorType_TableauPersonalAccessToken: {},\n\t\t// these do not have any cloud endpoint\n\t}\n\n\tfor _, det := range e.detectors {\n\t\tif endpoints, ok := det.(interface{ Endpoints(...string) []string }); ok {\n\t\t\tid := config.GetDetectorID(det)\n\t\t\tif len(endpoints.Endpoints()) == 0 {\n\t\t\t\tif _, ok := noCloudEndpointDetectors[det.Type()]; !ok {\n\t\t\t\t\tt.Fatalf(\"detector %q Endpoints() is empty\", id.String())\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcount++\n\t\t}\n\t}\n\n\tif count == 0 {\n\t\tt.Fatal(\"no detectors found implementing Endpoints(), did EndpointSetter change?\")\n\t}\n}\n\nfunc TestEngineignoreLine(t *testing.T) {\n\ttests := []struct {\n\t\tname             string\n\t\tcontent          string\n\t\texpectedFindings int\n\t}{\n\t\t{\n\t\t\tname: \"ignore at end of line\",\n\t\t\tcontent: `\n# tests/example_false_positive.py\n\ndef test_something():\n    connection_string = \"who-cares\"\n\n    # Ignoring this does not work\n    assert connection_string == \"postgres://master_user:master_password@hostname:1234/main\"  # trufflehog:ignore`,\n\t\t\texpectedFindings: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"ignore not on secret line\",\n\t\t\tcontent: `\n# tests/example_false_positive.py\n\ndef test_something():\n    connection_string = \"who-cares\"\n\n    # Ignoring this does not work\n\tassert some_other_stuff == \"blah\" # trufflehog:ignore\n    assert connection_string == \"postgres://master_user:master_password@hostname:1234/main\"`,\n\t\t\texpectedFindings: 1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\t\t\tdefer cancel()\n\n\t\t\ttmpFile, err := os.CreateTemp(\"\", \"test_creds\")\n\t\t\tassert.NoError(t, err)\n\t\t\tdefer os.Remove(tmpFile.Name())\n\n\t\t\terr = os.WriteFile(tmpFile.Name(), []byte(tt.content), os.ModeAppend)\n\t\t\tassert.NoError(t, err)\n\n\t\t\tconst defaultOutputBufferSize = 64\n\t\t\topts := []func(*sources.SourceManager){\n\t\t\t\tsources.WithSourceUnits(),\n\t\t\t\tsources.WithBufferedOutput(defaultOutputBufferSize),\n\t\t\t}\n\n\t\t\tsourceManager := sources.NewManager(opts...)\n\n\t\t\tconf := Config{\n\t\t\t\tConcurrency:   1,\n\t\t\t\tDecoders:      decoders.DefaultDecoders(),\n\t\t\t\tDetectors:     defaults.DefaultDetectors(),\n\t\t\t\tVerify:        false,\n\t\t\t\tSourceManager: sourceManager,\n\t\t\t\tDispatcher:    NewPrinterDispatcher(new(discardPrinter)),\n\t\t\t}\n\n\t\t\teng, err := NewEngine(ctx, &conf)\n\t\t\tassert.NoError(t, err)\n\n\t\t\teng.Start(ctx)\n\n\t\t\tcfg := sources.FilesystemConfig{Paths: []string{tmpFile.Name()}}\n\t\t\t_, err = eng.ScanFileSystem(ctx, cfg)\n\t\t\tassert.NoError(t, err)\n\n\t\t\tassert.NoError(t, eng.Finish(ctx))\n\t\t\tassert.Equal(t, tt.expectedFindings, int(eng.GetMetrics().UnverifiedSecretsFound))\n\t\t})\n\t}\n}\n\ntype passthroughDetector struct {\n\tdetectorType detectorspb.DetectorType\n\tkeywords     []string\n\tsecret       string\n}\n\nfunc (p passthroughDetector) FromData(_ aCtx.Context, verify bool, data []byte) ([]detectors.Result, error) {\n\traw := data\n\tif p.secret != \"\" {\n\t\traw = []byte(p.secret)\n\t}\n\treturn []detectors.Result{\n\t\t{\n\t\t\tRaw:      raw,\n\t\t\tVerified: verify,\n\t\t},\n\t}, nil\n}\n\nfunc (p passthroughDetector) Keywords() []string             { return p.keywords }\nfunc (p passthroughDetector) Type() detectorspb.DetectorType { return p.detectorType }\nfunc (p passthroughDetector) Description() string            { return \"fake detector for testing\" }\n\ntype passthroughDecoder struct{}\n\nfunc (p passthroughDecoder) FromChunk(chunk *sources.Chunk) *decoders.DecodableChunk {\n\treturn &decoders.DecodableChunk{\n\t\tChunk:       chunk,\n\t\tDecoderType: detectorspb.DecoderType(-1),\n\t}\n}\n\nfunc (p passthroughDecoder) Type() detectorspb.DecoderType { return detectorspb.DecoderType(-1) }\n\n// TestEngine_DetectChunk_UsesVerifyFlag validates that detectChunk correctly forwards detectableChunk.verify to\n// detectors.\nfunc TestEngine_DetectChunk_UsesVerifyFlag(t *testing.T) {\n\tctx := context.Background()\n\n\ttestCases := []struct {\n\t\tname   string\n\t\tverify bool\n\t}{\n\t\t{name: \"verify=true\", verify: true},\n\t\t{name: \"verify=false\", verify: false},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t// Arrange: Create a minimal engine.\n\t\t\te := &Engine{\n\t\t\t\tresults:           make(chan detectors.ResultWithMetadata, 1),\n\t\t\t\tverificationCache: verificationcache.New(nil, &verificationcache.InMemoryMetrics{}),\n\t\t\t}\n\n\t\t\t// Arrange: Create a detector match. We can't create one directly, so we have to use a minimal A-H core.\n\t\t\tahcore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{passthroughDetector{keywords: []string{\"keyword\"}}})\n\t\t\tdetectorMatches := ahcore.FindDetectorMatches([]byte(\"keyword\"))\n\t\t\trequire.Len(t, detectorMatches, 1)\n\n\t\t\t// Arrange: Create a chunk to detect.\n\t\t\tchunk := detectableChunk{\n\t\t\t\tdetector: detectorMatches[0],\n\t\t\t\tverify:   tc.verify,\n\t\t\t\twgDoneFn: func() {},\n\t\t\t}\n\n\t\t\t// Act\n\t\t\te.detectChunk(ctx, chunk)\n\t\t\tclose(e.results)\n\n\t\t\t// Assert: Confirm that a result was generated and that it has the expected verify flag.\n\t\t\tselect {\n\t\t\tcase result := <-e.results:\n\t\t\t\tassert.Equal(t, tc.verify, result.Result.Verified)\n\t\t\tdefault:\n\t\t\t\tt.Errorf(\"expected a result but did not get one\")\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestEngine_ScannerWorker_DetectableChunkHasCorrectVerifyFlag validates that scannerWorker generates detectableChunk\n// structs that have the correct verify flag set. It also validates that the original chunks' SourceVerify flags are\n// unchanged.\nfunc TestEngine_ScannerWorker_DetectableChunkHasCorrectVerifyFlag(t *testing.T) {\n\tctx := context.Background()\n\n\ttestCases := []struct {\n\t\tname         string\n\t\tengineVerify bool\n\t\tsourceVerify bool\n\t\twantVerify   bool\n\t}{\n\t\t{name: \"engineVerify=false,sourceVerify=false\", engineVerify: false, sourceVerify: false, wantVerify: false},\n\t\t{name: \"engineVerify=false,sourceVerify=true\", engineVerify: false, sourceVerify: true, wantVerify: false},\n\t\t{name: \"engineVerify=true,sourceVerify=false\", engineVerify: true, sourceVerify: false, wantVerify: false},\n\t\t{name: \"engineVerify=true,sourceVerify=true\", engineVerify: true, sourceVerify: true, wantVerify: true},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t// Arrange: Create a minimal engine.\n\t\t\tdetector := &passthroughDetector{keywords: []string{\"keyword\"}}\n\t\t\te := &Engine{\n\t\t\t\tAhoCorasickCore:      ahocorasick.NewAhoCorasickCore([]detectors.Detector{detector}),\n\t\t\t\tdecoders:             []decoders.Decoder{passthroughDecoder{}},\n\t\t\t\tdetectableChunksChan: make(chan detectableChunk, 1),\n\t\t\t\tsourceManager:        sources.NewManager(),\n\t\t\t\tverify:               tc.engineVerify,\n\t\t\t\tmaxDecodeDepth:       1,\n\t\t\t}\n\n\t\t\t// Arrange: Create a chunk to scan.\n\t\t\tchunk := sources.Chunk{\n\t\t\t\tData:         []byte(\"keyword\"),\n\t\t\t\tSourceVerify: tc.sourceVerify,\n\t\t\t}\n\n\t\t\t// Arrange: Enqueue a chunk to be scanned.\n\t\t\te.sourceManager.ScanChunk(&chunk)\n\n\t\t\t// Act\n\t\t\tgo e.scannerWorker(ctx)\n\n\t\t\t// Assert: Confirm that a chunk was generated, that its SourceVerify flag is unchanged, and that its verify\n\t\t\t// flag is correctly set.\n\t\t\tselect {\n\t\t\tcase chunk := <-e.detectableChunksChan:\n\t\t\t\tassert.Equal(t, tc.sourceVerify, chunk.chunk.SourceVerify)\n\t\t\t\tassert.Equal(t, tc.wantVerify, chunk.verify)\n\t\t\tcase <-time.After(1 * time.Second):\n\t\t\t\tt.Errorf(\"expected a detectableChunk but did not get one\")\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestEngine_VerificationOverlapWorker_DetectableChunkHasCorrectVerifyFlag validates that the results directly\n// generated by verificationOverlapWorker all came from detector invocations with the verify flag cleared (because these\n// results were generated from verification overlaps). It also validates that detectableChunk structs generated by\n// verificationOverlapWorker have their verify flags correctly set, and that these structs' original chunks'\n// SourceVerify flags are unchanged.\nfunc TestEngine_VerificationOverlapWorker_DetectableChunkHasCorrectVerifyFlag(t *testing.T) {\n\tctx := context.Background()\n\n\tt.Run(\"overlap\", func(t *testing.T) {\n\t\t// Arrange: Create a minimal engine\n\t\te := &Engine{\n\t\t\tdetectableChunksChan:          make(chan detectableChunk, 2),\n\t\t\tresults:                       make(chan detectors.ResultWithMetadata, 2),\n\t\t\tretainFalsePositives:          true,\n\t\t\tverificationOverlapChunksChan: make(chan verificationOverlapChunk, 2),\n\t\t\tverify:                        true,\n\t\t}\n\n\t\t// Arrange: Set up a fake detectableChunk processor so that any chunks (incorrectly) sent to\n\t\t// e.detectableChunksChan don't block the test.\n\t\tprocessedDetectableChunks := make(chan detectableChunk, 2)\n\t\tgo func() {\n\t\t\tfor chunk := range e.detectableChunksChan {\n\t\t\t\tchunk.wgDoneFn()\n\t\t\t\tprocessedDetectableChunks <- chunk\n\t\t\t}\n\t\t}()\n\n\t\t// Arrange: Create a chunk to \"scan.\"\n\t\tchunk := sources.Chunk{\n\t\t\tData:         []byte(\"keyword ;oahpow8heg;blaisd\"),\n\t\t\tSourceVerify: true,\n\t\t}\n\n\t\t// Arrange: Create overlapping detector matches. We can't create them directly, so we have to use a minimal A-H\n\t\t// core.\n\t\tahcore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{\n\t\t\tpassthroughDetector{detectorType: detectorspb.DetectorType(-1), keywords: []string{\"keyw\"}},\n\t\t\tpassthroughDetector{detectorType: detectorspb.DetectorType(-2), keywords: []string{\"keyword\"}},\n\t\t})\n\t\tdetectorMatches := ahcore.FindDetectorMatches(chunk.Data)\n\t\trequire.Len(t, detectorMatches, 2)\n\n\t\t// Arrange: Enqueue a verification overlap chunk\n\t\te.verificationOverlapChunksChan <- verificationOverlapChunk{\n\t\t\tchunk:                       chunk,\n\t\t\tdetectors:                   detectorMatches,\n\t\t\tverificationOverlapWgDoneFn: func() { close(e.verificationOverlapChunksChan) },\n\t\t}\n\n\t\t// Act\n\t\te.verificationOverlapWorker(ctx)\n\t\tclose(e.results)\n\t\tclose(e.detectableChunksChan)\n\t\tclose(processedDetectableChunks)\n\n\t\t// Assert: Confirm that every generated result is unverified (because overlap detection precluded it).\n\t\tfor result := range e.results {\n\t\t\tassert.False(t, result.Result.Verified)\n\t\t}\n\n\t\t// Assert: Confirm that every generated detectable chunk's Chunk.SourceVerify flag is unchanged and that its\n\t\t// verify flag is correctly set.\n\t\t// CMR: There should be not be any of these chunks. However, due to what I believe is an unrelated bug, there\n\t\t// are. This test ensures that even in that erroneous case, their contents are correct.\n\t\tfor detectableChunk := range processedDetectableChunks {\n\t\t\tassert.True(t, detectableChunk.verify)\n\t\t\tassert.True(t, detectableChunk.chunk.SourceVerify)\n\t\t}\n\t})\n\tt.Run(\"no overlap\", func(t *testing.T) {\n\t\t// Arrange: Create a minimal engine\n\t\te := &Engine{\n\t\t\tdetectableChunksChan:          make(chan detectableChunk, 2),\n\t\t\tretainFalsePositives:          true,\n\t\t\tverificationOverlapChunksChan: make(chan verificationOverlapChunk, 2),\n\t\t\tverify:                        true,\n\t\t}\n\n\t\t// Arrange: Set up a fake detectableChunk processor so that any chunks sent to e.detectableChunksChan don't\n\t\t// block the test.\n\t\tprocessedDetectableChunks := make(chan detectableChunk, 2)\n\t\tgo func() {\n\t\t\tfor chunk := range e.detectableChunksChan {\n\t\t\t\tchunk.wgDoneFn()\n\t\t\t\tprocessedDetectableChunks <- chunk\n\t\t\t}\n\t\t}()\n\n\t\t// Arrange: Create a chunk to \"scan.\"\n\t\tchunk := sources.Chunk{\n\t\t\tData:         []byte(\"keyword ;oahpow8heg;blaisd\"),\n\t\t\tSourceVerify: true,\n\t\t}\n\n\t\t// Arrange: Create non-overlapping detector matches. We can't create them directly, so we have to use a minimal\n\t\t// A-H core.\n\t\tahcore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{\n\t\t\tpassthroughDetector{detectorType: detectorspb.DetectorType(-1), keywords: []string{\"keyw\"}, secret: \"oahpow\"},\n\t\t\tpassthroughDetector{detectorType: detectorspb.DetectorType(-2), keywords: []string{\"keyword\"}, secret: \"blaisd\"},\n\t\t})\n\t\tdetectorMatches := ahcore.FindDetectorMatches(chunk.Data)\n\t\trequire.Len(t, detectorMatches, 2)\n\n\t\t// Arrange: Enqueue a verification overlap chunk\n\t\te.verificationOverlapChunksChan <- verificationOverlapChunk{\n\t\t\tchunk:                       chunk,\n\t\t\tdetectors:                   detectorMatches,\n\t\t\tverificationOverlapWgDoneFn: func() { close(e.verificationOverlapChunksChan) },\n\t\t}\n\n\t\t// Act\n\t\te.verificationOverlapWorker(ctx)\n\t\tclose(e.detectableChunksChan)\n\t\tclose(processedDetectableChunks)\n\n\t\t// Assert: Confirm that SourceVerify flags are unchanged, and verify flags are correctly set.\n\t\tfor detectableChunk := range processedDetectableChunks {\n\t\t\tassert.True(t, detectableChunk.chunk.SourceVerify)\n\t\t\tassert.True(t, detectableChunk.verify)\n\t\t}\n\t})\n}\n\nfunc TestEngine_IterativeDecoding(t *testing.T) {\n\tt.Parallel()\n\n\tconst (\n\t\t// base64(base64(\"my-secret-key-test-value\"))\n\t\tdoubleEncoded   = \"YlhrdGMyVmpjbVYwTFd0bGVTMTBaWE4wTFhaaGJIVmw=\"\n\t\tdetectorKeyword = \"my-secret\"\n\n\t\t// escapedUnicode(\"my-secret-key-test-value\")\n\t\tescapedUnicode = `\\u006d\\u0079\\u002d\\u0073\\u0065\\u0063\\u0072\\u0065\\u0074\\u002d\\u006b\\u0065\\u0079\\u002d\\u0074\\u0065\\u0073\\u0074\\u002d\\u0076\\u0061\\u006c\\u0075\\u0065`\n\n\t\t// escapedUnicode(escapedUnicode(\"my-secret-key-test-value\"))\n\t\tdoubleEscapedUnicode = `\\u005c\\u0075\\u0030\\u0030\\u0036\\u0064\\u005c\\u0075\\u0030\\u0030\\u0037\\u0039\\u005c\\u0075\\u0030\\u0030\\u0032\\u0064\\u005c\\u0075\\u0030\\u0030\\u0037\\u0033\\u005c\\u0075\\u0030\\u0030\\u0036\\u0035\\u005c\\u0075\\u0030\\u0030\\u0036\\u0033\\u005c\\u0075\\u0030\\u0030\\u0037\\u0032\\u005c\\u0075\\u0030\\u0030\\u0036\\u0035\\u005c\\u0075\\u0030\\u0030\\u0037\\u0034\\u005c\\u0075\\u0030\\u0030\\u0032\\u0064\\u005c\\u0075\\u0030\\u0030\\u0036\\u0062\\u005c\\u0075\\u0030\\u0030\\u0036\\u0035\\u005c\\u0075\\u0030\\u0030\\u0037\\u0039\\u005c\\u0075\\u0030\\u0030\\u0032\\u0064\\u005c\\u0075\\u0030\\u0030\\u0037\\u0034\\u005c\\u0075\\u0030\\u0030\\u0036\\u0035\\u005c\\u0075\\u0030\\u0030\\u0037\\u0033\\u005c\\u0075\\u0030\\u0030\\u0037\\u0034\\u005c\\u0075\\u0030\\u0030\\u0032\\u0064\\u005c\\u0075\\u0030\\u0030\\u0037\\u0036\\u005c\\u0075\\u0030\\u0030\\u0036\\u0031\\u005c\\u0075\\u0030\\u0030\\u0036\\u0063\\u005c\\u0075\\u0030\\u0030\\u0037\\u0035\\u005c\\u0075\\u0030\\u0030\\u0036\\u0035`\n\n\t\t// base64(escapedUnicode(\"my-secret-key-test-value\"))\n\t\tb64EscapedUnicode = \"XHUwMDZkXHUwMDc5XHUwMDJkXHUwMDczXHUwMDY1XHUwMDYzXHUwMDcyXHUwMDY1XHUwMDc0XHUwMDJkXHUwMDZiXHUwMDY1XHUwMDc5XHUwMDJkXHUwMDc0XHUwMDY1XHUwMDczXHUwMDc0XHUwMDJkXHUwMDc2XHUwMDYxXHUwMDZjXHUwMDc1XHUwMDY1\"\n\n\t\t// escapedUnicode(base64(\"my-secret-key-test-value\"))\n\t\tescapedUnicodeB64 = `\\u0062\\u0058\\u006b\\u0074\\u0063\\u0032\\u0056\\u006a\\u0063\\u006d\\u0056\\u0030\\u004c\\u0057\\u0074\\u006c\\u0065\\u0053\\u0031\\u0030\\u005a\\u0058\\u004e\\u0030\\u004c\\u0058\\u005a\\u0068\\u0062\\u0048\\u0056\\u006c`\n\t)\n\n\t// \"token: bXktc2VjcmV0LWtleS10ZXN0LXZhbHVl end\" as UTF-16LE\n\tutf16ContainingBase64 := []byte{\n\t\t116, 0, 111, 0, 107, 0, 101, 0, 110, 0, 58, 0, 32, 0,\n\t\t98, 0, 88, 0, 107, 0, 116, 0, 99, 0, 50, 0, 86, 0, 106, 0,\n\t\t99, 0, 109, 0, 86, 0, 48, 0, 76, 0, 87, 0, 116, 0, 108, 0,\n\t\t101, 0, 83, 0, 49, 0, 48, 0, 90, 0, 88, 0, 78, 0, 48, 0,\n\t\t76, 0, 88, 0, 90, 0, 104, 0, 98, 0, 72, 0, 86, 0, 108, 0,\n\t\t32, 0, 101, 0, 110, 0, 100, 0,\n\t}\n\n\ttests := []struct {\n\t\tname        string\n\t\tinput       []byte\n\t\tdepth       int\n\t\twantKeyword bool\n\t}{\n\t\t{\n\t\t\tname:        \"double base64, depth=1, miss\",\n\t\t\tinput:       []byte(\"token: \" + doubleEncoded),\n\t\t\tdepth:       1,\n\t\t\twantKeyword: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"double base64, depth=2, found\",\n\t\t\tinput:       []byte(\"token: \" + doubleEncoded),\n\t\t\tdepth:       2,\n\t\t\twantKeyword: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"utf16+base64, depth=1, miss\",\n\t\t\tinput:       utf16ContainingBase64,\n\t\t\tdepth:       1,\n\t\t\twantKeyword: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"utf16+base64, depth=2, found\",\n\t\t\tinput:       utf16ContainingBase64,\n\t\t\tdepth:       2,\n\t\t\twantKeyword: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"escaped unicode, depth=1, found\",\n\t\t\tinput:       []byte(\"token: \" + escapedUnicode),\n\t\t\tdepth:       1,\n\t\t\twantKeyword: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"double escaped unicode, depth=1, miss\",\n\t\t\tinput:       []byte(\"token: \" + doubleEscapedUnicode),\n\t\t\tdepth:       1,\n\t\t\twantKeyword: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"double escaped unicode, depth=2, found\",\n\t\t\tinput:       []byte(\"token: \" + doubleEscapedUnicode),\n\t\t\tdepth:       2,\n\t\t\twantKeyword: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"base64+escaped unicode, depth=1, miss\",\n\t\t\tinput:       []byte(\"token: \" + b64EscapedUnicode),\n\t\t\tdepth:       1,\n\t\t\twantKeyword: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"base64+escaped unicode, depth=2, found\",\n\t\t\tinput:       []byte(\"token: \" + b64EscapedUnicode),\n\t\t\tdepth:       2,\n\t\t\twantKeyword: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"escaped unicode+base64, depth=1, miss\",\n\t\t\tinput:       []byte(\"token: \" + escapedUnicodeB64),\n\t\t\tdepth:       1,\n\t\t\twantKeyword: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"escaped unicode+base64, depth=2, found\",\n\t\t\tinput:       []byte(\"token: \" + escapedUnicodeB64),\n\t\t\tdepth:       2,\n\t\t\twantKeyword: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tctx := context.Background()\n\n\t\t\tdetector := &passthroughDetector{\n\t\t\t\tkeywords:     []string{detectorKeyword},\n\t\t\t\tdetectorType: detectorspb.DetectorType(9999),\n\t\t\t}\n\t\t\te := &Engine{\n\t\t\t\tAhoCorasickCore:      ahocorasick.NewAhoCorasickCore([]detectors.Detector{detector}),\n\t\t\t\tdecoders:             decoders.DefaultDecoders(),\n\t\t\t\tdetectableChunksChan: make(chan detectableChunk, 64),\n\t\t\t\tsourceManager:        sources.NewManager(),\n\t\t\t\tmaxDecodeDepth:       tt.depth,\n\t\t\t}\n\n\t\t\te.sourceManager.ScanChunk(&sources.Chunk{Data: tt.input})\n\t\t\tgo e.scannerWorker(ctx)\n\n\t\t\tvar found bool\n\t\t\ttimeout := time.After(2 * time.Second)\n\t\tLoop:\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase dc := <-e.detectableChunksChan:\n\t\t\t\t\tdc.wgDoneFn()\n\t\t\t\t\tfound = true\n\t\t\t\t\tfor {\n\t\t\t\t\t\tselect {\n\t\t\t\t\t\tcase dc2 := <-e.detectableChunksChan:\n\t\t\t\t\t\t\tdc2.wgDoneFn()\n\t\t\t\t\t\tcase <-time.After(200 * time.Millisecond):\n\t\t\t\t\t\t\tbreak Loop\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\tcase <-timeout:\n\t\t\t\t\tbreak Loop\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif tt.wantKeyword {\n\t\t\t\tassert.True(t, found, \"expected detector match\")\n\t\t\t} else {\n\t\t\t\tassert.False(t, found, \"unexpected detector match\")\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/filesystem.go",
    "content": "package engine\n\nimport (\n\t\"runtime\"\n\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/filesystem\"\n)\n\n// ScanFileSystem scans a given file system.\nfunc (e *Engine) ScanFileSystem(ctx context.Context, c sources.FilesystemConfig) (sources.JobProgressRef, error) {\n\tconnection := &sourcespb.Filesystem{\n\t\tPaths:            c.Paths,\n\t\tIncludePathsFile: c.IncludePathsFile,\n\t\tExcludePathsFile: c.ExcludePathsFile,\n\t\tMaxSymlinkDepth:  c.MaxSymlinkDepth,\n\t}\n\tvar conn anypb.Any\n\terr := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"failed to marshal filesystem connection\")\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\n\tsourceName := \"trufflehog - filesystem\"\n\tsourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, filesystem.SourceType)\n\n\tfileSystemSource := &filesystem.Source{}\n\tif err := fileSystemSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\treturn e.sourceManager.EnumerateAndScan(ctx, sourceName, fileSystemSource)\n}\n"
  },
  {
    "path": "pkg/engine/filesystem_integration_test.go",
    "content": "//go:build integration\n// +build integration\n\npackage engine\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/defaults\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\n// createFilesystemTree is a helper function to create a temporary directory\n// that contains all of the provided files and contents on the operating\n// system's filesystem. Sub-directories will be created as needed. On success,\n// the root directory is returned and the caller is responsible for removing it\n// from the filesystem.\nfunc createFilesystemTree(files map[string]string) (string, error) {\n\tparentDir, err := os.MkdirTemp(\"\", \"trufflehog-integration-test\")\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tfor path, contents := range files {\n\t\tfullPath := filepath.Join(parentDir, path)\n\t\tif err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {\n\t\t\t_ = os.RemoveAll(parentDir)\n\t\t\treturn \"\", err\n\t\t}\n\t\tif err := os.WriteFile(fullPath, []byte(contents), 0644); err != nil {\n\t\t\t_ = os.RemoveAll(parentDir)\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\treturn parentDir, nil\n}\n\nfunc TestFilesystem(t *testing.T) {\n\t// Setup test directory.\n\trootDir, err := createFilesystemTree(map[string]string{\n\t\t\"/foo\":             \"bar\",\n\t\t\"/bar\":             \"baz\",\n\t\t\"/dir/a\":           \"a\",\n\t\t\"/dir/b\":           \"b\",\n\t\t\"/dir/c\":           \"c\",\n\t\t\"/.ignore/file\":    \"this should be ignored\",\n\t\t\"/.ignore/sub/dir\": \"this should also be ignored\",\n\t})\n\tassert.NoError(t, err)\n\tdefer os.RemoveAll(rootDir)\n\n\tconfigDir, err := createFilesystemTree(map[string]string{\n\t\t\"/exclude\": \".ignore\",\n\t\t// TODO: Test include configuration.\n\t})\n\tassert.NoError(t, err)\n\tdefer os.RemoveAll(configDir)\n\n\t// Run the scan.\n\tctx := context.Background()\n\te, err := NewEngine(ctx, &Config{\n\t\tDetectors:     defaults.DefaultDetectors(),\n\t\tSourceManager: sources.NewManager(),\n\t\tVerify:        false,\n\t})\n\tassert.NoError(t, err)\n\te.Start(ctx)\n\t_, err = e.ScanFileSystem(ctx, sources.FilesystemConfig{\n\t\tPaths:            []string{rootDir},\n\t\tExcludePathsFile: filepath.Join(configDir, \"exclude\"),\n\t})\n\tassert.NoError(t, err)\n\n\terr = e.Finish(ctx)\n\tassert.NoError(t, err)\n\n\t// Check the output provided by metrics.\n\tmetrics := e.GetMetrics()\n\tassert.Equal(t, uint64(5), metrics.ChunksScanned)\n\tassert.Equal(t, uint64(9), metrics.BytesScanned)\n}\n"
  },
  {
    "path": "pkg/engine/gcs.go",
    "content": "package engine\n\nimport (\n\t\"fmt\"\n\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/gcs\"\n)\n\n// ScanGCS with the provided options.\nfunc (e *Engine) ScanGCS(ctx context.Context, c sources.GCSConfig) (sources.JobProgressRef, error) {\n\t// Project ID is required if using any authenticated access.\n\tif c.ProjectID == \"\" && !c.WithoutAuth {\n\t\treturn sources.JobProgressRef{}, fmt.Errorf(\"project ID is required\")\n\t}\n\n\t// If using unauthenticated access, the project ID is not used.\n\tif c.ProjectID != \"\" && c.WithoutAuth {\n\t\tc.ProjectID = \"\"\n\t\tctx.Logger().Info(\"project ID is not used when using unauthenticated access, ignoring provided project ID\")\n\t}\n\n\tconnection := &sourcespb.GCS{\n\t\tProjectId:      c.ProjectID,\n\t\tIncludeBuckets: c.IncludeBuckets,\n\t\tExcludeBuckets: c.ExcludeBuckets,\n\t\tIncludeObjects: c.IncludeObjects,\n\t\tExcludeObjects: c.ExcludeObjects,\n\t}\n\n\t// Make sure only one auth method is selected.\n\tif !isAuthValid(ctx, c, connection) {\n\t\treturn sources.JobProgressRef{}, fmt.Errorf(\"multiple auth methods selected, please select only one\")\n\t}\n\n\tvar conn anypb.Any\n\terr := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})\n\tif err != nil {\n\t\treturn sources.JobProgressRef{}, fmt.Errorf(\"failed to marshal GCS connection: %w\", err)\n\t}\n\n\tsourceName := \"trufflehog - gcs\"\n\tsourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, gcs.SourceType)\n\n\tgcsSource := &gcs.Source{}\n\tif err := gcsSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, int(c.Concurrency)); err != nil {\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\treturn e.sourceManager.EnumerateAndScan(ctx, sourceName, gcsSource)\n}\n\nfunc isAuthValid(ctx context.Context, c sources.GCSConfig, connection *sourcespb.GCS) bool {\n\tvar isAuthSelected bool\n\n\tif c.WithoutAuth {\n\t\tisAuthSelected = true\n\t\tconnection.Credential = &sourcespb.GCS_Unauthenticated{}\n\t}\n\tif c.CloudCred {\n\t\tif isAuthSelected {\n\t\t\treturn false\n\t\t}\n\t\tisAuthSelected = true\n\t\tconnection.Credential = &sourcespb.GCS_Adc{}\n\t}\n\tif c.ServiceAccount != \"\" {\n\t\tif isAuthSelected {\n\t\t\treturn false\n\t\t}\n\t\tisAuthSelected = true\n\t\tconnection.Credential = &sourcespb.GCS_ServiceAccountFile{\n\t\t\tServiceAccountFile: c.ServiceAccount,\n\t\t}\n\t}\n\tif c.ApiKey != \"\" {\n\t\tif isAuthSelected {\n\t\t\treturn false\n\t\t}\n\t\tisAuthSelected = true\n\t\tconnection.Credential = &sourcespb.GCS_ApiKey{\n\t\t\tApiKey: c.ApiKey,\n\t\t}\n\t}\n\tif !isAuthSelected {\n\t\tctx.Logger().Info(\"no auth method selected, using unauthenticated\")\n\t\tconnection.Credential = &sourcespb.GCS_Unauthenticated{}\n\t}\n\n\treturn true\n}\n"
  },
  {
    "path": "pkg/engine/gcs_test.go",
    "content": "package engine\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/decoders\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/defaults\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nfunc TestScanGCS(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tgcsConfig sources.GCSConfig\n\t\twantErr   bool\n\t}{\n\t\t{\n\t\t\tname: \"scanned GCS\",\n\t\t\tgcsConfig: sources.GCSConfig{\n\t\t\t\tApiKey:         \"abc123\",\n\t\t\t\tProjectID:      \"test-project\",\n\t\t\t\tCloudCred:      false,\n\t\t\t\tWithoutAuth:    false,\n\t\t\t\tServiceAccount: \"\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"missing project ID, with auth\",\n\t\t\tgcsConfig: sources.GCSConfig{ApiKey: \"abc123\"},\n\t\t\twantErr:   true,\n\t\t},\n\t\t{\n\t\t\tname:      \"missing project ID, without auth, public scan\",\n\t\t\tgcsConfig: sources.GCSConfig{WithoutAuth: true},\n\t\t},\n\t\t{\n\t\t\tname: \"multiple selected auth methods\",\n\t\t\tgcsConfig: sources.GCSConfig{\n\t\t\t\tApiKey:         \"abc123\",\n\t\t\t\tProjectID:      \"test-project\",\n\t\t\t\tCloudCred:      true,\n\t\t\t\tWithoutAuth:    false,\n\t\t\t\tServiceAccount: \"\",\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"no auth method selected\",\n\t\t\tgcsConfig: sources.GCSConfig{\n\t\t\t\tProjectID:     \"test-project\",\n\t\t\t\tMaxObjectSize: 10 * 1024 * 1024,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tctx, cancel := context.WithCancel(context.TODO())\n\t\t\tdefer cancel()\n\n\t\t\tconst defaultOutputBufferSize = 64\n\t\t\topts := []func(*sources.SourceManager){\n\t\t\t\tsources.WithSourceUnits(),\n\t\t\t\tsources.WithBufferedOutput(defaultOutputBufferSize),\n\t\t\t}\n\n\t\t\tsourceManager := sources.NewManager(opts...)\n\n\t\t\tconf := Config{\n\t\t\t\tConcurrency:   1,\n\t\t\t\tDecoders:      decoders.DefaultDecoders(),\n\t\t\t\tDetectors:     defaults.DefaultDetectors(),\n\t\t\t\tVerify:        false,\n\t\t\t\tSourceManager: sourceManager,\n\t\t\t\tDispatcher:    NewPrinterDispatcher(new(discardPrinter)),\n\t\t\t}\n\n\t\t\te, err := NewEngine(ctx, &conf)\n\t\t\tassert.NoError(t, err)\n\n\t\t\te.Start(ctx)\n\n\t\t\tgo func() {\n\t\t\t\tresultCount := 0\n\t\t\t\tfor range e.ResultsChan() {\n\t\t\t\t\tresultCount++\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t_, err = e.ScanGCS(ctx, test.gcsConfig)\n\t\t\tif err != nil && !test.wantErr && !strings.Contains(err.Error(), \"googleapi: Error 400: Bad Request\") {\n\t\t\t\tt.Errorf(\"ScanGCS() got: %v, want: %v\", err, nil)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err := e.Finish(ctx); err != nil && !test.wantErr && !strings.Contains(err.Error(), \"googleapi: Error 400: Bad Request\") {\n\t\t\t\tt.Errorf(\"Finish() got: %v, want: %v\", err, nil)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err == nil && test.wantErr {\n\t\t\t\tt.Errorf(\"ScanGCS() got: %v, want: %v\", err, \"error\")\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/git.go",
    "content": "package engine\n\nimport (\n\t\"runtime\"\n\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git\"\n)\n\n// ScanGit scans any git source.\nfunc (e *Engine) ScanGit(ctx context.Context, c sources.GitConfig) (sources.JobProgressRef, error) {\n\tconnection := &sourcespb.Git{\n\t\tHead:                c.HeadRef,\n\t\tBase:                c.BaseRef,\n\t\tBare:                c.Bare,\n\t\tUri:                 c.URI,\n\t\tExcludeGlobs:        c.ExcludeGlobs,\n\t\tIncludePathsFile:    c.IncludePathsFile,\n\t\tExcludePathsFile:    c.ExcludePathsFile,\n\t\tMaxDepth:            int64(c.MaxDepth),\n\t\tSkipBinaries:        c.SkipBinaries,\n\t\tClonePath:           c.ClonePath,\n\t\tNoCleanup:           c.NoCleanup,\n\t\tPrintLegacyJson:     c.PrintLegacyJSON,\n\t\tTrustLocalGitConfig: c.TrustLocalGitConfig,\n\t}\n\n\tvar conn anypb.Any\n\tif err := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{}); err != nil {\n\t\tctx.Logger().Error(err, \"failed to marshal git connection\")\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\n\tsourceName := \"trufflehog - git\"\n\tsourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, git.SourceType)\n\n\tgitSource := &git.Source{}\n\tif err := gitSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\n\treturn e.sourceManager.EnumerateAndScan(ctx, sourceName, gitSource)\n}\n"
  },
  {
    "path": "pkg/engine/git_test.go",
    "content": "package engine\n\nimport (\n\t\"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/decoders\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/defaults\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/feature\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git\"\n)\n\ntype expResult struct {\n\tB          string\n\tLineNumber int64\n\tVerified   bool\n}\n\ntype discardPrinter struct{}\n\nfunc (p *discardPrinter) Print(context.Context, *detectors.ResultWithMetadata) error {\n\t// This method intentionally does nothing.\n\treturn nil\n}\n\nfunc TestGitEngine(t *testing.T) {\n\tctx := context.Background()\n\trepoUrl := \"https://github.com/dustin-decker/secretsandstuff.git\"\n\tpath, _, err := git.PrepareRepo(ctx, repoUrl, \"\", false, false)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\tdefer os.RemoveAll(path)\n\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\ttype testProfile struct {\n\t\texpected map[string]expResult\n\t\tbranch   string\n\t\tbase     string\n\t\tmaxDepth int\n\t}\n\tfor tName, tTest := range map[string]testProfile{\n\t\t\"all_secrets\": {\n\t\t\texpected: map[string]expResult{\n\t\t\t\t\"70001020fab32b1fcf2f1f0e5c66424eae649826\": {\"AKIAXYZDQCEN4B6JSJQI\", 2, true},\n\t\t\t\t\"84e9c75e388ae3e866e121087ea2dd45a71068f2\": {\"AKIAILE3JG6KMS3HZGCA\", 4, true},\n\t\t\t\t\"8afb0ecd4998b1179e428db5ebbcdc8221214432\": {\"369963c1434c377428ca8531fbc46c0c43d037a0\", 3, false},\n\t\t\t\t\"27fbead3bf883cdb7de9d7825ed401f28f9398f1\": {\"ffc7e0f9400fb6300167009e42d2f842cd7956e2\", 7, false},\n\t\t\t},\n\t\t},\n\t\t\"base_commit\": {\n\t\t\texpected: map[string]expResult{\n\t\t\t\t\"70001020fab32b1fcf2f1f0e5c66424eae649826\": {\"AKIAXYZDQCEN4B6JSJQI\", 2, true},\n\t\t\t},\n\t\t\tbase: \"2f251b8c1e72135a375b659951097ec7749d4af9\",\n\t\t},\n\t} {\n\t\tt.Run(tName, func(t *testing.T) {\n\t\t\tconst defaultOutputBufferSize = 64\n\t\t\topts := []func(*sources.SourceManager){\n\t\t\t\tsources.WithSourceUnits(),\n\t\t\t\tsources.WithBufferedOutput(defaultOutputBufferSize),\n\t\t\t}\n\n\t\t\tsourceManager := sources.NewManager(opts...)\n\n\t\t\tconf := Config{\n\t\t\t\tConcurrency:   1,\n\t\t\t\tDecoders:      decoders.DefaultDecoders(),\n\t\t\t\tDetectors:     defaults.DefaultDetectors(),\n\t\t\t\tVerify:        true,\n\t\t\t\tSourceManager: sourceManager,\n\t\t\t\tDispatcher:    NewPrinterDispatcher(new(discardPrinter)),\n\t\t\t}\n\n\t\t\te, err := NewEngine(ctx, &conf)\n\t\t\tassert.NoError(t, err)\n\n\t\t\te.Start(ctx)\n\n\t\t\tcfg := sources.GitConfig{\n\t\t\t\tURI:      path,\n\t\t\t\tHeadRef:  tTest.branch,\n\t\t\t\tBaseRef:  tTest.base,\n\t\t\t\tMaxDepth: tTest.maxDepth,\n\t\t\t}\n\t\t\tif _, err := e.ScanGit(ctx, cfg); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Wait for all the chunks to be processed.\n\t\t\tassert.Nil(t, e.Finish(ctx))\n\t\t\tfor result := range e.ResultsChan() {\n\t\t\t\tswitch meta := result.SourceMetadata.GetData().(type) {\n\t\t\t\tcase *source_metadatapb.MetaData_Git:\n\t\t\t\t\tif tTest.expected[meta.Git.Commit].B != string(result.Raw) {\n\t\t\t\t\t\tt.Errorf(\"%s: unexpected result. Got: %s, Expected: %s\", tName, string(result.Raw), tTest.expected[meta.Git.Commit].B)\n\t\t\t\t\t}\n\t\t\t\t\tif tTest.expected[meta.Git.Commit].LineNumber != result.SourceMetadata.GetGit().Line {\n\t\t\t\t\t\tt.Errorf(\"%s: unexpected line number. Got: %d, Expected: %d\", tName, result.SourceMetadata.GetGit().Line, tTest.expected[meta.Git.Commit].LineNumber)\n\t\t\t\t\t}\n\t\t\t\t\tif tTest.expected[meta.Git.Commit].Verified != result.Verified {\n\t\t\t\t\t\tt.Errorf(\"%s: unexpected verification. Got: %v, Expected: %v\", tName, result.Verified, tTest.expected[meta.Git.Commit].Verified)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t}\n\t\t\tmetrics := e.GetMetrics()\n\t\t\tassert.Equal(t, len(tTest.expected), int(metrics.VerifiedSecretsFound)+int(metrics.UnverifiedSecretsFound))\n\t\t})\n\t}\n}\n\nfunc TestGitEngineWithMirrorAndBareClones(t *testing.T) {\n\tctx := context.Background()\n\n\tparent, err := os.MkdirTemp(\"\", \"trufflehog-test-keys-*\")\n\tif err != nil {\n\t\tt.Fail()\n\t}\n\tdefer os.RemoveAll(parent)\n\tlocalRepo := filepath.Join(parent, \"test_keys.git\")\n\tcloneCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\tdefer cancel()\n\n\t// clone with --mirror and --bare from https://github.com/trufflesecurity/test_keys.git to local and then pass it in as a local path\n\tcloneCmd := exec.CommandContext(cloneCtx, \"git\", \"clone\", \"--mirror\", \"--bare\", \"https://github.com/trufflesecurity/test_keys.git\", localRepo)\n\tif _, err := cloneCmd.CombinedOutput(); err != nil {\n\t\tt.Fail()\n\t}\n\n\tfileURI := (&url.URL{Scheme: \"file\", Path: filepath.ToSlash(localRepo)}).String()\n\n\trun := func(t *testing.T, mirror bool, cfg sources.GitConfig) (uint64, uint64) {\n\t\tt.Helper()\n\n\t\tconst defaultOutputBufferSize = 64\n\t\topts := []func(*sources.SourceManager){\n\t\t\tsources.WithSourceUnits(),\n\t\t\tsources.WithBufferedOutput(defaultOutputBufferSize),\n\t\t}\n\t\tsourceManager := sources.NewManager(opts...)\n\n\t\tconf := Config{\n\t\t\tConcurrency:   1,\n\t\t\tDecoders:      decoders.DefaultDecoders(),\n\t\t\tDetectors:     defaults.DefaultDetectors(),\n\t\t\tVerify:        false, // avoid network-dependent verification in tests\n\t\t\tSourceManager: sourceManager,\n\t\t\tDispatcher:    NewPrinterDispatcher(new(discardPrinter)),\n\t\t}\n\n\t\tfeature.UseGitMirror.Store(false)\n\t\tif mirror {\n\t\t\tfeature.UseGitMirror.Store(true)\n\t\t\tdefer feature.UseGitMirror.Store(false)\n\t\t}\n\n\t\te, err := NewEngine(ctx, &conf)\n\t\tassert.NoError(t, err)\n\n\t\te.Start(ctx)\n\t\t_, err = e.ScanGit(ctx, cfg)\n\t\tassert.NoError(t, err)\n\t\tassert.NoError(t, e.Finish(ctx))\n\n\t\tm := e.GetMetrics()\n\t\tsecrets := m.VerifiedSecretsFound + m.UnverifiedSecretsFound\n\t\tbytes := m.BytesScanned\n\t\treturn secrets, bytes\n\t}\n\n\ts1, b1 := run(t, true, sources.GitConfig{URI: \"https://github.com/trufflesecurity/test_keys.git\"})\n\ts2, b2 := run(t, false, sources.GitConfig{URI: fileURI, Bare: true})\n\ts3, b3 := run(t, false, sources.GitConfig{URI: fileURI, Bare: true, TrustLocalGitConfig: true})\n\n\tassert.Greater(t, int(s1), 0)\n\tassert.Greater(t, int(b1), 0)\n\n\tassert.Equal(t, s1, s2)\n\tassert.Equal(t, s1, s3)\n\tassert.Equal(t, b1, b2)\n\tassert.Equal(t, b1, b3)\n}\n\nfunc BenchmarkGitEngine(b *testing.B) {\n\tctx := context.Background()\n\trepoUrl := \"https://github.com/dustin-decker/secretsandstuff.git\"\n\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\n\tconst defaultOutputBufferSize = 64\n\topts := []func(*sources.SourceManager){\n\t\tsources.WithSourceUnits(),\n\t\tsources.WithBufferedOutput(defaultOutputBufferSize),\n\t}\n\n\tsourceManager := sources.NewManager(opts...)\n\n\tconf := Config{\n\t\tConcurrency:   runtime.NumCPU(),\n\t\tDecoders:      decoders.DefaultDecoders(),\n\t\tDetectors:     defaults.DefaultDetectors(),\n\t\tVerify:        false,\n\t\tSourceManager: sourceManager,\n\t\tDispatcher:    NewPrinterDispatcher(new(discardPrinter)),\n\t}\n\n\te, err := NewEngine(ctx, &conf)\n\tassert.NoError(b, err)\n\n\tgo func() {\n\t\tresultCount := 0\n\t\tfor range e.ResultsChan() {\n\t\t\tresultCount++\n\t\t}\n\t}()\n\n\tcfg := sources.GitConfig{URI: repoUrl}\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tif _, err := e.ScanGit(ctx, cfg); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\tassert.Nil(b, e.Finish(ctx))\n}\n"
  },
  {
    "path": "pkg/engine/github.go",
    "content": "package engine\n\nimport (\n\tgogit \"github.com/go-git/go-git/v5\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/github\"\n)\n\n// ScanGitHub scans GitHub with the provided options.\nfunc (e *Engine) ScanGitHub(ctx context.Context, c sources.GithubConfig) (sources.JobProgressRef, error) {\n\tconnection := sourcespb.GitHub{\n\t\tEndpoint:                   c.Endpoint,\n\t\tOrganizations:              c.Orgs,\n\t\tRepositories:               c.Repos,\n\t\tScanUsers:                  c.IncludeMembers,\n\t\tIgnoreRepos:                c.ExcludeRepos,\n\t\tIncludeRepos:               c.IncludeRepos,\n\t\tIncludeForks:               c.IncludeForks,\n\t\tIncludeIssueComments:       c.IncludeIssueComments,\n\t\tIncludePullRequestComments: c.IncludePullRequestComments,\n\t\tIncludeGistComments:        c.IncludeGistComments,\n\t\tIncludeWikis:               c.IncludeWikis,\n\t\tSkipBinaries:               c.SkipBinaries,\n\t\tCommentsTimeframeDays:      c.CommentsTimeframeDays,\n\t\tRemoveAuthInUrl:            !c.AuthInUrl, // configuration uses the opposite field in proto to keep credentials in the URL by default.\n\t\tClonePath:                  c.ClonePath,\n\t\tNoCleanup:                  c.NoCleanup,\n\t\tIgnoreGists:                c.IgnoreGists,\n\t\tPrintLegacyJson:            c.PrintLegacyJSON,\n\t}\n\n\tif len(c.Token) > 0 {\n\t\tconnection.Credential = &sourcespb.GitHub_Token{\n\t\t\tToken: c.Token,\n\t\t}\n\t} else {\n\t\tconnection.Credential = &sourcespb.GitHub_Unauthenticated{}\n\t}\n\n\tvar conn anypb.Any\n\terr := anypb.MarshalFrom(&conn, &connection, proto.MarshalOptions{})\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"failed to marshal github connection\")\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\n\tlogOptions := &gogit.LogOptions{}\n\topts := []git.ScanOption{\n\t\tgit.ScanOptionFilter(c.Filter),\n\t\tgit.ScanOptionLogOptions(logOptions),\n\t}\n\tscanOptions := git.NewScanOptions(opts...)\n\n\tsourceName := \"trufflehog - github\"\n\tsourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, github.SourceType)\n\n\tgithubSource := &github.Source{}\n\tif err := githubSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, c.Concurrency); err != nil {\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\tgithubSource.WithScanOptions(scanOptions)\n\treturn e.sourceManager.EnumerateAndScan(ctx, sourceName, githubSource)\n}\n"
  },
  {
    "path": "pkg/engine/github_experimental.go",
    "content": "package engine\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\n\tgogit \"github.com/go-git/go-git/v5\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/github\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/github_experimental\"\n)\n\n// ScanGitHubExperimental scans GitHub using an experimental feature. Consider all functionality to be in an alpha release here.\nfunc (e *Engine) ScanGitHubExperimental(ctx context.Context, c sources.GitHubExperimentalConfig) (sources.JobProgressRef, error) {\n\tconnection := sourcespb.GitHubExperimental{\n\t\tRepository:         c.Repository,\n\t\tObjectDiscovery:    c.ObjectDiscovery,\n\t\tCollisionThreshold: int64(c.CollisionThreshold),\n\t\tDeleteCachedData:   c.DeleteCachedData,\n\t}\n\n\t// Check at least one experimental sub-module is being used.\n\t// Add to this list as more experimental sub-modules are added.\n\tif !c.ObjectDiscovery {\n\t\treturn sources.JobProgressRef{}, fmt.Errorf(\"at least one experimental submodule must be enabled\")\n\t}\n\n\tif len(c.Token) > 0 {\n\t\tconnection.Credential = &sourcespb.GitHubExperimental_Token{\n\t\t\tToken: c.Token,\n\t\t}\n\t} else {\n\t\treturn sources.JobProgressRef{}, fmt.Errorf(\"token is required for github experimental\")\n\t}\n\n\tvar conn anypb.Any\n\terr := anypb.MarshalFrom(&conn, &connection, proto.MarshalOptions{})\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"failed to marshal github experimental connection\")\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\n\tlogOptions := &gogit.LogOptions{}\n\topts := []git.ScanOption{\n\t\tgit.ScanOptionLogOptions(logOptions),\n\t}\n\tscanOptions := git.NewScanOptions(opts...)\n\n\tsourceName := \"trufflehog - github experimental (alpha release)\"\n\tsourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, github.SourceType)\n\n\tgithubExperimentalSource := &github_experimental.Source{}\n\tif err := githubExperimentalSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\tgithubExperimentalSource.WithScanOptions(scanOptions)\n\treturn e.sourceManager.EnumerateAndScan(ctx, sourceName, githubExperimentalSource)\n}\n"
  },
  {
    "path": "pkg/engine/gitlab.go",
    "content": "package engine\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\n\tgogit \"github.com/go-git/go-git/v5\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/gitlab\"\n)\n\n// ScanGitLab scans GitLab with the provided configuration.\nfunc (e *Engine) ScanGitLab(ctx context.Context, c sources.GitlabConfig) (sources.JobProgressRef, error) {\n\tlogOptions := &gogit.LogOptions{}\n\topts := []git.ScanOption{\n\t\tgit.ScanOptionFilter(c.Filter),\n\t\tgit.ScanOptionLogOptions(logOptions),\n\t}\n\tscanOptions := git.NewScanOptions(opts...)\n\n\tconnection := &sourcespb.GitLab{\n\t\tSkipBinaries:    c.SkipBinaries,\n\t\tRemoveAuthInUrl: !c.AuthInUrl, // configuration uses the opposite field in proto to keep credentials in the URL by default.\n\t}\n\n\tswitch {\n\tcase len(c.Token) > 0:\n\t\tconnection.Credential = &sourcespb.GitLab_Token{\n\t\t\tToken: c.Token,\n\t\t}\n\tdefault:\n\t\treturn sources.JobProgressRef{}, fmt.Errorf(\"must provide token\")\n\t}\n\n\tif len(c.Endpoint) > 0 {\n\t\tconnection.Endpoint = c.Endpoint\n\t}\n\n\tif len(c.Repos) > 0 {\n\t\tconnection.Repositories = c.Repos\n\t}\n\n\tif len(c.GroupIds) > 0 {\n\t\tconnection.GroupIds = c.GroupIds\n\t}\n\n\tif len(c.IncludeRepos) > 0 {\n\t\tconnection.IncludeRepos = c.IncludeRepos\n\t}\n\n\tif len(c.ExcludeRepos) > 0 {\n\t\tconnection.IgnoreRepos = c.ExcludeRepos\n\t}\n\n\tif c.ClonePath != \"\" {\n\t\tconnection.ClonePath = c.ClonePath\n\t}\n\n\tconnection.NoCleanup = c.NoCleanup\n\n\tconnection.PrintLegacyJson = c.PrintLegacyJSON\n\n\tvar conn anypb.Any\n\terr := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"failed to marshal gitlab connection\")\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\n\tsourceName := \"trufflehog - gitlab\"\n\tsourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, gitlab.SourceType)\n\n\tgitlabSource := &gitlab.Source{}\n\tif err := gitlabSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\tgitlabSource.WithScanOptions(scanOptions)\n\treturn e.sourceManager.EnumerateAndScan(ctx, sourceName, gitlabSource)\n}\n"
  },
  {
    "path": "pkg/engine/gitlab_integration_test.go",
    "content": "//go:build integration\n// +build integration\n\npackage engine\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/defaults\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nfunc TestGitLab(t *testing.T) {\n\t// Run the scan.\n\tctx := context.Background()\n\te, err := NewEngine(ctx, &Config{\n\t\tDetectors:     defaults.DefaultDetectors(),\n\t\tSourceManager: sources.NewManager(),\n\t\tVerify:        false,\n\t})\n\tassert.NoError(t, err)\n\te.Start(ctx)\n\n\tsecret, err := common.GetTestSecret(ctx)\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\t_, err = e.ScanGitLab(ctx, sources.GitlabConfig{\n\t\tToken: secret.MustGetField(\"GITLAB_TOKEN\"),\n\t})\n\tassert.NoError(t, err)\n\n\terr = e.Finish(ctx)\n\tassert.NoError(t, err)\n\n\t// Check the output provided by metrics.\n\tmetrics := e.GetMetrics()\n\tassert.GreaterOrEqual(t, metrics.ChunksScanned, uint64(23528))\n\tassert.GreaterOrEqual(t, metrics.BytesScanned, uint64(84982266))\n}\n"
  },
  {
    "path": "pkg/engine/huggingface.go",
    "content": "package engine\n\nimport (\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/huggingface\"\n)\n\n// HuggingFaceConfig represents the configuration for HuggingFace.\ntype HuggingfaceConfig struct {\n\tEndpoint           string\n\tModels             []string\n\tSpaces             []string\n\tDatasets           []string\n\tOrganizations      []string\n\tUsers              []string\n\tIncludeModels      []string\n\tIgnoreModels       []string\n\tIncludeSpaces      []string\n\tIgnoreSpaces       []string\n\tIncludeDatasets    []string\n\tIgnoreDatasets     []string\n\tSkipAllModels      bool\n\tSkipAllSpaces      bool\n\tSkipAllDatasets    bool\n\tIncludeDiscussions bool\n\tIncludePrs         bool\n\tToken              string\n\tConcurrency        int\n}\n\n// ScanGitHub scans HuggingFace with the provided options.\nfunc (e *Engine) ScanHuggingface(ctx context.Context, c HuggingfaceConfig) (sources.JobProgressRef, error) {\n\tconnection := sourcespb.Huggingface{\n\t\tEndpoint:           c.Endpoint,\n\t\tModels:             c.Models,\n\t\tSpaces:             c.Spaces,\n\t\tDatasets:           c.Datasets,\n\t\tOrganizations:      c.Organizations,\n\t\tUsers:              c.Users,\n\t\tIncludeModels:      c.IncludeModels,\n\t\tIgnoreModels:       c.IgnoreModels,\n\t\tIncludeSpaces:      c.IncludeSpaces,\n\t\tIgnoreSpaces:       c.IgnoreSpaces,\n\t\tIncludeDatasets:    c.IncludeDatasets,\n\t\tIgnoreDatasets:     c.IgnoreDatasets,\n\t\tSkipAllModels:      c.SkipAllModels,\n\t\tSkipAllSpaces:      c.SkipAllSpaces,\n\t\tSkipAllDatasets:    c.SkipAllDatasets,\n\t\tIncludeDiscussions: c.IncludeDiscussions,\n\t\tIncludePrs:         c.IncludePrs,\n\t}\n\tif len(c.Token) > 0 {\n\t\tconnection.Credential = &sourcespb.Huggingface_Token{\n\t\t\tToken: c.Token,\n\t\t}\n\t} else {\n\t\tconnection.Credential = &sourcespb.Huggingface_Unauthenticated{}\n\t}\n\n\tvar conn anypb.Any\n\terr := anypb.MarshalFrom(&conn, &connection, proto.MarshalOptions{})\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"failed to marshal huggingface connection\")\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\n\tsourceName := \"trufflehog - huggingface\"\n\tsourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, sourcespb.SourceType_SOURCE_TYPE_HUGGINGFACE)\n\n\thuggingfaceSource := &huggingface.Source{}\n\tif err := huggingfaceSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, c.Concurrency); err != nil {\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\treturn e.sourceManager.EnumerateAndScan(ctx, sourceName, huggingfaceSource)\n}\n"
  },
  {
    "path": "pkg/engine/jenkins.go",
    "content": "package engine\n\nimport (\n\t\"errors\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/credentialspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/jenkins\"\n)\n\ntype JenkinsConfig struct {\n\tEndpoint              string\n\tUsername              string\n\tPassword              string\n\tHeader                string\n\tInsecureSkipVerifyTLS bool\n}\n\n// ScanJenkins scans Jenkins logs.\nfunc (e *Engine) ScanJenkins(ctx context.Context, jenkinsConfig JenkinsConfig) (sources.JobProgressRef, error) {\n\tvar connection *sourcespb.Jenkins\n\tswitch {\n\tcase jenkinsConfig.Username != \"\" && jenkinsConfig.Password != \"\":\n\t\tconnection = &sourcespb.Jenkins{\n\t\t\tCredential: &sourcespb.Jenkins_BasicAuth{\n\t\t\t\tBasicAuth: &credentialspb.BasicAuth{\n\t\t\t\t\tUsername: jenkinsConfig.Username,\n\t\t\t\t\tPassword: jenkinsConfig.Password,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\tcase jenkinsConfig.Header != \"\":\n\t\tsplits := strings.Split(jenkinsConfig.Header, \":\")\n\t\tif len(splits) != 2 {\n\t\t\treturn sources.JobProgressRef{}, errors.New(\"invalid header format, expected key: value\")\n\t\t}\n\t\tkey := splits[0]\n\t\tvalue := splits[1]\n\n\t\tconnection = &sourcespb.Jenkins{\n\t\t\tCredential: &sourcespb.Jenkins_Header{\n\t\t\t\tHeader: &credentialspb.Header{\n\t\t\t\t\tKey:   key,\n\t\t\t\t\tValue: value,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\tdefault:\n\t\tconnection = &sourcespb.Jenkins{\n\t\t\tCredential: &sourcespb.Jenkins_Unauthenticated{\n\t\t\t\tUnauthenticated: &credentialspb.Unauthenticated{},\n\t\t\t},\n\t\t}\n\t}\n\n\tconnection.Endpoint = jenkinsConfig.Endpoint\n\tconnection.InsecureSkipVerifyTls = jenkinsConfig.InsecureSkipVerifyTLS\n\n\tvar conn anypb.Any\n\terr := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"failed to marshal Jenkins connection\")\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\n\tsourceName := \"trufflehog - Jenkins\"\n\tsourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, jenkins.SourceType)\n\n\tjenkinsSource := &jenkins.Source{}\n\tif err := jenkinsSource.Init(ctx, \"trufflehog - Jenkins\", jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\treturn e.sourceManager.EnumerateAndScan(ctx, sourceName, jenkinsSource)\n}\n"
  },
  {
    "path": "pkg/engine/json_enumerator.go",
    "content": "package engine\n\nimport (\n\t\"runtime\"\n\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/json_enumerator\"\n)\n\n// ScanJSONEnumeratorInput scans input that is in JSON Enumerator format\nfunc (e *Engine) ScanJSONEnumeratorInput(\n\tctx context.Context,\n\tc sources.JSONEnumeratorConfig,\n) (sources.JobProgressRef, error) {\n\tconnection := &sourcespb.JSONEnumerator{\n\t\tPaths: c.Paths,\n\t}\n\tvar conn anypb.Any\n\terr := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"failed to marshal JSON enumerator connection\")\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\n\tsourceName := \"trufflehog - JSON enumerator\"\n\tsourceID, jobID, err := e.sourceManager.GetIDs(ctx, sourceName, json_enumerator.SourceType)\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"failed to get IDs from source manager\")\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\n\tsource := &json_enumerator.Source{}\n\terr = source.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU())\n\tif err != nil {\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\treturn e.sourceManager.EnumerateAndScan(ctx, sourceName, source)\n}\n"
  },
  {
    "path": "pkg/engine/metrics.go",
    "content": "package engine\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\nvar (\n\t// Detector metrics.\n\tdetectorExecutionCount = promauto.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"detector_execution_count\",\n\t\t\tHelp:      \"Total number of times a detector has been executed.\",\n\t\t},\n\t\t[]string{\"detector_name\", \"job_id\", \"source_name\"},\n\t)\n\n\t// Note this is the time taken to call FromData on each detector, not necessarily the time taken\n\t// to verify a credential via an API call. If the regex match within FromData does not match, the\n\t// detector will return early. For now this is a good proxy for the time taken to verify a credential.\n\t// TODO (ahrav)\n\t// We can work on a more fine-grained metric later.\n\tdetectorExecutionDuration = promauto.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"detector_execution_duration\",\n\t\t\tHelp:      \"Duration of detector execution in milliseconds.\",\n\t\t\tBuckets:   prometheus.ExponentialBuckets(1, 5, 6),\n\t\t},\n\t\t[]string{\"detector_name\"},\n\t)\n\n\tjobBytesScanned = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"job_bytes_scanned\",\n\t\tHelp:      \"Total number of bytes scanned for a job.\",\n\t},\n\t\t[]string{\"source_type\", \"source_name\"},\n\t)\n\n\tscanBytesPerChunk = promauto.NewHistogramVec(prometheus.HistogramOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"scan_bytes_per_chunk\",\n\t\tHelp:      \"Total number of bytes in a chunk.\",\n\t\tBuckets:   prometheus.ExponentialBuckets(1, 2, 18),\n\t},\n\t\t[]string{\"source_type\"},\n\t)\n\n\tjobChunksScanned = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"job_chunks_scanned\",\n\t\tHelp:      \"Total number of chunks scanned for a job.\",\n\t},\n\t\t[]string{\"source_type\", \"source_name\"},\n\t)\n\n\tdetectBytesPerMatch = promauto.NewHistogram(prometheus.HistogramOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"detect_bytes_per_match\",\n\t\tHelp:      \"Total number of bytes used to detect a credential in a match per chunk.\",\n\t\tBuckets:   prometheus.ExponentialBuckets(1, 2, 18),\n\t})\n\n\tmatchesPerChunk = promauto.NewHistogram(prometheus.HistogramOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"matches_per_chunk\",\n\t\tHelp:      \"Total number of matches found in a chunk.\",\n\t\tBuckets:   prometheus.ExponentialBuckets(1, 2, 10),\n\t})\n\n\t// Metrics around latency for the different stages of the pipeline.\n\tchunksScannedLatency = promauto.NewHistogram(prometheus.HistogramOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"chunk_scanned_latency\",\n\t\tHelp:      \"Time taken to scan a chunk in microseconds.\",\n\t\tBuckets:   prometheus.ExponentialBuckets(1, 2, 22),\n\t})\n\n\tchunksDetectedLatency = promauto.NewHistogram(prometheus.HistogramOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"chunk_detected_latency\",\n\t\tHelp:      \"Time taken to detect a chunk in microseconds.\",\n\t\tBuckets:   prometheus.ExponentialBuckets(50, 2, 20),\n\t})\n\n\tchunksNotifiedLatency = promauto.NewHistogram(prometheus.HistogramOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"chunk_notified_latency\",\n\t\tHelp:      \"Time taken to notify a chunk in milliseconds.\",\n\t\tBuckets:   prometheus.ExponentialBuckets(5, 2, 12),\n\t})\n)\n"
  },
  {
    "path": "pkg/engine/postman.go",
    "content": "package engine\n\nimport (\n\t\"errors\"\n\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/postman\"\n)\n\n// ScanPostman scans Postman with the provided options.\nfunc (e *Engine) ScanPostman(ctx context.Context, c sources.PostmanConfig) (sources.JobProgressRef, error) {\n\tconnection := sourcespb.Postman{\n\t\tWorkspaces:          c.Workspaces,\n\t\tCollections:         c.Collections,\n\t\tEnvironments:        c.Environments,\n\t\tIncludeCollections:  c.IncludeCollections,\n\t\tIncludeEnvironments: c.IncludeEnvironments,\n\t\tExcludeCollections:  c.ExcludeCollections,\n\t\tExcludeEnvironments: c.ExcludeEnvironments,\n\t\tWorkspacePaths:      c.WorkspacePaths,\n\t\tCollectionPaths:     c.CollectionPaths,\n\t\tEnvironmentPaths:    c.EnvironmentPaths,\n\t}\n\n\t// Check if postman data is going to be accessed via an api call using a token, or\n\t// if it has been already exported and exists locally\n\tif len(c.Token) > 0 {\n\t\tconnection.Credential = &sourcespb.Postman_Token{\n\t\t\tToken: c.Token,\n\t\t}\n\t} else if len(c.WorkspacePaths) > 0 || len(c.CollectionPaths) > 0 || len(c.EnvironmentPaths) > 0 {\n\t\tconnection.Credential = &sourcespb.Postman_Unauthenticated{}\n\t} else {\n\t\treturn sources.JobProgressRef{}, errors.New(\"no path to locally exported data or API token provided\")\n\t}\n\n\tvar conn anypb.Any\n\terr := anypb.MarshalFrom(&conn, &connection, proto.MarshalOptions{})\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"failed to marshal Postman connection\")\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\n\tsourceName := \"trufflehog - postman\"\n\tsourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, postman.SourceType)\n\n\tpostmanSource := &postman.Source{\n\t\tDetectorKeywords: e.AhoCorasickCoreKeywords(),\n\t}\n\n\tif err := postmanSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, c.Concurrency); err != nil {\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\treturn e.sourceManager.EnumerateAndScan(ctx, sourceName, postmanSource)\n}\n"
  },
  {
    "path": "pkg/engine/postman_test.go",
    "content": "package engine\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/decoders\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/defaults\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nfunc TestPostmanEngine(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tpostmanConfig sources.PostmanConfig\n\t\twantErr       bool\n\t}{\n\t\t{\n\t\t\tname: \"scanned Postman with a token\",\n\t\t\tpostmanConfig: sources.PostmanConfig{\n\t\t\t\tToken: \"dummy_key\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"scanned Postman with workspacePath\",\n\t\t\tpostmanConfig: sources.PostmanConfig{\n\t\t\t\tWorkspacePaths: []string{\"Downloads/Test API.postman_collection.json\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"scanned Postman with environmentPath\",\n\t\t\tpostmanConfig: sources.PostmanConfig{\n\t\t\t\tEnvironmentPaths: []string{\"Downloads/Mobile - Points Unlock Redeemables.postman_environment.json\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"no token or file path provided\",\n\t\t\twantErr: true,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tctx, cancel := context.WithCancel(context.TODO())\n\t\t\tdefer cancel()\n\n\t\t\tconst defaultOutputBufferSize = 64\n\t\t\topts := []func(*sources.SourceManager){\n\t\t\t\tsources.WithSourceUnits(),\n\t\t\t\tsources.WithBufferedOutput(defaultOutputBufferSize),\n\t\t\t}\n\n\t\t\tsourceManager := sources.NewManager(opts...)\n\n\t\t\tconf := Config{\n\t\t\t\tConcurrency:   1,\n\t\t\t\tDecoders:      decoders.DefaultDecoders(),\n\t\t\t\tDetectors:     defaults.DefaultDetectors(),\n\t\t\t\tVerify:        false,\n\t\t\t\tSourceManager: sourceManager,\n\t\t\t\tDispatcher:    NewPrinterDispatcher(new(discardPrinter)),\n\t\t\t}\n\n\t\t\te, err := NewEngine(ctx, &conf)\n\t\t\tassert.NoError(t, err)\n\t\t\te.Start(ctx)\n\t\t\t_, err = e.ScanPostman(ctx, test.postmanConfig)\n\t\t\tif err != nil && !test.wantErr {\n\t\t\t\tt.Errorf(\"ScanPostman() got: %v, want: %v\", err, nil)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err == nil && test.wantErr {\n\t\t\t\tt.Errorf(\"ScanPostman() got: %v, want: %v\", err, \"error\")\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/s3.go",
    "content": "package engine\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/credentialspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/s3\"\n)\n\n// ScanS3 scans S3 buckets.\nfunc (e *Engine) ScanS3(ctx context.Context, c sources.S3Config) (sources.JobProgressRef, error) {\n\tconnection := &sourcespb.S3{\n\t\tCredential: &sourcespb.S3_Unauthenticated{},\n\t}\n\tif c.CloudCred {\n\t\tif len(c.Key) > 0 || len(c.Secret) > 0 || len(c.SessionToken) > 0 {\n\t\t\treturn sources.JobProgressRef{}, fmt.Errorf(\"cannot use cloud environment and static credentials together\")\n\t\t}\n\t\tconnection.Credential = &sourcespb.S3_CloudEnvironment{}\n\t}\n\tif len(c.Key) > 0 && len(c.Secret) > 0 {\n\t\tif len(c.SessionToken) > 0 {\n\t\t\tconnection.Credential = &sourcespb.S3_SessionToken{\n\t\t\t\tSessionToken: &credentialspb.AWSSessionTokenSecret{\n\t\t\t\t\tKey:          c.Key,\n\t\t\t\t\tSecret:       c.Secret,\n\t\t\t\t\tSessionToken: c.SessionToken,\n\t\t\t\t},\n\t\t\t}\n\t\t} else {\n\t\t\tconnection.Credential = &sourcespb.S3_AccessKey{\n\t\t\t\tAccessKey: &credentialspb.KeySecret{\n\t\t\t\t\tKey:    c.Key,\n\t\t\t\t\tSecret: c.Secret,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\t}\n\tif len(c.Buckets) > 0 {\n\t\tconnection.Buckets = c.Buckets\n\t}\n\tif len(c.IgnoreBuckets) > 0 {\n\t\tconnection.IgnoreBuckets = c.IgnoreBuckets\n\t}\n\n\tif len(c.Roles) > 0 {\n\t\tconnection.Roles = c.Roles\n\t}\n\n\tvar conn anypb.Any\n\terr := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"failed to marshal S3 connection\")\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\n\tsourceName := \"trufflehog - s3\"\n\tsourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, s3.SourceType)\n\n\ts3Source := &s3.Source{}\n\tif err := s3Source.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\treturn e.sourceManager.EnumerateAndScan(ctx, sourceName, s3Source)\n}\n"
  },
  {
    "path": "pkg/engine/scan.go",
    "content": "package engine\n\nimport (\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/postman\"\n)\n\n// ScanConfig starts a scan of all of the configured (but not initialized)\n// sources and returns their job references. If there is an error during\n// initialization or starting of the scan, an error is returned along with the\n// references that successfully started up to that point.\nfunc (e *Engine) ScanConfig(ctx context.Context, configuredSources ...sources.ConfiguredSource) ([]sources.JobProgressRef, error) {\n\tvar refs []sources.JobProgressRef\n\tfor _, configuredSource := range configuredSources {\n\t\tsourceID, jobID, _ := e.sourceManager.GetIDs(ctx, configuredSource.Name, configuredSource.SourceType())\n\n\t\tsource, err := configuredSource.Init(ctx, sourceID, jobID)\n\t\tif err != nil {\n\t\t\treturn refs, err\n\t\t}\n\t\t// Postman needs special initialization to set Keywords from\n\t\t// the engine.\n\t\tif postmanSource, ok := source.(*postman.Source); ok {\n\t\t\tpostmanSource.DetectorKeywords = e.AhoCorasickCoreKeywords()\n\t\t}\n\n\t\t// Start the scan.\n\t\tref, err := e.sourceManager.EnumerateAndScan(ctx, configuredSource.Name, source)\n\t\tif err != nil {\n\t\t\treturn refs, err\n\t\t}\n\t\trefs = append(refs, ref)\n\t}\n\treturn refs, nil\n}\n"
  },
  {
    "path": "pkg/engine/stdin.go",
    "content": "package engine\n\nimport (\n\t\"runtime\"\n\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/stdin\"\n)\n\n// ScanStdinInput scans input that is piped into the application\nfunc (e *Engine) ScanStdinInput(ctx context.Context, c sources.StdinConfig) (sources.JobProgressRef, error) {\n\tconnection := &sourcespb.Stdin{}\n\tvar conn anypb.Any\n\terr := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"failed to marshal stdin connection\")\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\n\tsourceName := \"trufflehog - stdin\"\n\tsourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, stdin.SourceType)\n\n\tstdinSource := &stdin.Source{}\n\tif err := stdinSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\treturn e.sourceManager.EnumerateAndScan(ctx, sourceName, stdinSource)\n}\n"
  },
  {
    "path": "pkg/engine/syslog.go",
    "content": "package engine\n\nimport (\n\t\"os\"\n\n\t\"github.com/go-errors/errors\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/syslog\"\n)\n\n// ScanSyslog is a source that scans syslog files.\nfunc (e *Engine) ScanSyslog(ctx context.Context, c sources.SyslogConfig) (sources.JobProgressRef, error) {\n\tconnection := &sourcespb.Syslog{\n\t\tProtocol:      c.Protocol,\n\t\tListenAddress: c.Address,\n\t\tFormat:        c.Format,\n\t}\n\n\tif c.CertPath != \"\" && c.KeyPath != \"\" {\n\t\tcert, err := os.ReadFile(c.CertPath)\n\t\tif err != nil {\n\t\t\treturn sources.JobProgressRef{}, errors.WrapPrefix(err, \"could not open TLS cert file\", 0)\n\t\t}\n\t\tconnection.TlsCert = string(cert)\n\n\t\tkey, err := os.ReadFile(c.KeyPath)\n\t\tif err != nil {\n\t\t\treturn sources.JobProgressRef{}, errors.WrapPrefix(err, \"could not open TLS key file\", 0)\n\t\t}\n\t\tconnection.TlsKey = string(key)\n\t}\n\n\tvar conn anypb.Any\n\terr := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})\n\tif err != nil {\n\t\treturn sources.JobProgressRef{}, errors.WrapPrefix(err, \"error unmarshalling connection\", 0)\n\t}\n\n\tsourceName := \"trufflehog - syslog\"\n\tsourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, syslog.SourceType)\n\tsyslogSource := &syslog.Source{}\n\tif err := syslogSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, c.Concurrency); err != nil {\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\tsyslogSource.InjectConnection(connection)\n\n\treturn e.sourceManager.EnumerateAndScan(ctx, sourceName, syslogSource)\n}\n"
  },
  {
    "path": "pkg/engine/testdata/secrets.txt",
    "content": "  1 aws AKIAWARWQKZNHMZBLY4I\n  2 secret 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\n sentry 27ac84f4bcdb4fca9701f4d6f6f58cd7d96b69c9d9754d40800645a51d668f90\n sentry 27ac84f4bcdb4fca9701f4d6f6f58cd7d96b69c9d9754d40800645a51d668f90\n sentry 27ac84f4bcdb4fca9701f4d6f6f58cd7d96b69c9d9754d40800645a51d668f90\n sentry 27ac84f4bcdb4fca9701f4d6f6f58cd7d96b69c9d9754d40800645a51d668f90"
  },
  {
    "path": "pkg/engine/testdata/verificationoverlap_detectors.yaml",
    "content": "# config.yaml\ndetectors:\n  - name: detector1\n    keywords:\n      - PMAK\n    regex:\n      api_key: \\b(PMAK-[a-zA-Z-0-9]{59})\\b\n\n  - name: detector2\n    keywords:\n      - ost \n    regex:\n      api_key: \\b([a-zA-Z-0-9]{59})\\b"
  },
  {
    "path": "pkg/engine/testdata/verificationoverlap_secrets.txt",
    "content": "\nPOSTMAN_API_KEY=\"PMAK-qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r\"\n"
  },
  {
    "path": "pkg/engine/testdata/verificationoverlap_secrets_fp.txt",
    "content": "\nPOSTMAN_API_KEY=\"ssample-qnwfsLyRSyfCwfpHaQP1UzDhrgpWvHjbYzjpRCMshjt417zWcrzyHUArs7r\"\n"
  },
  {
    "path": "pkg/engine/travisci.go",
    "content": "package engine\n\nimport (\n\t\"runtime\"\n\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/travisci\"\n)\n\n// ScanTravisCI scans TravisCI logs.\nfunc (e *Engine) ScanTravisCI(ctx context.Context, token string) (sources.JobProgressRef, error) {\n\tconnection := &sourcespb.TravisCI{\n\t\tCredential: &sourcespb.TravisCI_Token{\n\t\t\tToken: token,\n\t\t},\n\t}\n\n\tvar conn anypb.Any\n\terr := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"failed to marshal Travis CI connection\")\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\n\tsourceName := \"trufflehog - Travis CI\"\n\tsourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, travisci.SourceType)\n\n\ttravisSource := &travisci.Source{}\n\tif err := travisSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {\n\t\treturn sources.JobProgressRef{}, err\n\t}\n\treturn e.sourceManager.EnumerateAndScan(ctx, sourceName, travisSource)\n}\n"
  },
  {
    "path": "pkg/feature/feature.go",
    "content": "package feature\n\nimport (\n\t\"sync/atomic\"\n)\n\nvar (\n\tForceSkipBinaries              atomic.Bool\n\tForceSkipArchives              atomic.Bool\n\tGitCloneTimeoutDuration        atomic.Int64\n\tSkipAdditionalRefs             atomic.Bool\n\tEnableAPKHandler               atomic.Bool\n\tUserAgentSuffix                AtomicString\n\tUseSimplifiedGitlabEnumeration atomic.Bool\n\tUseGitMirror                   atomic.Bool\n\tGitlabProjectsPerPage          atomic.Int64\n\tUseGithubGraphQLAPI            atomic.Bool // use github graphql api to fetch issues, pr's and comments\n)\n\ntype AtomicString struct {\n\tvalue atomic.Value\n}\n\n// Load returns the current value of the atomic string\nfunc (as *AtomicString) Load() string {\n\tif v := as.value.Load(); v != nil {\n\t\treturn v.(string)\n\t}\n\treturn \"\"\n}\n\n// Store sets the value of the atomic string\nfunc (as *AtomicString) Store(newValue string) {\n\tas.value.Store(newValue)\n}\n\n// Swap atomically swaps the current string with a new one and returns the old value\nfunc (as *AtomicString) Swap(newValue string) string {\n\toldValue := as.Load()\n\tas.Store(newValue)\n\treturn oldValue\n}\n"
  },
  {
    "path": "pkg/gitparse/gitparse.go",
    "content": "package gitparse\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\tbufferwriter \"github.com/trufflesecurity/trufflehog/v3/pkg/writers/buffer_writer\"\n\tbufferedfilewriter \"github.com/trufflesecurity/trufflehog/v3/pkg/writers/buffered_file_writer\"\n)\n\nconst (\n\t// defaultDateFormat is the standard date format for git.\n\t// Uses ISO 8601 format to avoid locale-dependent weekday/month names.\n\tdefaultDateFormat = time.RFC3339\n\n\t// defaultMaxDiffSize is the maximum size for a diff. Larger diffs will be cut off.\n\tdefaultMaxDiffSize int64 = 2 * 1024 * 1024 * 1024 // 2GB\n\n\t// defaultMaxCommitSize is the maximum size for a commit. Larger commits will be cut off.\n\tdefaultMaxCommitSize int64 = 2 * 1024 * 1024 * 1024 // 2GB\n\n\t// defaultWaitDelay is the default time to wait after context cancellation before forcefully killing git processes.\n\tdefaultWaitDelay = 5 * time.Second\n)\n\n// contentWriter defines a common interface for writing, reading, and managing diff content.\n// It abstracts the underlying storage mechanism, allowing flexibility in how content is handled.\n// This interface enables the use of different content storage strategies (e.g., in-memory buffer, file-based storage)\n// based on performance needs or resource constraints, providing a unified way to interact with different content types.\ntype contentWriter interface { // Write appends data to the content storage.\n\t// Write appends data to the content storage.\n\tWrite(data []byte) (int, error)\n\t// ReadCloser provides a reader for accessing stored content.\n\tReadCloser() (io.ReadCloser, error)\n\t// CloseForWriting closes the content storage for writing.\n\tCloseForWriting() error\n\t// Len returns the current size of the content.\n\tLen() int\n\t// String returns the content as a string or an error if the content cannot be converted to a string.\n\tString() (string, error)\n}\n\n// Diff contains the information about a file diff in a commit.\n// It abstracts the underlying content representation, allowing for flexible handling of diff content.\n// The use of contentWriter enables the management of diff data either in memory or on disk,\n// based on its size, optimizing resource usage and performance.\ntype Diff struct {\n\tPathB     string\n\tLineStart int\n\tIsBinary  bool\n\n\tCommit *Commit\n\n\tcontentWriter contentWriter\n}\n\ntype diffOption func(*Diff)\n\n// withPathB sets the PathB option.\nfunc withPathB(pathB string) diffOption { return func(d *Diff) { d.PathB = pathB } }\n\n// withCustomContentWriter sets the useCustomContentWriter option.\nfunc withCustomContentWriter(cr contentWriter) diffOption {\n\treturn func(d *Diff) { d.contentWriter = cr }\n}\n\n// newDiff creates a new Diff with a threshold and an associated commit.\n// All Diffs must have an associated commit.\n// The contentWriter is used to manage the diff's content, allowing for flexible handling of diff data.\n// By default, a buffer is used as the contentWriter, but this can be overridden with a custom contentWriter.\nfunc newDiff(commit *Commit, opts ...diffOption) *Diff {\n\tdiff := &Diff{Commit: commit}\n\tfor _, opt := range opts {\n\t\topt(diff)\n\t}\n\n\tif diff.contentWriter == nil {\n\t\tdiff.contentWriter = bufferwriter.New()\n\t}\n\n\treturn diff\n}\n\n// Len returns the length of the storage.\nfunc (d *Diff) Len() int { return d.contentWriter.Len() }\n\n// ReadCloser returns a ReadCloser for the contentWriter.\nfunc (d *Diff) ReadCloser() (io.ReadCloser, error) { return d.contentWriter.ReadCloser() }\n\n// write delegates to the contentWriter.\nfunc (d *Diff) write(p []byte) error {\n\t_, err := d.contentWriter.Write(p)\n\treturn err\n}\n\n// finalize ensures proper closure of resources associated with the Diff.\n// handle the final flush in the finalize method, in case there's data remaining in the buffer.\n// This method should be called to release resources, especially when writing to a file.\nfunc (d *Diff) finalize() error { return d.contentWriter.CloseForWriting() }\n\n// Commit contains commit header info and diffs.\ntype Commit struct {\n\tHash      string\n\tAuthor    string\n\tCommitter string\n\tDate      time.Time\n\tMessage   strings.Builder\n\tSize      int // in bytes\n\n\thasDiffs bool\n}\n\n// Parser sets values used in GitParse.\ntype Parser struct {\n\tmaxDiffSize   int64\n\tmaxCommitSize int64\n\tdateFormat    string\n\twaitDelay     time.Duration\n\n\tuseCustomContentWriter bool\n}\n\ntype ParseState int\n\nconst (\n\tInitial ParseState = iota\n\tCommitLine\n\tMergeLine\n\tAuthorLine\n\tAuthorDateLine\n\tCommitterLine\n\tCommitterDateLine\n\tMessageStartLine\n\tMessageLine\n\tMessageEndLine\n\tNotesStartLine\n\tNotesLine\n\tNotesEndLine\n\tDiffLine\n\tModeLine\n\tIndexLine\n\tFromFileLine\n\tToFileLine\n\tBinaryFileLine\n\tHunkLineNumberLine\n\tHunkContentLine\n\tParseFailure\n)\n\nfunc (state ParseState) String() string {\n\treturn [...]string{\n\t\t\"Initial\",\n\t\t\"CommitLine\",\n\t\t\"MergeLine\",\n\t\t\"AuthorLine\",\n\t\t\"AuthorDateLine\",\n\t\t\"CommitterLine\",\n\t\t\"CommitterDateLine\",\n\t\t\"MessageStartLine\",\n\t\t\"MessageLine\",\n\t\t\"MessageEndLine\",\n\t\t\"NotesStartLine\",\n\t\t\"NotesLine\",\n\t\t\"NotesEndLine\",\n\t\t\"DiffLine\",\n\t\t\"ModeLine\",\n\t\t\"IndexLine\",\n\t\t\"FromFileLine\",\n\t\t\"ToFileLine\",\n\t\t\"BinaryFileLine\",\n\t\t\"HunkLineNumberLine\",\n\t\t\"HunkContentLine\",\n\t\t\"ParseFailure\",\n\t}[state]\n}\n\n// UseCustomContentWriter sets useCustomContentWriter option.\nfunc UseCustomContentWriter() Option {\n\treturn func(parser *Parser) { parser.useCustomContentWriter = true }\n}\n\n// WithMaxDiffSize sets maxDiffSize option. Diffs larger than maxDiffSize will\n// be truncated.\nfunc WithMaxDiffSize(maxDiffSize int64) Option {\n\treturn func(parser *Parser) {\n\t\tparser.maxDiffSize = maxDiffSize\n\t}\n}\n\n// WithMaxCommitSize sets maxCommitSize option. Commits larger than maxCommitSize\n// will be put in the commit channel and additional diffs will be added to a\n// new commit.\nfunc WithMaxCommitSize(maxCommitSize int64) Option {\n\treturn func(parser *Parser) {\n\t\tparser.maxCommitSize = maxCommitSize\n\t}\n}\n\n// WithWaitDelay sets the waitDelay option. This specifies how long to wait after\n// context cancellation before forcefully killing git processes.\nfunc WithWaitDelay(waitDelay time.Duration) Option {\n\treturn func(parser *Parser) {\n\t\tparser.waitDelay = waitDelay\n\t}\n}\n\n// Option is used for adding options to Config.\ntype Option func(*Parser)\n\n// NewParser creates a GitParse config from options and sets defaults.\nfunc NewParser(options ...Option) *Parser {\n\tparser := &Parser{\n\t\tdateFormat:    defaultDateFormat,\n\t\tmaxDiffSize:   defaultMaxDiffSize,\n\t\tmaxCommitSize: defaultMaxCommitSize,\n\t\twaitDelay:     defaultWaitDelay,\n\t}\n\tfor _, option := range options {\n\t\toption(parser)\n\t}\n\treturn parser\n}\n\n// RepoPath parses the output of the `git log` command for the `source` path.\n// The Diff chan will return diffs in the order they are parsed from the log.\nfunc (c *Parser) RepoPath(\n\tctx context.Context,\n\tsource string,\n\thead string,\n\tabbreviatedLog bool,\n\texcludedGlobs []string,\n\tisBare bool,\n\tadditionalArgs ...string,\n) (chan *Diff, error) {\n\targs := []string{\n\t\t\"-C\", source,\n\t\t\"log\",\n\t\t\"--patch\", // https://git-scm.com/docs/git-log#Documentation/git-log.txt---patch\n\t\t\"--full-history\",\n\t\t\"--date=iso-strict\",\n\t\t\"--pretty=fuller\", // https://git-scm.com/docs/git-log#_pretty_formats\n\t\t\"--notes\",         // https://git-scm.com/docs/git-log#Documentation/git-log.txt---notesltrefgt\n\t}\n\tif abbreviatedLog {\n\t\targs = append(args, \"--diff-filter=AM\")\n\t}\n\tif head != \"\" {\n\t\targs = append(args, head)\n\t} else {\n\t\targs = append(args, \"--all\")\n\t}\n\targs = append(args, additionalArgs...) // These need to come before --\n\tfor _, glob := range excludedGlobs {\n\t\targs = append(args, \"--\", \".\", \":(exclude)\"+glob)\n\t}\n\n\tcmd := exec.CommandContext(ctx, \"git\", args...)\n\tabsPath, err := filepath.Abs(source)\n\tif err == nil {\n\t\tif !isBare {\n\t\t\tcmd.Env = append(cmd.Env, \"GIT_DIR=\"+filepath.Join(absPath, \".git\"))\n\t\t} else {\n\t\t\tcmd.Env = append(cmd.Env,\n\t\t\t\t\"GIT_DIR=\"+absPath,\n\t\t\t)\n\t\t\t// We need those variables to handle incoming commits\n\t\t\t// while using trufflehog in pre-receive hooks\n\t\t\tif dir := os.Getenv(\"GIT_OBJECT_DIRECTORY\"); dir != \"\" {\n\t\t\t\tcmd.Env = append(cmd.Env, \"GIT_OBJECT_DIRECTORY=\"+dir)\n\t\t\t}\n\t\t\tif dir := os.Getenv(\"GIT_ALTERNATE_OBJECT_DIRECTORIES\"); dir != \"\" {\n\t\t\t\tcmd.Env = append(cmd.Env, \"GIT_ALTERNATE_OBJECT_DIRECTORIES=\"+dir)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn c.executeCommand(ctx, cmd, false, c.waitDelay)\n}\n\n// Staged parses the output of the `git diff` command for the `source` path.\nfunc (c *Parser) Staged(ctx context.Context, source string) (chan *Diff, error) {\n\t// Provide the --cached flag to diff to get the diff of the staged changes.\n\targs := []string{\"-C\", source, \"diff\", \"-p\", \"--cached\", \"--full-history\", \"--diff-filter=AM\", \"--date=iso-strict\"}\n\n\tcmd := exec.CommandContext(ctx, \"git\", args...)\n\n\tabsPath, err := filepath.Abs(source)\n\tif err == nil {\n\t\tcmd.Env = append(cmd.Env, \"GIT_DIR=\"+filepath.Join(absPath, \".git\"))\n\t}\n\n\treturn c.executeCommand(ctx, cmd, true, c.waitDelay)\n}\n\n// executeCommand runs an exec.Cmd, reads stdout and stderr, and waits for the Cmd to complete.\n// waitDelay specifies how long to wait after context cancellation before forcefully killing the process.\nfunc (c *Parser) executeCommand(ctx context.Context, cmd *exec.Cmd, isStaged bool, waitDelay time.Duration) (chan *Diff, error) {\n\tdiffChan := make(chan *Diff, 64)\n\n\tstdOut, err := cmd.StdoutPipe()\n\tif err != nil {\n\t\treturn diffChan, err\n\t}\n\tstdErr, err := cmd.StderrPipe()\n\tif err != nil {\n\t\treturn diffChan, err\n\t}\n\n\t// Set WaitDelay to allow the command additional time to exit after context cancellation\n\tcmd.WaitDelay = waitDelay\n\n\terr = cmd.Start()\n\tif err != nil {\n\t\treturn diffChan, err\n\t}\n\n\tgo func() {\n\t\tscanner := bufio.NewScanner(stdErr)\n\t\tfor scanner.Scan() {\n\t\t\tctx.Logger().V(2).Info(scanner.Text())\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tif err := cmd.Wait(); err != nil {\n\t\t\t\tctx.Logger().V(2).Info(\"Error waiting for git command to complete.\", \"error\", err)\n\t\t\t}\n\t\t}()\n\t\tc.FromReader(ctx, stdOut, diffChan, isStaged)\n\t\tif err := stdOut.Close(); err != nil {\n\t\t\tctx.Logger().V(2).Info(\"Error closing git stdout pipe.\", \"error\", err)\n\t\t}\n\t}()\n\n\treturn diffChan, nil\n}\n\nfunc (c *Parser) FromReader(ctx context.Context, stdOut io.Reader, diffChan chan *Diff, isStaged bool) {\n\toutReader := bufio.NewReader(stdOut)\n\tvar (\n\t\tcurrentCommit *Commit\n\n\t\ttotalLogSize int\n\t)\n\tvar latestState = Initial\n\n\tdiff := func(c *Commit, opts ...diffOption) *Diff {\n\t\topts = append(opts, withCustomContentWriter(bufferwriter.New()))\n\t\treturn newDiff(c, opts...)\n\t}\n\tif c.useCustomContentWriter {\n\t\tdiff = func(c *Commit, opts ...diffOption) *Diff {\n\t\t\topts = append(opts, withCustomContentWriter(bufferedfilewriter.New()))\n\t\t\treturn newDiff(c, opts...)\n\t\t}\n\t}\n\tcurrentDiff := diff(currentCommit)\n\n\tdefer common.RecoverWithExit(ctx)\n\tdefer close(diffChan)\n\tfor {\n\t\tif common.IsDone(ctx) {\n\t\t\tbreak\n\t\t}\n\n\t\tline, err := outReader.ReadBytes([]byte(\"\\n\")[0])\n\t\tif err != nil && len(line) == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tswitch {\n\t\tcase isCommitLine(isStaged, latestState, line):\n\t\t\tlatestState = CommitLine\n\n\t\t\t// If there is a currentDiff, add it to currentCommit.\n\t\t\tif currentDiff.Len() > 0 || currentDiff.IsBinary {\n\t\t\t\tif err := currentDiff.finalize(); err != nil {\n\t\t\t\t\tctx.Logger().Error(\n\t\t\t\t\t\terr,\n\t\t\t\t\t\t\"failed to finalize diff\",\n\t\t\t\t\t\t\"commit\", currentCommit.Hash,\n\t\t\t\t\t\t\"diff\", currentDiff.PathB,\n\t\t\t\t\t\t\"size\", currentDiff.Len(),\n\t\t\t\t\t\t\"latest_state\", latestState.String(),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tdiffChan <- currentDiff\n\t\t\t\tcurrentCommit.Size += currentDiff.Len()\n\t\t\t\tcurrentCommit.hasDiffs = true\n\t\t\t}\n\t\t\t// If there is a currentCommit, send it to the channel.\n\t\t\tif currentCommit != nil {\n\t\t\t\ttotalLogSize += currentCommit.Size\n\t\t\t\tif !currentCommit.hasDiffs {\n\t\t\t\t\t// Initialize an empty Diff instance associated with the given commit.\n\t\t\t\t\t// Since this diff represents \"no changes\", we only need to set the commit.\n\t\t\t\t\t// This is required to ensure commits that have no diffs are still processed.\n\t\t\t\t\tdiffChan <- &Diff{Commit: currentCommit}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Create a new currentDiff and currentCommit\n\t\t\tcurrentCommit = &Commit{Message: strings.Builder{}}\n\t\t\tcurrentDiff = diff(currentCommit)\n\t\t\t// Check that the commit line contains a hash and set it.\n\t\t\tif len(line) >= 47 {\n\t\t\t\tcurrentCommit.Hash = string(line[7:47])\n\t\t\t}\n\t\tcase isMergeLine(isStaged, latestState, line):\n\t\t\tlatestState = MergeLine\n\t\tcase isAuthorLine(isStaged, latestState, line):\n\t\t\tlatestState = AuthorLine\n\t\t\tcurrentCommit.Author = strings.TrimSpace(string(line[8:]))\n\t\tcase isAuthorDateLine(isStaged, latestState, line):\n\t\t\tlatestState = AuthorDateLine\n\n\t\t\tdate, err := time.Parse(c.dateFormat, strings.TrimSpace(string(line[12:])))\n\t\t\tif err != nil {\n\t\t\t\tctx.Logger().Error(err, \"failed to parse commit date\", \"commit\", currentCommit.Hash, \"latestState\", latestState.String())\n\t\t\t\tlatestState = ParseFailure\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcurrentCommit.Date = date\n\t\tcase isCommitterLine(isStaged, latestState, line):\n\t\t\tlatestState = CommitterLine\n\t\t\tcurrentCommit.Committer = strings.TrimSpace(string(line[8:]))\n\t\tcase isCommitterDateLine(isStaged, latestState, line):\n\t\t\tlatestState = CommitterDateLine\n\t\t\t// NoOp\n\t\tcase isMessageStartLine(isStaged, latestState, line):\n\t\t\tlatestState = MessageStartLine\n\t\t\t// NoOp\n\t\tcase isMessageLine(isStaged, latestState, line):\n\t\t\tlatestState = MessageLine\n\t\t\tcurrentCommit.Message.Write(line[4:]) // Messages are indented by 4 spaces.\n\n\t\tcase isMessageEndLine(isStaged, latestState, line):\n\t\t\tlatestState = MessageEndLine\n\t\t\t// NoOp\n\t\tcase isNotesStartLine(isStaged, latestState, line):\n\t\t\tlatestState = NotesStartLine\n\n\t\t\tcurrentCommit.Message.WriteString(\"\\n\")\n\t\t\tcurrentCommit.Message.Write(line)\n\t\tcase isNotesLine(isStaged, latestState, line):\n\t\t\tlatestState = NotesLine\n\t\t\tcurrentCommit.Message.Write(line[4:]) // Notes are indented by 4 spaces.\n\t\tcase isNotesEndLine(isStaged, latestState, line):\n\t\t\tlatestState = NotesEndLine\n\t\t\t// NoOp\n\t\tcase isDiffLine(isStaged, latestState, line):\n\t\t\tlatestState = DiffLine\n\n\t\t\tif currentDiff.Len() > 0 || currentDiff.IsBinary {\n\t\t\t\tif err := currentDiff.finalize(); err != nil {\n\t\t\t\t\tctx.Logger().Error(err,\n\t\t\t\t\t\t\"failed to finalize diff\",\n\t\t\t\t\t\t\"commit\", currentCommit.Hash,\n\t\t\t\t\t\t\"diff\", currentDiff.PathB,\n\t\t\t\t\t\t\"size\", currentDiff.Len(),\n\t\t\t\t\t\t\"latest_state\", latestState.String(),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tdiffChan <- currentDiff\n\t\t\t\tcurrentCommit.hasDiffs = true\n\t\t\t}\n\n\t\t\t// This should never be nil, but check in case the stdin stream is messed up.\n\t\t\tif currentCommit == nil {\n\t\t\t\tcurrentCommit = &Commit{}\n\t\t\t}\n\t\t\tcurrentDiff = diff(currentCommit)\n\t\tcase isModeLine(latestState, line):\n\t\t\tlatestState = ModeLine\n\t\t\t// NoOp\n\t\tcase isIndexLine(latestState, line):\n\t\t\tlatestState = IndexLine\n\t\t\t// NoOp\n\t\tcase isBinaryLine(latestState, line):\n\t\t\tlatestState = BinaryFileLine\n\n\t\t\tpath, ok := pathFromBinaryLine(line)\n\t\t\tif !ok {\n\t\t\t\terr = fmt.Errorf(`expected line to match 'Binary files a/fileA and b/fileB differ', got \"%s\"`, line)\n\t\t\t\tctx.Logger().Error(err, \"Failed to parse BinaryFileLine\")\n\t\t\t\tlatestState = ParseFailure\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Don't do anything if the file is deleted. (pathA has file path, pathB is /dev/null)\n\t\t\tif path != \"\" {\n\t\t\t\tcurrentDiff.PathB = path\n\t\t\t\tcurrentDiff.IsBinary = true\n\t\t\t}\n\t\tcase isFromFileLine(latestState, line):\n\t\t\tlatestState = FromFileLine\n\t\t\t// NoOp\n\t\tcase isToFileLine(latestState, line):\n\t\t\tlatestState = ToFileLine\n\n\t\t\tpath, ok := pathFromToFileLine(line)\n\t\t\tif !ok {\n\t\t\t\terr = fmt.Errorf(`expected line to match format '+++ b/path/to/file.go', got '%s'`, line)\n\t\t\t\tctx.Logger().Error(err, \"Failed to parse ToFileLine\")\n\t\t\t\tlatestState = ParseFailure\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcurrentDiff.PathB = path\n\t\tcase isHunkLineNumberLine(latestState, line):\n\t\t\tlatestState = HunkLineNumberLine\n\n\t\t\tif currentDiff.Len() > 0 || currentDiff.IsBinary {\n\t\t\t\tif err := currentDiff.finalize(); err != nil {\n\t\t\t\t\tctx.Logger().Error(\n\t\t\t\t\t\terr,\n\t\t\t\t\t\t\"failed to finalize diff\",\n\t\t\t\t\t\t\"commit\", currentCommit.Hash,\n\t\t\t\t\t\t\"diff\", currentDiff.PathB,\n\t\t\t\t\t\t\"size\", currentDiff.Len(),\n\t\t\t\t\t\t\"latest_state\", latestState.String(),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tdiffChan <- currentDiff\n\t\t\t}\n\t\t\tcurrentDiff = diff(currentCommit, withPathB(currentDiff.PathB))\n\n\t\t\twords := bytes.Split(line, []byte(\" \"))\n\t\t\tif len(words) >= 3 {\n\t\t\t\tstartSlice := bytes.Split(words[2], []byte(\",\"))\n\t\t\t\tlineStart, err := strconv.Atoi(string(startSlice[0]))\n\t\t\t\tif err == nil {\n\t\t\t\t\tcurrentDiff.LineStart = lineStart\n\t\t\t\t}\n\t\t\t}\n\t\tcase isHunkContextLine(latestState, line):\n\t\t\tif latestState != HunkContentLine {\n\t\t\t\tlatestState = HunkContentLine\n\t\t\t}\n\t\t\t// TODO: Why do we care about this? It creates empty lines in the diff. If there are no plusLines, it's just newlines.\n\t\t\tif err := currentDiff.write([]byte(\"\\n\")); err != nil {\n\t\t\t\tctx.Logger().Error(err, \"failed to write to diff\")\n\t\t\t}\n\t\tcase isHunkPlusLine(latestState, line):\n\t\t\tif latestState != HunkContentLine {\n\t\t\t\tlatestState = HunkContentLine\n\t\t\t}\n\n\t\t\tif err := currentDiff.write(line[1:]); err != nil {\n\t\t\t\tctx.Logger().Error(err, \"failed to write to diff\")\n\t\t\t}\n\t\t\t// NoOp. We only care about additions.\n\t\tcase isHunkMinusLine(latestState, line),\n\t\t\tisHunkNewlineWarningLine(latestState, line),\n\t\t\tisHunkEmptyLine(latestState, line):\n\t\t\tif latestState != HunkContentLine {\n\t\t\t\tlatestState = HunkContentLine\n\t\t\t}\n\t\t\t// NoOp\n\t\tcase isCommitSeparatorLine(latestState, line):\n\t\t\t// NoOp\n\t\tdefault:\n\t\t\t// Skip ahead until we find the next diff or commit.\n\t\t\tif latestState == ParseFailure {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Here be dragons...\n\t\t\t// Build an informative error message.\n\t\t\terr := fmt.Errorf(`invalid line \"%s\" after state \"%s\"`, line, latestState)\n\t\t\tvar logger logr.Logger\n\t\t\tif currentCommit != nil && currentCommit.Hash != \"\" {\n\t\t\t\tlogger = ctx.Logger().WithValues(\"commit\", currentCommit.Hash)\n\t\t\t} else {\n\t\t\t\tlogger = ctx.Logger()\n\t\t\t}\n\t\t\tlogger.Error(err, \"failed to parse Git input. Recovering at the latest commit or diff...\")\n\n\t\t\tlatestState = ParseFailure\n\t\t}\n\n\t\tif int64(currentDiff.Len()) > c.maxDiffSize {\n\t\t\tctx.Logger().V(2).Info(fmt.Sprintf(\n\t\t\t\t\"Diff for %s exceeded MaxDiffSize(%d)\", currentDiff.PathB, c.maxDiffSize,\n\t\t\t))\n\t\t\tbreak\n\t\t}\n\t}\n\tcleanupParse(ctx, currentCommit, currentDiff, diffChan, &totalLogSize)\n\n\tctx.Logger().V(2).Info(\"finished parsing git log.\", \"total_log_size\", totalLogSize)\n}\n\nfunc isMergeLine(isStaged bool, latestState ParseState, line []byte) bool {\n\tif isStaged || latestState != CommitLine {\n\t\treturn false\n\t}\n\tif len(line) > 6 && bytes.Equal(line[:6], []byte(\"Merge:\")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// commit 7a95bbf0199e280a0e42dbb1d1a3f56cdd0f6e05\nfunc isCommitLine(isStaged bool, latestState ParseState, line []byte) bool {\n\tif isStaged || !(latestState == Initial ||\n\t\tlatestState == MessageStartLine ||\n\t\tlatestState == MessageEndLine ||\n\t\tlatestState == ModeLine ||\n\t\tlatestState == IndexLine ||\n\t\tlatestState == BinaryFileLine ||\n\t\tlatestState == ToFileLine ||\n\t\tlatestState == HunkContentLine ||\n\t\tlatestState == ParseFailure) {\n\t\treturn false\n\t}\n\n\tif len(line) > 7 && bytes.Equal(line[:7], []byte(\"commit \")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Author: Bill Rich <bill.rich@trufflesec.com>\nfunc isAuthorLine(isStaged bool, latestState ParseState, line []byte) bool {\n\tif isStaged || !(latestState == CommitLine || latestState == MergeLine) {\n\t\treturn false\n\t}\n\tif len(line) > 8 && bytes.Equal(line[:7], []byte(\"Author:\")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// AuthorDate: 2021-08-10T15:20:40+01:00\nfunc isAuthorDateLine(isStaged bool, latestState ParseState, line []byte) bool {\n\tif isStaged || latestState != AuthorLine {\n\t\treturn false\n\t}\n\tif len(line) > 10 && bytes.Equal(line[:11], []byte(\"AuthorDate:\")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Commit: Bill Rich <bill.rich@trufflesec.com>\nfunc isCommitterLine(isStaged bool, latestState ParseState, line []byte) bool {\n\tif isStaged || latestState != AuthorDateLine {\n\t\treturn false\n\t}\n\tif len(line) > 8 && bytes.Equal(line[:7], []byte(\"Commit:\")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// CommitDate: Wed Apr 17 19:59:28 2024 -0400\nfunc isCommitterDateLine(isStaged bool, latestState ParseState, line []byte) bool {\n\tif isStaged || latestState != CommitterLine {\n\t\treturn false\n\t}\n\tif len(line) > 10 && bytes.Equal(line[:11], []byte(\"CommitDate:\")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Line directly after CommitterDate with only a newline.\nfunc isMessageStartLine(isStaged bool, latestState ParseState, line []byte) bool {\n\tif isStaged || latestState != CommitterDateLine {\n\t\treturn false\n\t}\n\t// TODO: Improve the implementation of this and isMessageEndLine\n\tif len(strings.TrimRight(string(line[:]), \"\\r\\n\")) == 0 {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Line that starts with 4 spaces\nfunc isMessageLine(isStaged bool, latestState ParseState, line []byte) bool {\n\tif isStaged || !(latestState == MessageStartLine || latestState == MessageLine) {\n\t\treturn false\n\t}\n\tif len(line) > 4 && bytes.Equal(line[:4], []byte(\"    \")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Line directly after MessageLine with only a newline.\nfunc isMessageEndLine(isStaged bool, latestState ParseState, line []byte) bool {\n\tif isStaged || latestState != MessageLine {\n\t\treturn false\n\t}\n\tif len(strings.TrimRight(string(line[:]), \"\\r\\n\")) == 0 {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// `Notes:` or `Notes (context):`\n// See https://tylercipriani.com/blog/2022/11/19/git-notes-gits-coolest-most-unloved-feature/\nfunc isNotesStartLine(isStaged bool, latestState ParseState, line []byte) bool {\n\tif isStaged || latestState != MessageEndLine {\n\t\treturn false\n\t}\n\tif len(line) > 5 && bytes.Equal(line[:5], []byte(\"Notes\")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Line after NotesStartLine that starts with 4 spaces\nfunc isNotesLine(isStaged bool, latestState ParseState, line []byte) bool {\n\tif isStaged || !(latestState == NotesStartLine || latestState == NotesLine) {\n\t\treturn false\n\t}\n\tif len(line) > 4 && bytes.Equal(line[:4], []byte(\"    \")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Line directly after NotesLine with only a newline.\nfunc isNotesEndLine(isStaged bool, latestState ParseState, line []byte) bool {\n\tif isStaged || latestState != NotesLine {\n\t\treturn false\n\t}\n\tif len(strings.TrimRight(string(line[:]), \"\\r\\n\")) == 0 {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// diff --git a/internal/addrs/move_endpoint_module.go b/internal/addrs/move_endpoint_module.go\nfunc isDiffLine(isStaged bool, latestState ParseState, line []byte) bool {\n\tif !(latestState == MessageStartLine || // Empty commit messages can go from MessageStart->Diff\n\t\tlatestState == MessageEndLine ||\n\t\tlatestState == NotesEndLine ||\n\t\tlatestState == BinaryFileLine ||\n\t\tlatestState == ModeLine ||\n\t\tlatestState == IndexLine ||\n\t\tlatestState == HunkContentLine ||\n\t\tlatestState == ParseFailure) {\n\t\tif !(isStaged && latestState == Initial) {\n\t\t\treturn false\n\t\t}\n\t}\n\tif len(line) > 11 && bytes.Equal(line[:11], []byte(\"diff --git \")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// old mode 100644\n// new mode 100755\n// new file mode 100644\n// similarity index 100%\n// rename from old.txt\n// rename to new.txt\n// deleted file mode 100644\nfunc isModeLine(latestState ParseState, line []byte) bool {\n\tif !(latestState == DiffLine || latestState == ModeLine) {\n\t\treturn false\n\t}\n\t// This could probably be better written.\n\tif (len(line) > 17 && bytes.Equal(line[:17], []byte(\"deleted file mode\"))) ||\n\t\t(len(line) > 16 && bytes.Equal(line[:16], []byte(\"similarity index\"))) ||\n\t\t(len(line) > 13 && bytes.Equal(line[:13], []byte(\"new file mode\"))) ||\n\t\t(len(line) > 11 && bytes.Equal(line[:11], []byte(\"rename from\"))) ||\n\t\t(len(line) > 9 && bytes.Equal(line[:9], []byte(\"rename to\"))) ||\n\t\t(len(line) > 8 && bytes.Equal(line[:8], []byte(\"old mode\"))) ||\n\t\t(len(line) > 8 && bytes.Equal(line[:8], []byte(\"new mode\"))) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// index 1ed6fbee1..aea1e643a 100644\n// index 00000000..e69de29b\nfunc isIndexLine(latestState ParseState, line []byte) bool {\n\tif !(latestState == DiffLine || latestState == ModeLine) {\n\t\treturn false\n\t}\n\tif len(line) > 6 && bytes.Equal(line[:6], []byte(\"index \")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Binary files /dev/null and b/plugin.sig differ\nfunc isBinaryLine(latestState ParseState, line []byte) bool {\n\tif latestState != IndexLine {\n\t\treturn false\n\t}\n\tif len(line) > 7 && bytes.Equal(line[:6], []byte(\"Binary\")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Get the b/ file path. Ignoring the edge case of files having `and /b` in the name for simplicity.\nfunc pathFromBinaryLine(line []byte) (string, bool) {\n\tif bytes.Contains(line, []byte(\"and /dev/null\")) {\n\t\treturn \"\", true\n\t}\n\n\tvar (\n\t\tpath string\n\t\terr  error\n\t)\n\tif _, after, ok := bytes.Cut(line, []byte(\" and b/\")); ok {\n\t\t// drop the \" differ\\n\"\n\t\tpath = string(after[:len(after)-8])\n\t} else if _, after, ok = bytes.Cut(line, []byte(` and \"b/`)); ok {\n\t\t// Edge case where the path is quoted.\n\t\t// https://github.com/trufflesecurity/trufflehog/issues/2384\n\n\t\t// Drop the `\" differ\\n` and handle escaped characters in the path.\n\t\t// e.g., \"\\342\\200\\224\" instead of \"—\".\n\t\t// See https://github.com/trufflesecurity/trufflehog/issues/2418\n\t\tpath, err = strconv.Unquote(`\"` + string(after[:len(after)-9]) + `\"`)\n\t\tif err != nil {\n\t\t\treturn \"\", false\n\t\t}\n\t} else {\n\t\t// Unknown format.\n\t\treturn \"\", false\n\t}\n\n\treturn path, true\n}\n\n// --- a/internal/addrs/move_endpoint_module.go\n// --- /dev/null\nfunc isFromFileLine(latestState ParseState, line []byte) bool {\n\tif !(latestState == IndexLine || latestState == ModeLine) {\n\t\treturn false\n\t}\n\tif len(line) >= 6 && bytes.Equal(line[:4], []byte(\"--- \")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// +++ b/internal/addrs/move_endpoint_module.go\nfunc isToFileLine(latestState ParseState, line []byte) bool {\n\tif latestState != FromFileLine {\n\t\treturn false\n\t}\n\tif len(line) >= 6 && bytes.Equal(line[:4], []byte(\"+++ \")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Get the b/ file path.\nfunc pathFromToFileLine(line []byte) (string, bool) {\n\t// Normalize paths, as they can end in `\\n`, `\\t\\n`, etc.\n\t// See https://github.com/trufflesecurity/trufflehog/issues/1060\n\tline = bytes.TrimSpace(line)\n\n\t// File was deleted.\n\tif bytes.Equal(line, []byte(\"+++ /dev/null\")) {\n\t\treturn \"\", true\n\t}\n\n\tvar (\n\t\tpath string\n\t\terr  error\n\t)\n\tif _, after, ok := bytes.Cut(line, []byte(\"+++ b/\")); ok {\n\t\tpath = string(after)\n\t} else if _, after, ok = bytes.Cut(line, []byte(`+++ \"b/`)); ok {\n\t\t// Edge case where the path is quoted.\n\t\t// e.g., `+++ \"b/C++/1 \\320\\243\\321\\200\\320\\276\\320\\272/B.c\"`\n\n\t\t// Drop the trailing `\"` and handle escaped characters in the path\n\t\t// e.g., \"\\342\\200\\224\" instead of \"—\".\n\t\t// See https://github.com/trufflesecurity/trufflehog/issues/2418\n\t\tpath, err = strconv.Unquote(`\"` + string(after[:len(after)-1]) + `\"`)\n\t\tif err != nil {\n\t\t\treturn \"\", false\n\t\t}\n\t} else {\n\t\t// Unknown format.\n\t\treturn \"\", false\n\t}\n\n\treturn path, true\n}\n\n// @@ -298 +298 @@ func maxRetryErrorHandler(resp *http.Response, err error, numTries int)\nfunc isHunkLineNumberLine(latestState ParseState, line []byte) bool {\n\tif !(latestState == ToFileLine || latestState == HunkContentLine) {\n\t\treturn false\n\t}\n\tif len(line) >= 8 && bytes.Equal(line[:2], []byte(\"@@\")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// fmt.Println(\"ok\")\n// (There's a space before `fmt` that gets removed by the formatter.)\nfunc isHunkContextLine(latestState ParseState, line []byte) bool {\n\tif !(latestState == HunkLineNumberLine || latestState == HunkContentLine) {\n\t\treturn false\n\t}\n\tif len(line) >= 1 && bytes.Equal(line[:1], []byte(\" \")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// +fmt.Println(\"ok\")\nfunc isHunkPlusLine(latestState ParseState, line []byte) bool {\n\tif !(latestState == HunkLineNumberLine || latestState == HunkContentLine) {\n\t\treturn false\n\t}\n\tif len(line) >= 1 && bytes.Equal(line[:1], []byte(\"+\")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// -fmt.Println(\"ok\")\nfunc isHunkMinusLine(latestState ParseState, line []byte) bool {\n\tif !(latestState == HunkLineNumberLine || latestState == HunkContentLine) {\n\t\treturn false\n\t}\n\tif len(line) >= 1 && bytes.Equal(line[:1], []byte(\"-\")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// \\ No newline at end of file\nfunc isHunkNewlineWarningLine(latestState ParseState, line []byte) bool {\n\tif latestState != HunkContentLine {\n\t\treturn false\n\t}\n\tif len(line) >= 27 && bytes.Equal(line[:27], []byte(\"\\\\ No newline at end of file\")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Newline after hunk, or an empty line, e.g.\n// +}\n//\n// commit 00920984e3435057f09cee5468850f7546dfa637 (tag: v3.42.0)\nfunc isHunkEmptyLine(latestState ParseState, line []byte) bool {\n\tif !(latestState == HunkLineNumberLine || latestState == HunkContentLine) {\n\t\treturn false\n\t}\n\t// TODO: Can this also be `\\n\\r`?\n\tif len(line) == 1 && bytes.Equal(line[:1], []byte(\"\\n\")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc isCommitSeparatorLine(latestState ParseState, line []byte) bool {\n\tif (latestState == ModeLine || latestState == IndexLine || latestState == BinaryFileLine || latestState == ToFileLine) &&\n\t\tlen(line) == 1 && bytes.Equal(line[:1], []byte(\"\\n\")) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc cleanupParse(ctx context.Context, currentCommit *Commit, currentDiff *Diff, diffChan chan *Diff, totalLogSize *int) {\n\tif err := currentDiff.finalize(); err != nil {\n\t\tctx.Logger().Error(err, \"failed to finalize diff\")\n\t\treturn\n\t}\n\n\t// Ignore empty or binary diffs (this condition may be redundant).\n\tif currentDiff != nil && (currentDiff.Len() > 0 || currentDiff.IsBinary) {\n\t\tcurrentDiff.Commit = currentCommit\n\t\tdiffChan <- currentDiff\n\t}\n\tif currentCommit != nil {\n\t\tif totalLogSize != nil {\n\t\t\t*totalLogSize += currentCommit.Size\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/gitparse/gitparse_test.go",
    "content": "package gitparse\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/process\"\n\tbufferwriter \"github.com/trufflesecurity/trufflehog/v3/pkg/writers/buffer_writer\"\n\tbufferedfilewriter \"github.com/trufflesecurity/trufflehog/v3/pkg/writers/buffered_file_writer\"\n)\n\ntype testCaseLine struct {\n\tlatestState ParseState\n\tline        []byte\n}\n\nfunc TestLineChecksWithStaged(t *testing.T) {\n\ttype testCase struct {\n\t\tpasses   []testCaseLine\n\t\tfails    []testCaseLine\n\t\tfunction func(bool, ParseState, []byte) bool\n\t}\n\n\ttests := map[string]testCase{\n\t\t\"commitLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tInitial,\n\t\t\t\t\t[]byte(\"commit 15c6105be1a18eeed1247478340dca69d02196ed\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tModeLine,\n\t\t\t\t\t[]byte(\"commit 7bd16429f1f708746dabf970e54b05d2b4734997 (HEAD -> master)\\n\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndexLine,\n\t\t\t\t\t[]byte(\"commit 9d60549cea17c830df3f99398993e8f6fd154468\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tMessageStartLine,\n\t\t\t\t\t[]byte(\"commit 4727ffb7ad6dc5130bf4b4dd166e00705abdd018\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tMessageEndLine,\n\t\t\t\t\t[]byte(\"commit 2a057632d7f5fa3d1c77b9aa037263211c0e0290\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHunkContentLine,\n\t\t\t\t\t[]byte(\"commit b38857edb46bd0e2c86db53615ff469aa7b7966b (HEAD -> feat/git-diff-parse, origin/main, origin/HEAD, main)\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tBinaryFileLine,\n\t\t\t\t\t[]byte(\"commit fb76eaf17b2b923bcc3e59314cf3605bce9a8bcd (tag: v3.40.0)\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tInitial,\n\t\t\t\t\t[]byte(`fatal: ambiguous argument 'branch_2..branch_1': unknown revision or path not in the working tree.\n\t\t\t\t\tUse '--' to separate paths from revisions, like this:\n\t\t\t\t\t'git <command> [<revision>...] -- [<file>...]'`),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tCommitLine,\n\t\t\t\t\t[]byte(\"commit 15c6105be1a18eeed1247478340dca69d02196ed\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isCommitLine,\n\t\t},\n\t\t\"mergeLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tCommitLine,\n\t\t\t\t\t[]byte(\"Merge: f21a95535a2 ed08d10bcf5\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tCommitterDateLine,\n\t\t\t\t\t[]byte(\"    Merge pull request #34511 from cescoffier/duplicated-context-doc\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tCommitLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isMergeLine,\n\t\t},\n\t\t\"authorLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tCommitLine,\n\t\t\t\t\t[]byte(\"Author: Zachary Rice <zachary.rice@trufflesec.com>\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tCommitLine,\n\t\t\t\t\t[]byte(\"Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tCommitLine,\n\t\t\t\t\t[]byte(\"Date:   Tue Jun 20 13:55:31 2023 -0500\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tAuthorLine,\n\t\t\t\t\t[]byte(\"Author: Bill Rich <bill.rich@trufflesec.com>\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isAuthorLine,\n\t\t},\n\t\t\"authorDateLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tAuthorLine,\n\t\t\t\t\t[]byte(\"AuthorDate: 2022-01-18T16:59:18-08:00\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tAuthorDateLine,\n\t\t\t\t\t[]byte(\"\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tAuthorLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isAuthorDateLine,\n\t\t},\n\t\t\"committerLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tAuthorDateLine,\n\t\t\t\t\t[]byte(\"Commit: Zachary Rice <zachary.rice@trufflesec.com>\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tAuthorDateLine,\n\t\t\t\t\t[]byte(\"Commit: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tCommitLine,\n\t\t\t\t\t[]byte(\"Date:   Tue Jun 20 13:55:31 2023 -0500\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tAuthorLine,\n\t\t\t\t\t[]byte(\"Author: Bill Rich <bill.rich@trufflesec.com>\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isCommitterLine,\n\t\t},\n\t\t\"committerDateLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tCommitterLine,\n\t\t\t\t\t[]byte(\"CommitDate: 2022-01-18T16:59:18-08:00\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tCommitterDateLine,\n\t\t\t\t\t[]byte(\"\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tCommitterLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isCommitterDateLine,\n\t\t},\n\t\t\"messageStartLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tCommitterDateLine,\n\t\t\t\t\t[]byte(\"\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tAuthorLine,\n\t\t\t\t\t[]byte(\"Date:   Tue Jun 20 13:21:19 2023 -0700\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tCommitterDateLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isMessageStartLine,\n\t\t},\n\t\t\"messageLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tMessageStartLine,\n\t\t\t\t\t[]byte(\"    Initial docs and release automation (#5)\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tMessageLine,\n\t\t\t\t\t[]byte(\"    Bump github.com/googleapis/gax-go/v2 from 2.10.0 to 2.11.0 (#1406)\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tAuthorLine,\n\t\t\t\t\t[]byte(\"Date:   Tue Jun 20 13:21:19 2023 -0700\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tCommitterDateLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isMessageLine,\n\t\t},\n\t\t\"messageEndLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tMessageLine,\n\t\t\t\t\t[]byte(\"\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tMessageStartLine,\n\t\t\t\t\t[]byte(\"    Initial commit\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tMessageLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isMessageEndLine,\n\t\t},\n\t\t\"notesStartLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tMessageEndLine,\n\t\t\t\t\t[]byte(\"Notes:\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tMessageEndLine,\n\t\t\t\t\t[]byte(\"Notes (review):\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tMessageStartLine,\n\t\t\t\t\t[]byte(\"\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tMessageEndLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isNotesStartLine,\n\t\t},\n\t\t\"notesLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tNotesStartLine,\n\t\t\t\t\t[]byte(\"    Submitted-by: Random J Developer <random@developer.example.org>\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tMessageEndLine,\n\t\t\t\t\t[]byte(\"\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tMessageEndLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isNotesLine,\n\t\t},\n\t\t\"notesEndLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tNotesLine,\n\t\t\t\t\t[]byte(\"\\n\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tMessageEndLine,\n\t\t\t\t\t[]byte(\"\\n\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tNotesLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isNotesEndLine,\n\t\t},\n\t\t\"diffLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tMessageEndLine,\n\t\t\t\t\t[]byte(\"diff --git a/pkg/sources/source_unit.go b/pkg/sources/source_unit.go\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tMessageEndLine,\n\t\t\t\t\t[]byte(\"diff --git a/ Lunch and Learn - HCDiag.pdf b/ Lunch and Learn - HCDiag.pdf\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tNotesEndLine,\n\t\t\t\t\t[]byte(\"diff --git \\\"a/one.txt\\\" \\\"b/one.txt\\\"\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tBinaryFileLine,\n\t\t\t\t\t[]byte(\"diff --git a/pkg/decoders/utf16_test.go b/pkg/decoders/utf16_test.go\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHunkContentLine,\n\t\t\t\t\t[]byte(\"diff --git a/pkg/decoders/utf8.go b/pkg/decoders/utf8.go\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tModeLine,\n\t\t\t\t\t[]byte(\"diff --git a/pkg/decoders/utf8.go b/pkg/decoders/utf8.go\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tCommitterDateLine,\n\t\t\t\t\t[]byte(\"    Make trace error message so newlines aren't escaped (#1396)\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tMessageLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tNotesLine,\n\t\t\t\t\t[]byte(\"diff --git a/pkg/decoders/utf8.go b/pkg/decoders/utf8.go\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isDiffLine,\n\t\t},\n\t}\n\n\tfor name, test := range tests {\n\t\tfor _, pass := range test.passes {\n\t\t\tif !test.function(false, pass.latestState, pass.line) {\n\t\t\t\tt.Errorf(\"%s: Parser did not recognize correct line. (%s)\", name, string(pass.line))\n\t\t\t}\n\t\t}\n\t\tfor _, fail := range test.fails {\n\t\t\tif test.function(false, fail.latestState, fail.line) {\n\t\t\t\tt.Errorf(\"%s: Parser did not recognize incorrect line. (%s)\", name, string(fail.line))\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestLineChecksNoStaged(t *testing.T) {\n\ttype testCase struct {\n\t\tpasses   []testCaseLine\n\t\tfails    []testCaseLine\n\t\tfunction func(ParseState, []byte) bool\n\t}\n\n\ttests := map[string]testCase{\n\t\t\"modeLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tDiffLine,\n\t\t\t\t\t[]byte(\"old mode 100644\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tModeLine,\n\t\t\t\t\t[]byte(\"new mode 100755\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDiffLine,\n\t\t\t\t\t[]byte(\"new file mode 100644\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDiffLine,\n\t\t\t\t\t[]byte(\"similarity index 100%\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tModeLine,\n\t\t\t\t\t[]byte(\"rename from old.txt\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tModeLine,\n\t\t\t\t\t[]byte(\"rename to new.txt\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDiffLine,\n\t\t\t\t\t[]byte(\"deleted file mode 100644\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tMessageLine,\n\t\t\t\t\t[]byte(\"diff --git a/pkg/common/recover.go b/pkg/common/recover.go\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDiffLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isModeLine,\n\t\t},\n\t\t\"indexLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tDiffLine,\n\t\t\t\t\t[]byte(\"index 0a7a5b4..7682212 100644\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tModeLine,\n\t\t\t\t\t[]byte(\"index 1ed6fbee1..aea1e643a 100644\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tModeLine,\n\t\t\t\t\t[]byte(\"index 00000000..e69de29b\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tMessageLine,\n\t\t\t\t\t[]byte(\"diff --git a/pkg/sources/gitlab/gitlab.go b/pkg/sources/gitlab/gitlab.go\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tDiffLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isIndexLine,\n\t\t},\n\t\t\"binaryLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tIndexLine,\n\t\t\t\t\t[]byte(\"Binary files /dev/null and b/plugin.sig differ\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndexLine,\n\t\t\t\t\t[]byte(\"Binary files /dev/null and b/ Lunch and Learn - HCDiag.pdf differ\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tDiffLine,\n\t\t\t\t\t[]byte(\"index eb54cf4f..00000000\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndexLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isBinaryLine,\n\t\t},\n\t\t\"fromFileLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tIndexLine,\n\t\t\t\t\t[]byte(\"--- a/internal/addrs/move_endpoint_module.go\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndexLine,\n\t\t\t\t\t[]byte(\"--- /dev/null\"),\n\t\t\t\t},\n\t\t\t\t// New file (https://github.com/trufflesecurity/trufflehog/issues/2109)\n\t\t\t\t// diff --git a/libs/Unfit-1.0 b/libs/Unfit-1.0\n\t\t\t\t// new file mode 160000\n\t\t\t\t{\n\t\t\t\t\tModeLine,\n\t\t\t\t\t[]byte(\"--- /dev/null\"),\n\t\t\t\t},\n\t\t\t\t// Uncommon but valid prefixes. Will these ever show up?\n\t\t\t\t// https://stackoverflow.com/a/2530012\n\t\t\t\t// https://git-scm.com/docs/git-config#Documentation/git-config.txt-diffmnemonicPrefix\n\t\t\t\t{\n\t\t\t\t\tIndexLine,\n\t\t\t\t\t[]byte(\"--- i/pkg/sources/filesystem/filesystem.go\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndexLine,\n\t\t\t\t\t[]byte(\"--- w/pkg/sources/gcs/gcs.go\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndexLine,\n\t\t\t\t\t[]byte(\"--- c/pkg/sources/git/git.go\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndexLine,\n\t\t\t\t\t[]byte(\"--- o/pkg/sources/github/github.go\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tModeLine,\n\t\t\t\t\t[]byte(\"index 00000000..05370a3c\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tIndexLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isFromFileLine,\n\t\t},\n\t\t\"toFileLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tFromFileLine,\n\t\t\t\t\t[]byte(\"+++ a/internal/addrs/move_endpoint_module.go\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tFromFileLine,\n\t\t\t\t\t[]byte(\"+++ /dev/null\"),\n\t\t\t\t},\n\t\t\t\t// Uncommon but valid prefixes. Will these ever show up?\n\t\t\t\t// https://stackoverflow.com/a/2530012\n\t\t\t\t// https://git-scm.com/docs/git-config#Documentation/git-config.txt-diffmnemonicPrefix\n\t\t\t\t{\n\t\t\t\t\tFromFileLine,\n\t\t\t\t\t[]byte(\"+++ i/pkg/sources/filesystem/filesystem.go\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tFromFileLine,\n\t\t\t\t\t[]byte(\"+++ w/pkg/sources/gcs/gcs.go\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tFromFileLine,\n\t\t\t\t\t[]byte(\"+++ c/pkg/sources/git/git.go\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tFromFileLine,\n\t\t\t\t\t[]byte(\"+++ o/pkg/sources/github/github.go\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tIndexLine,\n\t\t\t\t\t[]byte(\"--- a/pkg/detectors/shortcut/shortcut_test.go\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tFromFileLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHunkContentLine,\n\t\t\t\t\t[]byte(\"+++ The application will interface with REDACTED and REDACTED (REDACTED team)\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isToFileLine,\n\t\t},\n\t\t\"lineNumberLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tToFileLine,\n\t\t\t\t\t[]byte(\"@@ -298 +298 @@ func maxRetryErrorHandler(resp *http.Response, err error, numTries int)\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHunkContentLine,\n\t\t\t\t\t[]byte(\"@@ -121 +121 @@ require (\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tFromFileLine,\n\t\t\t\t\t[]byte(\"+++ b/Dockerfile\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tToFileLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isHunkLineNumberLine,\n\t\t},\n\t\t\"hunkContextLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tHunkLineNumberLine,\n\t\t\t\t\t[]byte(\" fmt.Println\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHunkContentLine,\n\t\t\t\t\t[]byte(\"        ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tToFileLine,\n\t\t\t\t\t[]byte(\"@@ -176 +176 @@ require (\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHunkLineNumberLine,\n\t\t\t\t\t[]byte(\"+import (\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isHunkContextLine,\n\t\t},\n\t\t\"hunkPlusLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tHunkLineNumberLine,\n\t\t\t\t\t[]byte(\"+       github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHunkContentLine,\n\t\t\t\t\t[]byte(\"+cloud.google.com/go/storage v1.31.0/go.mod h1:81ams1PrhW16L4kF7qg+4mTq7SRs5HsbDTM0bWvrwJ0=\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tToFileLine,\n\t\t\t\t\t[]byte(\"@@ -176 +176 @@ require (\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHunkLineNumberLine,\n\t\t\t\t\t[]byte(\"-import (\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHunkLineNumberLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isHunkPlusLine,\n\t\t},\n\t\t\"hunkMinusLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tHunkLineNumberLine,\n\t\t\t\t\t[]byte(\"-fmt.Println\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHunkContentLine,\n\t\t\t\t\t[]byte(`-       return []string{\"sql\", \"database\", \"Data Source\"}`),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tToFileLine,\n\t\t\t\t\t[]byte(\"@@ -176 +176 @@ require (\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHunkLineNumberLine,\n\t\t\t\t\t[]byte(\"+import (\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHunkLineNumberLine,\n\t\t\t\t\t[]byte(\"notcorrect\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isHunkMinusLine,\n\t\t},\n\t\t\"hunkNewlineWarningLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tHunkContentLine,\n\t\t\t\t\t[]byte(\"\\\\ No newline at end of file\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tToFileLine,\n\t\t\t\t\t[]byte(\"@@ -176 +176 @@ require (\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHunkContentLine,\n\t\t\t\t\t[]byte(\" \\\\ No newline at end of file is the current error\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isHunkNewlineWarningLine,\n\t\t},\n\t\t\"hunkEmptyLine\": {\n\t\t\tpasses: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tHunkContentLine,\n\t\t\t\t\t[]byte(\"\\n\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfails: []testCaseLine{\n\t\t\t\t{\n\t\t\t\t\tHunkLineNumberLine,\n\t\t\t\t\t[]byte(`               return \"\", errors.WrapPrefix(err, \"repo remote cannot be sanitized as URI\", 0)`),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tHunkContentLine,\n\t\t\t\t\t[]byte(\" \\n\"),\n\t\t\t\t},\n\t\t\t},\n\t\t\tfunction: isHunkEmptyLine,\n\t\t},\n\t}\n\n\tfor name, test := range tests {\n\t\tfor _, pass := range test.passes {\n\t\t\tif !test.function(pass.latestState, pass.line) {\n\t\t\t\tt.Errorf(\"%s: Parser did not recognize correct line. (%s)\", name, string(pass.line))\n\t\t\t}\n\t\t}\n\t\tfor _, fail := range test.fails {\n\t\t\tif test.function(fail.latestState, fail.line) {\n\t\t\t\tt.Errorf(\"%s: Parser did not recognize incorrect line. (%s)\", name, string(fail.line))\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestBinaryPathParse(t *testing.T) {\n\tcases := map[string]string{\n\t\t\"Binary files a/trufflehog_3.42.0_linux_arm64.tar.gz and /dev/null differ\\n\":                                                                                         \"\",\n\t\t\"Binary files /dev/null and b/plugin.sig differ\\n\":                                                                                                                   \"plugin.sig\",\n\t\t\"Binary files /dev/null and b/ Lunch and Learn - HCDiag.pdf differ\\n\":                                                                                                \" Lunch and Learn - HCDiag.pdf\",\n\t\t\"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\",\n\t\t\"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\",\n\t}\n\n\tfor name, expected := range cases {\n\t\tfilename, ok := pathFromBinaryLine([]byte(name))\n\t\tif !ok {\n\t\t\tt.Errorf(\"Failed to get path: %s\", name)\n\t\t}\n\t\tif filename != expected {\n\t\t\tt.Errorf(\"Expected: %s, Got: %s\", expected, filename)\n\t\t}\n\t}\n}\n\nfunc TestToFileLinePathParse(t *testing.T) {\n\tcases := map[string]string{\n\t\t\"+++ /dev/null\\n\":      \"\",\n\t\t\"+++ b/embeds.xml\\t\\n\": \"embeds.xml\",\n\t\t\"+++ \\\"b/C++/1 \\\\320\\\\243\\\\321\\\\200\\\\320\\\\276\\\\320\\\\272/B.c\\\"\\t\\n\": \"C++/1 Урок/B.c\",\n\t}\n\n\tfor name, expected := range cases {\n\t\tfilename, ok := pathFromToFileLine([]byte(name))\n\t\tif !ok {\n\t\t\tt.Errorf(\"Failed to get path: %s\", name)\n\t\t}\n\t\tif filename != expected {\n\t\t\tt.Errorf(\"Expected: %s, Got: %s\", expected, filename)\n\t\t}\n\t}\n}\n\n// `asserts` on `Diff`'s _structure_, giving better test output than just comparing two\n// diffs.\nfunc assertDiffEqualToExpected(t *testing.T, expected *Diff, actual *Diff) {\n\n\t// Use `cmp.Diff` to automatically compare all the exported fields.  This allows this test to grow automatically if\n\t// new exported fields are added to these structs.  However, the most important field we want to test is unexpected\n\t// (i.e. contentWriter) which is where the actual content of the diff is stored.  We break this out next.\n\topts := []cmp.Option{\n\t\tcmpopts.IgnoreUnexported(Diff{}, Commit{}, strings.Builder{}),\n\t\tcmpopts.IgnoreFields(Commit{}, \"Size\"),\n\t}\n\tif diff := cmp.Diff(expected, actual, opts...); diff != \"\" {\n\t\tt.Errorf(\"%s\", diff)\n\t}\n\n\t// Here's where we compare the actual content of the diff. We break that out and test it separately so that we can\n\t// keep this test relatively easy to understand and still get meaningful test output on the diff itself\n\n\t// If the test author hasn't specified a contentWriter, then we don't want to explode, but we _do_\n\t// want to confirm that the actual diff _also_ is nil there\n\tif expected.contentWriter == nil {\n\t\tassert.Nil(t, actual.contentWriter)\n\t}\n\t// Check that the content of the diff itself is as expected for non-binary diffs\n\tif expected.contentWriter != nil && !actual.IsBinary {\n\t\tassert.Equal(t, expected.contentWriter.Len(), actual.contentWriter.Len())\n\t\texpectedDiffStr, err := expected.contentWriter.String()\n\t\trequire.NoError(t, err)\n\t\tactualDiffStr, err := actual.contentWriter.String()\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, expectedDiffStr, actualDiffStr)\n\t}\n\n\t// TODO - Add test coverage for binary diffs (if it isn't already elsewhere)\n}\n\nfunc TestCommitParsing(t *testing.T) {\n\t// Feels bad to skip tests forever and then just forget about them.  Skip for a while.\n\tif time.Now().Before(time.Date(2025, time.July, 1, 0, 0, 0, 0, time.UTC)) {\n\t\tt.Skip(\"This is failing intermittently.  Skipping for now\")\n\t}\n\texpected := expectedDiffs()\n\n\tbeforeProcesses := process.GetGitProcessList()\n\n\tr := bytes.NewReader([]byte(commitLog))\n\tdiffChan := make(chan *Diff)\n\tparser := NewParser()\n\tgo func() {\n\t\tparser.FromReader(context.Background(), r, diffChan, false)\n\t}()\n\ti := 0\n\tfor diff := range diffChan {\n\t\tif len(expected) <= i {\n\t\t\tt.Errorf(\"Missing expected case for commit: %+v\", diff)\n\t\t\tbreak\n\t\t}\n\n\t\tassertDiffEqualToExpected(t, expected[i], diff)\n\t\ti++\n\t}\n\n\tafterProcesses := process.GetGitProcessList()\n\tzombies := process.DetectGitZombies(beforeProcesses, afterProcesses)\n\n\tif len(zombies) > 0 {\n\t\tt.Errorf(\"Detected %d zombie git processes: %v\", len(zombies), zombies)\n\t}\n}\n\nfunc newBufferedFileWriterWithContent(content []byte) *bufferedfilewriter.BufferedFileWriter {\n\tb := bufferedfilewriter.New()\n\t_, err := b.Write(content) // Using Write method to add content\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn b\n}\n\nfunc newBufferWithContent(content []byte) *bufferwriter.BufferWriter {\n\tb := bufferwriter.New()\n\t_, _ = b.Write(content) // Using Write method to add content\n\treturn b\n}\n\nfunc TestStagedDiffParsing(t *testing.T) {\n\texpected := []*Diff{\n\t\t{\n\t\t\tPathB:     \"aws\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"[default]\\naws_access_key_id = AKIAXYZDQCEN4B6JSJQI\\naws_secret_access_key = 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\\noutput = json\\nregion = us-east-2\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"aws2\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: 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\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java\",\n\t\t\tLineStart: 3,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: 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\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:    \"trufflehog_3.42.0_linux_arm64.tar.gz\",\n\t\t\tIsBinary: true,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent(nil),\n\t\t},\n\t\t{\n\t\t\tPathB:     \"tzu\",\n\t\t\tLineStart: 11,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"\\n\\n\\n\\nSource: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"lao\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: 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\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"tzu\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: 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\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t}\n\n\tr := bytes.NewReader([]byte(stagedDiffs))\n\tdiffChan := make(chan *Diff)\n\tparser := NewParser()\n\tgo func() {\n\t\tparser.FromReader(context.Background(), r, diffChan, true)\n\t}()\n\ti := 0\n\tfor diff := range diffChan {\n\t\tif len(expected) <= i {\n\t\t\tt.Errorf(\"Missing expected case for commit: %+v\", diff)\n\t\t\tbreak\n\t\t}\n\n\t\tassertDiffEqualToExpected(t, expected[i], diff)\n\t\ti++\n\t}\n}\n\nfunc TestStagedDiffParsingBufferedFileWriter(t *testing.T) {\n\texpected := []*Diff{\n\t\t{\n\t\t\tPathB:     \"aws\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: newBufferedFileWriterWithContent([]byte(\"[default]\\naws_access_key_id = AKIAXYZDQCEN4B6JSJQI\\naws_secret_access_key = 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\\noutput = json\\nregion = us-east-2\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"aws2\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: 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\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java\",\n\t\t\tLineStart: 3,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: 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\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:    \"trufflehog_3.42.0_linux_arm64.tar.gz\",\n\t\t\tIsBinary: true,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: newBufferedFileWriterWithContent(nil),\n\t\t},\n\t\t{\n\t\t\tPathB:     \"tzu\",\n\t\t\tLineStart: 11,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: newBufferedFileWriterWithContent([]byte(\"\\n\\n\\n\\nSource: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"lao\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: 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\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"tzu\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: 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\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t}\n\n\tr := bytes.NewReader([]byte(stagedDiffs))\n\tdiffChan := make(chan *Diff)\n\tparser := NewParser()\n\tgo func() {\n\t\tparser.FromReader(context.Background(), r, diffChan, true)\n\t}()\n\ti := 0\n\tfor diff := range diffChan {\n\t\tif len(expected) <= i {\n\t\t\tt.Errorf(\"Missing expected case for commit: %+v\", diff)\n\t\t\tbreak\n\t\t}\n\n\t\tassertDiffEqualToExpected(t, expected[i], diff)\n\t\ti++\n\t}\n}\n\nfunc TestCommitParseFailureRecovery(t *testing.T) {\n\texpected := []*Diff{\n\t\t{\n\t\t\tPathB:     \".travis.yml\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"df393b4125c2aa217211b2429b8963d0cefcee27\",\n\t\t\t\tAuthor:    \"Stephen <stephen@egroat.com>\",\n\t\t\t\tCommitter: \"Stephen <stephen@egroat.com>\",\n\t\t\t\tDate:      newTime(\"2017-12-06T14:44:41-08:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Add travis testing\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: 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\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"3d76a97faad96e0f326afb61c232b9c2a18dca35\",\n\t\t\t\tAuthor:    \"John Smith <john.smith@example.com>\",\n\t\t\t\tCommitter: \"John Smith <john.smith@example.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-11T18:03:54-04:00\"),\n\t\t\t\tMessage:   strings.Builder{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tPathB:     \"tzu\",\n\t\t\tLineStart: 11,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"7bd16429f1f708746dabf970e54b05d2b4734997\",\n\t\t\t\tAuthor:    \"John Smith <john.smith@example.com>\",\n\t\t\t\tCommitter: \"John Smith <john.smith@example.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-11T18:10:49-04:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Change file\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"\\n\\n\\n\\nSource: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t}\n\n\tr := bytes.NewReader([]byte(recoverableCommits))\n\tdiffChan := make(chan *Diff)\n\tparser := NewParser()\n\tgo func() {\n\t\tparser.FromReader(context.Background(), r, diffChan, false)\n\t}()\n\ti := 0\n\tfor diff := range diffChan {\n\t\tassertDiffEqualToExpected(t, expected[i], diff)\n\t\ti++\n\t}\n}\n\nfunc TestCommitParseFailureRecoveryBufferedFileWriter(t *testing.T) {\n\texpected := []*Diff{\n\t\t{\n\t\t\tPathB:     \".travis.yml\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"df393b4125c2aa217211b2429b8963d0cefcee27\",\n\t\t\t\tAuthor:    \"Stephen <stephen@egroat.com>\",\n\t\t\t\tCommitter: \"Stephen <stephen@egroat.com>\",\n\t\t\t\tDate:      newTime(\"2017-12-06T14:44:41-08:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Add travis testing\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: 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\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"3d76a97faad96e0f326afb61c232b9c2a18dca35\",\n\t\t\t\tAuthor:    \"John Smith <john.smith@example.com>\",\n\t\t\t\tCommitter: \"John Smith <john.smith@example.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-11T18:03:54-04:00\"),\n\t\t\t\tMessage:   strings.Builder{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tPathB:     \"tzu\",\n\t\t\tLineStart: 11,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"7bd16429f1f708746dabf970e54b05d2b4734997\",\n\t\t\t\tAuthor:    \"John Smith <john.smith@example.com>\",\n\t\t\t\tCommitter: \"John Smith <john.smith@example.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-11T18:10:49-04:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Change file\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: newBufferedFileWriterWithContent([]byte(\"\\n\\n\\n\\nSource: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t}\n\n\tr := bytes.NewReader([]byte(recoverableCommits))\n\tdiffChan := make(chan *Diff)\n\tparser := NewParser()\n\tgo func() {\n\t\tparser.FromReader(context.Background(), r, diffChan, false)\n\t}()\n\ti := 0\n\tfor diff := range diffChan {\n\t\tif len(expected) <= i {\n\t\t\tt.Errorf(\"Missing expected case for commit: %+v\", diff)\n\t\t\tbreak\n\t\t}\n\n\t\tassertDiffEqualToExpected(t, expected[i], diff)\n\t\ti++\n\t}\n}\n\nconst recoverableCommits = `commit df393b4125c2aa217211b2429b8963d0cefcee27\nAuthor: Stephen <stephen@egroat.com>\nAuthorDate: 2017-12-06T14:44:41-08:00\nCommit: Stephen <stephen@egroat.com>\nCommitDate: 2017-12-06T14:44:41-08:00\n\n    Add travis testing\n\ndiff --git a/.gitignore b/.gitignore\nindex ede6aa39..bb85dcc3 100644\n--- a/.gitignore\n+++ b/.gitignore\n>>>>ERRANT LINE<<<<\n /build/\n /dist/\n /truffleHog.egg-info/\n-*/__pycache__/\n+**/__pycache__/\n+**/*.pyc\ndiff --git a/.travis.yml b/.travis.yml\nnew file mode 100644\nindex 00000000..33b6f107\n--- /dev/null\n+++ b/.travis.yml\n@@ -0,0 +1,13 @@\n+language: python\n+python:\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\"\ndiff --git a/requirements.txt b/requirements.txt\nnew file mode 100644\nindex 00000000..e69de29b\n\ncommit 3d76a97faad96e0f326afb61c232b9c2a18dca35 (HEAD -> master)\nAuthor: John Smith <john.smith@example.com>\nAuthorDate: 2023-07-11T18:03:54-04:00\nCommit: John Smith <john.smith@example.com>\nCommitDate: 2023-07-11T18:03:54-04:00\n\ndiff --git a/sample.txt b/sample.txt\nnew file mode 100644\nindex 0000000..af5626b\n--- /dev/null\n+++ b/sample.txt\n@@ -0,0 +1 @@\n!!!ERROR!!!\n\ncommit 7bd16429f1f708746dabf970e54b05d2b4734997 (HEAD -> master)\nAuthor: John Smith <john.smith@example.com>\nAuthorDate: 2023-07-11T18:10:49-04:00\nCommit: John Smith <john.smith@example.com>\nCommitDate: 2023-07-11T18:10:49-04:00\n\n    Change file\n\ndiff --git a/tzu b/tzu\nindex 5af88a8..c729cdb 100644\n--- a/tzu\n+++ b/tzu\n@@ -11,3 +11,5 @@ But after they are produced,\n They both may be called deep and profound.\n Deeper and more profound,\n The door of all subtleties!\n+\n+Source: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format\n`\n\nfunc TestDiffParseFailureRecovery(t *testing.T) {\n\texpected := []*Diff{\n\t\t{\n\t\t\tPathB:     \"aws\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"[default]\\naws_access_key_id = AKIAXYZDQCEN4B6JSJQI\\naws_secret_access_key = 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\\noutput = json\\nregion = us-east-2\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"tzu\",\n\t\t\tLineStart: 11,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"\\n\\n\\n\\nSource: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"tzu\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: 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\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t}\n\n\tr := bytes.NewReader([]byte(recoverableDiffs))\n\tdiffChan := make(chan *Diff)\n\tparser := NewParser()\n\tgo func() {\n\t\tparser.FromReader(context.Background(), r, diffChan, true)\n\t}()\n\ti := 0\n\tfor diff := range diffChan {\n\t\tif len(expected) <= i {\n\t\t\tt.Errorf(\"Missing expected case for commit: %+v\", diff)\n\t\t\tbreak\n\t\t}\n\n\t\tassertDiffEqualToExpected(t, expected[i], diff)\n\t\ti++\n\t}\n}\n\nfunc TestDiffParseFailureRecoveryBufferedFileWriter(t *testing.T) {\n\texpected := []*Diff{\n\t\t{\n\t\t\tPathB:     \"aws\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"[default]\\naws_access_key_id = AKIAXYZDQCEN4B6JSJQI\\naws_secret_access_key = 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\\noutput = json\\nregion = us-east-2\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"tzu\",\n\t\t\tLineStart: 11,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"\\n\\n\\n\\nSource: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"tzu\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:    \"\",\n\t\t\t\tAuthor:  \"\",\n\t\t\t\tDate:    newTime(\"0001-01-01 00:00:00 +0000 UTC\"),\n\t\t\t\tMessage: strings.Builder{},\n\t\t\t},\n\t\t\tcontentWriter: 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\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t}\n\n\tr := bytes.NewReader([]byte(recoverableDiffs))\n\tdiffChan := make(chan *Diff)\n\tparser := NewParser()\n\tgo func() {\n\t\tparser.FromReader(context.Background(), r, diffChan, true)\n\t}()\n\ti := 0\n\tfor diff := range diffChan {\n\t\tif len(expected) <= i {\n\t\t\tt.Errorf(\"Missing expected case for commit: %+v\", diff)\n\t\t\tbreak\n\t\t}\n\n\t\tassertDiffEqualToExpected(t, expected[i], diff)\n\t\ti++\n\t}\n}\n\nconst recoverableDiffs = `diff --git a/aws b/aws\nindex 2ee133b..12b4843 100644\n--- a/aws\n+++ b/aws\n@@ -1,7 +1,5 @@\n-blah blaj\n-\n-this is the secret: [Default]\n-Access key Id: AKIAILE3JG6KMS3HZGCA\n-Secret Access Key: 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\n-\n-okay thank you bye\n+[default]\n+aws_access_key_id = AKIAXYZDQCEN4B6JSJQI\n+aws_secret_access_key = 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\n+output = json\n+region = us-east-2\n\ndiff --git a/aws2 b/aws2\nindex 239b415..2ee133b 100644\n--- a/aws2\n+++ b/aws2\n!!!ERROR!!!\n blah blaj\n \n-this is the secret: AKIA2E0A8F3B244C9986\n+this is the secret: [Default]\n+Access key Id: AKIAILE3JG6KMS3HZGCA\n+Secret Access Key: 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\n \n-okay thank you bye\n\\ No newline at end of file\n+okay thank you bye\n\ndiff --git c/requirements.txt i/requirements.txt\nnew file mode 100644\nindex 00000000..e69de29b\n\ndiff --git a/tzu b/tzu\nindex 5af88a8..c729cdb 100644\n--- a/tzu\n+++ b/tzu\n@@ -11,3 +11,5 @@ But after they are produced,\n They both may be called deep and profound.\n Deeper and more profound,\n The door of all subtleties!\n+\n+Source: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format\n\ndiff --git a/lao b/lao\nnew file mode 100644\n!!!ERROR!!!!\n--- /dev/null\n+++ b/lao\n@@ -0,0 +1,11 @@\n+The Way that can be told of is not the eternal Way;\n+The name that can be named is not the eternal name.\n+The Nameless is the origin of Heaven and Earth;\n+The Named is the mother of all things.\n+Therefore let there always be non-being,\n+  so we may see their subtlety,\n+And let there always be being,\n+  so we may see their outcome.\n+The two are the same,\n+But after they are produced,\n+  they have different names.\ndiff --git a/tzu b/tzu\nnew file mode 100644\nindex 0000000..5af88a8\n--- /dev/null\n+++ b/tzu\n@@ -0,0 +1,13 @@\n+The Nameless is the origin of Heaven and Earth;\n+The named is the mother of all things.\n+\n+Therefore let there always be non-being,\n+  so we may see their subtlety,\n+And let there always be being,\n+  so we may see their outcome.\n+The two are the same,\n+But after they are produced,\n+  they have different names.\n+They both may be called deep and profound.\n+Deeper and more profound,\n+The door of all subtleties!\n`\n\nfunc TestMaxDiffSize(t *testing.T) {\n\tparser := NewParser(WithMaxDiffSize(1024 * 1024)) // Setting max diff size to 1MB for the test\n\tbuilder := strings.Builder{}\n\tbuilder.WriteString(singleCommitSingleDiff)\n\n\t// Generate a diff that is larger than the maxDiffSize.\n\tfor i := int64(0); i <= parser.maxDiffSize/1024+10; i++ {\n\t\tbuilder.WriteString(\"+\" + strings.Repeat(\"0\", 1024) + \"\\n\")\n\t}\n\tbigReader := strings.NewReader(builder.String())\n\n\tdiffChan := make(chan *Diff, 1)                                          // Buffer to prevent blocking\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) // Timeout to prevent long wait\n\tdefer cancel()\n\n\tgo func() {\n\t\tparser.FromReader(ctx, bigReader, diffChan, false)\n\t}()\n\n\tselect {\n\tcase diff := <-diffChan:\n\t\tif int64(diff.Len()) > parser.maxDiffSize+1024 {\n\t\t\tt.Errorf(\"diff did not match MaxDiffSize. Got: %d, expected (max): %d\", diff.Len(), parser.maxDiffSize+1024)\n\t\t}\n\tcase <-ctx.Done():\n\t\tt.Fatal(\"Test timed out\")\n\t}\n}\n\nfunc TestMaxCommitSize(t *testing.T) {\n\tparser := NewParser(WithMaxCommitSize(1))\n\tcommitText := bytes.Buffer{}\n\tcommitText.WriteString(singleCommitMultiDiff)\n\tdiffChan := make(chan *Diff)\n\tctx, cancel := context.WithTimeout(context.Background(), time.Duration(1)*time.Second)\n\tdefer cancel()\n\tgo func() {\n\t\tparser.FromReader(ctx, &commitText, diffChan, false)\n\t}()\n\tdiffCount := 0\n\tfor range diffChan {\n\t\tdiffCount++\n\t}\n\tif diffCount != 2 {\n\t\tt.Errorf(\"Commit count does not match. Got: %d, expected: %d\", diffCount, 2)\n\t}\n\n}\nfunc TestWaitDelay(t *testing.T) {\n\t// Test that WithWaitDelay sets the waitDelay correctly\n\tcustomDelay := 10 * time.Second\n\tparser := NewParser(WithWaitDelay(customDelay))\n\tif parser.waitDelay != customDelay {\n\t\tt.Errorf(\"waitDelay not set correctly. Got: %v, expected: %v\", parser.waitDelay, customDelay)\n\t}\n\n\t// Test that default waitDelay is used when not specified\n\tdefaultParser := NewParser()\n\tif defaultParser.waitDelay != defaultWaitDelay {\n\t\tt.Errorf(\"default waitDelay not set correctly. Got: %v, expected: %v\", defaultParser.waitDelay, defaultWaitDelay)\n\t}\n\n\t// Test that zero value is allowed (caller can set to 0 if they want no delay)\n\tzeroDelayParser := NewParser(WithWaitDelay(0))\n\tif zeroDelayParser.waitDelay != 0 {\n\t\tt.Errorf(\"zero waitDelay not set correctly. Got: %v, expected: 0\", zeroDelayParser.waitDelay)\n\t}\n}\n\nconst commitLog = `commit e50b135fd29e91b2fbb25923797f5ecffe59f359\nAuthor: lionzxy <nikita@kulikof.ru>\nAuthorDate: 2017-03-01T18:20:04+03:00\nCommit: lionzxy <nikita@kulikof.ru>\nCommitDate: 2017-03-01T18:20:04+03:00\n\n    Все работает, но он не принимает :(\n\ndiff --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\"\nindex 85bfb17..89b08b5 100644\n--- \"a/C++/1 \\320\\243\\321\\200\\320\\276\\320\\272/.idea/workspace.xml\"\n+++ \"b/C++/1 \\320\\243\\321\\200\\320\\276\\320\\272/.idea/workspace.xml\"\n@@ -29,8 +29,8 @@\n       <file leaf-file-name=\"CMakeLists.txt\" pinned=\"false\" current-in-tab=\"false\">\n         <entry file=\"file://$PROJECT_DIR$/CMakeLists.txt\">\n           <provider selected=\"true\" editor-type-id=\"text-editor\">\n-            <state relative-caret-position=\"0\">\n-              <caret line=\"0\" column=\"0\" lean-forward=\"false\" selection-start-line=\"0\" selection-start-column=\"0\" selection-end-line=\"0\" selection-end-column=\"0\" />\n+            <state relative-caret-position=\"72\">\n+              <caret line=\"4\" column=\"0\" lean-forward=\"false\" selection-start-line=\"4\" selection-start-column=\"0\" selection-end-line=\"4\" selection-end-column=\"0\" />\n               <folding />\n             </state>\n           </provider>\n\ncommit fd6e99e7a80199b76a694603be57c5ade1de18e7\nAuthor: Jaliborc <jaliborc@gmail.com>\nAuthorDate: 2011-04-25T16:28:06+01:00\nCommit: Jaliborc <jaliborc@gmail.com>\nCommitDate: 2011-04-25T16:28:06+01:00\n\n    Added Unusable coloring\n\nNotes:\n    Message-Id: <1264640755-22447-1-git-send-email-user@example.de>\n\ndiff --git a/components/item.lua b/components/item.lua\nindex fc74534..f8d7d50 100755\n--- a/components/item.lua\n+++ b/components/item.lua\n@@ -9,6 +9,7 @@ ItemSlot:Hide()\n Bagnon.ItemSlot = ItemSlot\n\n local ItemSearch = LibStub('LibItemSearch-1.0')\n+local Unfit = LibStub('Unfit-1.0')\n\n local function hasBlizzQuestHighlight()\n        return GetContainerItemQuestInfo and true or false\ndiff --git a/embeds.xml b/embeds.xml\nindex d3f4e7c..0c2df69 100755\n--- a/embeds.xml\n+++ b/embeds.xml\n@@ -6,6 +6,7 @@\n        <Include file=\"libs\\AceConsole-3.0\\AceConsole-3.0.xml\"/>\n        <Include file=\"libs\\AceLocale-3.0\\AceLocale-3.0.xml\"/>\n\n+       <Script file=\"libs\\Unfit-1.0\\Unfit-1.0.lua\"/>\n        <Script file=\"libs\\LibDataBroker-1.1.lua\"/>\n        <Script file=\"libs\\LibItemSearch-1.0\\LibItemSearch-1.0.lua\"/>\n </Ui>\n\\ No newline at end of file\ndiff --git a/libs/Unfit-1.0 b/libs/Unfit-1.0\nnew file mode 160000\n--- /dev/null\n+++ b/libs/Unfit-1.0\n@@ -0,0 +1 @@\n+Subproject commit 0000000000000000000000000000000000000000\n\ncommit 4727ffb7ad6dc5130bf4b4dd166e00705abdd018 (HEAD -> master)\nAuthor: John Smith <john.smith@example.com>\nAuthorDate: 2023-07-11T22:26:11-04:00\nCommit: John Smith <john.smith@example.com>\nCommitDate: 2023-07-11T22:26:11-04:00\n\ncommit c904e0f5cd9f30ae520c66bd5f70806219fe7ca2 (HEAD -> master)\nAuthor: John Smith <john.smith@example.com>\nAuthorDate: 2023-07-10T10:17:11-04:00\nCommit: John Smith <john.smith@example.com>\nCommitDate: 2023-07-10T10:17:11-04:00\n\n    Empty Commit\n\ncommit 3d76a97faad96e0f326afb61c232b9c2a18dca35 (HEAD -> master)\nAuthor: John Smith <john.smith@example.com>\nAuthorDate: 2023-07-11T18:03:54-04:00\nCommit: John Smith <john.smith@example.com>\nCommitDate: 2023-07-11T18:03:54-04:00\n\ndiff --git a/sample.txt b/sample.txt\nnew file mode 100644\nindex 0000000..af5626b\n--- /dev/null\n+++ b/sample.txt\n@@ -0,0 +1 @@\n+Hello, world!\n\ncommit df393b4125c2aa217211b2429b8963d0cefcee27\nAuthor: Stephen <stephen@egroat.com>\nAuthorDate: 2017-12-06T14:44:41-08:00\nCommit: Stephen <stephen@egroat.com>\nCommitDate: 2017-12-06T14:44:41-08:00\n\n    Add travis testing\n\ndiff --git a/.gitignore b/.gitignore\nindex ede6aa39..bb85dcc3 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -1,4 +1,5 @@\n /build/\n /dist/\n /truffleHog.egg-info/\n-*/__pycache__/\n+**/__pycache__/\n+**/*.pyc\ndiff --git a/.travis.yml b/.travis.yml\nnew file mode 100644\nindex 00000000..33b6f107\n--- /dev/null\n+++ b/.travis.yml\n@@ -0,0 +1,13 @@\n+language: python\n+python:\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\"\ndiff --git a/requirements.txt b/requirements.txt\nnew file mode 100644\nindex 00000000..e69de29b\n\ncommit 4218c39d99b5f30153f62471c1be1c1596f0a4d4\nAuthor: Dustin Decker <dustin@trufflesec.com>\nAuthorDate: 2022-01-13T12:02:24-08:00\nCommit: Dustin Decker <dustin@trufflesec.com>\nCommitDate: 2022-01-13T12:02:24-08:00\n\n    Initial CLI w/ partially implemented Git source and demo detector (#1)\n\ndiff --git a/Dockerfile b/Dockerfile\nnew file mode 100644\nindex 00000000..e69de29b\ndiff --git a/Makefile b/Makefile\nnew file mode 100644\nindex 00000000..453cf52c\n--- /dev/null\n+++ b/Makefile\n@@ -0,0 +1,32 @@\n+PROTOS_IMAGE=us-docker.pkg.dev/thog-artifacts/public/go-ci-1.17-1\n+\n+.PHONY: check\n+.PHONY: test\n+.PHONY: test-race\n+.PHONY: run\n+.PHONY: install\n+.PHONY: protos\n+.PHONY: protos-windows\n+.PHONY: vendor\n+\n+install:\n+       CGO_ENABLED=0 go install .\n+\n+check:\n+       go fmt $(shell go list ./... | grep -v /vendor/)\n+       go vet $(shell go list ./... | grep -v /vendor/)\n+\n+test:\n+       CGO_ENABLED=0 go test $(shell go list ./... | grep -v /vendor/)\n+\n+test-race:\n+       CGO_ENABLED=1 go test -race $(shell go list ./... | grep -v /vendor/)\n+\n+bench:\n+       CGO_ENABLED=0 go test $(shell go list ./pkg/secrets/... | grep -v /vendor/) -benchmem -run=xxx -bench .\n+\n+run:\n+       CGO_ENABLED=0 go run . git file://.\n+\n+protos:\n+       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\"\ndiff --git a/README.md b/README.md\nnew file mode 100644\nindex 00000000..e69de29b\ndiff --git a/go.mod b/go.mod\nnew file mode 100644\nindex 00000000..7fb2f73c\n--- /dev/null\n+++ b/go.mod\n\ncommit 934cf5d255fd8e28b33f5a6ba64276caf0b284bf (HEAD -> master)\nAuthor: John Smith <john.smith@example.com>\nAuthorDate: 2023-07-11T18:43:22-04:00\nCommit: John Smith <john.smith@example.com>\nCommitDate: 2023-07-11T18:43:22-04:00\n\n    Test toFile/plusLine parsing\n\ndiff --git a/plusLine.txt b/plusLine.txt\nnew file mode 100644\nindex 0000000..451be67\n--- /dev/null\n+++ b/plusLine.txt\n@@ -0,0 +1,3 @@\n+-- test\n+++ test\n+\n\ncommit 2a5d703b02b52d65c65ee9f7928f158b919ab741\nAuthor: Sergey Beryozkin <sberyozkin@gmail.com>\nAuthorDate: 2023-07-07T17:44:26+01:00\nCommit: Sergey Beryozkin <sberyozkin@gmail.com>\nCommitDate: 2023-07-07T17:44:26+01:00\n\n    Do not refresh OIDC session if the user is requesting logout\n\ndiff --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\nindex 096f5b4b092..4150096851c 100644\n--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutTokenCache.java\n+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutTokenCache.java\n@@ -45,6 +45,10 @@ public TokenVerificationResult removeTokenVerification(String token) {\n         return entry == null ? null : entry.result;\n     }\n\n+    public boolean containsTokenVerification(String token) {\n+        return cacheMap.containsKey(token);\n+    }\n+\n     public void clearCache() {\n         cacheMap.clear();\n         size.set(0);\ndiff --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\nindex a9a9699eecd..435cefdf313 100644\n--- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java\n+++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java\n@@ -1014,7 +1023,7 @@ private String buildUri(RoutingContext context, boolean forceHttps, String autho\n                 .toString();\n     }\n\n-    private boolean isLogout(RoutingContext context, TenantConfigContext configContext) {\n+    private boolean isRpInitiatedLogout(RoutingContext context, TenantConfigContext configContext) {\n         return isEqualToRequestPath(configContext.oidcConfig.logout.path, context, configContext);\n     }\n\n@@ -1205,4 +1214,38 @@ static String getCookieSuffix(OidcTenantConfig oidcConfig) {\n                 ? (tenantIdSuffix + UNDERSCORE + oidcConfig.authentication.cookieSuffix.get())\n                 : tenantIdSuffix;\n     }\n+\n+    private class LogoutCall implements Function<SecurityIdentity, Uni<?>> {\n+        RoutingContext context;\n+        TenantConfigContext configContext;\n+        String idToken;\n+\n+        LogoutCall(RoutingContext context, TenantConfigContext configContext, String idToken) {\n+            this.context = context;\n+            this.configContext = configContext;\n+            this.idToken = idToken;\n+        }\n+\n+        @Override\n+        public Uni<Void> apply(SecurityIdentity identity) {\n+            if (isRpInitiatedLogout(context, configContext)) {\n+                LOG.debug(\"Performing an RP initiated logout\");\n+                fireEvent(SecurityEvent.Type.OIDC_LOGOUT_RP_INITIATED, identity);\n+                return buildLogoutRedirectUriUni(context, configContext, idToken);\n+            }\n+            if (isBackChannelLogoutPendingAndValid(configContext, identity)\n+                    || isFrontChannelLogoutValid(context, configContext,\n+                            identity)) {\n+                return removeSessionCookie(context, configContext.oidcConfig)\n+                        .map(new Function<Void, Void>() {\n+                            @Override\n+                            public Void apply(Void t) {\n+                                throw new LogoutException();\n+                            }\n+                        });\n+\n+            }\n+            return VOID_UNI;\n+        }\n+    }\n }\ndiff --git a/integration-tests/oidc-wiremock/src/main/resources/application.properties b/integration-tests/oidc-wiremock/src/main/resources/application.properties\nindex bb6917d30bc..4e8bfb21b4c 100644\n--- a/integration-tests/oidc-wiremock/src/main/resources/application.properties\n+++ b/integration-tests/oidc-wiremock/src/main/resources/application.properties\n@@ -20,6 +20,8 @@ quarkus.oidc.code-flow.logout.extra-params.client_id=${quarkus.oidc.code-flow.cl\n quarkus.oidc.code-flow.credentials.secret=secret\n quarkus.oidc.code-flow.application-type=web-app\n quarkus.oidc.code-flow.token.audience=https://server.example.com\n+quarkus.oidc.code-flow.token.refresh-expired=true\n+quarkus.oidc.code-flow.token.refresh-token-time-skew=5M\n\n quarkus.oidc.code-flow-encrypted-id-token-jwk.auth-server-url=${keycloak.url}/realms/quarkus/\n quarkus.oidc.code-flow-encrypted-id-token-jwk.client-id=quarkus-web-app\ndiff --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\nindex 51e1b9a932d..472c2743bc4 100644\n--- a/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java\n+++ b/integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java\n@@ -6,7 +6,6 @@\n import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;\n import static com.github.tomakehurst.wiremock.client.WireMock.matching;\n import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;\n-import static com.github.tomakehurst.wiremock.client.WireMock.verify;\n import static org.junit.jupiter.api.Assertions.assertEquals;\n import static org.junit.jupiter.api.Assertions.assertNotNull;\n import static org.junit.jupiter.api.Assertions.assertNull;\n@@ -77,7 +76,7 @@ public void testCodeFlow() throws IOException {\n\n             assertEquals(\"alice, cache size: 0\", page.getBody().asNormalizedText());\n             assertNotNull(getSessionCookie(webClient, \"code-flow\"));\n-\n+            // Logout\n             page = webClient.getPage(\"http://localhost:8081/code-flow/logout\");\n             assertEquals(\"Welcome, clientId: quarkus-web-app\", page.getBody().asNormalizedText());\n             assertNull(getSessionCookie(webClient, \"code-flow\"));\n\ncommit 2a057632d7f5fa3d1c77b9aa037263211c0e0290\nAuthor: rjtmahinay <rjt.mahinay@gmail.com>\nAuthorDate: 2023-07-10T01:22:32+08:00\nCommit: rjtmahinay <rjt.mahinay@gmail.com>\nCommitDate: 2023-07-10T01:22:32+08:00\n\n    Add QuarkusApplication javadoc\n    \n    * Fix #34463\n\ndiff --git a/core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java b/core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java\nindex 350685123d5..87d2220eb98 100644\n--- a/core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java\n+++ b/core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java\n@@ -2,0 +3,4 @@\n+/**\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\ncommit bca2d17491015ea1522f34517223b5a366aea73c (HEAD -> master)\nAuthor: John Smith <john.smith@example.com>\nAuthorDate: 2023-07-11T18:12:21-04:00\nCommit: John Smith <john.smith@example.com>\nCommitDate: 2023-07-11T18:12:21-04:00\n\n    Delete binary file\n\ndiff --git a/trufflehog_3.42.0_linux_arm64.tar.gz b/trufflehog_3.42.0_linux_arm64.tar.gz\ndeleted file mode 100644\nindex 7682212..0000000\nBinary files a/trufflehog_3.42.0_linux_arm64.tar.gz and /dev/null differ\n\ncommit afc6dc5d47f28366638da877ecb6b819c69e659b\nAuthor: John Smith <john.smith@example.com>\nAuthorDate: 2023-07-10T12:21:33-04:00\nCommit: John Smith <john.smith@example.com>\nCommitDate: 2023-07-10T12:21:33-04:00\n\n    Change binary file\n\ndiff --git a/trufflehog_3.42.0_linux_arm64.tar.gz b/trufflehog_3.42.0_linux_arm64.tar.gz\nindex 0a7a5b4..7682212 100644\nBinary files a/trufflehog_3.42.0_linux_arm64.tar.gz and b/trufflehog_3.42.0_linux_arm64.tar.gz differ\n\ncommit 638595917417c5c8a956937b28c5127719023363\nAuthor: John Smith <john.smith@example.com>\nAuthorDate: 2023-07-10T12:20:35-04:00\nCommit: John Smith <john.smith@example.com>\nCommitDate: 2023-07-10T12:20:35-04:00\n\n    Add binary file\n\ndiff --git a/trufflehog_3.42.0_linux_arm64.tar.gz b/trufflehog_3.42.0_linux_arm64.tar.gz\nnew file mode 100644\nindex 0000000..0a7a5b4\nBinary files /dev/null and b/trufflehog_3.42.0_linux_arm64.tar.gz differ\n\ncommit ce0f5d1fe0272f180ccb660196f439c0c2f4ec8e (HEAD -> master)\nAuthor: John Smith <john.smith@example.com>\nAuthorDate: 2023-07-11T18:08:52-04:00\nCommit: John Smith <john.smith@example.com>\nCommitDate: 2023-07-11T18:08:52-04:00\n\n    Delete file\n\ndiff --git a/lao b/lao\ndeleted file mode 100644\nindex 635ef2c..0000000\n--- a/lao\n+++ /dev/null\n@@ -1,11 +0,0 @@\n-The Way that can be told of is not the eternal Way;\n-The name that can be named is not the eternal name.\n-The Nameless is the origin of Heaven and Earth;\n-The Named is the mother of all things.\n-Therefore let there always be non-being,\n-  so we may see their subtlety,\n-And let there always be being,\n-  so we may see their outcome.\n-The two are the same,\n-But after they are produced,\n-  they have different names.\n\ncommit d606a729383371558473b70a6a7b1ca264b0d205\nAuthor: John Smith <john.smith@example.com>\nAuthorDate: 2023-07-10T14:17:04-04:00\nCommit: John Smith <john.smith@example.com>\nCommitDate: 2023-07-10T14:17:04-04:00\n\n    Rename file\n\ndiff --git a/tzu b/tzu.txt\nsimilarity index 100%\nrename from tzu\nrename to tzu.txt\n\ncommit 7bd16429f1f708746dabf970e54b05d2b4734997 (HEAD -> master)\nAuthor: John Smith <john.smith@example.com>\nAuthorDate: 2023-07-11T18:10:49-04:00\nCommit: John Smith <john.smith@example.com>\nCommitDate: 2023-07-11T18:10:49-04:00\n\n    Change file\n\ndiff --git a/tzu b/tzu\nindex 5af88a8..c729cdb 100644\n--- a/tzu\n+++ b/tzu\n@@ -11,3 +11,5 @@ But after they are produced,\n They both may be called deep and profound.\n Deeper and more profound,\n The door of all subtleties!\n+\n+Source: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format\n\ncommit c7062674c17192caa284615ab2fa9778c6602164 (HEAD -> master)\nAuthor: John Smith <john.smith@example.com>\nAuthorDate: 2023-07-10T10:15:18-04:00\nCommit: John Smith <john.smith@example.com>\nCommitDate: 2023-07-10T10:15:18-04:00\n\n    Create files\n\ndiff --git a/lao b/lao\nnew file mode 100644\nindex 0000000..635ef2c\n--- /dev/null\n+++ b/lao\n@@ -0,0 +1,11 @@\n+The Way that can be told of is not the eternal Way;\n+The name that can be named is not the eternal name.\n+The Nameless is the origin of Heaven and Earth;\n+The Named is the mother of all things.\n+Therefore let there always be non-being,\n+  so we may see their subtlety,\n+And let there always be being,\n+  so we may see their outcome.\n+The two are the same,\n+But after they are produced,\n+  they have different names.\ndiff --git a/tzu b/tzu\nnew file mode 100644\nindex 0000000..5af88a8\n--- /dev/null\n+++ b/tzu\n@@ -0,0 +1,13 @@\n+The Nameless is the origin of Heaven and Earth;\n+The named is the mother of all things.\n+\n+Therefore let there always be non-being,\n+  so we may see their subtlety,\n+And let there always be being,\n+  so we may see their outcome.\n+The two are the same,\n+But after they are produced,\n+  they have different names.\n+They both may be called deep and profound.\n+Deeper and more profound,\n+The door of all subtleties!\n`\n\nfunc newTime(timestamp string) time.Time {\n\tdate, _ := time.Parse(defaultDateFormat, timestamp)\n\treturn date\n}\n\nfunc newStringBuilderValue(value string) strings.Builder {\n\tbuilder := strings.Builder{}\n\tbuilder.Write([]byte(value))\n\treturn builder\n}\n\n// This throws a nasty panic if it's a top-level var.\nfunc expectedDiffs() []*Diff {\n\treturn []*Diff{\n\t\t{\n\t\t\tPathB:     \"C++/1 \\320\\243\\321\\200\\320\\276\\320\\272/.idea/workspace.xml\",\n\t\t\tLineStart: 29,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"e50b135fd29e91b2fbb25923797f5ecffe59f359\",\n\t\t\t\tAuthor:    \"lionzxy <nikita@kulikof.ru>\",\n\t\t\t\tCommitter: \"lionzxy <nikita@kulikof.ru>\",\n\t\t\t\tDate:      newTime(\"2017-03-01T18:20:04+03:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Все работает, но он не принимает :(\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"\\n\\n\\n            <state relative-caret-position=\\\"72\\\">\\n              <caret line=\\\"4\\\" column=\\\"0\\\" lean-forward=\\\"false\\\" selection-start-line=\\\"4\\\" selection-start-column=\\\"0\\\" selection-end-line=\\\"4\\\" selection-end-column=\\\"0\\\" />\\n\\n\\n\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"components/item.lua\",\n\t\t\tLineStart: 9,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"fd6e99e7a80199b76a694603be57c5ade1de18e7\",\n\t\t\t\tAuthor:    \"Jaliborc <jaliborc@gmail.com>\",\n\t\t\t\tCommitter: \"Jaliborc <jaliborc@gmail.com>\",\n\t\t\t\tDate:      newTime(\"2011-04-25T16:28:06+01:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Added Unusable coloring\\n\\nNotes:\\nMessage-Id: <1264640755-22447-1-git-send-email-user@example.de>\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"\\n\\nlocal Unfit = LibStub('Unfit-1.0')\\n\\n\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:         \"embeds.xml\",\n\t\t\tLineStart:     6,\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"\\n\\n       <Script file=\\\"libs\\\\Unfit-1.0\\\\Unfit-1.0.lua\\\"/>\\n\\n\\n\\n\")),\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"fd6e99e7a80199b76a694603be57c5ade1de18e7\",\n\t\t\t\tAuthor:    \"Jaliborc <jaliborc@gmail.com>\",\n\t\t\t\tCommitter: \"Jaliborc <jaliborc@gmail.com>\",\n\t\t\t\tDate:      newTime(\"2011-04-25T16:28:06+01:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Added Unusable coloring\\n\\nNotes:\\nMessage-Id: <1264640755-22447-1-git-send-email-user@example.de>\\n\"),\n\t\t\t},\n\t\t\tIsBinary: false,\n\t\t},\n\t\t{\n\t\t\tPathB:         \"libs/Unfit-1.0\",\n\t\t\tLineStart:     1,\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"Subproject commit 0000000000000000000000000000000000000000\\n\")),\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"fd6e99e7a80199b76a694603be57c5ade1de18e7\",\n\t\t\t\tAuthor:    \"Jaliborc <jaliborc@gmail.com>\",\n\t\t\t\tCommitter: \"Jaliborc <jaliborc@gmail.com>\",\n\t\t\t\tDate:      newTime(\"2011-04-25T16:28:06+01:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Added Unusable coloring\\n\\nNotes:\\nMessage-Id: <1264640755-22447-1-git-send-email-user@example.de>\\n\"),\n\t\t\t},\n\t\t\tIsBinary: false,\n\t\t},\n\t\t{\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"4727ffb7ad6dc5130bf4b4dd166e00705abdd018\",\n\t\t\t\tAuthor:    \"John Smith <john.smith@example.com>\",\n\t\t\t\tCommitter: \"John Smith <john.smith@example.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-11T22:26:11-04:00\"),\n\t\t\t\tMessage:   strings.Builder{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"c904e0f5cd9f30ae520c66bd5f70806219fe7ca2\",\n\t\t\t\tAuthor:    \"John Smith <john.smith@example.com>\",\n\t\t\t\tCommitter: \"John Smith <john.smith@example.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-10T10:17:11-04:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Empty Commit\\n\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tPathB:         \"sample.txt\",\n\t\t\tLineStart:     1,\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"Hello, world!\\n\")),\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"3d76a97faad96e0f326afb61c232b9c2a18dca35\",\n\t\t\t\tAuthor:    \"John Smith <john.smith@example.com>\",\n\t\t\t\tCommitter: \"John Smith <john.smith@example.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-11T18:03:54-04:00\"),\n\t\t\t\tMessage:   strings.Builder{},\n\t\t\t},\n\t\t\tIsBinary: false,\n\t\t},\n\t\t{\n\t\t\tPathB:         \".gitignore\",\n\t\t\tLineStart:     1,\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"\\n\\n\\n**/__pycache__/\\n**/*.pyc\\n\")),\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"df393b4125c2aa217211b2429b8963d0cefcee27\",\n\t\t\t\tAuthor:    \"Stephen <stephen@egroat.com>\",\n\t\t\t\tCommitter: \"Stephen <stephen@egroat.com>\",\n\t\t\t\tDate:      newTime(\"2017-12-06T14:44:41-08:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Add travis testing\\n\"),\n\t\t\t},\n\t\t\tIsBinary: false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \".travis.yml\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"df393b4125c2aa217211b2429b8963d0cefcee27\",\n\t\t\t\tAuthor:    \"Stephen <stephen@egroat.com>\",\n\t\t\t\tCommitter: \"Stephen <stephen@egroat.com>\",\n\t\t\t\tDate:      newTime(\"2017-12-06T14:44:41-08:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Add travis testing\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: 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`)),\n\t\t\tIsBinary: false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"Makefile\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"4218c39d99b5f30153f62471c1be1c1596f0a4d4\",\n\t\t\t\tAuthor:    \"Dustin Decker <dustin@trufflesec.com>\",\n\t\t\t\tCommitter: \"Dustin Decker <dustin@trufflesec.com>\",\n\t\t\t\tDate:      newTime(\"2022-01-13T12:02:24-08:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Initial CLI w/ partially implemented Git source and demo detector (#1)\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(`PROTOS_IMAGE=us-docker.pkg.dev/thog-artifacts/public/go-ci-1.17-1\n\n.PHONY: check\n.PHONY: test\n.PHONY: test-race\n.PHONY: run\n.PHONY: install\n.PHONY: protos\n.PHONY: protos-windows\n.PHONY: vendor\n\ninstall:\n       CGO_ENABLED=0 go install .\n\ncheck:\n       go fmt $(shell go list ./... | grep -v /vendor/)\n       go vet $(shell go list ./... | grep -v /vendor/)\n\ntest:\n       CGO_ENABLED=0 go test $(shell go list ./... | grep -v /vendor/)\n\ntest-race:\n       CGO_ENABLED=1 go test -race $(shell go list ./... | grep -v /vendor/)\n\nbench:\n       CGO_ENABLED=0 go test $(shell go list ./pkg/secrets/... | grep -v /vendor/) -benchmem -run=xxx -bench .\n\nrun:\n       CGO_ENABLED=0 go run . git file://.\n\nprotos:\n       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\"\n`)),\n\t\t\tIsBinary: false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"plusLine.txt\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"934cf5d255fd8e28b33f5a6ba64276caf0b284bf\",\n\t\t\t\tAuthor:    \"John Smith <john.smith@example.com>\",\n\t\t\t\tCommitter: \"John Smith <john.smith@example.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-11T18:43:22-04:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Test toFile/plusLine parsing\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"-- test\\n++ test\\n\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BackChannelLogoutTokenCache.java\",\n\t\t\tLineStart: 45,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"2a5d703b02b52d65c65ee9f7928f158b919ab741\",\n\t\t\t\tAuthor:    \"Sergey Beryozkin <sberyozkin@gmail.com>\",\n\t\t\t\tCommitter: \"Sergey Beryozkin <sberyozkin@gmail.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-07T17:44:26+01:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Do not refresh OIDC session if the user is requesting logout\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"\\n\\n    public boolean containsTokenVerification(String token) {\\n        return cacheMap.containsKey(token);\\n    }\\n\\n\\n\\n\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java\",\n\t\t\tLineStart: 1023,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"2a5d703b02b52d65c65ee9f7928f158b919ab741\",\n\t\t\t\tAuthor:    \"Sergey Beryozkin <sberyozkin@gmail.com>\",\n\t\t\t\tCommitter: \"Sergey Beryozkin <sberyozkin@gmail.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-07T17:44:26+01:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Do not refresh OIDC session if the user is requesting logout\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"\\n\\n    private boolean isRpInitiatedLogout(RoutingContext context, TenantConfigContext configContext) {\\n\\n\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java\",\n\t\t\tLineStart: 1214,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"2a5d703b02b52d65c65ee9f7928f158b919ab741\",\n\t\t\t\tAuthor:    \"Sergey Beryozkin <sberyozkin@gmail.com>\",\n\t\t\t\tCommitter: \"Sergey Beryozkin <sberyozkin@gmail.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-07T17:44:26+01:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Do not refresh OIDC session if the user is requesting logout\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"\\n\\n\\n\\n    private class LogoutCall implements Function<SecurityIdentity, Uni<?>> {\\n        RoutingContext context;\\n        TenantConfigContext configContext;\\n        String idToken;\\n\\n        LogoutCall(RoutingContext context, TenantConfigContext configContext, String idToken) {\\n            this.context = context;\\n            this.configContext = configContext;\\n            this.idToken = idToken;\\n        }\\n\\n        @Override\\n        public Uni<Void> apply(SecurityIdentity identity) {\\n            if (isRpInitiatedLogout(context, configContext)) {\\n                LOG.debug(\\\"Performing an RP initiated logout\\\");\\n                fireEvent(SecurityEvent.Type.OIDC_LOGOUT_RP_INITIATED, identity);\\n                return buildLogoutRedirectUriUni(context, configContext, idToken);\\n            }\\n            if (isBackChannelLogoutPendingAndValid(configContext, identity)\\n                    || isFrontChannelLogoutValid(context, configContext,\\n                            identity)) {\\n                return removeSessionCookie(context, configContext.oidcConfig)\\n                        .map(new Function<Void, Void>() {\\n                            @Override\\n                            public Void apply(Void t) {\\n                                throw new LogoutException();\\n                            }\\n                        });\\n\\n            }\\n            return VOID_UNI;\\n        }\\n    }\\n\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"integration-tests/oidc-wiremock/src/main/resources/application.properties\",\n\t\t\tLineStart: 20,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"2a5d703b02b52d65c65ee9f7928f158b919ab741\",\n\t\t\t\tAuthor:    \"Sergey Beryozkin <sberyozkin@gmail.com>\",\n\t\t\t\tCommitter: \"Sergey Beryozkin <sberyozkin@gmail.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-07T17:44:26+01:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Do not refresh OIDC session if the user is requesting logout\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"\\n\\n\\nquarkus.oidc.code-flow.token.refresh-expired=true\\nquarkus.oidc.code-flow.token.refresh-token-time-skew=5M\\n\\n\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t// WTF, shouldn't this be filtered out?\n\t\t{\n\t\t\tPathB:     \"integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java\",\n\t\t\tLineStart: 6,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"2a5d703b02b52d65c65ee9f7928f158b919ab741\",\n\t\t\t\tAuthor:    \"Sergey Beryozkin <sberyozkin@gmail.com>\",\n\t\t\t\tCommitter: \"Sergey Beryozkin <sberyozkin@gmail.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-07T17:44:26+01:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Do not refresh OIDC session if the user is requesting logout\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"\\n\\n\\n\\n\\n\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java\",\n\t\t\tLineStart: 76,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"2a5d703b02b52d65c65ee9f7928f158b919ab741\",\n\t\t\t\tAuthor:    \"Sergey Beryozkin <sberyozkin@gmail.com>\",\n\t\t\t\tCommitter: \"Sergey Beryozkin <sberyozkin@gmail.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-07T17:44:26+01:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Do not refresh OIDC session if the user is requesting logout\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"\\n\\n            // Logout\\n\\n\\n\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java\",\n\t\t\tLineStart: 3,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"2a057632d7f5fa3d1c77b9aa037263211c0e0290\",\n\t\t\t\tAuthor:    \"rjtmahinay <rjt.mahinay@gmail.com>\",\n\t\t\t\tCommitter: \"rjtmahinay <rjt.mahinay@gmail.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-10T01:22:32+08:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Add QuarkusApplication javadoc\\n\\n* Fix #34463\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: 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\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"bca2d17491015ea1522f34517223b5a366aea73c\",\n\t\t\t\tAuthor:    \"John Smith <john.smith@example.com>\",\n\t\t\t\tCommitter: \"John Smith <john.smith@example.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-11T18:12:21-04:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Delete binary file\\n\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tPathB: \"trufflehog_3.42.0_linux_arm64.tar.gz\",\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"afc6dc5d47f28366638da877ecb6b819c69e659b\",\n\t\t\t\tAuthor:    \"John Smith <john.smith@example.com>\",\n\t\t\t\tCommitter: \"John Smith <john.smith@example.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-10T12:21:33-04:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Change binary file\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"\")),\n\t\t\tIsBinary:      true,\n\t\t},\n\t\t{\n\t\t\tPathB: \"trufflehog_3.42.0_linux_arm64.tar.gz\",\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"638595917417c5c8a956937b28c5127719023363\",\n\t\t\t\tAuthor:    \"John Smith <john.smith@example.com>\",\n\t\t\t\tCommitter: \"John Smith <john.smith@example.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-10T12:20:35-04:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Add binary file\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"\")),\n\t\t\tIsBinary:      true,\n\t\t},\n\t\t{\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"ce0f5d1fe0272f180ccb660196f439c0c2f4ec8e\",\n\t\t\t\tAuthor:    \"John Smith <john.smith@example.com>\",\n\t\t\t\tCommitter: \"John Smith <john.smith@example.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-11T18:08:52-04:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Delete file\\n\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"d606a729383371558473b70a6a7b1ca264b0d205\",\n\t\t\t\tAuthor:    \"John Smith <john.smith@example.com>\",\n\t\t\t\tCommitter: \"John Smith <john.smith@example.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-10T14:17:04-04:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Rename file\\n\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tPathB:     \"tzu\",\n\t\t\tLineStart: 11,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"7bd16429f1f708746dabf970e54b05d2b4734997\",\n\t\t\t\tAuthor:    \"John Smith <john.smith@example.com>\",\n\t\t\t\tCommitter: \"John Smith <john.smith@example.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-11T18:10:49-04:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Change file\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: newBufferWithContent([]byte(\"\\n\\n\\n\\nSource: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format\\n\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"lao\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"c7062674c17192caa284615ab2fa9778c6602164\",\n\t\t\t\tAuthor:    \"John Smith <john.smith@example.com>\",\n\t\t\t\tCommitter: \"John Smith <john.smith@example.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-10T10:15:18-04:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Create files\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: 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\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t\t{\n\t\t\tPathB:     \"tzu\",\n\t\t\tLineStart: 1,\n\t\t\tCommit: &Commit{\n\t\t\t\tHash:      \"c7062674c17192caa284615ab2fa9778c6602164\",\n\t\t\t\tAuthor:    \"John Smith <john.smith@example.com>\",\n\t\t\t\tCommitter: \"John Smith <john.smith@example.com>\",\n\t\t\t\tDate:      newTime(\"2023-07-10T10:15:18-04:00\"),\n\t\t\t\tMessage:   newStringBuilderValue(\"Create files\\n\"),\n\t\t\t},\n\t\t\tcontentWriter: 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\")),\n\t\t\tIsBinary:      false,\n\t\t},\n\t}\n}\n\nconst stagedDiffs = `diff --git a/aws b/aws\nindex 2ee133b..12b4843 100644\n--- a/aws\n+++ b/aws\n@@ -1,7 +1,5 @@\n-blah blaj\n-\n-this is the secret: [Default]\n-Access key Id: AKIAILE3JG6KMS3HZGCA\n-Secret Access Key: 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\n-\n-okay thank you bye\n+[default]\n+aws_access_key_id = AKIAXYZDQCEN4B6JSJQI\n+aws_secret_access_key = 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\n+output = json\n+region = us-east-2\n\ndiff --git a/aws2 b/aws2\nindex 239b415..2ee133b 100644\n--- a/aws2\n+++ b/aws2\n@@ -1,5 +1,7 @@\n blah blaj\n \n-this is the secret: AKIA2E0A8F3B244C9986\n+this is the secret: [Default]\n+Access key Id: AKIAILE3JG6KMS3HZGCA\n+Secret Access Key: 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\n \n-okay thank you bye\n\\ No newline at end of file\n+okay thank you bye\n\ndiff --git c/requirements.txt i/requirements.txt\nnew file mode 100644\nindex 00000000..e69de29b\n\ndiff --git a/core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java b/core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java\nindex 350685123d5..87d2220eb98 100644\n--- a/core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java\n+++ b/core/runtime/src/main/java/io/quarkus/runtime/QuarkusApplication.java\n@@ -2,0 +3,4 @@\n+/**\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\ndiff --git a/trufflehog_3.42.0_linux_arm64.tar.gz b/trufflehog_3.42.0_linux_arm64.tar.gz\nnew file mode 100644\nindex 0000000..0a7a5b4\nBinary files /dev/null and b/trufflehog_3.42.0_linux_arm64.tar.gz differ\n\ndiff --git a/lao b/lao\ndeleted file mode 100644\nindex 635ef2c..0000000\n--- a/lao\n+++ /dev/null\n@@ -1,11 +0,0 @@\n-The Way that can be told of is not the eternal Way;\n-The name that can be named is not the eternal name.\n-The Nameless is the origin of Heaven and Earth;\n-The Named is the mother of all things.\n-Therefore let there always be non-being,\n-  so we may see their subtlety,\n-And let there always be being,\n-  so we may see their outcome.\n-The two are the same,\n-But after they are produced,\n-  they have different names.\n\ndiff --git a/tzu b/tzu.txt\nsimilarity index 100%\nrename from tzu\nrename to tzu.txt\n\ndiff --git a/tzu b/tzu\nindex 5af88a8..c729cdb 100644\n--- a/tzu\n+++ b/tzu\n@@ -11,3 +11,5 @@ But after they are produced,\n They both may be called deep and profound.\n Deeper and more profound,\n The door of all subtleties!\n+\n+Source: https://www.gnu.org/software/diffutils/manual/diffutils.html#An-Example-of-Unified-Format\n\ndiff --git a/lao b/lao\nnew file mode 100644\nindex 0000000..635ef2c\n--- /dev/null\n+++ b/lao\n@@ -0,0 +1,11 @@\n+The Way that can be told of is not the eternal Way;\n+The name that can be named is not the eternal name.\n+The Nameless is the origin of Heaven and Earth;\n+The Named is the mother of all things.\n+Therefore let there always be non-being,\n+  so we may see their subtlety,\n+And let there always be being,\n+  so we may see their outcome.\n+The two are the same,\n+But after they are produced,\n+  they have different names.\ndiff --git a/tzu b/tzu\nnew file mode 100644\nindex 0000000..5af88a8\n--- /dev/null\n+++ b/tzu\n@@ -0,0 +1,13 @@\n+The Nameless is the origin of Heaven and Earth;\n+The named is the mother of all things.\n+\n+Therefore let there always be non-being,\n+  so we may see their subtlety,\n+And let there always be being,\n+  so we may see their outcome.\n+The two are the same,\n+But after they are produced,\n+  they have different names.\n+They both may be called deep and profound.\n+Deeper and more profound,\n+The door of all subtleties!\n`\n\nconst singleCommitMultiDiff = `commit 70001020fab32b1fcf2f1f0e5c66424eae649826 (HEAD -> master, origin/master, origin/HEAD)\nAuthor: Dustin Decker <humanatcomputer@gmail.com>\nAuthorDate: 2021-03-15T23:27:16-07:00\nCommit: Dustin Decker <humanatcomputer@gmail.com>\nCommitDate: 2021-03-15T23:27:16-07:00\n\n    Update aws\n\ndiff --git a/aws b/aws\nindex 2ee133b..12b4843 100644\n--- a/aws\n+++ b/aws\n@@ -1,7 +1,5 @@\n-blah blaj\n-\n-this is the secret: [Default]\n-Access key Id: AKIAILE3JG6KMS3HZGCA\n-Secret Access Key: 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\n-\n-okay thank you bye\n+[default]\n+aws_access_key_id = AKIAXYZDQCEN4B6JSJQI\n+aws_secret_access_key = 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\n+output = json\n+region = us-east-2\n\ndiff --git a/aws b/aws\nindex 239b415..2ee133b 100644\n--- a/aws\n+++ b/aws\n@@ -1,5 +1,7 @@\n blah blaj\n \n-this is the secret: AKIA2E0A8F3B244C9986\n+this is the secret: [Default]\n+Access key Id: AKIAILE3JG6KMS3HZGCA\n+Secret Access Key: 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\n \n-okay thank you bye\n\\ No newline at end of file\n+okay thank you bye\n`\n\nconst singleCommitSingleDiff = `commit 70001020fab32b1fcf2f1f0e5c66424eae649826 (HEAD -> master, origin/master, origin/HEAD)\nAuthor: Dustin Decker <humanatcomputer@gmail.com>\nAuthorDate: 2021-03-15T23:27:16-07:00\nCommit: Dustin Decker <humanatcomputer@gmail.com>\nCommitDate: 2021-03-15T23:27:16-07:00\n\n    Update aws\n\ndiff --git a/aws b/aws\nindex 2ee133b..12b4843 100644\n--- a/aws\n+++ b/aws\n@@ -1,7 +1,5 @@\n-blah blaj\n-\n-this is the secret: [Default]\n-Access key Id: AKIAILE3JG6KMS3HZGCA\n-Secret Access Key: 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\n-\n-okay thank you bye\n+[default]\n+aws_access_key_id = AKIAXYZDQCEN4B6JSJQI\n+aws_secret_access_key = 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\n+output = json\n+region = us-east-2\n`\n"
  },
  {
    "path": "pkg/giturl/giturl.go",
    "content": "package giturl\n\nimport (\n\t\"net/url\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\ntype provider string\n\nconst (\n\tproviderGithub    provider = \"Github\"\n\tproviderGitlab    provider = \"Gitlab\"\n\tproviderBitbucket provider = \"Bitbucket\"\n\tproviderAzure     provider = \"Azure\"\n\n\turlGithub    = \"github.com/\"\n\turlGitlab    = \"gitlab.com/\"\n\turlBitbucket = \"bitbucket.org/\"\n\turlAzure     = \"dev.azure.com/\"\n)\n\nfunc determineProvider(repo string) provider {\n\tswitch {\n\tcase strings.Contains(repo, urlGithub):\n\t\treturn providerGithub\n\tcase strings.Contains(repo, urlGitlab):\n\t\treturn providerGitlab\n\tcase strings.Contains(repo, urlBitbucket):\n\t\treturn providerBitbucket\n\tcase strings.Contains(repo, urlAzure):\n\t\treturn providerAzure\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n\nfunc NormalizeBitbucketRepo(repoURL string) (string, error) {\n\tif !strings.HasPrefix(repoURL, \"https\") {\n\t\treturn \"\", errors.New(\"Bitbucket requires https repo urls: e.g. https://bitbucket.org/org/repo.git\")\n\t}\n\n\treturn NormalizeOrgRepoURL(providerBitbucket, repoURL)\n}\n\nfunc NormalizeGerritProject(project string) (string, error) {\n\treturn \"\", errors.Errorf(\"Not yet implemented\")\n}\n\nfunc NormalizeGithubRepo(repoURL string) (string, error) {\n\treturn NormalizeOrgRepoURL(providerGithub, repoURL)\n}\n\nfunc NormalizeGitlabRepo(repoURL string) (string, error) {\n\tif !strings.HasPrefix(repoURL, \"http:\") && !strings.HasPrefix(repoURL, \"https:\") {\n\t\treturn \"\", errors.New(\"Gitlab requires http/https repo urls: e.g. https://gitlab.com/org/repo.git\")\n\t}\n\n\treturn NormalizeOrgRepoURL(providerGitlab, repoURL)\n}\n\n// NormalizeOrgRepoURL attempts to normalize repos for any provider using the example.com/org/repo style.\n// e.g. %s, Gitlab and Bitbucket\nfunc NormalizeOrgRepoURL(provider provider, repoURL string) (string, error) {\n\tif strings.HasSuffix(repoURL, \".git\") {\n\t\treturn repoURL, nil\n\t}\n\n\tparsed, err := url.Parse(repoURL)\n\tif err != nil {\n\t\treturn \"\", errors.Wrapf(err, \"Unable to parse %s Repo URL\", provider)\n\t}\n\n\t// The provider repo url should have a path length of 3\n\t//               0 / 1 / 2     (3 total)\n\t// e.g. example.com/org/repo\n\t// If it is 1, or the 2nd section is empty, it's likely just the org.\n\t// If it is 3, there's something at the end that shouldn't be there.\n\t// Let the user know in any case.\n\tswitch parts := strings.Split(parsed.Path, \"/\"); {\n\tcase len(parts) <= 1:\n\t\treturn \"\", errors.Errorf(\"%s repo appears to be missing the path. Repo url: %q\", provider, repoURL)\n\n\tcase len(parts) == 2:\n\t\torg := parts[1]\n\n\t\tif len(org) == 0 {\n\t\t\treturn \"\", errors.Errorf(\"%s repo appears to be missing the org name. Repo url: %q\", provider, repoURL)\n\t\t} else {\n\t\t\treturn \"\", errors.Errorf(\"%s repo appears to be missing the repo name. Org: %q Repo url: %q\", provider, org, repoURL)\n\t\t}\n\n\tcase len(parts) == 3:\n\t\torg, repo := parts[1], parts[2]\n\n\t\tif len(org) == 0 {\n\t\t\treturn \"\", errors.Errorf(\"%s repo appears to be missing the org name. Repo url: %q\", provider, repoURL)\n\t\t}\n\t\tif len(repo) == 0 {\n\t\t\treturn \"\", errors.Errorf(\"%s repo appears to be missing the repo name. Org: %q Repo url: %q\", provider, org, repoURL)\n\t\t}\n\n\tcase len(parts) > 3 && strings.HasSuffix(parsed.Path, \"/\"):\n\t\treturn \"\", errors.Errorf(\"%s repo contains a trailing slash. Repo url: %q\", provider, repoURL)\n\t}\n\n\t// If we're here it's probably a provider repo without \".git\" at the end, so add it and return\n\tparsed.Path += \".git\"\n\treturn parsed.String(), nil\n}\n\n// GenerateLink crafts a link to the specific file from a commit.\n// Supports GitHub, GitLab, Bitbucket, and Azure Repos.\n// If the provider supports hyperlinks to specific lines, the line number will be included.\nfunc GenerateLink(repo, commit, file string, line int64) string {\n\t// Some paths contain '%' which breaks |url.Parse| if not encoded.\n\t// https://developer.mozilla.org/en-US/docs/Glossary/Percent-encoding\n\tfile = strings.ReplaceAll(file, \"%\", \"%25\")\n\tfile = strings.ReplaceAll(file, \"[\", \"%5B\")\n\tfile = strings.ReplaceAll(file, \"]\", \"%5D\")\n\n\tswitch determineProvider(repo) {\n\tcase providerBitbucket:\n\t\treturn repo[:len(repo)-4] + \"/commits/\" + commit\n\n\tcase providerAzure:\n\t\t// Azure Repos format: ?path=/file&version=GC<commit>&line=N&lineEnd=N+1&lineStartColumn=1\n\t\t// where GC prefix stands for \"Git Commit\" (vs GB for Git Branch, GT for Git Tag)\n\t\tbaseLink := repo + \"?version=GC\" + commit\n\t\tif file != \"\" {\n\t\t\tbaseLink = repo + \"?path=/\" + file + \"&version=GC\" + commit\n\t\t}\n\t\tif line > 0 {\n\t\t\tlineStr := strconv.FormatInt(line, 10)\n\t\t\tlineEndStr := strconv.FormatInt(line+1, 10)\n\t\t\tbaseLink += \"&line=\" + lineStr + \"&lineEnd=\" + lineEndStr + \"&lineStartColumn=1\"\n\t\t}\n\t\treturn baseLink\n\n\tcase providerGithub, providerGitlab:\n\t\t// If the provider name isn't one of the cloud defaults, it is probably an on-prem github or gitlab.\n\t\t// So do the same thing.\n\t\tfallthrough\n\tdefault:\n\t\tvar baseLink string\n\n\t\t// Gist links are formatted differently\n\t\tif strings.HasPrefix(repo, \"https://gist.github.com\") {\n\t\t\tbaseLink = repo[:len(repo)-4] + \"/\"\n\t\t\tif commit != \"\" {\n\t\t\t\tbaseLink += commit + \"/\"\n\t\t\t}\n\t\t\tif file != \"\" {\n\t\t\t\tcleanedFileName := strings.ReplaceAll(file, \".\", \"-\")\n\t\t\t\tbaseLink += \"#file-\" + cleanedFileName\n\t\t\t}\n\t\t\tif line > 0 {\n\t\t\t\tif strings.Contains(baseLink, \"#\") {\n\t\t\t\t\tbaseLink += \"-L\" + strconv.FormatInt(line, 10)\n\t\t\t\t} else {\n\t\t\t\t\tbaseLink += \"#L\" + strconv.FormatInt(line, 10)\n\t\t\t\t}\n\t\t\t}\n\t\t} else if strings.HasSuffix(repo, \".wiki.git\") {\n\t\t\t// GitHub Wiki links are formatted differently\n\t\t\tbaseLink = repo[:len(repo)-9] + \"/wiki/\"\n\t\t\tif file != \"\" {\n\t\t\t\t// remove file extension\n\t\t\t\tbaseLink += strings.TrimSuffix(file, filepath.Ext(file)) + \"/\"\n\t\t\t}\n\t\t\tif commit != \"\" {\n\t\t\t\tbaseLink += commit\n\t\t\t}\n\t\t\tif line > 0 {\n\t\t\t\tbaseLink += \"#L\" + strconv.FormatInt(line, 10)\n\t\t\t}\n\t\t} else if file == \"\" {\n\t\t\tbaseLink = repo[:len(repo)-4] + \"/commit/\" + commit\n\t\t} else {\n\t\t\tbaseLink = repo[:len(repo)-4] + \"/blob/\" + commit + \"/\" + file\n\t\t\tif line > 0 {\n\t\t\t\tbaseLink += \"#L\" + strconv.FormatInt(line, 10)\n\t\t\t}\n\t\t}\n\t\treturn baseLink\n\t}\n}\n\nvar linePattern = regexp.MustCompile(`L\\d+`)\n\n// UpdateLinkLineNumber updates the line number in a repository link.\n// Used post-link generation to refine reported issue locations within large scanned blocks.\nfunc UpdateLinkLineNumber(ctx context.Context, link string, newLine int64) string {\n\tlink = strings.Replace(link, \"%\", \"%25\", -1)\n\tlink = strings.Replace(link, \"[\", \"%5B\", -1)\n\tlink = strings.Replace(link, \"]\", \"%5D\", -1)\n\tparsedURL, err := url.Parse(link)\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"unable to parse link to update line number\", \"link\", link)\n\t\treturn link\n\t}\n\n\tif newLine <= 0 {\n\t\t// Don't change the link if the line number is 0.\n\t\treturn link\n\t}\n\n\tswitch determineProvider(link) {\n\tcase providerBitbucket:\n\t\t// For Bitbucket, it doesn't support line links (based on the GenerateLink function).\n\t\t// So we don't need to change anything.\n\t\treturn link\n\n\tcase providerAzure:\n\t\t// For Azure, line numbers use query parameters: ?line=N&lineEnd=N+1&lineStartColumn=1\n\t\tquery := parsedURL.Query()\n\t\tlineStr := strconv.FormatInt(newLine, 10)\n\t\tlineEndStr := strconv.FormatInt(newLine+1, 10)\n\t\tquery.Set(\"line\", lineStr)\n\t\tquery.Set(\"lineEnd\", lineEndStr)\n\t\tquery.Set(\"lineStartColumn\", \"1\")\n\t\tparsedURL.RawQuery = query.Encode()\n\n\tcase providerGithub, providerGitlab:\n\t\t// If the provider name isn't one of the cloud defaults, it is probably an on-prem github or gitlab.\n\t\t// So do the same thing.\n\t\tfallthrough\n\tdefault:\n\t\t// Assumed format: .../blob/<commit>/file.go#L<number>\n\t\tfragment := \"L\" + strconv.FormatInt(newLine, 10)\n\t\tif linePattern.MatchString(parsedURL.Fragment) {\n\t\t\tparsedURL.Fragment = linePattern.ReplaceAllString(parsedURL.Fragment, fragment)\n\t\t} else {\n\t\t\tparsedURL.Fragment += fragment\n\t\t}\n\t}\n\n\treturn parsedURL.String()\n}\n"
  },
  {
    "path": "pkg/giturl/giturl_test.go",
    "content": "package giturl\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc Test_NormalizeOrgRepoURL(t *testing.T) {\n\tt.Parallel()\n\n\ttests := map[string]struct {\n\t\tProvider provider\n\t\tRepo     string\n\t\tOut      string\n\t\tErr      error\n\t}{\n\t\t\"github is good\":             {Provider: providerGithub, Repo: \"https://github.com/org/repo\", Out: \"https://github.com/org/repo.git\", Err: nil},\n\t\t\"gitlab is good\":             {Provider: providerGitlab, Repo: \"https://gitlab.com/org/repo\", Out: \"https://gitlab.com/org/repo.git\", Err: nil},\n\t\t\"bitbucket is good\":          {Provider: providerBitbucket, Repo: \"https://bitbucket.com/org/repo\", Out: \"https://bitbucket.com/org/repo.git\", Err: nil},\n\t\t\"example provider is good\":   {Provider: \"example\", Repo: \"https://example.com/org/repo\", Out: \"https://example.com/org/repo.git\", Err: nil},\n\t\t\"example provider problem\":   {Provider: \"example\", Repo: \"https://example.com/org\", Out: \"\", Err: errors.Errorf(\"example repo appears to be missing the repo name. Org: %q Repo url: %q\", \"org\", \"https://example.com/org\")},\n\t\t\"no path\":                    {Provider: providerGithub, Repo: \"https://github.com\", Out: \"\", Err: errors.Errorf(\"Github repo appears to be missing the path. Repo url: %q\", \"https://github.com\")},\n\t\t\"org but no repo\":            {Provider: providerGithub, Repo: \"https://github.com/org\", Out: \"\", Err: errors.Errorf(\"Github repo appears to be missing the repo name. Org: %q Repo url: %q\", \"org\", \"https://github.com/org\")},\n\t\t\"org but no repo with slash\": {Provider: providerGithub, Repo: \"https://github.com/org/\", Out: \"\", Err: errors.Errorf(\"Github repo appears to be missing the repo name. Org: %q Repo url: %q\", \"org\", \"https://github.com/org/\")},\n\t\t\"two slashes\":                {Provider: providerGithub, Repo: \"https://github.com//\", Out: \"\", Err: errors.Errorf(\"Github repo appears to be missing the org name. Repo url: %q\", \"https://github.com//\")},\n\t\t\"repo with trailing slash\":   {Provider: providerGithub, Repo: \"https://github.com/org/repo/\", Out: \"\", Err: errors.Errorf(\"Github repo contains a trailing slash. Repo url: %q\", \"https://github.com/org/repo/\")},\n\t\t\"too many url path parts\":    {Provider: providerGithub, Repo: \"https://github.com/org/repo/unknown/\", Out: \"\", Err: errors.Errorf(\"Github repo contains a trailing slash. Repo url: %q\", \"https://github.com/org/repo/unknown/\")},\n\t}\n\n\tfor name, tt := range tests {\n\t\tout, err := NormalizeOrgRepoURL(tt.Provider, tt.Repo)\n\n\t\tswitch {\n\t\tcase err != nil && tt.Err != nil && (err.Error() != tt.Err.Error()):\n\t\t\tt.Errorf(\"Test %q, error does not match expected error, \\n got: %v \\nwant: %v\", name, err.Error(), tt.Err.Error())\n\t\tcase (err != nil && tt.Err == nil) || (err == nil && tt.Err != nil):\n\t\t\tt.Errorf(\"Test %q, error does not match expected error, \\n got: %v \\nwant: %v\", name, err, tt.Err)\n\t\t}\n\n\t\tif out != tt.Out {\n\t\t\tt.Errorf(\"Test %q, output does not match expected out, got: %q want: %q\", name, out, tt.Out)\n\t\t}\n\t}\n}\n\nfunc Test_NormalizeBitbucketRepo(t *testing.T) {\n\tt.Parallel()\n\n\ttests := map[string]struct {\n\t\tRepo string\n\t\tOut  string\n\t\tErr  error\n\t}{\n\t\t\"good\":                  {Repo: \"https://bitbucket.org/org/repo\", Out: \"https://bitbucket.org/org/repo.git\", Err: nil},\n\t\t\"bitbucket needs https\": {Repo: \"git@bitbucket.org:org/repo.git\", Out: \"\", Err: errors.New(\"Bitbucket requires https repo urls: e.g. https://bitbucket.org/org/repo.git\")},\n\t}\n\n\tfor name, tt := range tests {\n\t\tout, err := NormalizeBitbucketRepo(tt.Repo)\n\n\t\tswitch {\n\t\tcase err != nil && tt.Err != nil && (err.Error() != tt.Err.Error()):\n\t\t\tt.Errorf(\"Test %q, error does not match expected error, \\n got: %v \\nwant: %v\", name, err.Error(), tt.Err.Error())\n\t\tcase (err != nil && tt.Err == nil) || (err == nil && tt.Err != nil):\n\t\t\tt.Errorf(\"Test %q, error does not match expected error, \\n got: %v \\nwant: %v\", name, err, tt.Err)\n\t\t}\n\n\t\tif out != tt.Out {\n\t\t\tt.Errorf(\"Test %q, output does not match expected out, got: %q want: %q\", name, out, tt.Out)\n\t\t}\n\t}\n}\n\nfunc Test_NormalizeGitlabRepo(t *testing.T) {\n\tt.Parallel()\n\n\ttests := map[string]struct {\n\t\tRepo string\n\t\tOut  string\n\t\tErr  error\n\t}{\n\t\t\"good\":                    {Repo: \"https://gitlab.com/org/repo\", Out: \"https://gitlab.com/org/repo.git\", Err: nil},\n\t\t\"gitlab needs http/https\": {Repo: \"git@gitlab.com:org/repo.git:\", Out: \"\", Err: errors.New(\"Gitlab requires http/https repo urls: e.g. https://gitlab.com/org/repo.git\")},\n\t}\n\n\tfor name, tt := range tests {\n\t\tout, err := NormalizeGitlabRepo(tt.Repo)\n\n\t\tswitch {\n\t\tcase err != nil && tt.Err != nil && (err.Error() != tt.Err.Error()):\n\t\t\tt.Errorf(\"Test %q, error does not match expected error, \\n got: %v \\nwant: %v\", name, err.Error(), tt.Err.Error())\n\t\tcase (err != nil && tt.Err == nil) || (err == nil && tt.Err != nil):\n\t\t\tt.Errorf(\"Test %q, error does not match expected error, \\n got: %v \\nwant: %v\", name, err, tt.Err)\n\t\t}\n\n\t\tif out != tt.Out {\n\t\t\tt.Errorf(\"Test %q, output does not match expected out, got: %q want: %q\", name, out, tt.Out)\n\t\t}\n\t}\n}\n\nfunc TestGenerateLink(t *testing.T) {\n\tt.Parallel()\n\n\ttype args struct {\n\t\trepo   string\n\t\tcommit string\n\t\tfile   string\n\t\tline   int64\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"github link gen\",\n\t\t\targs: args{\n\t\t\t\trepo:   \"https://github.com/trufflesec-julian/confluence-go-api.git\",\n\t\t\t\tcommit: \"047b4a2ba42fc5b6c0bd535c5307434a666db5ec\",\n\t\t\t\tfile:   \".gitignore\",\n\t\t\t},\n\t\t\twant: \"https://github.com/trufflesec-julian/confluence-go-api/blob/047b4a2ba42fc5b6c0bd535c5307434a666db5ec/.gitignore\",\n\t\t},\n\t\t{\n\t\t\tname: \"github link gen with line\",\n\t\t\targs: args{\n\t\t\t\trepo:   \"https://github.com/trufflesec-julian/confluence-go-api.git\",\n\t\t\t\tcommit: \"047b4a2ba42fc5b6c0bd535c5307434a666db5ec\",\n\t\t\t\tfile:   \".gitignore\",\n\t\t\t\tline:   int64(4),\n\t\t\t},\n\t\t\twant: \"https://github.com/trufflesec-julian/confluence-go-api/blob/047b4a2ba42fc5b6c0bd535c5307434a666db5ec/.gitignore#L4\",\n\t\t},\n\t\t{\n\t\t\tname: \"github link gen - no file\",\n\t\t\targs: args{\n\t\t\t\trepo:   \"https://github.com/trufflesec-julian/confluence-go-api.git\",\n\t\t\t\tcommit: \"047b4a2ba42fc5b6c0bd535c5307434a666db5ec\",\n\t\t\t},\n\t\t\twant: \"https://github.com/trufflesec-julian/confluence-go-api/commit/047b4a2ba42fc5b6c0bd535c5307434a666db5ec\",\n\t\t},\n\t\t{\n\t\t\tname: \"Azure link gen\",\n\t\t\targs: args{\n\t\t\t\trepo:   \"https://dev.azure.com/org/project/_git/repo\",\n\t\t\t\tcommit: \"abcdef\",\n\t\t\t\tfile:   \"main.go\",\n\t\t\t},\n\t\t\twant: \"https://dev.azure.com/org/project/_git/repo?path=/main.go&version=GCabcdef\",\n\t\t},\n\t\t{\n\t\t\tname: \"Azure link gen with line\",\n\t\t\targs: args{\n\t\t\t\trepo:   \"https://dev.azure.com/org/project/_git/repo\",\n\t\t\t\tcommit: \"abcdef\",\n\t\t\t\tfile:   \"main.go\",\n\t\t\t\tline:   int64(20),\n\t\t\t},\n\t\t\twant: \"https://dev.azure.com/org/project/_git/repo?path=/main.go&version=GCabcdef&line=20&lineEnd=21&lineStartColumn=1\",\n\t\t},\n\t\t{\n\t\t\tname: \"Azure link gen - no file\",\n\t\t\targs: args{\n\t\t\t\trepo:   \"https://dev.azure.com/org/project/_git/repo\",\n\t\t\t\tcommit: \"abcdef\",\n\t\t\t\tfile:   \"\",\n\t\t\t},\n\t\t\twant: \"https://dev.azure.com/org/project/_git/repo?version=GCabcdef\",\n\t\t},\n\t\t{\n\t\t\tname: \"Unknown provider on-prem instance\",\n\t\t\targs: args{\n\t\t\t\trepo:   \"https://onprem.customdomain.com/org/repo.git\",\n\t\t\t\tcommit: \"xyz123\",\n\t\t\t\tfile:   \"main.go\",\n\t\t\t\tline:   int64(30),\n\t\t\t},\n\t\t\twant: \"https://onprem.customdomain.com/org/repo/blob/xyz123/main.go#L30\",\n\t\t},\n\t\t{\n\t\t\tname: \"Unknown provider on-prem instance - no file\",\n\t\t\targs: args{\n\t\t\t\trepo:   \"https://onprem.customdomain.com/org/repo.git\",\n\t\t\t\tcommit: \"xyz123\",\n\t\t\t},\n\t\t\twant: \"https://onprem.customdomain.com/org/repo/commit/xyz123\",\n\t\t},\n\t\t{\n\t\t\tname: \"gist link gen\",\n\t\t\targs: args{\n\t\t\t\trepo:   \"https://gist.github.com/joeleonjr/be68e34b002e236160dbb394bbda86fb.git\",\n\t\t\t\tcommit: \"e94c5a1d5607e68f1cae4962bc4dce5de522371b\",\n\t\t\t\tfile:   \"test\",\n\t\t\t\tline:   int64(4),\n\t\t\t},\n\t\t\twant: \"https://gist.github.com/joeleonjr/be68e34b002e236160dbb394bbda86fb/e94c5a1d5607e68f1cae4962bc4dce5de522371b/#file-test-L4\",\n\t\t},\n\t\t{\n\t\t\tname: \"gist link gen - file with multiple extensions\",\n\t\t\targs: args{\n\t\t\t\trepo:   \"https://gist.github.com/joeleonjr/be68e34b002e236160dbb394bbda86fb.git\",\n\t\t\t\tcommit: \"c64bf2345256cca7d2621f9cb78401e8860f82c8\",\n\t\t\t\tfile:   \"test.txt.ps1\",\n\t\t\t\tline:   int64(4),\n\t\t\t},\n\t\t\twant: \"https://gist.github.com/joeleonjr/be68e34b002e236160dbb394bbda86fb/c64bf2345256cca7d2621f9cb78401e8860f82c8/#file-test-txt-ps1-L4\",\n\t\t},\n\t\t{\n\t\t\tname: \"link gen - file percent in path\",\n\t\t\targs: args{\n\t\t\t\trepo:   \"https://github.com/GeekMasher/tree-sitter-hcl.git\",\n\t\t\t\tcommit: \"a7f23cc5795769262f5515e52902f86c1b768994\",\n\t\t\t\tfile:   \"example/real_world_stuff/coreos/coreos%tectonic-installer%installer%frontend%ui-tests%output%metal.tfvars\",\n\t\t\t\tline:   int64(1),\n\t\t\t},\n\t\t\twant: \"https://github.com/GeekMasher/tree-sitter-hcl/blob/a7f23cc5795769262f5515e52902f86c1b768994/example/real_world_stuff/coreos/coreos%25tectonic-installer%25installer%25frontend%25ui-tests%25output%25metal.tfvars#L1\",\n\t\t},\n\t\t{\n\t\t\tname: \"github wiki link gen\",\n\t\t\targs: args{\n\t\t\t\trepo:   \"https://github.com/hxnyk/hxnyk.wiki.git\",\n\t\t\t\tcommit: \"e5fdc764d6d405fc0e4e90e4bcf192357b1a1a87\",\n\t\t\t\tfile:   \"Home.md\",\n\t\t\t\tline:   int64(5),\n\t\t\t},\n\t\t\twant: \"https://github.com/hxnyk/hxnyk/wiki/Home/e5fdc764d6d405fc0e4e90e4bcf192357b1a1a87#L5\",\n\t\t},\n\t\t{\n\t\t\tname: \"github wiki link gen - no line\",\n\t\t\targs: args{\n\t\t\t\trepo:   \"https://github.com/hxnyk/hxnyk.wiki.git\",\n\t\t\t\tcommit: \"e5fdc764d6d405fc0e4e90e4bcf192357b1a1a87\",\n\t\t\t\tfile:   \"Home.md\",\n\t\t\t},\n\t\t\twant: \"https://github.com/hxnyk/hxnyk/wiki/Home/e5fdc764d6d405fc0e4e90e4bcf192357b1a1a87\",\n\t\t},\n\t\t{\n\t\t\tname: \"github wiki link gen - no commit no line\",\n\t\t\targs: args{\n\t\t\t\trepo: \"https://github.com/hxnyk/hxnyk.wiki.git\",\n\t\t\t\tfile: \"Home.md\",\n\t\t\t},\n\t\t\twant: \"https://github.com/hxnyk/hxnyk/wiki/Home/\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := GenerateLink(tt.args.repo, tt.args.commit, tt.args.file, tt.args.line); got != tt.want {\n\t\t\t\tt.Errorf(\"generateLink() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateLinkLineNumber(t *testing.T) {\n\tt.Parallel()\n\n\ttype args struct {\n\t\tlink    string\n\t\tnewLine int64\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"Update bitbucket, no line number supported\",\n\t\t\targs: args{\n\t\t\t\tlink:    \"https://bitbucket.org/org/repo/blob/xyz123/main.go\",\n\t\t\t\tnewLine: int64(10),\n\t\t\t},\n\t\t\twant: \"https://bitbucket.org/org/repo/blob/xyz123/main.go\",\n\t\t},\n\t\t{\n\t\t\tname: \"Update github link with line\",\n\t\t\targs: args{\n\t\t\t\tlink:    \"https://github.com/trufflesec-julian/confluence-go-api/blob/047b4a2ba42fc5b6c0bd535c5307434a666db5ec/.gitignore#L4\",\n\t\t\t\tnewLine: int64(10),\n\t\t\t},\n\t\t\twant: \"https://github.com/trufflesec-julian/confluence-go-api/blob/047b4a2ba42fc5b6c0bd535c5307434a666db5ec/.gitignore#L10\",\n\t\t},\n\t\t{\n\t\t\tname: \"Update Azure link with line\",\n\t\t\targs: args{\n\t\t\t\tlink:    \"https://dev.azure.com/org/project/_git/repo?path=/main.go&version=GCabcdef&line=20&lineEnd=21&lineStartColumn=1\",\n\t\t\t\tnewLine: int64(40),\n\t\t\t},\n\t\t\twant: \"https://dev.azure.com/org/project/_git/repo?line=40&lineEnd=41&lineStartColumn=1&path=%2Fmain.go&version=GCabcdef\",\n\t\t},\n\t\t{\n\t\t\tname: \"Add line to github link without line\",\n\t\t\targs: args{\n\t\t\t\tlink:    \"https://github.com/trufflesec-julian/confluence-go-api/blob/047b4a2ba42fc5b6c0bd535c5307434a666db5ec/.gitignore\",\n\t\t\t\tnewLine: int64(7),\n\t\t\t},\n\t\t\twant: \"https://github.com/trufflesec-julian/confluence-go-api/blob/047b4a2ba42fc5b6c0bd535c5307434a666db5ec/.gitignore#L7\",\n\t\t},\n\t\t{\n\t\t\tname: \"Update Unknown provider on-prem instance with line\",\n\t\t\targs: args{\n\t\t\t\tlink:    \"https://onprem.customdomain.com/org/repo/blob/xyz123/main.go#L30\",\n\t\t\t\tnewLine: int64(50),\n\t\t\t},\n\t\t\twant: \"https://onprem.customdomain.com/org/repo/blob/xyz123/main.go#L50\",\n\t\t},\n\t\t{\n\t\t\tname: \"Update Unknown provider on-prem instance without line\",\n\t\t\targs: args{\n\t\t\t\tlink:    \"https://onprem.customdomain.com/org/repo/commit/xyz123\",\n\t\t\t\tnewLine: int64(50),\n\t\t\t},\n\t\t\twant: \"https://onprem.customdomain.com/org/repo/commit/xyz123#L50\",\n\t\t},\n\t\t{\n\t\t\tname: \"Don't include line when it's 0\",\n\t\t\targs: args{\n\t\t\t\tlink:    \"https://github.com/coinbase/cbpay-js/issues/181\",\n\t\t\t\tnewLine: int64(0),\n\t\t\t},\n\t\t\twant: \"https://github.com/coinbase/cbpay-js/issues/181\",\n\t\t},\n\t\t{\n\t\t\tname: \"Encode percent\",\n\t\t\targs: args{\n\t\t\t\tlink:    \"https://github.com/coinbase/cbpay-js/blob/abcdefg/folder/%/name\",\n\t\t\t\tnewLine: int64(0),\n\t\t\t},\n\t\t\twant: \"https://github.com/coinbase/cbpay-js/blob/abcdefg/folder/%25/name\",\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid link\",\n\t\t\targs: args{\n\t\t\t\tlink:    \"definitely not a link\",\n\t\t\t\tnewLine: int64(50),\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Encode brackets\",\n\t\t\targs: args{\n\t\t\t\tlink:    \"https://github.com/coinbase/cbpay-js/blob/abcdefg/folder/[name]/file\",\n\t\t\t\tnewLine: int64(0),\n\t\t\t},\n\t\t\twant: \"https://github.com/coinbase/cbpay-js/blob/abcdefg/folder/%5Bname%5D/file\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := UpdateLinkLineNumber(context.Background(), tt.args.link, tt.args.newLine)\n\t\t\tif got != tt.want && !tt.wantErr {\n\t\t\t\tt.Errorf(\"UpdateLinkLineNumber() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/handlers/apk.go",
    "content": "package handlers\n\nimport (\n\t\"archive/zip\"\n\t\"bytes\"\n\t\"encoding/xml\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tahocorasick \"github.com/BobuSumisu/aho-corasick\"\n\t\"github.com/avast/apkparser\"\n\tdextk \"github.com/csnewman/dextk\"\n\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/engine/defaults\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/iobuf\"\n)\n\n// General Note: There are tools that can fully decompile an apk (e.g. jadx, apktool, etc.)\n// However, none of these are in golang + they take awhile to run +\n// they will decompile files that most likely don't contain secrets. So instead, we have a\n// lightweight version that will search for secrets in the most common files that contain them.\n// And run in a fraction of the time (ex: 15 seconds vs. 5 minutes)\n\n// ToDo: Scan nested APKs (aka XAPK files). ATM the archive.go file will skip over them.\n// ToDo: Provide file location information to secret output.\n\nvar (\n\tkeywordMatcherOnce sync.Once\n\tkeywordMatcher     *detectorKeywordMatcher\n)\n\nfunc defaultDetectorKeywords() []string {\n\tallDetectors := defaults.DefaultDetectors()\n\n\t// Remove keywords that cause lots of false positives.\n\tvar exclusions = []string{\n\t\t\"AKIA\", \"SG.\", \"pat\", \"token\", \"gh\", \"github\", \"sql\", \"database\", \"http\", \"key\", \"api-\", \"sdk-\", \"float\", \"-us\", \"gh\", \"pat\", \"token\", \"sid\", \"http\", \"private\", \"key\", \"segment\", \"close\", \"protocols\", \"verifier\", \"box\", \"privacy\", \"dm\", \"sl.\", \"vf\", \"flat\",\n\t}\n\n\tvar keywords []string\n\texclusionSet := make(map[string]struct{})\n\tfor _, excl := range exclusions {\n\t\texclusionSet[strings.ToLower(excl)] = struct{}{}\n\t}\n\n\t// Aggregate all keywords from detectors.\n\tfor _, detector := range allDetectors {\n\t\tfor _, kw := range detector.Keywords() {\n\t\t\tkwLower := strings.ToLower(kw)\n\t\t\tif _, excluded := exclusionSet[kwLower]; !excluded {\n\t\t\t\tkeywords = append(keywords, kwLower)\n\t\t\t}\n\t\t}\n\t}\n\treturn keywords\n}\n\n// detectorKeywordMatcher encapsulates the Aho-Corasick trie for efficient keyword matching.\n// It is used to scan APK file contents for keywords associated with our credential detectors.\n// By only processing files/sections that contain these keywords, we can efficiently filter\n// out irrelevant data and focus on content that is more likely to contain credentials.\n// The Aho-Corasick algorithm provides fast, simultaneous matching of multiple patterns in\n// a single pass through the text, which is crucial for performance when scanning large APK files.\ntype detectorKeywordMatcher struct{ trie *ahocorasick.Trie }\n\n// getDefaultDetectorKeywordMatcher creates or returns the singleton detectorKeywordMatcher.\n// This is implemented as a singleton for several important reasons:\n// 1. Building the Aho-Corasick trie is computationally expensive and should only be done once.\n// 2. The trie is immutable after construction and can be safely shared across goroutines.\n// 3. The keyword list from the detectors is static for a given program execution.\n// 4. Memory efficiency - we avoid duplicating the trie structure for each handler instance.\nfunc getDefaultDetectorKeywordMatcher() *detectorKeywordMatcher {\n\tkeywordMatcherOnce.Do(func() {\n\t\tkeywords := defaultDetectorKeywords()\n\t\tkeywordMatcher = &detectorKeywordMatcher{\n\t\t\ttrie: ahocorasick.NewTrieBuilder().AddStrings(keywords).Build(),\n\t\t}\n\t})\n\treturn keywordMatcher\n}\n\n// FindKeywords scans the input text and returns a slice of matched keywords.\n// The method is thread-safe and uses a read lock since the trie is immutable.\n// It returns unique matches only, eliminating duplicates that may occur when\n// the same keyword appears multiple times in the input text.\nfunc (km *detectorKeywordMatcher) FindKeywords(text []byte) []string {\n\tmatches := km.trie.Match(bytes.ToLower(text))\n\tfound := make([]string, 0, len(matches))\n\tseen := make(map[string]struct{}) // To avoid duplicate entries\n\n\tfor _, match := range matches {\n\t\tkeyword := match.MatchString()\n\t\tif _, exists := seen[keyword]; !exists {\n\t\t\tfound = append(found, keyword)\n\t\t\tseen[keyword] = struct{}{}\n\t\t}\n\t}\n\treturn found\n}\n\nvar (\n\tstringInstructionType  = \"const-string\"\n\ttargetInstructionTypes = []string{stringInstructionType, \"iput-object\", \"sput-object\", \"const-class\", \"invoke-virtual\", \"invoke-super\", \"invoke-direct\", \"invoke-static\", \"invoke-interface\"}\n\t// Note: We're only looking at a subset of instructions.\n\t// If expanding, update precompiled REGEX below.\n\t// - const-string: loads a string into a register (value)\n\t// - iput-object: stores a string into a field (key)\n\t// - the rest have to do with function, methods, objects and classes.\n\treIPutRegex       = regexp.MustCompile(`iput-object obj=\\d+ field=com/[a-zA-Z0-9/_]+:([a-zA-Z0-9_]+):`)\n\treSPutRegex       = regexp.MustCompile(`sput-object field=com/[a-zA-Z0-9/_]+:([a-zA-Z0-9_]+):`)\n\treConstRegex      = regexp.MustCompile(`const-string(?:/jumbo)? dst=\\d+ value='([^']*)'`)\n\treConstClassRegex = regexp.MustCompile(`const-class dst=\\d+ value='[a-zA-Z0-9/_$]+/([a-zA-Z0-9]+)(?:\\$|;)`)\n\treInvokeRegex     = regexp.MustCompile(`invoke-(?:virtual|super|direct|static|interface)(?:/range)? method=[a-zA-Z0-9/._$]+/([a-zA-Z0-9_$]+:[a-zA-Z0-9_<]+)`)\n\treInstructions    = []*regexp.Regexp{\n\t\treIPutRegex,\n\t\treSPutRegex,\n\t\treConstRegex,\n\t\treConstClassRegex,\n\t\treInvokeRegex,\n\t}\n)\n\n// apkHandler handles apk archive formats.\ntype apkHandler struct {\n\tkeywordMatcher *detectorKeywordMatcher\n\t*defaultHandler\n}\n\n// newAPKHandler creates an apkHandler.\nfunc newAPKHandler() *apkHandler {\n\treturn &apkHandler{\n\t\tdefaultHandler: newDefaultHandler(apkHandlerType),\n\t\tkeywordMatcher: getDefaultDetectorKeywordMatcher(),\n\t}\n}\n\n// HandleFile processes apk formatted files.\n// Fatal errors that will stop processing:\n// - Unable to create ZIP reader from input\n// - Unable to parse resources.arsc file\n// - Panics during processing (recovered but returned as errors)\n//\n// Non-fatal errors that will be logged and continue processing:\n// - Failed to process individual files within the APK\n// - Failed to process resources.arsc contents\n// - Failed to process individual dex classes\n// - Failed to decode specific XML files\nfunc (h *apkHandler) HandleFile(ctx logContext.Context, input fileReader) chan DataOrErr {\n\tapkChan := make(chan DataOrErr, defaultBufferSize)\n\n\tgo func() {\n\t\tdefer close(apkChan)\n\n\t\t// Defer a panic recovery to handle any panics that occur during the APK processing.\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\t// Return the panic as an error.\n\t\t\t\tvar panicErr error\n\t\t\t\tif e, ok := r.(error); ok {\n\t\t\t\t\tpanicErr = e\n\t\t\t\t} else {\n\t\t\t\t\tpanicErr = fmt.Errorf(\"panic occurred: %v\", r)\n\t\t\t\t}\n\t\t\t\tctx.Logger().Error(panicErr, \"Panic occurred when reading apk archive\")\n\t\t\t}\n\t\t}()\n\n\t\tstart := time.Now()\n\t\terr := h.processAPK(ctx, input, apkChan)\n\t\tif err == nil {\n\t\t\th.metrics.incFilesProcessed()\n\t\t}\n\n\t\th.measureLatencyAndHandleErrors(ctx, start, err, apkChan)\n\t}()\n\n\treturn apkChan\n}\n\n// processAPK processes the apk file and sends the extracted data to the provided channel.\nfunc (h *apkHandler) processAPK(ctx logContext.Context, input fileReader, apkChan chan DataOrErr) error {\n\t// Create a ZIP reader from the input fileReader\n\tzipReader, err := createZipReader(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Extract the resources.arsc file into a ResourceTable (needed for XML decoding)\n\tresTable, err := parseResTable(zipReader)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Process the ResourceTable file for secrets\n\tif err := h.processResources(ctx, resTable, apkChan); err != nil {\n\t\tctx.Logger().Error(err, \"failed to process resources.arsc\")\n\t}\n\n\t// Process all files for secrets\n\tfor _, file := range zipReader.File {\n\t\tif err := h.processFile(ctx, file, resTable, apkChan); err != nil {\n\t\t\tctx.Logger().V(2).Info(fmt.Sprintf(\"failed to process file: %s\", file.Name), \"error\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// processResources processes the resources.arsc file and sends the extracted data to the provided channel.\nfunc (h *apkHandler) processResources(ctx logContext.Context, resTable *apkparser.ResourceTable, apkChan chan DataOrErr) error {\n\tif resTable == nil {\n\t\treturn errors.New(\"ResourceTable is nil\")\n\t}\n\trscStrRdr, err := extractStringsFromResTable(resTable)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse strings from resources.arsc: %w\", err)\n\t}\n\treturn h.handleAPKFileContent(ctx, rscStrRdr, \"resources.arsc\", apkChan)\n}\n\n// processFile processes the file and sends the extracted data to the provided channel.\nfunc (h *apkHandler) processFile(\n\tctx logContext.Context,\n\tfile *zip.File,\n\tresTable *apkparser.ResourceTable,\n\tapkChan chan DataOrErr,\n) error {\n\t// check if the file is empty\n\tif file.UncompressedSize64 == 0 {\n\t\treturn nil\n\t}\n\n\t// Open the file from the zip archive\n\tf, err := openFile(file)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read file %s: %w\", file.Name, err)\n\t}\n\tdefer f.Close()\n\n\trdr := iobuf.NewBufferedReaderSeeker(f)\n\tdefer rdr.Close()\n\n\tvar contentReader io.Reader\n\t// Decode the file based on its extension\n\tswitch strings.ToLower(filepath.Ext(file.Name)) {\n\tcase \".xml\":\n\t\tcontentReader, err = decodeXML(rdr, resTable)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to decode xml file %s: %w\", file.Name, err)\n\t\t}\n\tcase \".dex\":\n\t\tcontentReader, err = h.processDexFile(ctx, rdr)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to decode dex file %s: %w\", file.Name, err)\n\t\t}\n\tdefault:\n\t\tcontentReader = rdr\n\t}\n\treturn h.handleAPKFileContent(ctx, contentReader, file.Name, apkChan)\n}\n\n// handleAPKFileContent sends the extracted data to the provided channel via the handleNonArchiveContent function.\nfunc (h *apkHandler) handleAPKFileContent(\n\tctx logContext.Context,\n\trdr io.Reader,\n\tfileName string,\n\tapkChan chan DataOrErr,\n) error {\n\tmimeReader, err := newMimeTypeReader(rdr)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create mimeTypeReader for file %s: %w\", fileName, err)\n\t}\n\tctx = logContext.WithValues(\n\t\tctx,\n\t\t\"filename\", fileName,\n\t)\n\treturn h.handleNonArchiveContent(ctx, mimeReader, apkChan)\n}\n\n// createZipReader creates a new ZIP reader from the input fileReader.\nfunc createZipReader(input fileReader) (*zip.Reader, error) {\n\tsize, err := input.Size()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tzipReader, err := zip.NewReader(input, size)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn zipReader, err\n}\n\n// parseResTable parses the resources.arsc file and returns the ResourceTable.\nfunc parseResTable(zipReader *zip.Reader) (*apkparser.ResourceTable, error) {\n\tfor _, file := range zipReader.File {\n\t\tif file.Name == \"resources.arsc\" {\n\t\t\trdr, err := openFile(file)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tresTable, err := apkparser.ParseResourceTable(rdr)\n\t\t\trdr.Close()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\treturn resTable, nil\n\t\t}\n\t}\n\treturn nil, errors.New(\"resources.arsc file not found in the APK archive\")\n}\n\n// openFile opens the file from the zip archive and returns the data as an io.ReadCloser\n// Note: responsibility of calling function to close the reader\nfunc openFile(file *zip.File) (io.ReadCloser, error) {\n\trc, err := file.Open()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn rc, nil\n}\n\n// extractStringsFromResTable extracts the strings from the resources table\n// Note: This is a hacky way to get the strings from the resources table\n// APK strings are typically (always?) stored in the 0x7f000000-0x7fffffff range\n// https://chromium.googlesource.com/chromium/src/+/master/build/android/docs/life_of_a_resource.md\nfunc extractStringsFromResTable(resTable *apkparser.ResourceTable) (io.Reader, error) {\n\tvar resourceStrings bytes.Buffer\n\tinStrings := false\n\tfor i := 0x7f000000; i <= 0x7fffffff; i++ {\n\t\tentry, _ := resTable.GetResourceEntry(uint32(i))\n\t\tif entry == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif entry.ResourceType == \"string\" {\n\t\t\tinStrings = true\n\t\t\tval, err := entry.GetValue().String()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\t// Write directly to the buffer\n\t\t\tresourceStrings.WriteString(entry.Key)\n\t\t\tresourceStrings.WriteString(\": \")\n\t\t\tresourceStrings.WriteString(val)\n\t\t\tresourceStrings.WriteString(\"\\n\")\n\t\t}\n\t\t// Exit the loop if we've finished processing the strings\n\t\tif inStrings && entry.ResourceType != \"string\" {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn &resourceStrings, nil\n}\n\n// processDexFile decodes the dex file and returns the relevant instructions\nfunc (h *apkHandler) processDexFile(ctx logContext.Context, rdr io.ReaderAt) (io.Reader, error) {\n\tdexReader, err := dextk.Read(rdr, dextk.WithReadCache(16))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Get relevant instruction data from the dex file\n\tvar dexOutput bytes.Buffer\n\tci := dexReader.ClassIter()\n\tfor ci.HasNext() {\n\t\tnode, err := ci.Next()\n\t\tif err != nil {\n\t\t\tctx.Logger().Error(err, \"failed to process a dex class\")\n\t\t\tbreak\n\t\t}\n\t\th.processDexClass(ctx, dexReader, node, &dexOutput)\n\t}\n\n\treturn &dexOutput, nil\n}\n\n// processDexClass processes a single class node's methods\nfunc (h *apkHandler) processDexClass(\n\tctx logContext.Context,\n\tdexReader *dextk.Reader,\n\tnode dextk.ClassNode,\n\tdexOutput *bytes.Buffer,\n) {\n\tvar classOutput bytes.Buffer\n\tmethodValues := make(map[string]struct{})\n\n\t// Process Direct Methods\n\tprocessDexMethod(ctx, dexReader, node.DirectMethods, &classOutput, methodValues)\n\t// Process Virtual Methods\n\tprocessDexMethod(ctx, dexReader, node.VirtualMethods, &classOutput, methodValues)\n\n\t// Write the classOutput to the dexOutput\n\tdexOutput.Write(classOutput.Bytes())\n\n\t// Check if classOutput contains any of the default keywords\n\tfoundKeywords := h.keywordMatcher.FindKeywords(classOutput.Bytes())\n\n\t// For each found keyword, create a keyword:value pair and append to dexOutput\n\tfor str := range methodValues {\n\t\tfor _, keyword := range foundKeywords {\n\t\t\tdexOutput.WriteString(keyword + \":\" + str + \"\\n\")\n\t\t}\n\t}\n}\n\n// processDexMethod iterates over a slice of methods, processes each method,\n// handles errors, and writes the output to dexOutput.\nfunc processDexMethod(\n\tctx logContext.Context,\n\tdexReader *dextk.Reader,\n\tmethods []dextk.MethodNode,\n\tclassOutput *bytes.Buffer,\n\tmethodValues map[string]struct{},\n) {\n\tfor _, method := range methods {\n\t\ts, err := parseDexInstructions(dexReader, method, methodValues)\n\t\tif err != nil {\n\t\t\tctx.Logger().V(2).Info(\"failed to process dex method\", \"error\", err)\n\t\t\tcontinue\n\t\t}\n\t\tclassOutput.Write(s.Bytes())\n\t}\n}\n\n// parseDexInstructions processes a dex method and returns the string representation of the instruction\nfunc parseDexInstructions(r *dextk.Reader, m dextk.MethodNode, methodValues map[string]struct{}) (*bytes.Buffer, error) {\n\tvar instrBuf bytes.Buffer\n\n\tif m.CodeOff == 0 {\n\t\treturn &instrBuf, nil\n\t}\n\n\tc, err := r.ReadCodeAndParse(m.CodeOff)\n\tif err != nil {\n\t\treturn &instrBuf, err\n\t}\n\n\t// Iterate over the instructions and extract the relevant values\n\tfor _, o := range c.Ops {\n\t\toStr := o.String()\n\n\t\tinstructionType := getInstructionType(oStr)\n\t\tif instructionType == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tval := formatAndFilterInstruction(oStr)\n\t\tif val != \"\" {\n\t\t\tinstrBuf.WriteString(val + \"\\n\")\n\t\t\tif instructionType == stringInstructionType {\n\t\t\t\tmethodValues[val] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\treturn &instrBuf, nil\n}\n\n// getInstructionType checks for specific target instructions\nfunc getInstructionType(instruction string) string {\n\tfor _, t := range targetInstructionTypes {\n\t\tif strings.HasPrefix(instruction, t) {\n\t\t\treturn t\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// formatAndFilterInstruction looks for a match to our regex and returns it\n// Note: This is critical for ensuring secret + keyword are in close proximity.\n// If we expand the instructions we're looking at, this function will need to be updated.\nfunc formatAndFilterInstruction(line string) string {\n\tfor _, re := range reInstructions {\n\t\tmatches := re.FindStringSubmatch(line)\n\t\tif len(matches) > 1 {\n\t\t\treturn matches[1]\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc decodeXML(rdr io.ReadSeeker, resTable *apkparser.ResourceTable) (io.Reader, error) {\n\t// Create a buffer to store the formatted XML data\n\t// Note: in the future, consider a custom writer that spills to disk if the buffer gets too large\n\tvar buf bytes.Buffer\n\tenc := xml.NewEncoder(&buf)\n\n\t// Parse the XML data using the apkparser library + resource table\n\terr := apkparser.ParseXml(rdr, enc, resTable)\n\tif err == nil {\n\t\treturn &buf, nil\n\t}\n\n\t// If the error is due to plaintext XML, return the plaintext XML.\n\tif errors.Is(err, apkparser.ErrPlainTextManifest) {\n\t\tif _, err := rdr.Seek(0, io.SeekStart); err != nil {\n\t\t\treturn rdr, fmt.Errorf(\"error resetting reader after XML parsing error: %w\", err)\n\t\t}\n\t\treturn rdr, nil\n\t}\n\treturn nil, err\n}\n"
  },
  {
    "path": "pkg/handlers/apk_test.go",
    "content": "package handlers\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestAPKHandler(t *testing.T) {\n\ttests := map[string]struct {\n\t\tarchiveURL      string\n\t\texpectedChunks  int\n\t\texpectedSecrets int\n\t\tmatchString     string\n\t}{\n\t\t\"apk_with_3_leaked_keys\": {\n\t\t\tarchiveURL:     \"https://github.com/joeleonjr/leakyAPK/raw/refs/heads/main/aws_leak.apk\",\n\t\t\texpectedChunks: 942,\n\t\t\t// Note: the secret count is 4 instead of 3 b/c we're not actually running the secret detection engine,\n\t\t\t// we're just looking for a string match. There is one extra string match in the APK (but only 3 detected secrets).\n\t\t\texpectedSecrets: 4,\n\t\t\tmatchString:     \"AKIA2UC3BSXMLSCLTUUS\",\n\t\t},\n\t}\n\n\tfor name, testCase := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tresp, err := http.Get(testCase.archiveURL)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Equal(t, http.StatusOK, resp.StatusCode)\n\t\t\tdefer resp.Body.Close()\n\n\t\t\thandler := newAPKHandler()\n\n\t\t\tnewReader, err := newFileReader(context.Background(), resp.Body)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error creating reusable reader: %s\", err)\n\t\t\t}\n\t\t\tdefer newReader.Close()\n\n\t\t\tarchiveChan := handler.HandleFile(context.Background(), newReader)\n\n\t\t\tchunkCount := 0\n\t\t\tsecretCount := 0\n\t\t\tre := regexp.MustCompile(testCase.matchString)\n\t\t\tmatched := false\n\t\t\tfor chunk := range archiveChan {\n\t\t\t\tchunkCount++\n\t\t\t\tif re.Match(chunk.Data) {\n\t\t\t\t\tsecretCount++\n\t\t\t\t\tmatched = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tassert.True(t, matched)\n\t\t\t// The APK handler's chunk count may increase over time as new keywords are added\n\t\t\t// as the default detector list grows. We use GreaterOrEqual to ensure the test remains\n\t\t\t// stable while allowing for this expected growth.\n\t\t\tassert.GreaterOrEqual(t, chunkCount, testCase.expectedChunks)\n\t\t\tassert.Equal(t, testCase.expectedSecrets, secretCount)\n\t\t})\n\t}\n}\n\nfunc TestOpenInvalidAPK(t *testing.T) {\n\treader := strings.NewReader(\"invalid apk\")\n\n\tctx := context.AddLogger(context.Background())\n\thandler := apkHandler{}\n\n\trdr, err := newFileReader(ctx, io.NopCloser(reader))\n\tassert.NoError(t, err)\n\tdefer rdr.Close()\n\n\tarchiveChan := make(chan DataOrErr)\n\n\terr = handler.processAPK(ctx, rdr, archiveChan)\n\tassert.Contains(t, err.Error(), \"zip: not a valid zip file\")\n}\n\nfunc TestOpenValidZipInvalidAPK(t *testing.T) {\n\t// Grabbed from archive_test.go\n\tvalidZipURL := \"https://raw.githubusercontent.com/bill-rich/bad-secrets/master/aws-canary-creds.zip\"\n\n\tresp, err := http.Get(validZipURL)\n\tassert.NoError(t, err)\n\tassert.Equal(t, http.StatusOK, resp.StatusCode)\n\tdefer resp.Body.Close()\n\n\thandler := newAPKHandler()\n\n\tnewReader, err := newFileReader(context.Background(), resp.Body)\n\tif err != nil {\n\t\tt.Errorf(\"error creating reusable reader: %s\", err)\n\t}\n\tassert.NoError(t, err)\n\tdefer newReader.Close()\n\n\tarchiveChan := make(chan DataOrErr)\n\tctx := context.AddLogger(context.Background())\n\n\terr = handler.processAPK(ctx, newReader, archiveChan)\n\tassert.Contains(t, err.Error(), \"resources.arsc file not found\")\n}\n"
  },
  {
    "path": "pkg/handlers/ar.go",
    "content": "package handlers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n\n\t\"pault.ag/go/debian/deb\"\n\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/feature\"\n)\n\n// arHandler handles AR archive formats.\ntype arHandler struct{ *defaultHandler }\n\n// newARHandler creates an arHandler.\nfunc newARHandler() *arHandler {\n\treturn &arHandler{defaultHandler: newDefaultHandler(arHandlerType)}\n}\n\n// HandleFile processes AR formatted files and returns a channel of DataOrErr.\n// Fatal errors that will terminate processing include:\n// - Context cancellation\n// - Context deadline exceeded\n// - Errors loading the AR file\n// - Panics during processing (recovered and returned as fatal errors)\n//\n// Non-fatal errors that will be logged but allow processing to continue include:\n// - Errors creating mime-type readers for individual AR entries\n// - Errors handling content within AR entries\nfunc (h *arHandler) HandleFile(ctx logContext.Context, input fileReader) chan DataOrErr {\n\tdataOrErrChan := make(chan DataOrErr, defaultBufferSize)\n\n\tif feature.ForceSkipArchives.Load() {\n\t\tclose(dataOrErrChan)\n\t\treturn dataOrErrChan\n\t}\n\n\tgo func() {\n\t\tdefer close(dataOrErrChan)\n\n\t\t// Defer a panic recovery to handle any panics that occur during the AR processing.\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tvar panicErr error\n\t\t\t\tif e, ok := r.(error); ok {\n\t\t\t\t\tpanicErr = e\n\t\t\t\t} else {\n\t\t\t\t\tpanicErr = fmt.Errorf(\"panic occurred: %v\", r)\n\t\t\t\t}\n\t\t\t\tdataOrErrChan <- DataOrErr{\n\t\t\t\t\tErr: fmt.Errorf(\"%w: panic error: %v\", ErrProcessingFatal, panicErr),\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\tstart := time.Now()\n\t\tarReader, err := deb.LoadAr(input)\n\t\tif err != nil {\n\t\t\tdataOrErrChan <- DataOrErr{\n\t\t\t\tErr: fmt.Errorf(\"%w: loading AR error: %v\", ErrProcessingFatal, err),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = h.processARFiles(ctx, arReader, dataOrErrChan)\n\t\tif err == nil {\n\t\t\th.metrics.incFilesProcessed()\n\t\t}\n\n\t\t// Update the metrics for the file processing and handle any errors.\n\t\th.measureLatencyAndHandleErrors(ctx, start, err, dataOrErrChan)\n\t}()\n\n\treturn dataOrErrChan\n}\n\nfunc (h *arHandler) processARFiles(ctx logContext.Context, reader *deb.Ar, dataOrErrChan chan DataOrErr) error {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\tdefault:\n\t\t\tarEntry, err := reader.Next()\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\t\tctx.Logger().V(3).Info(\"AR archive fully processed\")\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\treturn fmt.Errorf(\"error reading AR payload: %w\", err)\n\t\t\t}\n\n\t\t\tfileSize := arEntry.Size\n\t\t\tfileCtx := logContext.WithValues(ctx, \"filename\", arEntry.Name, \"size\", fileSize)\n\n\t\t\trdr, err := newMimeTypeReader(arEntry.Data)\n\t\t\tif err != nil {\n\t\t\t\tdataOrErrChan <- DataOrErr{\n\t\t\t\t\tErr: fmt.Errorf(\"%w: error creating AR mime-type reader: %v\", ErrProcessingWarning, err),\n\t\t\t\t}\n\t\t\t\th.metrics.incErrors()\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif err := h.handleNonArchiveContent(fileCtx, rdr, dataOrErrChan); err != nil {\n\t\t\t\tdataOrErrChan <- DataOrErr{\n\t\t\t\t\tErr: fmt.Errorf(\"%w: error handling archive content in AR: %v\", ErrProcessingWarning, err),\n\t\t\t\t}\n\t\t\t\th.metrics.incErrors()\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\th.metrics.incFilesProcessed()\n\t\t\th.metrics.observeFileSize(fileSize)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/handlers/ar_test.go",
    "content": "package handlers\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestHandleARFile(t *testing.T) {\n\tfile, err := os.Open(\"testdata/test.deb\")\n\tassert.Nil(t, err)\n\tdefer file.Close()\n\n\tctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)\n\tdefer cancel()\n\n\trdr, err := newFileReader(ctx, file)\n\tassert.NoError(t, err)\n\tdefer rdr.Close()\n\n\thandler := newARHandler()\n\tdataOrErrChan := handler.HandleFile(context.AddLogger(ctx), rdr)\n\tassert.NoError(t, err)\n\n\twantChunkCount := 102\n\tcount := 0\n\tfor range dataOrErrChan {\n\t\tcount++\n\t}\n\n\tassert.Equal(t, wantChunkCount, count)\n}\n"
  },
  {
    "path": "pkg/handlers/archive.go",
    "content": "package handlers\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/mholt/archives\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/feature\"\n)\n\ntype ctxKey int\n\nconst (\n\tdepthKey          ctxKey = iota\n\tdefaultBufferSize        = 512\n)\n\nvar (\n\t// NOTE: This is a temporary workaround for |openArchive| incrementing depth twice per archive.\n\t// See: https://github.com/trufflesecurity/trufflehog/issues/2942\n\tmaxDepth   = 5 * 2\n\tmaxSize    = 2 << 30 // 2 GB\n\tmaxTimeout = time.Duration(60) * time.Second\n)\n\n// SetArchiveMaxSize sets the maximum size of the archive.\nfunc SetArchiveMaxSize(size int) { maxSize = size }\n\n// SetArchiveMaxDepth sets the maximum depth of the archive.\nfunc SetArchiveMaxDepth(depth int) { maxDepth = depth }\n\n// SetArchiveMaxTimeout sets the maximum timeout for the archive handler.\nfunc SetArchiveMaxTimeout(timeout time.Duration) { maxTimeout = timeout }\n\n// archiveHandler is a handler for common archive files that are supported by the archiver library.\ntype archiveHandler struct{ *defaultHandler }\n\nfunc newArchiveHandler() *archiveHandler {\n\treturn &archiveHandler{defaultHandler: newDefaultHandler(archiveHandlerType)}\n}\n\n// HandleFile processes archive files and returns a channel of DataOrErr.\n//\n// Fatal errors that will terminate processing include:\n// - Context cancellation\n// - Context deadline exceeded\n// - Panics during archive processing (recovered and returned as fatal errors)\n// - Maximum archive depth exceeded\n// - Unknown archive formats\n// - Errors opening decompressors\n// - Errors creating readers for decompressed content\n// - Errors during archive extraction\n//\n// Non-fatal errors that will be logged but allow processing to continue include:\n// - Empty readers encountered during nested archive processing\n// - Files exceeding maximum size limits\n// - Files with ignored extensions or binary content\n// - Errors opening individual files within archives\nfunc (h *archiveHandler) HandleFile(ctx logContext.Context, input fileReader) chan DataOrErr {\n\tdataOrErrChan := make(chan DataOrErr, defaultBufferSize)\n\n\tif feature.ForceSkipArchives.Load() {\n\t\tclose(dataOrErrChan)\n\t\treturn dataOrErrChan\n\t}\n\n\tgo func() {\n\t\tdefer close(dataOrErrChan)\n\n\t\t// The underlying 7zip library may panic when attempting to open an archive.\n\t\t// This is due to an Index Out Of Range (IOOR) error when reading the archive header.\n\t\t// See: https://github.com/bodgit/sevenzip/blob/74bff0da9b233317e4ea7dd8c184a315db71af2a/types.go#L846\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tvar panicErr error\n\t\t\t\tif e, ok := r.(error); ok {\n\t\t\t\t\tpanicErr = e\n\t\t\t\t} else {\n\t\t\t\t\tpanicErr = fmt.Errorf(\"panic occurred: %v\", r)\n\t\t\t\t}\n\t\t\t\tdataOrErrChan <- DataOrErr{\n\t\t\t\t\tErr: fmt.Errorf(\"%w: panic error: %v\", ErrProcessingFatal, panicErr),\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\tstart := time.Now()\n\t\terr := h.openArchive(ctx, 0, input, dataOrErrChan)\n\t\tif err == nil {\n\t\t\th.metrics.incFilesProcessed()\n\t\t}\n\n\t\t// Update the metrics for the file processing and handle any errors.\n\t\th.measureLatencyAndHandleErrors(ctx, start, err, dataOrErrChan)\n\t}()\n\n\treturn dataOrErrChan\n}\n\nvar ErrMaxDepthReached = errors.New(\"max archive depth reached\")\n\n// openArchive recursively extracts content from an archive up to a maximum depth, handling nested archives if necessary.\n// It takes a reader from which it attempts to identify and process the archive format. Depending on the archive type,\n// it either decompresses or extracts the contents directly, sending data to the provided channel.\n// Returns an error if the archive cannot be processed due to issues like exceeding maximum depth or unsupported formats.\nfunc (h *archiveHandler) openArchive(\n\tctx logContext.Context,\n\tdepth int,\n\treader fileReader,\n\tdataOrErrChan chan DataOrErr,\n) error {\n\tctx.Logger().V(4).Info(\"Starting archive processing\", \"depth\", depth)\n\tdefer ctx.Logger().V(4).Info(\"Finished archive processing\", \"depth\", depth)\n\n\tif common.IsDone(ctx) {\n\t\treturn ctx.Err()\n\t}\n\n\tif depth >= maxDepth {\n\t\th.metrics.incMaxArchiveDepthCount()\n\t\treturn ErrMaxDepthReached\n\t}\n\n\tif reader.format == nil {\n\t\tif depth > 0 {\n\t\t\treturn h.handleNonArchiveContent(ctx, newMimeTypeReaderFromFileReader(reader), dataOrErrChan)\n\t\t}\n\t\treturn fmt.Errorf(\"unknown archive format\")\n\t}\n\n\tswitch archive := reader.format.(type) {\n\tcase archives.Decompressor:\n\t\t// Decompress tha archive and feed the decompressed data back into the archive handler to extract any nested archives.\n\t\tcompReader, err := archive.OpenReader(reader)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error opening decompressor with format: %s %w\", reader.format.MediaType(), err)\n\t\t}\n\t\tdefer compReader.Close()\n\n\t\trdr, err := newFileReader(ctx, compReader)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, ErrEmptyReader) {\n\t\t\t\tctx.Logger().V(5).Info(\"empty reader, skipping file\")\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn fmt.Errorf(\n\t\t\t\t\"error creating reader for decompressor with format: %s %w\",\n\t\t\t\treader.format.MediaType(),\n\t\t\t\terr,\n\t\t\t)\n\t\t}\n\t\tdefer rdr.Close()\n\n\t\treturn h.openArchive(ctx, depth+1, rdr, dataOrErrChan)\n\tcase archives.Extractor:\n\t\terr := archive.Extract(logContext.WithValue(ctx, depthKey, depth+1), reader, h.extractorHandler(dataOrErrChan))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error extracting archive with format: %s: %w\", reader.format.MediaType(), err)\n\t\t}\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown archive type: %s\", reader.format.MediaType())\n\t}\n}\n\n// extractorHandler creates a closure that handles individual files extracted by an archiver.\n// It logs the extraction, checks for cancellation, and decides whether to skip the file based on its name or type,\n// particularly for binary files if configured to skip. If the file is not skipped, it recursively calls openArchive\n// to handle nested archives or to continue processing based on the file's content and depth in the archive structure.\nfunc (h *archiveHandler) extractorHandler(dataOrErrChan chan DataOrErr) func(context.Context, archives.FileInfo) error {\n\treturn func(ctx context.Context, file archives.FileInfo) error {\n\t\tif common.IsDone(ctx) {\n\t\t\treturn ctx.Err()\n\t\t}\n\n\t\tlCtx := logContext.WithValues(\n\t\t\tlogContext.AddLogger(ctx),\n\t\t\t\"filename\", file.Name(),\n\t\t\t\"size\", file.Size(),\n\t\t)\n\n\t\tif file.IsDir() || file.LinkTarget != \"\" {\n\t\t\tlCtx.Logger().V(4).Info(\"skipping directory or symlink\")\n\t\t\treturn nil\n\t\t}\n\n\t\tdepth := 0\n\t\tif ctxDepth, ok := ctx.Value(depthKey).(int); ok {\n\t\t\tdepth = ctxDepth\n\t\t}\n\n\t\tfileSize := file.Size()\n\t\tif int(fileSize) > maxSize {\n\t\t\tlCtx.Logger().V(2).Info(\"skipping file: size exceeds max allowed\", \"size\", fileSize, \"limit\", maxSize)\n\t\t\th.metrics.incFilesSkipped()\n\t\t\treturn nil\n\t\t}\n\n\t\tif common.SkipFile(file.Name()) || common.IsBinary(file.Name()) {\n\t\t\tlCtx.Logger().V(4).Info(\"skipping file: extension is ignored\")\n\t\t\th.metrics.incFilesSkipped()\n\t\t\treturn nil\n\t\t}\n\n\t\tf, err := file.Open()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error opening file %s: %w\", file.Name(), err)\n\t\t}\n\t\tdefer f.Close()\n\n\t\t// Archiver v4 is in alpha and using an experimental version of\n\t\t// rardecode. There is a bug somewhere with rar decoder format 29\n\t\t// that can lead to a panic. An issue is open in rardecode repo\n\t\t// https://github.com/nwaples/rardecode/issues/30.\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\t// Return the panic as an error.\n\t\t\t\tif e, ok := r.(error); ok {\n\t\t\t\t\terr = e\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"panic occurred: %v\", r)\n\t\t\t\t}\n\t\t\t\tlCtx.Logger().Error(err, \"Panic occurred when reading archive\")\n\t\t\t}\n\t\t}()\n\n\t\trdr, err := newFileReader(ctx, f)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, ErrEmptyReader) {\n\t\t\t\tlCtx.Logger().V(5).Info(\"empty reader, skipping file\")\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"error creating reader for file %s: %w\", file.Name(), err)\n\t\t}\n\t\tdefer rdr.Close()\n\n\t\th.metrics.incFilesProcessed()\n\t\th.metrics.observeFileSize(fileSize)\n\n\t\tlCtx.Logger().V(4).Info(\"Opened file successfully\", \"filename\", file.Name(), \"size\", file.Size())\n\t\treturn h.openArchive(lCtx, depth, rdr, dataOrErrChan)\n\t}\n}\n"
  },
  {
    "path": "pkg/handlers/archive_test.go",
    "content": "package handlers\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestArchiveHandler(t *testing.T) {\n\ttests := map[string]struct {\n\t\tarchiveURL     string\n\t\texpectedChunks int\n\t\tmatchString    string\n\t\texpectErr      bool\n\t}{\n\t\t\"gzip-single\": {\n\t\t\t\"https://raw.githubusercontent.com/bill-rich/bad-secrets/master/one-zip.gz\",\n\t\t\t1,\n\t\t\t\"AKIAYVP4CIPPH5TNP3SW\",\n\t\t\tfalse,\n\t\t},\n\t\t\"gzip-nested\": {\n\t\t\t\"https://raw.githubusercontent.com/bill-rich/bad-secrets/master/double-zip.gz\",\n\t\t\t1,\n\t\t\t\"AKIAYVP4CIPPH5TNP3SW\",\n\t\t\tfalse,\n\t\t},\n\t\t\"gzip-too-deep\": {\n\t\t\t\"https://raw.githubusercontent.com/bill-rich/bad-secrets/master/six-zip.gz\",\n\t\t\t0,\n\t\t\t\"\",\n\t\t\ttrue,\n\t\t},\n\t\t\"tar-single\": {\n\t\t\t\"https://raw.githubusercontent.com/bill-rich/bad-secrets/master/one.tar\",\n\t\t\t1,\n\t\t\t\"AKIAYVP4CIPPH5TNP3SW\",\n\t\t\tfalse,\n\t\t},\n\t\t\"tar-nested\": {\n\t\t\t\"https://raw.githubusercontent.com/bill-rich/bad-secrets/master/two.tar\",\n\t\t\t1,\n\t\t\t\"AKIAYVP4CIPPH5TNP3SW\",\n\t\t\tfalse,\n\t\t},\n\t\t\"tar-too-deep\": {\n\t\t\t\"https://raw.githubusercontent.com/bill-rich/bad-secrets/master/six.tar\",\n\t\t\t0,\n\t\t\t\"\",\n\t\t\ttrue,\n\t\t},\n\t\t\"targz-single\": {\n\t\t\t\"https://raw.githubusercontent.com/bill-rich/bad-secrets/master/tar-archive.tar.gz\",\n\t\t\t1,\n\t\t\t\"AKIAYVP4CIPPH5TNP3SW\",\n\t\t\tfalse,\n\t\t},\n\t\t\"gzip-large\": {\n\t\t\t\"https://raw.githubusercontent.com/bill-rich/bad-secrets/master/FifteenMB.gz\",\n\t\t\t1543,\n\t\t\t\"AKIAYVP4CIPPH5TNP3SW\",\n\t\t\tfalse,\n\t\t},\n\t\t\"zip-single\": {\n\t\t\t\"https://raw.githubusercontent.com/bill-rich/bad-secrets/master/aws-canary-creds.zip\",\n\t\t\t1,\n\t\t\t\"AKIAYVP4CIPPH5TNP3SW\",\n\t\t\tfalse,\n\t\t},\n\t}\n\n\tfor name, testCase := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tresp, err := http.Get(testCase.archiveURL)\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Equal(t, http.StatusOK, resp.StatusCode)\n\t\t\tdefer resp.Body.Close()\n\n\t\t\thandler := newArchiveHandler()\n\n\t\t\tnewReader, err := newFileReader(context.Background(), resp.Body)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"error creating reusable reader: %s\", err)\n\t\t\t}\n\t\t\tdefer newReader.Close()\n\n\t\t\tdataOrErrChan := handler.HandleFile(logContext.Background(), newReader)\n\t\t\tif testCase.expectErr {\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tcount := 0\n\t\t\tre := regexp.MustCompile(testCase.matchString)\n\t\t\tmatched := false\n\t\t\tfor chunk := range dataOrErrChan {\n\t\t\t\tcount++\n\t\t\t\tif re.Match(chunk.Data) {\n\t\t\t\t\tmatched = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tassert.True(t, matched)\n\t\t\tassert.Equal(t, testCase.expectedChunks, count)\n\t\t})\n\t}\n}\n\nfunc TestOpenInvalidArchive(t *testing.T) {\n\treader := strings.NewReader(\"invalid archive\")\n\n\tctx := logContext.AddLogger(context.Background())\n\thandler := archiveHandler{}\n\n\trdr, err := newFileReader(ctx, io.NopCloser(reader))\n\tassert.NoError(t, err)\n\tdefer rdr.Close()\n\n\tdataOrErrChan := make(chan DataOrErr)\n\n\terr = handler.openArchive(ctx, 0, rdr, dataOrErrChan)\n\tassert.Error(t, err)\n}\n"
  },
  {
    "path": "pkg/handlers/default.go",
    "content": "package handlers\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\n// defaultHandler is a handler for non-archive files.\n// It is embedded in other specialized handlers to provide a consistent way of handling non-archive content\n// once it has been extracted or decompressed by the specific handler.\n// This allows the specialized handlers to focus on their specific archive formats while leveraging\n// the common functionality provided by the defaultHandler for processing the extracted content.\ntype defaultHandler struct {\n\tchunkReader sources.ChunkReader\n\tmetrics     *metrics\n}\n\n// defaultHandlerOption is a functional option for configuring a defaultHandler.\ntype defaultHandlerOption func(*defaultHandler)\n\n// withChunkReader sets a custom ChunkReader for the handler.\n// This is primarily used for testing to inject mock chunk readers.\nfunc withChunkReader(cr sources.ChunkReader) defaultHandlerOption {\n\treturn func(h *defaultHandler) { h.chunkReader = cr }\n}\n\n// newDefaultHandler creates a defaultHandler with metrics configured based on the provided handlerType.\n// The handlerType parameter is used to initialize the metrics instance with the appropriate handler type,\n// ensuring that the metrics recorded within the defaultHandler methods are correctly attributed to the\n// specific handler that invoked them.\nfunc newDefaultHandler(handlerType handlerType, opts ...defaultHandlerOption) *defaultHandler {\n\th := &defaultHandler{\n\t\tchunkReader: sources.NewChunkReader(),\n\t\tmetrics:     newHandlerMetrics(handlerType),\n\t}\n\tfor _, opt := range opts {\n\t\topt(h)\n\t}\n\treturn h\n}\n\n// HandleFile processes non-archive files.\n//\n// Fatal errors that will terminate processing include:\n// - Context cancellation\n// - Context deadline exceeded\n// - Errors writing to the data channel\n//\n// Non-fatal errors that will be logged but allow processing to continue include:\n// - Errors reading individual chunks from the input (wrapped as ErrProcessingWarning)\nfunc (h *defaultHandler) HandleFile(ctx logContext.Context, input fileReader) chan DataOrErr {\n\t// Shared channel for both archive and non-archive content.\n\tdataOrErrChan := make(chan DataOrErr, defaultBufferSize)\n\n\tgo func() {\n\t\tdefer close(dataOrErrChan)\n\n\t\tstart := time.Now()\n\t\terr := h.handleNonArchiveContent(ctx, newMimeTypeReaderFromFileReader(input), dataOrErrChan)\n\t\tif err == nil {\n\t\t\th.metrics.incFilesProcessed()\n\t\t}\n\n\t\t// Update the metrics for the file processing and handle errors.\n\t\th.measureLatencyAndHandleErrors(ctx, start, err, dataOrErrChan)\n\t}()\n\n\treturn dataOrErrChan\n}\n\n// measureLatencyAndHandleErrors measures the latency of the file processing and updates the metrics accordingly.\n// It also records errors and timeouts in the metrics.\nfunc (h *defaultHandler) measureLatencyAndHandleErrors(\n\tctx logContext.Context,\n\tstart time.Time,\n\terr error,\n\tdataErrChan chan<- DataOrErr,\n) {\n\tif err == nil {\n\t\th.metrics.observeHandleFileLatency(time.Since(start).Milliseconds())\n\t\treturn\n\t}\n\tdataOrErr := DataOrErr{}\n\n\th.metrics.incErrors()\n\tif errors.Is(err, context.DeadlineExceeded) {\n\t\th.metrics.incFileProcessingTimeouts()\n\t\tdataOrErr.Err = fmt.Errorf(\"%w: error processing chunk\", err)\n\t\tif err := common.CancellableWrite(ctx, dataErrChan, dataOrErr); err != nil {\n\t\t\tctx.Logger().Error(err, \"error writing to data channel\")\n\t\t}\n\t\treturn\n\t}\n\n\tdataOrErr.Err = err\n\tif err := common.CancellableWrite(ctx, dataErrChan, dataOrErr); err != nil {\n\t\tctx.Logger().Error(err, \"error writing to data channel\")\n\t}\n}\n\n// handleNonArchiveContent processes files that do not contain nested archives, serving as the final stage in the\n// extraction/decompression process. It reads the content to detect its MIME type and decides whether to skip based\n// on the type, particularly for binary files. It manages reading file chunks and writing them to the archive channel,\n// effectively collecting the final bytes for further processing. This function is a key component in ensuring that all\n// file content, regardless of being an archive or not, is handled appropriately.\n// It also tracks line numbers for each chunk to enable accurate line number reporting for secrets.\nfunc (h *defaultHandler) handleNonArchiveContent(\n\tctx logContext.Context,\n\treader mimeTypeReader,\n\tdataOrErrChan chan DataOrErr,\n) error {\n\tmimeExt := reader.mimeExt\n\n\tif common.SkipFile(mimeExt) || common.IsBinary(mimeExt) {\n\t\tctx.Logger().V(4).Info(\"skipping file: extension is ignored\", \"ext\", mimeExt)\n\t\th.metrics.incFilesSkipped()\n\t\t// Make sure we consume the reader to avoid potentially blocking indefinitely.\n\t\t_, _ = io.Copy(io.Discard, reader)\n\t\treturn nil\n\t}\n\n\t// Track the current line number (1-indexed) across chunks.\n\t// This allows accurate line number reporting for secrets found in later chunks.\n\tcurrentLine := int64(1)\n\tfor chunkResult := range h.chunkReader(ctx, reader) {\n\t\tdataOrErr := DataOrErr{}\n\t\tif err := chunkResult.Error(); err != nil {\n\t\t\th.metrics.incErrors()\n\t\t\tdataOrErr.Err = fmt.Errorf(\"%w: error reading chunk: %v\", ErrProcessingWarning, err)\n\t\t\tif writeErr := common.CancellableWrite(ctx, dataOrErrChan, dataOrErr); writeErr != nil {\n\t\t\t\treturn fmt.Errorf(\"%w: error writing to data channel: %v\", ErrProcessingFatal, writeErr)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tchunkBytes := chunkResult.Bytes()\n\t\tdataOrErr.Data = chunkBytes\n\t\tdataOrErr.LineNumber = currentLine\n\t\tif err := common.CancellableWrite(ctx, dataOrErrChan, dataOrErr); err != nil {\n\t\t\treturn err\n\t\t}\n\t\th.metrics.incBytesProcessed(len(chunkBytes))\n\n\t\t// Count newlines in the content portion only (excluding peek chunkResult) to avoid\n\t\t// double-counting newlines that will appear at the start of the next chunk.\n\t\tcontentSize := chunkResult.ContentSize()\n\t\tcurrentLine += int64(bytes.Count(chunkBytes[:contentSize], []byte{'\\n'}))\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/handlers/default_test.go",
    "content": "package handlers\n\nimport (\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestHandleNonArchiveFile(t *testing.T) {\n\tfile, err := os.Open(\"testdata/nonarchive.txt\")\n\tassert.Nil(t, err)\n\tdefer file.Close()\n\n\tctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)\n\tdefer cancel()\n\n\trdr, err := newFileReader(ctx, file)\n\tassert.NoError(t, err)\n\tdefer rdr.Close()\n\n\thandler := newDefaultHandler(defaultHandlerType)\n\tdataOrErrChan := handler.HandleFile(context.AddLogger(ctx), rdr)\n\tassert.NoError(t, err)\n\n\twantChunkCount := 6\n\tcount := 0\n\tfor range dataOrErrChan {\n\t\tcount++\n\t}\n\n\tassert.Equal(t, wantChunkCount, count)\n}\n\n// TestHandleFileLineNumbers verifies that line numbers are correctly tracked\n// across multiple chunks when processing filesystem files.\n// This is a regression test for https://github.com/trufflesecurity/trufflehog/issues/1876\nfunc TestHandleFileLineNumbers(t *testing.T) {\n\tt.Run(\"single chunk starts at line 1\", func(t *testing.T) {\n\t\t// Create a mock chunk reader with one chunk containing 3 lines.\n\t\tchunks := []sources.ChunkResult{\n\t\t\tsources.NewChunkResult([]byte(\"line1\\nline2\\nline3\\n\"), 18),\n\t\t}\n\n\t\thandler := newDefaultHandler(defaultHandlerType, withChunkReader(mockChunkReader(chunks)))\n\t\treader, err := newFileReader(context.Background(), strings.NewReader(\"ignored\"))\n\t\trequire.NoError(t, err)\n\n\t\tvar results []DataOrErr\n\t\tfor dataOrErr := range handler.HandleFile(context.Background(), reader) {\n\t\t\tresults = append(results, dataOrErr)\n\t\t}\n\n\t\trequire.Len(t, results, 1)\n\t\tassert.Equal(t, int64(1), results[0].LineNumber, \"first chunk should start at line 1\")\n\t})\n\n\tt.Run(\"multiple chunks track line numbers correctly\", func(t *testing.T) {\n\t\t// Create mock chunks with known newline counts.\n\t\t// Chunk 1: 10 lines (contentSize covers all data)\n\t\t// Chunk 2: 5 lines\n\t\t// Chunk 3: 3 lines\n\t\tchunk1Data := []byte(strings.Repeat(\"line\\n\", 10)) // 10 newlines\n\t\tchunk2Data := []byte(strings.Repeat(\"line\\n\", 5))  // 5 newlines\n\t\tchunk3Data := []byte(strings.Repeat(\"line\\n\", 3))  // 3 newlines\n\n\t\tchunks := []sources.ChunkResult{\n\t\t\tsources.NewChunkResult(chunk1Data, len(chunk1Data)),\n\t\t\tsources.NewChunkResult(chunk2Data, len(chunk2Data)),\n\t\t\tsources.NewChunkResult(chunk3Data, len(chunk3Data)),\n\t\t}\n\n\t\thandler := newDefaultHandler(defaultHandlerType, withChunkReader(mockChunkReader(chunks)))\n\t\treader, err := newFileReader(context.Background(), strings.NewReader(\"ignored\"))\n\t\trequire.NoError(t, err)\n\n\t\tvar results []DataOrErr\n\t\tfor dataOrErr := range handler.HandleFile(context.Background(), reader) {\n\t\t\tresults = append(results, dataOrErr)\n\t\t}\n\n\t\trequire.Len(t, results, 3)\n\t\tassert.Equal(t, int64(1), results[0].LineNumber, \"chunk 1 should start at line 1\")\n\t\tassert.Equal(t, int64(11), results[1].LineNumber, \"chunk 2 should start at line 11 (1 + 10)\")\n\t\tassert.Equal(t, int64(16), results[2].LineNumber, \"chunk 3 should start at line 16 (11 + 5)\")\n\t})\n\n\tt.Run(\"contentSize excludes peek data from line counting\", func(t *testing.T) {\n\t\t// Simulate peek overlap: chunk has 15 lines total but only 10 are content.\n\t\t// The remaining 5 are \"peek\" data that shouldn't be counted.\n\t\tfullData := []byte(strings.Repeat(\"line\\n\", 15))         // 15 newlines in data\n\t\tcontentSize := len([]byte(strings.Repeat(\"line\\n\", 10))) // Only 10 are content\n\n\t\tchunks := []sources.ChunkResult{\n\t\t\tsources.NewChunkResult(fullData, contentSize),\n\t\t\tsources.NewChunkResult([]byte(\"final\\n\"), 6),\n\t\t}\n\n\t\thandler := newDefaultHandler(defaultHandlerType, withChunkReader(mockChunkReader(chunks)))\n\t\treader, err := newFileReader(context.Background(), strings.NewReader(\"ignored\"))\n\t\trequire.NoError(t, err)\n\n\t\tvar results []DataOrErr\n\t\tfor dataOrErr := range handler.HandleFile(context.Background(), reader) {\n\t\t\tresults = append(results, dataOrErr)\n\t\t}\n\n\t\trequire.Len(t, results, 2)\n\t\tassert.Equal(t, int64(1), results[0].LineNumber)\n\t\t// Second chunk should start at line 11 (only 10 lines counted from first chunk's content)\n\t\tassert.Equal(t, int64(11), results[1].LineNumber, \"peek data should not be counted\")\n\t})\n}\n"
  },
  {
    "path": "pkg/handlers/handlers.go",
    "content": "package handlers\n\nimport (\n\t\"archive/zip\"\n\t\"bufio\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"path/filepath\"\n\n\t\"github.com/gabriel-vasile/mimetype\"\n\t\"github.com/mholt/archives\"\n\t\"google.golang.org/protobuf/proto\"\n\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/feature\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/iobuf\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\n// fileReader is a custom reader that wraps an io.Reader and provides additional functionality for identifying\n// and handling different file types. It abstracts away the complexity of detecting file formats, MIME types,\n// and archive types, allowing for a more modular and extensible file handling process.\n//\n// fileReader leverages the archiver and mimetype packages for file type identification and provides information\n// about the detected file format, MIME type, and whether the file is an archive. This information can be\n// used by FileHandler implementations to make decisions on how to process the file.\n//\n// The IsGenericArchive field indicates whether the file represents an archive format that is supported by the\n// archiver library. This allows FileHandler implementations to determine if the file can be processed using\n// the default archive handling capabilities provided by the archiver package.\n//\n// By encapsulating the file type detection logic, fileReader simplifies the implementation of FileHandler and\n// promotes a more cohesive and maintainable codebase. It also embeds a BufferedFileReader to provide efficient\n// random access to the file content.\ntype fileReader struct {\n\tformat           archives.Format\n\tmime             *mimetype.MIME\n\tisGenericArchive bool\n\n\t*iobuf.BufferedReadSeeker\n}\n\nvar (\n\tErrEmptyReader = errors.New(\"reader is empty\")\n\n\t// ErrProcessingFatal indicates a severe error that requires stopping the file processing.\n\tErrProcessingFatal = errors.New(\"fatal error processing file\")\n\n\t// ErrProcessingWarning indicates a recoverable error that can be logged,\n\t// allowing processing to continue.\n\tErrProcessingWarning = errors.New(\"error processing file\")\n)\n\ntype readerConfig struct{ fileExtension string }\n\ntype readerOption func(*readerConfig)\n\nfunc withFileExtension(ext string) readerOption {\n\treturn func(c *readerConfig) { c.fileExtension = ext }\n}\n\n// mimeTypeReader wraps an io.Reader with MIME type information.\n// This type is used to pass content through the processing pipeline\n// while carrying its detected MIME type, avoiding redundant type detection.\ntype mimeTypeReader struct {\n\tmimeExt  string\n\tmimeName mimeType\n\tio.Reader\n}\n\n// newMimeTypeReaderFromFileReader creates a new mimeTypeReader from a fileReader.\nfunc newMimeTypeReaderFromFileReader(r fileReader) mimeTypeReader {\n\treturn mimeTypeReader{\n\t\tmimeExt:  r.mime.Extension(),\n\t\tmimeName: mimeType(r.mime.String()),\n\t\tReader:   r.BufferedReadSeeker,\n\t}\n}\n\n// newMimeTypeReader creates a new mimeTypeReader from an io.Reader.\n// It uses a bufio.Reader to perform MIME type detection on the input reader\n// without consuming it, by peeking into the first 3072 bytes of the input.\n// This encapsulates both the original reader and the detected MIME type information.\n// This function is particularly useful for specialized archive handlers\n// that need to pass extracted content to the default handler without modifying the original reader.\nfunc newMimeTypeReader(r io.Reader) (mimeTypeReader, error) {\n\tconst defaultMinBufferSize = 3072\n\tbufReader := bufio.NewReaderSize(r, defaultMinBufferSize)\n\t// A buffer of 512 bytes is used since many file formats store their magic numbers within the first 512 bytes.\n\t// If fewer bytes are read, MIME type detection may still succeed.\n\tbuffer, err := bufReader.Peek(defaultMinBufferSize)\n\tif err != nil && !errors.Is(err, io.EOF) {\n\t\treturn mimeTypeReader{}, fmt.Errorf(\"unable to read file for MIME type detection: %w\", err)\n\t}\n\n\tmime := mimetype.Detect(buffer)\n\n\treturn mimeTypeReader{mimeExt: mime.Extension(), mimeName: mimeType(mime.String()), Reader: bufReader}, nil\n}\n\n// newFileReader creates a fileReader from an io.Reader, optionally using BufferedFileWriter for certain formats.\n// The caller is responsible for closing the reader when it is no longer needed.\nfunc newFileReader(ctx context.Context, r io.Reader, options ...readerOption) (fReader fileReader, err error) {\n\tvar cfg readerConfig\n\n\tfor _, opt := range options {\n\t\topt(&cfg)\n\t}\n\t// To detect the MIME type of the input data, we need a reader that supports seeking.\n\t// This allows us to read the data multiple times if necessary without losing the original position.\n\t// We use a BufferedReaderSeeker to wrap the original reader, enabling this functionality.\n\tfReader.BufferedReadSeeker = iobuf.NewBufferedReaderSeeker(r)\n\n\t// If an error occurs during MIME type detection, it is important we close the BufferedReaderSeeker\n\t// to release any resources it holds (checked out buffers or temp file).\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tif closeErr := fReader.Close(); closeErr != nil {\n\t\t\t\terr = fmt.Errorf(\"%w; error closing reader: %w\", err, closeErr)\n\t\t\t}\n\t\t}\n\t}()\n\n\tvar mime *mimetype.MIME\n\tmime, err = mimetype.DetectReader(fReader)\n\tif err != nil {\n\t\treturn fReader, fmt.Errorf(\"unable to detect MIME type: %w\", err)\n\t}\n\tfReader.mime = mime\n\n\t// Reset the reader to the beginning because DetectReader consumes the reader.\n\tif _, err = fReader.Seek(0, io.SeekStart); err != nil {\n\t\treturn fReader, fmt.Errorf(\"error resetting reader after MIME detection: %w\", err)\n\t}\n\n\t// Check for APK files\n\tif shouldHandleAsAPK(cfg, fReader) {\n\t\tisAPK, err := isAPKFile(&fReader)\n\t\tif err != nil {\n\t\t\treturn fReader, fmt.Errorf(\"error checking for APK: %w\", err)\n\t\t}\n\t\tif isAPK {\n\t\t\treturn handleAPKFile(&fReader)\n\t\t}\n\t}\n\n\t// If a MIME type is known to not be an archive type, we might as well return here rather than\n\t// paying the I/O penalty of an archiver.Identify() call that won't identify anything.\n\tif _, ok := skipArchiverMimeTypes[mimeType(mime.String())]; ok {\n\t\treturn fReader, nil\n\t}\n\n\tvar format archives.Format\n\tformat, _, err = archives.Identify(ctx, \"\", fReader)\n\tswitch {\n\tcase err == nil:\n\t\tfReader.isGenericArchive = true\n\t\tfReader.format = format\n\n\tcase errors.Is(err, archives.NoMatch):\n\t\t// Not an archive handled by archiver.\n\t\t// Continue with the default reader.\n\tdefault:\n\t\treturn fReader, fmt.Errorf(\"error identifying archive: %w\", err)\n\t}\n\n\t// Reset the reader to the beginning again to allow the handler to read from the start.\n\t// This is necessary because Identify consumes the reader.\n\tif _, err = fReader.Seek(0, io.SeekStart); err != nil {\n\t\treturn fReader, fmt.Errorf(\"error resetting reader after archive identification: %w\", err)\n\t}\n\n\treturn fReader, nil\n}\n\n// DataOrErr represents a result that can either contain data or an error.\n// The Data field holds the byte slice of data, and the Err field holds any error that occurred.\n// This structure is used to handle asynchronous file processing where each chunk of data\n// or potential error needs to be communicated back to the caller. It allows for\n// efficient streaming of file contents while also providing a way to propagate errors\n// that may occur during the file handling process.\ntype DataOrErr struct {\n\tData []byte\n\tErr  error\n\t// LineNumber is the 1-indexed starting line of this data within the file.\n\tLineNumber int64\n}\n\n// FileHandler represents a handler for files.\n// It has a single method, HandleFile, which takes a context and a fileReader as input,\n// and returns a channel of byte slices and an error.\ntype FileHandler interface {\n\tHandleFile(ctx logContext.Context, reader fileReader) chan DataOrErr\n}\n\n// fileHandlingConfig encapsulates configuration settings that control the behavior of file processing.\ntype fileHandlingConfig struct{ skipArchives bool }\n\n// newFileHandlingConfig creates a default fileHandlingConfig with default settings.\n// Optional functional parameters can customize the configuration.\nfunc newFileHandlingConfig(options ...func(*fileHandlingConfig)) fileHandlingConfig {\n\tconfig := fileHandlingConfig{}\n\tfor _, option := range options {\n\t\toption(&config)\n\t}\n\n\treturn config\n}\n\n// WithSkipArchives sets the skipArchives field of the fileHandlingConfig.\n// If skip is true, the FileHandler will skip archive files.\nfunc WithSkipArchives(skip bool) func(*fileHandlingConfig) {\n\treturn func(c *fileHandlingConfig) { c.skipArchives = skip }\n}\n\ntype handlerType string\n\nconst (\n\tarchiveHandlerType handlerType = \"archive\"\n\tarHandlerType      handlerType = \"ar\"\n\trpmHandlerType     handlerType = \"rpm\"\n\tapkHandlerType     handlerType = \"apk\"\n\tdefaultHandlerType handlerType = \"default\"\n\tapkExt                         = \".apk\"\n)\n\ntype mimeType string\n\nconst (\n\trpmMime      mimeType = \"application/x-rpm\"\n\tcpioMime     mimeType = \"application/cpio\"\n\tunixArMime   mimeType = \"application/x-unix-archive\"\n\tarMime       mimeType = \"application/x-archive\"\n\tdebMime      mimeType = \"application/vnd.debian.binary-package\"\n\ttextMime     mimeType = \"text/plain; charset=utf-8\"\n\txmlMime      mimeType = \"text/xml\"\n\tjsonMime     mimeType = \"application/json\"\n\tcsvMime      mimeType = \"text/csv\"\n\ttsvMime      mimeType = \"text/tab-separated-values\"\n\tgeoJSONMine  mimeType = \"application/vnd.geo+json\"\n\tndjsonMime   mimeType = \"application/x-ndjson\"\n\thtmlMime     mimeType = \"text/html\"\n\tphpTextMime  mimeType = \"text/x-php\"\n\trtfTextMime  mimeType = \"text/rtf\"\n\tjsAppMime    mimeType = \"application/javascript\"\n\tjsTextMime   mimeType = \"text/javascript\"\n\tjsMime       mimeType = \"application/x-javascript\"\n\tsrtMime      mimeType = \"application/x-subrip\"\n\tsrtXMime     mimeType = \"application/x-srt\"\n\tsrtTextMime  mimeType = \"text/x-srt\"\n\tvttMime      mimeType = \"text/vtt\"\n\tluaMime      mimeType = \"text/x-lua\"\n\tperlMime     mimeType = \"text/x-perl\"\n\tpythonMime   mimeType = \"text/x-python\"\n\tpyAppMime    mimeType = \"application/x-python\"\n\tpyScriptMime mimeType = \"application/x-script.python\"\n\ttclTextMime  mimeType = \"text/x-tcl\"\n\ttclMime      mimeType = \"application/x-tcl\"\n\tapkMime      mimeType = \"application/vnd.android.package-archive\"\n\tzipMime      mimeType = \"application/zip\"\n\tjarMime      mimeType = \"application/java-archive\"\n\tmsgMime      mimeType = \"application/vnd.ms-outlook\"\n\tdocMime      mimeType = \"application/msword\"\n)\n\n// skipArchiverMimeTypes is a set of MIME types that should bypass archiver library processing because they are either\n// text-based or archives not supported by the library.\nvar skipArchiverMimeTypes = map[mimeType]struct{}{\n\tarMime:       {},\n\tunixArMime:   {},\n\tdebMime:      {},\n\trpmMime:      {},\n\tcpioMime:     {},\n\ttextMime:     {},\n\txmlMime:      {},\n\tjsonMime:     {},\n\tcsvMime:      {},\n\ttsvMime:      {},\n\tgeoJSONMine:  {},\n\tndjsonMime:   {},\n\thtmlMime:     {},\n\tphpTextMime:  {},\n\trtfTextMime:  {},\n\tjsAppMime:    {},\n\tjsTextMime:   {},\n\tjsMime:       {},\n\tsrtMime:      {},\n\tsrtXMime:     {},\n\tsrtTextMime:  {},\n\tvttMime:      {},\n\tluaMime:      {},\n\tperlMime:     {},\n\tpythonMime:   {},\n\tpyAppMime:    {},\n\tpyScriptMime: {},\n\ttclTextMime:  {},\n\ttclMime:      {},\n\tapkMime:      {},\n\tmsgMime:      {},\n\tdocMime:      {},\n}\n\n// selectHandler dynamically selects and configures a FileHandler based on the provided |mimetype| type and archive flag.\n// The fileReader contains information about the MIME type and whether the file is an archive.\n// This method uses specialized handlers for specific file types:\n// - arHandler is used for Unix archives and Debian packages ('arMime', 'unixArMime', and 'debMime').\n// - rpmHandler is used for RPM and CPIO archives ('rpmMime' and 'cpioMime').\n// - apkHandler is used for APK archives ('apkMime').\n// - archiveHandler is used for common archive formats supported by the archiver library (.zip, .tar, .gz, etc.).\n// - defaultHandler is used for non-archive files.\n// The selected handler is then returned, ready to handle the file according to its specific format and requirements.\nfunc selectHandler(mimeT mimeType, isGenericArchive bool) FileHandler {\n\tswitch mimeT {\n\tcase arMime, unixArMime, debMime:\n\t\treturn newARHandler()\n\tcase rpmMime, cpioMime:\n\t\treturn newRPMHandler()\n\tcase apkMime:\n\t\treturn newAPKHandler()\n\tdefault:\n\t\tif isGenericArchive {\n\t\t\treturn newArchiveHandler()\n\t\t}\n\t\treturn newDefaultHandler(defaultHandlerType)\n\t}\n}\n\n// HandleFile orchestrates the complete file handling process for a given file.\n// It determines the MIME type of the file,\n// selects the appropriate handler based on this type, and processes the file.\n// This function initializes the handling process and delegates to the specific\n// handler to manage file extraction or processing.\n//\n// The function will return nil (success) in the following cases:\n// - If the reader is empty (ErrEmptyReader)\n// - If skipArchives option is true and the file is detected as an archive\n// - If all chunks are processed successfully without critical errors\n//\n// The function will return an error in the following cases:\n// - If the reader is nil\n// - If there's an error creating the file reader\n// - If there's an error closing the reader\n// - If a critical error occurs during chunk processing (context cancellation, deadline exceeded, or ErrProcessingFatal)\n// - If there's an error reporting a chunk\n//\n// Non-critical errors during chunk processing are logged\n// but do not cause the function to return an error.\nfunc HandleFile(\n\tctx logContext.Context,\n\treader io.Reader,\n\tchunkSkel *sources.Chunk,\n\treporter sources.ChunkReporter,\n\toptions ...func(*fileHandlingConfig),\n) error {\n\tif reader == nil {\n\t\treturn errors.New(\"reader is nil\")\n\t}\n\n\treaderOption := withFileExtension(getFileExtension(chunkSkel))\n\trdr, err := newFileReader(ctx, reader, readerOption)\n\tif err != nil {\n\t\tif errors.Is(err, ErrEmptyReader) {\n\t\t\tctx.Logger().V(5).Info(\"empty reader, skipping file\")\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"unable to HandleFile, error creating file reader: %w\", err)\n\t}\n\tdefer func() {\n\t\t// Ensure all data is read to prevent broken pipe.\n\t\tif closeErr := rdr.Close(); closeErr != nil {\n\t\t\tif err != nil {\n\t\t\t\terr = errors.Join(err, closeErr)\n\t\t\t} else {\n\t\t\t\terr = fmt.Errorf(\"error closing reader: %w\", closeErr)\n\t\t\t}\n\t\t}\n\t}()\n\n\tctx = logContext.WithValues(ctx, \"mime\", rdr.mime.String())\n\n\tmimeT := mimeType(rdr.mime.String())\n\tconfig := newFileHandlingConfig(options...)\n\tif config.skipArchives && rdr.isGenericArchive {\n\t\tctx.Logger().V(5).Info(\"skipping archive file\", \"mime\", mimeT)\n\t\treturn nil\n\t}\n\n\tprocessingCtx, cancel := logContext.WithTimeout(ctx, maxTimeout)\n\tdefer cancel()\n\n\thandler := selectHandler(mimeT, rdr.isGenericArchive)\n\tdataOrErrChan := handler.HandleFile(processingCtx, rdr) // Delegate to the specific handler to process the file.\n\n\treturn handleChunksWithError(processingCtx, dataOrErrChan, chunkSkel, reporter)\n}\n\n// handleChunksWithError processes data and errors received from the dataErrChan channel.\n// For each DataOrErr received:\n// - If it contains data, the function creates a chunk based on chunkSkel and reports it through the reporter.\n// - If it contains an error, the function handles it based on severity:\n//   - Fatal errors (context cancellation, deadline exceeded, ErrProcessingFatal) cause immediate termination\n//   - Non-fatal errors (ErrProcessingWarning and others) are logged and processing continues\n//\n// The function also listens for context cancellation to gracefully terminate processing if the context is done.\n// It returns nil upon successful processing of all data, or the first encountered fatal error.\n// Line numbers from DataOrErr are propagated to the chunk's source metadata for accurate reporting.\nfunc handleChunksWithError(\n\tctx logContext.Context,\n\tdataErrChan <-chan DataOrErr,\n\tchunkSkel *sources.Chunk,\n\treporter sources.ChunkReporter,\n) error {\n\tfor {\n\t\tselect {\n\t\tcase dataOrErr, ok := <-dataErrChan:\n\t\t\tif !ok {\n\t\t\t\t// Channel closed, processing complete.\n\t\t\t\tctx.Logger().V(5).Info(\"dataErrChan closed, all chunks processed\")\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif dataOrErr.Err != nil {\n\t\t\t\tif isFatal(dataOrErr.Err) {\n\t\t\t\t\treturn dataOrErr.Err\n\t\t\t\t}\n\t\t\t\tctx.Logger().Error(dataOrErr.Err, \"non-critical error processing chunk\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif len(dataOrErr.Data) > 0 {\n\t\t\t\tchunk := *chunkSkel\n\t\t\t\tchunk.Data = dataOrErr.Data\n\t\t\t\tpopulateChunkLineNumber(&chunk, dataOrErr.LineNumber)\n\n\t\t\t\tif err := reporter.ChunkOk(ctx, chunk); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error reporting chunk: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\t}\n\t}\n}\n\n// populateChunkLineNumber sets the line number in the chunk's source metadata.\n// It clones the metadata to avoid sharing state between chunks, since chunk\n// skeletons reuse the same metadata pointer. Line number is expected to be 1-indexed.\n// If lineNumber is 0 or metadata is nil, no changes are made.\nfunc populateChunkLineNumber(chunk *sources.Chunk, lineNumber int64) {\n\tif lineNumber == 0 || chunk.SourceMetadata == nil {\n\t\treturn\n\t}\n\n\tcloned := proto.CloneOf(chunk.SourceMetadata)\n\tswitch m := cloned.Data.(type) {\n\tcase *source_metadatapb.MetaData_Filesystem:\n\t\tm.Filesystem.Line = lineNumber\n\tcase *source_metadatapb.MetaData_AzureRepos:\n\t\tm.AzureRepos.Line = lineNumber\n\tcase *source_metadatapb.MetaData_Bitbucket:\n\t\tm.Bitbucket.Line = lineNumber\n\tcase *source_metadatapb.MetaData_Gerrit:\n\t\tm.Gerrit.Line = lineNumber\n\tcase *source_metadatapb.MetaData_Github:\n\t\tm.Github.Line = lineNumber\n\tcase *source_metadatapb.MetaData_Gitlab:\n\t\tm.Gitlab.Line = lineNumber\n\tcase *source_metadatapb.MetaData_Git:\n\t\tm.Git.Line = lineNumber\n\tcase *source_metadatapb.MetaData_Huggingface:\n\t\tm.Huggingface.Line = lineNumber\n\t}\n\tchunk.SourceMetadata = cloned\n}\n\n// isFatal determines whether the given error is a fatal error that should\n// terminate processing the current file, or a non-critical error that can be logged and ignored.\n// \"Fatal\" errors include context cancellation, deadline exceeded, and the\n// ErrProcessingFatal error. Non-fatal errors include the ErrProcessingWarning\n// error as well as any other error that is not one of the fatal errors.\nfunc isFatal(err error) bool {\n\tswitch {\n\tcase errors.Is(err, context.Canceled) ||\n\t\terrors.Is(err, context.DeadlineExceeded) ||\n\t\terrors.Is(err, ErrProcessingFatal):\n\t\treturn true\n\tcase errors.Is(err, ErrProcessingWarning):\n\t\treturn false\n\tdefault:\n\t\treturn false\n\t}\n}\n\n// getFileExtension extracts the file extension from the chunk's SourceMetadata.\n// It considers all sources defined in the MetaData message.\n// Note: Probably should add this as a method to the source_metadatapb object.\n// then it'd just be chunkSkel.SourceMetadata.GetFileExtension()\nfunc getFileExtension(chunkSkel *sources.Chunk) string {\n\tif chunkSkel == nil || chunkSkel.SourceMetadata == nil {\n\t\treturn \"\"\n\t}\n\n\tvar fileName string\n\n\t// Inspect the SourceMetadata to determine the source type\n\tswitch metadata := chunkSkel.SourceMetadata.Data.(type) {\n\tcase *source_metadatapb.MetaData_Artifactory:\n\t\tfileName = metadata.Artifactory.Path\n\tcase *source_metadatapb.MetaData_Azure:\n\t\tfileName = metadata.Azure.File\n\tcase *source_metadatapb.MetaData_AzureRepos:\n\t\tfileName = metadata.AzureRepos.File\n\tcase *source_metadatapb.MetaData_Bitbucket:\n\t\tfileName = metadata.Bitbucket.File\n\tcase *source_metadatapb.MetaData_Buildkite:\n\t\tfileName = metadata.Buildkite.Link\n\tcase *source_metadatapb.MetaData_Circleci:\n\t\tfileName = metadata.Circleci.Link\n\tcase *source_metadatapb.MetaData_Confluence:\n\t\tfileName = metadata.Confluence.File\n\tcase *source_metadatapb.MetaData_Docker:\n\t\tfileName = metadata.Docker.File\n\tcase *source_metadatapb.MetaData_Ecr:\n\t\tfileName = metadata.Ecr.File\n\tcase *source_metadatapb.MetaData_Filesystem:\n\t\tfileName = metadata.Filesystem.File\n\tcase *source_metadatapb.MetaData_Git:\n\t\tfileName = metadata.Git.File\n\tcase *source_metadatapb.MetaData_Github:\n\t\tfileName = metadata.Github.File\n\tcase *source_metadatapb.MetaData_Gitlab:\n\t\tfileName = metadata.Gitlab.File\n\tcase *source_metadatapb.MetaData_Gcs:\n\t\tfileName = metadata.Gcs.Filename\n\tcase *source_metadatapb.MetaData_GoogleDrive:\n\t\tfileName = metadata.GoogleDrive.File\n\tcase *source_metadatapb.MetaData_Huggingface:\n\t\tfileName = metadata.Huggingface.File\n\tcase *source_metadatapb.MetaData_Jira:\n\t\tfileName = metadata.Jira.Link\n\tcase *source_metadatapb.MetaData_Jenkins:\n\t\tfileName = metadata.Jenkins.Link\n\tcase *source_metadatapb.MetaData_Npm:\n\t\tfileName = metadata.Npm.File\n\tcase *source_metadatapb.MetaData_Pypi:\n\t\tfileName = metadata.Pypi.File\n\tcase *source_metadatapb.MetaData_S3:\n\t\tfileName = metadata.S3.File\n\tcase *source_metadatapb.MetaData_Slack:\n\t\tfileName = metadata.Slack.File\n\tcase *source_metadatapb.MetaData_Sharepoint:\n\t\tfileName = metadata.Sharepoint.Link\n\tcase *source_metadatapb.MetaData_Gerrit:\n\t\tfileName = metadata.Gerrit.File\n\tcase *source_metadatapb.MetaData_Test:\n\t\tfileName = metadata.Test.File\n\tcase *source_metadatapb.MetaData_Teams:\n\t\tfileName = metadata.Teams.File\n\tcase *source_metadatapb.MetaData_TravisCI:\n\t\tfileName = metadata.TravisCI.Link\n\t// Add other sources if they have a file or equivalent field\n\t// Skipping Syslog, Forager, Postman, Vector, Webhook and Elasticsearch\n\tdefault:\n\t\treturn \"\"\n\t}\n\n\t// Use filepath.Ext to extract the file extension from the file name\n\text := filepath.Ext(fileName)\n\treturn ext\n}\n\n// shouldHandleAsAPK checks if the file should be handled as an APK based on config and MIME type.\n// Note: We can't extend the mimetype package with an APK detection function b/c it would require adjusting settings\n// so that all files are fully read into a byte slice for detection (mimetype.SetLimit(0)), which would bloat memory.\n// Instead we call the isAPKFile function in here after ensuring it's a zip/jar file and has an .apk extension.\nfunc shouldHandleAsAPK(cfg readerConfig, fReader fileReader) bool {\n\treturn feature.EnableAPKHandler.Load() &&\n\t\tcfg.fileExtension == apkExt &&\n\t\t(fReader.mime.String() == string(zipMime) || fReader.mime.String() == string(jarMime))\n}\n\nfunc isAPKFile(r *fileReader) (bool, error) {\n\tsize, _ := r.Size()\n\tzipReader, err := zip.NewReader(r, size)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"error creating zip reader: %w\", err)\n\t}\n\n\thasManifest := false\n\thasClasses := false\n\n\tfor _, file := range zipReader.File {\n\t\tswitch file.Name {\n\t\tcase \"AndroidManifest.xml\":\n\t\t\thasManifest = true\n\t\tcase \"classes.dex\":\n\t\t\thasClasses = true\n\t\tdefault:\n\t\t\t// Skip other files.\n\t\t}\n\t\tif hasManifest && hasClasses {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\n\treturn false, nil\n}\n\n// handleAPKFile configures the MIME type for an APK and resets the reader.\nfunc handleAPKFile(fReader *fileReader) (fileReader, error) {\n\t// Extend the MIME type to recognize APK files\n\tmimetype.Lookup(\"application/zip\").Extend(func(r []byte, l uint32) bool { return false }, string(apkMime), \".apk\")\n\tfReader.mime = mimetype.Lookup(string(apkMime))\n\n\t// Reset reader for further handling\n\tif _, err := fReader.Seek(0, io.SeekStart); err != nil {\n\t\treturn *fReader, fmt.Errorf(\"error resetting reader after APK detection: %w\", err)\n\t}\n\treturn *fReader, nil\n}\n"
  },
  {
    "path": "pkg/handlers/handlers_test.go",
    "content": "package handlers\n\nimport (\n\t\"archive/zip\"\n\t\"bytes\"\n\tstdctx \"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\t\"testing/iotest\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\tdiskbufferreader \"github.com/trufflesecurity/disk-buffer-reader\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nfunc TestHandleFileCancelledContext(t *testing.T) {\n\treporter := sources.ChanReporter{Ch: make(chan *sources.Chunk, 2)}\n\n\tcanceledCtx, cancel := context.WithCancel(context.Background())\n\tcancel()\n\treader, err := diskbufferreader.New(strings.NewReader(\"file\"))\n\tassert.NoError(t, err)\n\tassert.Error(t, HandleFile(canceledCtx, reader, &sources.Chunk{}, reporter))\n}\n\nfunc TestHandleFile(t *testing.T) {\n\treporter := sources.ChanReporter{Ch: make(chan *sources.Chunk, 513)}\n\n\t// Only one chunk is sent on the channel.\n\t// TODO: Embed a zip without making an HTTP request.\n\tresp, err := http.Get(\"https://raw.githubusercontent.com/bill-rich/bad-secrets/master/aws-canary-creds.zip\")\n\tassert.NoError(t, err)\n\tdefer func() {\n\t\tif resp != nil && resp.Body != nil {\n\t\t\tresp.Body.Close()\n\t\t}\n\t}()\n\n\tassert.Equal(t, 0, len(reporter.Ch))\n\tassert.NoError(t, HandleFile(context.Background(), resp.Body, &sources.Chunk{}, reporter))\n\tassert.Equal(t, 1, len(reporter.Ch))\n}\n\nfunc TestHandleHTTPJson(t *testing.T) {\n\tresp, err := http.Get(\"https://raw.githubusercontent.com/ahrav/nothing-to-see-here/main/sm_random_data.json\")\n\tassert.NoError(t, err)\n\tdefer func() {\n\t\tif resp != nil && resp.Body != nil {\n\t\t\tresp.Body.Close()\n\t\t}\n\t}()\n\n\tchunkCh := make(chan *sources.Chunk, 1)\n\tgo func() {\n\t\tdefer close(chunkCh)\n\t\terr := HandleFile(context.Background(), resp.Body, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh})\n\t\tassert.NoError(t, err)\n\t}()\n\n\twantCount := 513\n\tcount := 0\n\tfor range chunkCh {\n\t\tcount++\n\t}\n\tassert.Equal(t, wantCount, count)\n}\n\nfunc TestHandleHTTPJsonZip(t *testing.T) {\n\tresp, err := http.Get(\"https://raw.githubusercontent.com/ahrav/nothing-to-see-here/main/sm.zip\")\n\tassert.NoError(t, err)\n\tdefer func() {\n\t\tif resp != nil && resp.Body != nil {\n\t\t\tresp.Body.Close()\n\t\t}\n\t}()\n\n\tchunkCh := make(chan *sources.Chunk, 1)\n\tgo func() {\n\t\tdefer close(chunkCh)\n\t\terr := HandleFile(context.Background(), resp.Body, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh})\n\t\tassert.NoError(t, err)\n\t}()\n\n\twantCount := 513\n\tcount := 0\n\tfor range chunkCh {\n\t\tcount++\n\t}\n\tassert.Equal(t, wantCount, count)\n}\n\nfunc BenchmarkHandleHTTPJsonZip(b *testing.B) {\n\tb.ReportAllocs()\n\tfor i := 0; i < b.N; i++ {\n\t\tfunc() {\n\t\t\tresp, err := http.Get(\"https://raw.githubusercontent.com/ahrav/nothing-to-see-here/main/sm.zip\")\n\t\t\tassert.NoError(b, err)\n\n\t\t\tdefer func() {\n\t\t\t\tif resp != nil && resp.Body != nil {\n\t\t\t\t\tresp.Body.Close()\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tchunkCh := make(chan *sources.Chunk, 1)\n\n\t\t\tb.StartTimer()\n\t\t\tgo func() {\n\t\t\t\tdefer close(chunkCh)\n\t\t\t\terr := HandleFile(context.Background(), resp.Body, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh})\n\t\t\t\tassert.NoError(b, err)\n\t\t\t}()\n\n\t\t\tfor range chunkCh {\n\t\t\t}\n\n\t\t\tb.StopTimer()\n\t\t}()\n\t}\n}\n\nfunc BenchmarkHandleFile(b *testing.B) {\n\tfile, err := os.Open(\"testdata/test.tgz\")\n\tassert.Nil(b, err)\n\tdefer file.Close()\n\n\tb.ReportAllocs()\n\tfor i := 0; i < b.N; i++ {\n\t\tsourceChan := make(chan *sources.Chunk, 1)\n\t\tb.StartTimer()\n\t\tgo func() {\n\t\t\tdefer close(sourceChan)\n\t\t\terr := HandleFile(context.Background(), file, &sources.Chunk{}, sources.ChanReporter{Ch: sourceChan})\n\t\t\tassert.NoError(b, err)\n\t\t}()\n\n\t\tfor range sourceChan {\n\t\t}\n\t\tb.StopTimer()\n\n\t\t_, err = file.Seek(0, io.SeekStart)\n\t\tassert.NoError(b, err)\n\t}\n}\n\nfunc TestSkipArchive(t *testing.T) {\n\tfile, err := os.Open(\"testdata/test.tgz\")\n\tassert.Nil(t, err)\n\n\tchunkCh := make(chan *sources.Chunk)\n\tgo func() {\n\t\tdefer close(chunkCh)\n\t\terr := HandleFile(context.Background(), file, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh}, WithSkipArchives(true))\n\t\tassert.NoError(t, err)\n\t}()\n\n\twantCount := 0\n\tcount := 0\n\tfor range chunkCh {\n\t\tcount++\n\t}\n\tassert.Equal(t, wantCount, count)\n}\n\nfunc TestHandleNestedArchives(t *testing.T) {\n\tfile, err := os.Open(\"testdata/nested-dirs.zip\")\n\tassert.Nil(t, err)\n\n\tchunkCh := make(chan *sources.Chunk)\n\tgo func() {\n\t\tdefer close(chunkCh)\n\t\terr := HandleFile(context.Background(), file, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh})\n\t\tassert.NoError(t, err)\n\t}()\n\n\twantCount := 8\n\tcount := 0\n\tfor range chunkCh {\n\t\tcount++\n\t}\n\tassert.Equal(t, wantCount, count)\n}\n\nfunc TestHandleCompressedZip(t *testing.T) {\n\tfile, err := os.Open(\"testdata/example.zip.gz\")\n\tassert.Nil(t, err)\n\n\tchunkCh := make(chan *sources.Chunk)\n\tgo func() {\n\t\tdefer close(chunkCh)\n\t\terr := HandleFile(context.Background(), file, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh})\n\t\tassert.NoError(t, err)\n\t}()\n\n\twantCount := 2\n\tcount := 0\n\tfor range chunkCh {\n\t\tcount++\n\t}\n\tassert.Equal(t, wantCount, count)\n}\n\nfunc TestHandleNestedCompressedArchive(t *testing.T) {\n\tfile, err := os.Open(\"testdata/nested-compressed-archive.tar.gz\")\n\tassert.Nil(t, err)\n\n\tchunkCh := make(chan *sources.Chunk)\n\tgo func() {\n\t\tdefer close(chunkCh)\n\t\terr := HandleFile(context.Background(), file, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh})\n\t\tassert.NoError(t, err)\n\t}()\n\n\twantCount := 4\n\tcount := 0\n\tfor range chunkCh {\n\t\tcount++\n\t}\n\tassert.Equal(t, wantCount, count)\n}\n\nfunc TestExtractTarContent(t *testing.T) {\n\tfile, err := os.Open(\"testdata/test.tgz\")\n\tassert.Nil(t, err)\n\n\tchunkCh := make(chan *sources.Chunk)\n\tgo func() {\n\t\tdefer close(chunkCh)\n\t\terr := HandleFile(context.Background(), file, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh})\n\t\tassert.NoError(t, err)\n\t}()\n\n\twantCount := 4\n\tcount := 0\n\tfor range chunkCh {\n\t\tcount++\n\t}\n\tassert.Equal(t, wantCount, count)\n}\n\nfunc TestNestedDirArchive(t *testing.T) {\n\tfile, err := os.Open(\"testdata/dir-archive.zip\")\n\tassert.Nil(t, err)\n\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\tsourceChan := make(chan *sources.Chunk, 1)\n\n\tgo func() {\n\t\tdefer close(sourceChan)\n\t\terr := HandleFile(ctx, file, &sources.Chunk{}, sources.ChanReporter{Ch: sourceChan})\n\t\tassert.NoError(t, err)\n\t}()\n\n\tcount := 0\n\twant := 4\n\tfor range sourceChan {\n\t\tcount++\n\t}\n\tassert.Equal(t, want, count)\n}\n\nfunc TestHandleFileRPM(t *testing.T) {\n\twantChunkCount := 179\n\treporter := sources.ChanReporter{Ch: make(chan *sources.Chunk, wantChunkCount)}\n\n\tfile, err := os.Open(\"testdata/test.rpm\")\n\tassert.Nil(t, err)\n\n\tassert.Equal(t, 0, len(reporter.Ch))\n\tassert.NoError(t, HandleFile(context.Background(), file, &sources.Chunk{}, reporter))\n\tassert.Equal(t, wantChunkCount, len(reporter.Ch))\n}\n\nfunc TestHandleFileAR(t *testing.T) {\n\twantChunkCount := 102\n\treporter := sources.ChanReporter{Ch: make(chan *sources.Chunk, wantChunkCount)}\n\n\tfile, err := os.Open(\"testdata/test.deb\")\n\tassert.Nil(t, err)\n\n\tassert.Equal(t, 0, len(reporter.Ch))\n\tassert.NoError(t, HandleFile(context.Background(), file, &sources.Chunk{}, reporter))\n\tassert.Equal(t, wantChunkCount, len(reporter.Ch))\n}\n\nfunc TestHandleFileMSG(t *testing.T) {\n\twantChunkCount := 5\n\treporter := sources.ChanReporter{Ch: make(chan *sources.Chunk, wantChunkCount)}\n\n\tfile, err := os.Open(\"testdata/test.msg\")\n\trequire.NoError(t, err)\n\n\tassert.Empty(t, reporter.Ch)\n\tassert.NoError(t, HandleFile(context.Background(), file, &sources.Chunk{}, reporter))\n\tassert.Equal(t, wantChunkCount, len(reporter.Ch))\n}\n\nfunc TestHandleFileDOC(t *testing.T) {\n\twantChunkCount := 3\n\treporter := sources.ChanReporter{Ch: make(chan *sources.Chunk, wantChunkCount)}\n\n\tfile, err := os.Open(\"testdata/test.doc\")\n\trequire.NoError(t, err)\n\n\tassert.Empty(t, reporter.Ch)\n\tassert.NoError(t, HandleFile(context.Background(), file, &sources.Chunk{}, reporter))\n\tassert.Equal(t, wantChunkCount, len(reporter.Ch))\n}\n\nfunc BenchmarkHandleAR(b *testing.B) {\n\tfile, err := os.Open(\"testdata/test.deb\")\n\tassert.Nil(b, err)\n\tdefer file.Close()\n\n\tb.ReportAllocs()\n\tfor i := 0; i < b.N; i++ {\n\t\tsourceChan := make(chan *sources.Chunk, 1)\n\n\t\tb.StartTimer()\n\t\tgo func() {\n\t\t\tdefer close(sourceChan)\n\t\t\terr := HandleFile(context.Background(), file, &sources.Chunk{}, sources.ChanReporter{Ch: sourceChan})\n\t\t\tassert.NoError(b, err)\n\t\t}()\n\n\t\tfor range sourceChan {\n\t\t}\n\t\tb.StopTimer()\n\n\t\t_, err = file.Seek(0, io.SeekStart)\n\t\tassert.NoError(b, err)\n\t}\n}\n\nfunc TestHandleFileNonArchive(t *testing.T) {\n\twantChunkCount := 6\n\treporter := sources.ChanReporter{Ch: make(chan *sources.Chunk, wantChunkCount)}\n\n\tfile, err := os.Open(\"testdata/nonarchive.txt\")\n\tassert.NoError(t, err)\n\n\tctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)\n\tdefer cancel()\n\n\tassert.NoError(t, HandleFile(ctx, file, &sources.Chunk{}, reporter))\n\tassert.NoError(t, err)\n\tassert.Equal(t, wantChunkCount, len(reporter.Ch))\n}\n\nfunc TestExtractTarContentWithEmptyFile(t *testing.T) {\n\tfile, err := os.Open(\"testdata/testdir.zip\")\n\tassert.Nil(t, err)\n\n\tchunkCh := make(chan *sources.Chunk, 1)\n\tgo func() {\n\t\tdefer close(chunkCh)\n\t\terr := HandleFile(context.Background(), file, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh})\n\t\tassert.NoError(t, err)\n\t}()\n\n\twantCount := 4\n\tcount := 0\n\tfor range chunkCh {\n\t\tcount++\n\t}\n\tassert.Equal(t, wantCount, count)\n}\n\nfunc TestHandleTar(t *testing.T) {\n\tfile, err := os.Open(\"testdata/test.tar\")\n\tassert.Nil(t, err)\n\tdefer file.Close()\n\n\tchunkCh := make(chan *sources.Chunk, 1)\n\tgo func() {\n\t\tdefer close(chunkCh)\n\t\terr := HandleFile(context.Background(), file, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh})\n\t\tassert.NoError(t, err)\n\t}()\n\n\twantCount := 1\n\tcount := 0\n\tfor range chunkCh {\n\t\tcount++\n\t}\n\tassert.Equal(t, wantCount, count)\n}\n\nfunc BenchmarkHandleTar(b *testing.B) {\n\tfile, err := os.Open(\"testdata/test.tar\")\n\tassert.Nil(b, err)\n\tdefer file.Close()\n\n\tb.ReportAllocs()\n\tfor i := 0; i < b.N; i++ {\n\t\tsourceChan := make(chan *sources.Chunk, 1)\n\n\t\tb.StartTimer()\n\t\tgo func() {\n\t\t\tdefer close(sourceChan)\n\t\t\terr := HandleFile(context.Background(), file, &sources.Chunk{}, sources.ChanReporter{Ch: sourceChan})\n\t\t\tassert.NoError(b, err)\n\t\t}()\n\n\t\tfor range sourceChan {\n\t\t}\n\t\tb.StopTimer()\n\n\t\t_, err = file.Seek(0, io.SeekStart)\n\t\tassert.NoError(b, err)\n\t}\n}\n\nfunc TestHandleLargeHTTPJson(t *testing.T) {\n\tresp, err := http.Get(\"https://raw.githubusercontent.com/ahrav/nothing-to-see-here/main/md_random_data.json.zip\")\n\tif !assert.NoError(t, err) {\n\t\treturn\n\t}\n\n\tdefer func() {\n\t\tif resp != nil && resp.Body != nil {\n\t\t\tresp.Body.Close()\n\t\t}\n\t}()\n\n\tchunkCh := make(chan *sources.Chunk, 1)\n\tgo func() {\n\t\tdefer close(chunkCh)\n\t\terr := HandleFile(context.Background(), resp.Body, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh})\n\t\tassert.NoError(t, err)\n\t}()\n\n\twantCount := 5121\n\tcount := 0\n\tfor range chunkCh {\n\t\tcount++\n\t}\n\tassert.Equal(t, wantCount, count)\n}\n\nfunc TestHandlePipe(t *testing.T) {\n\tr, w := io.Pipe()\n\n\tgo func() {\n\t\tdefer w.Close()\n\t\tfile, err := os.Open(\"testdata/test.tar\")\n\t\tassert.NoError(t, err)\n\t\tdefer file.Close()\n\t\t_, err = io.Copy(w, file)\n\t\tassert.NoError(t, err)\n\t}()\n\n\tchunkCh := make(chan *sources.Chunk, 1)\n\tgo func() {\n\t\tdefer close(chunkCh)\n\t\terr := HandleFile(context.Background(), r, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh})\n\t\tassert.NoError(t, err)\n\t}()\n\n\twantCount := 1\n\tcount := 0\n\tfor range chunkCh {\n\t\tcount++\n\t}\n\tassert.Equal(t, wantCount, count)\n}\n\nfunc TestHandleZipCommandStdoutPipe(t *testing.T) {\n\tcmd := exec.Command(\"zip\", \"-j\", \"-\", \"testdata/nested-dirs.zip\")\n\tstdout, err := cmd.StdoutPipe()\n\tassert.NoError(t, err)\n\n\terr = cmd.Start()\n\tassert.NoError(t, err)\n\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\n\tchunkCh := make(chan *sources.Chunk, 1)\n\tgo func() {\n\t\tdefer close(chunkCh)\n\t\terr := HandleFile(ctx, stdout, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh})\n\t\tassert.NoError(t, err)\n\t}()\n\n\twantCount := 8\n\tcount := 0\n\tfor range chunkCh {\n\t\tcount++\n\t}\n\n\t// cmd.Wait() should be called after all the reading from the pipe is done.\n\t// https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/os/exec/exec.go;l=1051-1053\n\terr = cmd.Wait()\n\tassert.NoError(t, err)\n\n\tassert.Equal(t, wantCount, count)\n}\n\nfunc TestHandleGitCatFile(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tfileName       string\n\t\tfileSize       int\n\t\tsupportedType  bool\n\t\texpectedChunks int\n\t}{\n\t\t{\n\t\t\tname:           \"LargeBlob\",\n\t\t\tfileName:       \"largefile.bin\",\n\t\t\tfileSize:       50 * 1024 * 1024, // 50 MB\n\t\t\tsupportedType:  true,\n\t\t\texpectedChunks: 5120,\n\t\t},\n\t\t{\n\t\t\tname:           \"UnsupportedType\",\n\t\t\tfileName:       \"unsupported.so\",\n\t\t\tfileSize:       1024 * 1024, // 1 MB\n\t\t\tsupportedType:  false,\n\t\t\texpectedChunks: 0,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// Set up a temporary git repository with the specified file.\n\t\t\tvar gitDir string\n\t\t\tif tt.supportedType {\n\t\t\t\tgitDir = setupTempGitRepo(t, tt.fileName, tt.fileSize)\n\t\t\t} else {\n\t\t\t\tgitDir = setupTempGitRepoWithUnsupportedFile(t, tt.fileName, tt.fileSize)\n\t\t\t}\n\t\t\tdefer os.RemoveAll(gitDir)\n\n\t\t\tcmd := exec.Command(\"git\", \"-C\", gitDir, \"rev-parse\", \"HEAD\")\n\t\t\thashBytes, err := cmd.Output()\n\t\t\tassert.NoError(t, err, \"Failed to get commit hash\")\n\t\t\tcommitHash := strings.TrimSpace(string(hashBytes))\n\n\t\t\t// Create a pipe to simulate the git cat-file stdout.\n\t\t\tcmd = exec.Command(\"git\", \"-C\", gitDir, \"cat-file\", \"blob\", fmt.Sprintf(\"%s:%s\", commitHash, tt.fileName))\n\n\t\t\tvar stderr bytes.Buffer\n\t\t\tcmd.Stderr = &stderr\n\n\t\t\tstdout, err := cmd.StdoutPipe()\n\t\t\tassert.NoError(t, err, \"Failed to create stdout pipe\")\n\n\t\t\terr = cmd.Start()\n\t\t\tassert.NoError(t, err, \"Failed to start git cat-file command\")\n\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)\n\t\t\tdefer cancel()\n\n\t\t\tchunkCh := make(chan *sources.Chunk, 1000)\n\n\t\t\tgo func() {\n\t\t\t\tdefer close(chunkCh)\n\t\t\t\terr := HandleFile(ctx, stdout, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh}, WithSkipArchives(false))\n\t\t\t\tassert.NoError(t, err, \"HandleFile should not return an error\")\n\t\t\t}()\n\n\t\t\tcount := 0\n\t\t\tfor range chunkCh {\n\t\t\t\tcount++\n\t\t\t}\n\n\t\t\t// cmd.Wait() should be called after all the reading from the pipe is done.\n\t\t\t// https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/os/exec/exec.go;l=1051-1053\n\t\t\terr = cmd.Wait()\n\t\t\tassert.NoError(t, err, \"git cat-file command should complete without error\")\n\n\t\t\tassert.Equal(t, tt.expectedChunks, count, \"Number of chunks should match the expected value\")\n\t\t})\n\t}\n}\n\nfunc setupTempGitRepoWithUnsupportedFile(t *testing.T, fileName string, fileSize int) string {\n\tt.Helper()\n\treturn setupTempGitRepoCommon(t, fileName, fileSize, true)\n}\n\nfunc setupTempGitRepo(t *testing.T, archiveName string, fileSize int) string {\n\tt.Helper()\n\treturn setupTempGitRepoCommon(t, archiveName, fileSize, false)\n}\n\nfunc setupTempGitRepoCommon(t *testing.T, fileName string, fileSize int, isUnsupported bool) string {\n\tt.Helper()\n\n\ttempDir := t.TempDir()\n\n\tcmd := exec.Command(\"git\", \"init\", tempDir)\n\tvar initStderr bytes.Buffer\n\tcmd.Stderr = &initStderr\n\terr := cmd.Run()\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to initialize git repository: %v, stderr: %s\", err, initStderr.String())\n\t}\n\n\tcmds := [][]string{\n\t\t{\"git\", \"-C\", tempDir, \"config\", \"user.name\", \"Test User\"},\n\t\t{\"git\", \"-C\", tempDir, \"config\", \"user.email\", \"test@example.com\"},\n\t\t{\"git\", \"-C\", tempDir, \"config\", \"commit.gpgsign\", \"false\"},\n\t}\n\n\tfor _, cmdArgs := range cmds {\n\t\tcmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) //nolint:gosec\n\t\tvar cmdStderr bytes.Buffer\n\t\tcmd.Stderr = &cmdStderr\n\t\terr := cmd.Run()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to set git config: %v, stderr: %s\", err, cmdStderr.String())\n\t\t}\n\t}\n\n\tfilePath := filepath.Join(tempDir, fileName)\n\n\t// Create the file with appropriate content.\n\tf, err := os.Create(filePath)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create file: %v\", err)\n\t}\n\tdefer f.Close()\n\n\tif isUnsupported {\n\t\t// Write ELF header for unsupported file.\n\t\t// https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html\n\t\telfHeader := []byte{\n\t\t\t0x7f, 'E', 'L', 'F', // ELF magic number\n\t\t\t2,                   // 64-bit format\n\t\t\t1,                   // Little endian\n\t\t\t1,                   // Current version of ELF\n\t\t\t0,                   // Target OS ABI\n\t\t\t0,                   // ABI Version\n\t\t\t0, 0, 0, 0, 0, 0, 0, // 7 bytes of padding\n\t\t\t3, 0, // Relocatable file\n\t\t\t0x3e, 0, // AMD x86-64 architecture\n\t\t\t1, 0, 0, 0, // ELF version\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // Entry point\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // Program header offset\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, // Section header offset\n\t\t}\n\t\t_, err = f.Write(elfHeader)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to write ELF header: %v\", err)\n\t\t}\n\t} else {\n\t\t// Write ZIP content for supported file.\n\t\tzipWriter := zip.NewWriter(f)\n\t\theader := &zip.FileHeader{\n\t\t\tName:   \"largefile.txt\",\n\t\t\tMethod: zip.Store, // No compression\n\t\t}\n\t\tzipFileWriter, err := zipWriter.CreateHeader(header)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Failed to create file in ZIP archive: %v\", err)\n\t\t}\n\n\t\tdataChunk := bytes.Repeat([]byte(\"A\"), 1024) // 1KB chunk\n\t\ttotalWritten := 0\n\t\tfor totalWritten < fileSize {\n\t\t\tremaining := fileSize - totalWritten\n\t\t\tif remaining < len(dataChunk) {\n\t\t\t\t_, err = zipFileWriter.Write(dataChunk[:remaining])\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Failed to write to inner file in ZIP archive: %v\", err)\n\t\t\t\t}\n\t\t\t\ttotalWritten += remaining\n\t\t\t} else {\n\t\t\t\t_, err = zipFileWriter.Write(dataChunk)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"Failed to write to inner file in ZIP archive: %v\", err)\n\t\t\t\t}\n\t\t\t\ttotalWritten += len(dataChunk)\n\t\t\t}\n\t\t}\n\n\t\tif err := zipWriter.Close(); err != nil {\n\t\t\tt.Fatalf(\"Failed to close ZIP writer: %v\", err)\n\t\t}\n\t}\n\n\t// Add and commit the file to Git.\n\tcmd = exec.Command(\"git\", \"-C\", tempDir, \"add\", fileName)\n\tvar addStderr bytes.Buffer\n\tcmd.Stderr = &addStderr\n\terr = cmd.Run()\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to add file to git: %v, stderr: %s\", err, addStderr.String())\n\t}\n\n\tcmd = exec.Command(\"git\", \"-C\", tempDir, \"commit\", \"-m\", \"Add file\")\n\tvar commitStderr bytes.Buffer\n\tcmd.Stderr = &commitStderr\n\terr = cmd.Run()\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to commit file to git: %v, stderr: %s\", err, commitStderr.String())\n\t}\n\n\treturn tempDir\n}\n\nfunc TestHandleFileNewFileReaderFailure(t *testing.T) {\n\tcustomReader := iotest.ErrReader(errors.New(\"simulated newFileReader error\"))\n\n\tchunkSkel := &sources.Chunk{}\n\tchunkCh := make(chan *sources.Chunk)\n\treporter := sources.ChanReporter{Ch: chunkCh}\n\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\n\terr := HandleFile(ctx, customReader, chunkSkel, reporter)\n\n\tassert.Error(t, err, \"HandleFile should return an error when newFileReader fails\")\n}\n\n// errorInjectingReader is a custom io.Reader that injects an error after reading a certain number of bytes.\ntype errorInjectingReader struct {\n\treader        io.Reader\n\tinjectAfter   int64 // Number of bytes after which to inject the error\n\tinjected      bool\n\tbytesRead     int64\n\terrorToInject error\n}\n\nfunc (eir *errorInjectingReader) Read(p []byte) (int, error) {\n\tif eir.injectAfter > 0 && eir.bytesRead >= eir.injectAfter && !eir.injected {\n\t\teir.injected = true\n\t\treturn 0, eir.errorToInject\n\t}\n\n\tn, err := eir.reader.Read(p)\n\teir.bytesRead += int64(n)\n\treturn n, err\n}\n\n// TestHandleGitCatFileWithPipeError tests that when an error is injected during the HandleFile processing,\n// the error is reported and the git cat-file command completes successfully.\nfunc TestHandleGitCatFileWithPipeError(t *testing.T) {\n\tfileName := \"largefile_with_error.bin\"\n\tfileSize := 100 * 1024               // 100 KB\n\tinjectErrorAfter := int64(50 * 1024) // Inject error after 50 KB\n\tsimulatedError := errors.New(\"simulated error during newFileReader\")\n\n\tgitDir := setupTempGitRepo(t, fileName, fileSize)\n\tdefer os.RemoveAll(gitDir)\n\n\tcommitHash := getGitCommitHash(t, gitDir)\n\n\tcmd := exec.Command(\"git\", \"-C\", gitDir, \"cat-file\", \"blob\", fmt.Sprintf(\"%s:%s\", commitHash, fileName))\n\n\tvar stderr bytes.Buffer\n\tcmd.Stderr = &stderr\n\n\tstdout, err := cmd.StdoutPipe()\n\tassert.NoError(t, err, \"Failed to create stdout pipe\")\n\n\terr = cmd.Start()\n\tassert.NoError(t, err, \"Failed to start git cat-file command\")\n\n\t// Wrap the stdout with errorInjectingReader to simulate an error after reading injectErrorAfter bytes.\n\twrappedReader := &errorInjectingReader{\n\t\treader:        stdout,\n\t\tinjectAfter:   injectErrorAfter,\n\t\tinjected:      false,\n\t\tbytesRead:     0,\n\t\terrorToInject: simulatedError,\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\n\tchunkCh := make(chan *sources.Chunk, 1000)\n\n\tgo func() {\n\t\tdefer close(chunkCh)\n\t\terr = HandleFile(ctx, wrappedReader, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh}, WithSkipArchives(false))\n\t\tassert.NoError(t, err, \"HandleFile should not return an error\")\n\t}()\n\n\tfor range chunkCh {\n\t}\n\n\terr = cmd.Wait()\n\tassert.NoError(t, err, \"git cat-file command should complete without error\")\n}\n\n// getGitCommitHash retrieves the current commit hash of the Git repository.\nfunc getGitCommitHash(t *testing.T, gitDir string) string {\n\tt.Helper()\n\tcmd := exec.Command(\"git\", \"-C\", gitDir, \"rev-parse\", \"HEAD\")\n\thashBytes, err := cmd.Output()\n\tassert.NoError(t, err, \"Failed to get commit hash\")\n\tcommitHash := strings.TrimSpace(string(hashBytes))\n\treturn commitHash\n}\n\ntype mockReporter struct{ reportedChunks int }\n\nfunc (m *mockReporter) ChunkOk(context.Context, sources.Chunk) error {\n\tm.reportedChunks++\n\treturn nil\n}\n\nfunc (m *mockReporter) ChunkErr(context.Context, error) error { return nil }\n\nfunc TestHandleChunksWithError(t *testing.T) {\n\ttests := []struct {\n\t\tname                   string\n\t\tinput                  []DataOrErr\n\t\texpectedErr            error\n\t\texpectedReportedChunks int\n\t}{\n\t\t{\n\t\t\tname:  \"Non-Critical Error\",\n\t\t\tinput: []DataOrErr{{Err: ErrProcessingWarning}},\n\t\t},\n\t\t{\n\t\t\tname:        \"Critical Error\",\n\t\t\tinput:       []DataOrErr{{Err: ErrProcessingFatal}},\n\t\t\texpectedErr: ErrProcessingFatal,\n\t\t},\n\t\t{\n\t\t\tname: \"No Error\",\n\t\t\tinput: []DataOrErr{\n\t\t\t\t{Data: []byte(\"test data\")},\n\t\t\t\t{Data: []byte(\"more data\")},\n\t\t\t},\n\t\t\texpectedReportedChunks: 2,\n\t\t},\n\t\t{\n\t\t\tname:        \"Context Canceled\",\n\t\t\tinput:       []DataOrErr{{Err: stdctx.Canceled}},\n\t\t\texpectedErr: stdctx.Canceled,\n\t\t},\n\t\t{\n\t\t\tname:        \"Context Deadline Exceeded\",\n\t\t\tinput:       []DataOrErr{{Err: stdctx.DeadlineExceeded}},\n\t\t\texpectedErr: stdctx.DeadlineExceeded,\n\t\t},\n\t\t{\n\t\t\tname:  \"EOF Error\",\n\t\t\tinput: []DataOrErr{{Err: io.EOF}},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tctx := context.Background()\n\t\t\tchunkSkel := &sources.Chunk{}\n\t\t\treporter := new(mockReporter)\n\n\t\t\tdataErrChan := make(chan DataOrErr, len(tc.input))\n\t\t\tfor _, de := range tc.input {\n\t\t\t\tdataErrChan <- de\n\t\t\t}\n\t\t\tclose(dataErrChan)\n\n\t\t\terr := handleChunksWithError(ctx, dataErrChan, chunkSkel, reporter)\n\n\t\t\tif tc.expectedErr != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.expectedErr, \"handleChunksWithError should return the expected error\")\n\t\t\t} else {\n\t\t\t\tassert.NoError(t, err, \"handleChunksWithError should not return an error for non-critical errors\")\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.expectedReportedChunks, reporter.reportedChunks, \"should have reported the expected number of chunks\")\n\t\t})\n\t}\n}\n\n// mockChunkReader creates a ChunkReader that returns predefined chunks.\n// Each chunk has data and contentSize (contentSize is used to determine\n// how many newlines to count for line tracking).\nfunc mockChunkReader(chunks []sources.ChunkResult) sources.ChunkReader {\n\treturn func(ctx context.Context, reader io.Reader) <-chan sources.ChunkResult {\n\t\tch := make(chan sources.ChunkResult, len(chunks))\n\t\tfor _, c := range chunks {\n\t\t\tch <- c\n\t\t}\n\t\tclose(ch)\n\t\treturn ch\n\t}\n}\n\n// TestPopulateChunkLineNumber verifies that populateChunkLineNumber correctly clones\n// metadata and sets line numbers for different metadata types.\nfunc TestPopulateChunkLineNumber(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\tmetadata   *source_metadatapb.MetaData\n\t\tlineNumber int64\n\t\tgetLine    func(*source_metadatapb.MetaData) int64\n\t}{\n\t\t{\n\t\t\tname: \"Filesystem metadata\",\n\t\t\tmetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Filesystem{\n\t\t\t\t\tFilesystem: &source_metadatapb.Filesystem{File: \"test.txt\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlineNumber: 42,\n\t\t\tgetLine: func(m *source_metadatapb.MetaData) int64 {\n\t\t\t\treturn m.Data.(*source_metadatapb.MetaData_Filesystem).Filesystem.Line\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Git metadata\",\n\t\t\tmetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Git{\n\t\t\t\t\tGit: &source_metadatapb.Git{File: \"test.go\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlineNumber: 100,\n\t\t\tgetLine: func(m *source_metadatapb.MetaData) int64 {\n\t\t\t\treturn m.Data.(*source_metadatapb.MetaData_Git).Git.Line\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Github metadata\",\n\t\t\tmetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\tGithub: &source_metadatapb.Github{File: \"test.py\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlineNumber: 200,\n\t\t\tgetLine: func(m *source_metadatapb.MetaData) int64 {\n\t\t\t\treturn m.Data.(*source_metadatapb.MetaData_Github).Github.Line\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:       \"nil metadata\",\n\t\t\tmetadata:   nil,\n\t\t\tlineNumber: 10,\n\t\t\tgetLine:    func(m *source_metadatapb.MetaData) int64 { return 0 },\n\t\t},\n\t\t{\n\t\t\tname: \"zero line number\",\n\t\t\tmetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Filesystem{\n\t\t\t\t\tFilesystem: &source_metadatapb.Filesystem{File: \"test.txt\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tlineNumber: 0,\n\t\t\tgetLine: func(m *source_metadatapb.MetaData) int64 {\n\t\t\t\treturn m.Data.(*source_metadatapb.MetaData_Filesystem).Filesystem.Line\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tchunk := &sources.Chunk{SourceMetadata: tc.metadata}\n\t\t\toriginalMetadata := tc.metadata\n\n\t\t\tpopulateChunkLineNumber(chunk, tc.lineNumber)\n\n\t\t\tif tc.metadata == nil || tc.lineNumber == 0 {\n\t\t\t\t// Metadata should remain unchanged\n\t\t\t\tassert.Equal(t, originalMetadata, chunk.SourceMetadata)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Verify the line number is set correctly\n\t\t\tactualLine := tc.getLine(chunk.SourceMetadata)\n\t\t\tassert.Equal(t, tc.lineNumber, actualLine,\n\t\t\t\t\"line number should be set to %d, got %d\", tc.lineNumber, actualLine)\n\n\t\t\t// Verify the original is not modified (metadata was cloned)\n\t\t\toriginalLine := tc.getLine(tc.metadata)\n\t\t\tassert.Equal(t, int64(0), originalLine,\n\t\t\t\t\"original metadata should not be modified, but line is %d\", originalLine)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/handlers/metrics.go",
    "content": "package handlers\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\ntype metrics struct {\n\thandlerType            handlerType\n\thandleFileLatency      *prometheus.HistogramVec\n\tbytesProcessed         *prometheus.CounterVec\n\tfilesProcessed         *prometheus.CounterVec\n\terrorsEncountered      *prometheus.CounterVec\n\tfilesSkipped           *prometheus.CounterVec\n\tmaxArchiveDepthCount   *prometheus.CounterVec\n\tfileSize               *prometheus.HistogramVec\n\tfileProcessingTimeouts *prometheus.CounterVec\n}\n\nvar (\n\thandleFileLatency = promauto.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"handlers_file_latency_milliseconds\",\n\t\t\tHelp:      \"Latency of the HandleFile method\",\n\t\t\tBuckets:   prometheus.ExponentialBuckets(1, 5, 6),\n\t\t},\n\t\t[]string{\"handler_type\"},\n\t)\n\tbytesProcessed = promauto.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"handlers_bytes_processed_total\",\n\t\t\tHelp:      \"Total number of bytes processed\",\n\t\t},\n\t\t[]string{\"handler_type\"},\n\t)\n\tfilesProcessed = promauto.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"handlers_files_processed_total\",\n\t\t\tHelp:      \"Total number of files processed\",\n\t\t},\n\t\t[]string{\"handler_type\"},\n\t)\n\terrorsEncountered = promauto.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"handlers_errors_encountered_total\",\n\t\t\tHelp:      \"Total number of errors encountered\",\n\t\t},\n\t\t[]string{\"handler_type\"},\n\t)\n\tfilesSkipped = promauto.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"handlers_files_skipped_total\",\n\t\t\tHelp:      \"Total number of files skipped\",\n\t\t},\n\t\t[]string{\"handler_type\"},\n\t)\n\tmaxArchiveDepthCount = promauto.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"handlers_max_archive_depth_reached_total\",\n\t\t\tHelp:      \"Total number of times the maximum archive depth was reached\",\n\t\t},\n\t\t[]string{\"handler_type\"},\n\t)\n\tfileSize = promauto.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"handlers_file_size_bytes\",\n\t\t\tHelp:      \"Sizes of files handled by the handler\",\n\t\t\tBuckets:   prometheus.ExponentialBuckets(1, 2, 4),\n\t\t},\n\t\t[]string{\"handler_type\"},\n\t)\n\tfileProcessingTimeouts = promauto.NewCounterVec(\n\t\tprometheus.CounterOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"handlers_file_processing_timeouts_total\",\n\t\t\tHelp:      \"Total number of file processing timeouts encountered\",\n\t\t},\n\t\t[]string{\"handler_type\"},\n\t)\n)\n\n// newHandlerMetrics creates a new metrics instance configured with Prometheus metrics specific to a file handler.\n// The function takes a handlerType parameter, which represents the type of the handler (e.g., \"default\", \"ar\", \"rpm\").\n// The handlerType is used as a label for each metric, allowing for differentiation and aggregation of metrics\n// based on the handler type.\n//\n// The function initializes and returns a pointer to a metrics struct that contains the following Prometheus metrics:\n//\n//   - handleFileLatency: a HistogramVec metric that measures the latency of the HandleFile method.\n//     It uses exponential buckets with a base of 1 and a factor of 5, up to 6 buckets.\n//     The metric is labeled with the handlerType.\n//\n//   - bytesProcessed: a CounterVec metric that tracks the total number of bytes processed by the handler.\n//     It is labeled with the handlerType.\n//\n//   - filesProcessed: a CounterVec metric that tracks the total number of files processed by the handler.\n//     It is labeled with the handlerType.\n//\n//   - errorsEncountered: a CounterVec metric that tracks the total number of errors encountered by the handler.\n//     It is labeled with the handlerType.\n//\n//   - filesSkipped: a CounterVec metric that tracks the total number of files skipped by the handler.\n//     It is labeled with the handlerType.\n//\n//   - maxArchiveDepthCount: a CounterVec metric that tracks the total number of times the maximum archive depth was reached.\n//     It is labeled with the handlerType.\n//\n//   - fileSize: a HistogramVec metric that measures the sizes of files handled by the handler.\n//     It uses exponential buckets with a base of 1 and a factor of 2, up to 4 buckets.\n//     It is labeled with the handlerType.\n//\n//   - fileProcessingTimeouts: a CounterVec metric that tracks the total number of file processing timeouts\n//     encountered by the handler.\n//     It is labeled with the handlerType.\n//\n// The metrics are created with a common namespace and subsystem defined in the common package.\n// This helps to organize and group related metrics together.\n//\n// By initializing the metrics with the handlerType label, the function enables accurate attribution and aggregation\n// of metrics based on the specific handler type. This allows for fine-grained monitoring and analysis of\n// file handler performance.\nfunc newHandlerMetrics(t handlerType) *metrics {\n\treturn &metrics{\n\t\thandlerType:            t,\n\t\thandleFileLatency:      handleFileLatency,\n\t\tbytesProcessed:         bytesProcessed,\n\t\tfilesProcessed:         filesProcessed,\n\t\terrorsEncountered:      errorsEncountered,\n\t\tfilesSkipped:           filesSkipped,\n\t\tmaxArchiveDepthCount:   maxArchiveDepthCount,\n\t\tfileSize:               fileSize,\n\t\tfileProcessingTimeouts: fileProcessingTimeouts,\n\t}\n}\n\nfunc (m *metrics) observeHandleFileLatency(duration int64) {\n\tm.handleFileLatency.WithLabelValues(string(m.handlerType)).Observe(float64(duration))\n}\n\nfunc (m *metrics) incBytesProcessed(bytes int) {\n\tm.bytesProcessed.WithLabelValues(string(m.handlerType)).Add(float64(bytes))\n}\n\nfunc (m *metrics) incFilesProcessed() {\n\tm.filesProcessed.WithLabelValues(string(m.handlerType)).Inc()\n}\n\nfunc (m *metrics) incErrors() {\n\tm.errorsEncountered.WithLabelValues(string(m.handlerType)).Inc()\n}\n\nfunc (m *metrics) incFilesSkipped() {\n\tm.filesSkipped.WithLabelValues(string(m.handlerType)).Inc()\n}\n\nfunc (m *metrics) incMaxArchiveDepthCount() {\n\tm.maxArchiveDepthCount.WithLabelValues(string(m.handlerType)).Inc()\n}\n\nfunc (m *metrics) observeFileSize(size int64) {\n\tm.fileSize.WithLabelValues(string(m.handlerType)).Observe(float64(size))\n}\n\nfunc (m *metrics) incFileProcessingTimeouts() {\n\tm.fileProcessingTimeouts.WithLabelValues(string(m.handlerType)).Inc()\n}\n"
  },
  {
    "path": "pkg/handlers/rpm.go",
    "content": "package handlers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n\n\t\"github.com/sassoftware/go-rpmutils\"\n\n\tlogContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/feature\"\n)\n\n// rpmHandler specializes archiveHandler to manage RPM package files.\ntype rpmHandler struct{ *defaultHandler }\n\n// newRPMHandler creates an rpmHandler with the provided metrics.\nfunc newRPMHandler() *rpmHandler {\n\treturn &rpmHandler{defaultHandler: newDefaultHandler(rpmHandlerType)}\n}\n\n// HandleFile processes RPM formatted files.\n// It returns a channel of DataOrErr that will receive either file data\n// or errors encountered during processing.\n//\n// Fatal errors that will terminate processing include:\n// - Context cancellation or deadline exceeded\n// - Errors reading or uncompressing the RPM file\n// - Panics during processing (wrapped as ErrProcessingFatal)\n//\n// Non-fatal errors that will be reported but allow processing to continue include:\n// - Errors processing individual files within the RPM archive (wrapped as ErrProcessingWarning)\n//\n// The handler will skip processing entirely if ForceSkipArchives is enabled.\nfunc (h *rpmHandler) HandleFile(ctx logContext.Context, input fileReader) chan DataOrErr {\n\tdataOrErrChan := make(chan DataOrErr, defaultBufferSize)\n\n\tif feature.ForceSkipArchives.Load() {\n\t\tclose(dataOrErrChan)\n\t\treturn dataOrErrChan\n\t}\n\n\tgo func() {\n\t\tdefer close(dataOrErrChan)\n\n\t\t// Defer a panic recovery to handle any panics that occur during the RPM processing.\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tvar panicErr error\n\t\t\t\tif e, ok := r.(error); ok {\n\t\t\t\t\tpanicErr = e\n\t\t\t\t} else {\n\t\t\t\t\tpanicErr = fmt.Errorf(\"panic occurred: %v\", r)\n\t\t\t\t}\n\t\t\t\tdataOrErrChan <- DataOrErr{\n\t\t\t\t\tErr: fmt.Errorf(\"%w: panic error: %v\", ErrProcessingFatal, panicErr),\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\tstart := time.Now()\n\t\trpm, err := rpmutils.ReadRpm(input)\n\t\tif err != nil {\n\t\t\tdataOrErrChan <- DataOrErr{\n\t\t\t\tErr: fmt.Errorf(\"%w: reading rpm error: %v\", ErrProcessingFatal, err),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\treader, err := rpm.PayloadReaderExtended()\n\t\tif err != nil {\n\t\t\tdataOrErrChan <- DataOrErr{\n\t\t\t\tErr: fmt.Errorf(\"%w: uncompressing rpm error: %v\", ErrProcessingFatal, err),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\terr = h.processRPMFiles(ctx, reader, dataOrErrChan)\n\t\tif err == nil {\n\t\t\th.metrics.incFilesProcessed()\n\t\t}\n\n\t\t// Update the metrics for the file processing and handle any errors.\n\t\th.measureLatencyAndHandleErrors(ctx, start, err, dataOrErrChan)\n\t}()\n\n\treturn dataOrErrChan\n}\n\nfunc (h *rpmHandler) processRPMFiles(\n\tctx logContext.Context,\n\treader rpmutils.PayloadReader,\n\tdataOrErrChan chan DataOrErr,\n) error {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\tdefault:\n\t\t\tfileInfo, err := reader.Next()\n\t\t\tif err != nil {\n\t\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\t\tctx.Logger().V(3).Info(\"RPM payload archive fully processed\")\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\treturn fmt.Errorf(\"error reading RPM payload: %w\", err)\n\t\t\t}\n\n\t\t\tfileSize := fileInfo.Size()\n\t\t\tfileCtx := logContext.WithValues(ctx, \"filename\", fileInfo.Name, \"size\", fileSize)\n\n\t\t\trdr, err := newMimeTypeReader(reader)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"error creating mime-type reader: %w\", err)\n\t\t\t}\n\n\t\t\tif err := h.handleNonArchiveContent(fileCtx, rdr, dataOrErrChan); err != nil {\n\t\t\t\tdataOrErrChan <- DataOrErr{\n\t\t\t\t\tErr: fmt.Errorf(\"%w: error processing RPM archive: %v\", ErrProcessingWarning, err),\n\t\t\t\t}\n\t\t\t\th.metrics.incErrors()\n\t\t\t}\n\n\t\t\th.metrics.incFilesProcessed()\n\t\t\th.metrics.observeFileSize(fileSize)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/handlers/rpm_test.go",
    "content": "package handlers\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestHandleRPMFile(t *testing.T) {\n\tfile, err := os.Open(\"testdata/test.rpm\")\n\tassert.Nil(t, err)\n\tdefer file.Close()\n\n\tctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)\n\tdefer cancel()\n\n\trdr, err := newFileReader(ctx, file)\n\tassert.NoError(t, err)\n\tdefer rdr.Close()\n\n\thandler := newRPMHandler()\n\tdataOrErrChan := handler.HandleFile(context.AddLogger(ctx), rdr)\n\tassert.NoError(t, err)\n\n\twantChunkCount := 179\n\tcount := 0\n\tfor range dataOrErrChan {\n\t\tcount++\n\t}\n\n\tassert.Equal(t, wantChunkCount, count)\n}\n"
  },
  {
    "path": "pkg/handlers/testdata/nonarchive.txt",
    "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper con, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\n"
  },
  {
    "path": "pkg/hasher/blake2b.go",
    "content": "package hasher\n\nimport \"golang.org/x/crypto/blake2b\"\n\n// Blake2b implements the Hasher interface using Blake2b algorithm.\ntype Blake2b struct{ baseHasher }\n\n// NewBlake2B creates a new Blake2b hasher.\nfunc NewBlake2B() *Blake2b {\n\th, _ := blake2b.New256(nil)\n\treturn &Blake2b{baseHasher: baseHasher{hash: h}}\n}\n"
  },
  {
    "path": "pkg/hasher/hasher.go",
    "content": "// Package hasher provides a generic interface and base implementation for hashing data.\npackage hasher\n\nimport (\n\t\"fmt\"\n\t\"hash\"\n)\n\n// Hasher defines a generic interface for hashing data.\n// Implementations of this interface may choose to be safe for concurrent use,\n// but it is not a requirement. Users should check the documentation of specific\n// implementations for concurrent safety guarantees.\ntype Hasher interface {\n\t// Hash takes input data and returns the hashed result.\n\t// It returns an error if the input data is too large.\n\t// The function is idempotent - calling it multiple times with the same input\n\t// will produce the same output, assuming the underlying hash function is deterministic.\n\tHash(data []byte) ([]byte, error)\n}\n\n// baseHasher provides a base implementation for the Hasher interface.\n// It uses the hash.Hash interface from the standard library to perform the actual hashing.\n// This implementation is not safe for concurrent use. Each goroutine/worker should\n// use its own instance of baseHasher for concurrent operations.\n// Implementations that require concurrent access should wrap baseHasher with a mutex. (e.g., MutexHasher)\ntype baseHasher struct{ hash hash.Hash }\n\n// InputTooLargeError is returned when the input data exceeds the maximum allowed size.\ntype InputTooLargeError struct {\n\tinputSize int\n\tmaxSize   int\n}\n\nfunc (e *InputTooLargeError) Error() string {\n\treturn fmt.Sprintf(\"input data exceeds the maximum allowed size: %d > %d\", e.inputSize, e.maxSize)\n}\n\nconst maxInputSize = 1 << 14 // 16KB\n\n// Hash computes the hash of the given data.\n// It returns an InputTooLargeError if the input data exceeds the maximum allowed size.\n// This method resets the underlying hash before each computation to ensure\n// that previous hashing operations do not affect the result.\nfunc (b *baseHasher) Hash(data []byte) ([]byte, error) {\n\tif len(data) > maxInputSize {\n\t\treturn nil, &InputTooLargeError{inputSize: len(data), maxSize: maxInputSize}\n\t}\n\tb.hash.Reset()\n\t// nolint:errcheck\n\t// The hash.Hash interface does not return errors on Write.\n\t// (https://cs.opensource.google/go/go/+/refs/tags/go1.23.1:src/hash/hash.go;l=27-28)\n\t_, _ = b.hash.Write(data)\n\treturn b.hash.Sum(nil), nil\n}\n"
  },
  {
    "path": "pkg/hasher/hasher_test.go",
    "content": "package hasher\n\nimport (\n\t\"bytes\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestHasherHash(t *testing.T) {\n\ttestCases := []struct {\n\t\tname        string\n\t\thasher      Hasher\n\t\tinput       []byte\n\t\texpectedHex string\n\t\texpectError error\n\t}{\n\t\t{\n\t\t\tname:        \"Blake2b with 'Hello, World!'\",\n\t\t\thasher:      NewBlake2B(),\n\t\t\tinput:       []byte(\"Hello, World!\"),\n\t\t\texpectedHex: \"511bc81dde11180838c562c82bb35f3223f46061ebde4a955c27b3f489cf1e03\",\n\t\t},\n\t\t{\n\t\t\tname:        \"Blake2b input at max size\",\n\t\t\thasher:      NewBlake2B(),\n\t\t\tinput:       bytes.Repeat([]byte(\"a\"), maxInputSize),\n\t\t\texpectedHex: \"605fd8458957df95394e9bf812f385264267c679e4899dc198ca67db4029d0ea\",\n\t\t},\n\t\t{\n\t\t\tname:        \"Blake2b empty input\",\n\t\t\thasher:      NewBlake2B(),\n\t\t\tinput:       []byte(\"\"),\n\t\t\texpectedHex: \"0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tgot, err := tc.hasher.Hash(tc.input)\n\t\t\tcheckError(t, err, tc.expectError, len(tc.input))\n\n\t\t\tif tc.expectError != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\texpected, err := hex.DecodeString(tc.expectedHex)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"invalid expected hex string '%s': %v\", tc.expectedHex, err)\n\t\t\t}\n\n\t\t\tif !bytes.Equal(got, expected) {\n\t\t\t\tt.Errorf(\"hash mismatch.\\nGot:      %x\\nExpected: %x\", got, expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc checkError(t *testing.T, err, expectError error, inputSize int) {\n\tt.Helper()\n\n\tif expectError != nil {\n\t\tvar inputTooLargeError *InputTooLargeError\n\t\tif errors.As(expectError, &inputTooLargeError) {\n\t\t\tvar inputTooLargeErr *InputTooLargeError\n\t\t\tif assert.ErrorAs(t, err, &inputTooLargeErr) {\n\t\t\t\tassert.Equal(t, inputSize, inputTooLargeErr.inputSize)\n\t\t\t\tassert.Equal(t, maxInputSize, inputTooLargeErr.maxSize)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tassert.NoError(t, err)\n\t}\n}\n\nfunc TestBlake2bHashIdempotency(t *testing.T) {\n\tt.Parallel()\n\n\thasher := NewBlake2B()\n\tinput := bytes.Repeat([]byte(\"a\"), maxInputSize)\n\n\thash1, err1 := hasher.Hash(input)\n\tassert.NoError(t, err1, \"unexpected error on first hash\")\n\n\thash2, err2 := hasher.Hash(input)\n\tassert.NoError(t, err2, \"unexpected error on second hash\")\n\n\tif !bytes.Equal(hash1, hash2) {\n\t\tt.Errorf(\"hash results are not identical.\\nFirst:  %x\\nSecond: %x\", hash1, hash2)\n\t}\n}\n\nvar sampleData = []byte(\"The quick brown fox jumps over the lazy dog\")\n\n// BenchmarkHasherPerGoroutine_Blake2b benchmarks hashing using separate Blake2b Hasher instances\n// for each goroutine, eliminating the need for synchronization.\nfunc BenchmarkHasherPerGoroutine_Blake2b(b *testing.B) {\n\tb.ReportAllocs()\n\tb.ResetTimer()\n\n\tb.RunParallel(func(pb *testing.PB) {\n\t\thasher := NewBlake2B()\n\t\tfor pb.Next() {\n\t\t\t_, err := hasher.Hash(sampleData)\n\t\t\tassert.NoError(b, err)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/iobuf/bufferedreaderseeker.go",
    "content": "// Package iobuf provides a buffered reading interface with seeking capabilities.\n//\n// For small amounts of data, it uses an in-memory buffer (bytes.Buffer) to store\n// read bytes. When the amount of data exceeds a specified threshold, it switches\n// to disk-based buffering using a temporary file. This approach balances memory\n// usage and performance, allowing efficient handling of both small and large data streams.\npackage iobuf\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/buffers/buffer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/buffers/pool\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cleantemp\"\n)\n\nconst defaultBufferSize = 1 << 16 // 64KB\n\nvar defaultBufferPool *pool.Pool\n\nfunc init() { defaultBufferPool = pool.NewBufferPool(defaultBufferSize) }\n\n// BufferedReadSeeker provides a buffered reading interface with seeking capabilities.\n// It wraps an io.Reader and optionally an io.Seeker, allowing for efficient\n// reading and seeking operations, even on non-seekable underlying readers.\n//\n// For small amounts of data, it uses an in-memory buffer (bytes.Buffer) to store\n// read bytes. When the amount of data exceeds a specified threshold, it switches\n// to disk-based buffering using a temporary file. This approach balances memory\n// usage and performance, allowing efficient handling of both small and large data streams.\n//\n// The struct manages the transition between in-memory and disk-based buffering\n// transparently, providing a seamless reading and seeking experience regardless\n// of the underlying data size or the seekability of the original reader.\n//\n// If the underlying reader is seekable, direct seeking operations are performed\n// on it. For non-seekable readers, seeking is emulated using the buffer or\n// temporary file.\n//\n// The caller MUST call Close() when done using the BufferedReadSeeker to clean up\n// resources like temporary files and buffers.\ntype BufferedReadSeeker struct {\n\treader io.Reader\n\tseeker io.Seeker // If the reader supports seeking, it's stored here for direct access\n\n\tbufPool *pool.Pool     // Pool for storing buffers for reuse.\n\tbuf     *buffer.Buffer // Buffer for storing data under the threshold in memory.\n\n\tbytesRead int64 // Total number of bytes read from the underlying reader\n\tindex     int64 // Current position in the virtual stream\n\n\tthreshold      int64    // Threshold for switching to file buffering\n\ttempFile       *os.File // Temporary file for disk-based buffering\n\ttempFileName   string   // Name of the temporary file\n\tdiskBufferSize int64    // Size of data written to disk\n\n\t// Fields to provide a quick way to determine the total size of the reader\n\t// without having to seek.\n\ttotalSize int64 // Total size of the reader\n\tsizeKnown bool  // Whether the total size of the reader is known\n}\n\n// asSeeker checks if a reader reliably supports seeking operations.\n// Some types, like os.File when used as a pipe, may implement io.Seeker\n// but do not actually support seeking.\nfunc asSeeker(r io.Reader) io.Seeker {\n\tseeker, ok := r.(io.Seeker)\n\tif !ok {\n\t\treturn nil\n\t}\n\n\t_, err := seeker.Seek(0, io.SeekCurrent)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn seeker\n}\n\n// NewBufferedReaderSeeker creates and initializes a BufferedReadSeeker.\n// It takes an io.Reader and checks if it supports seeking.\n// If the reader supports seeking, it is stored in the seeker field.\n//\n// The caller MUST call Close() when done using the returned BufferedReadSeeker\n// to clean up resources like temporary files and buffers.\nfunc NewBufferedReaderSeeker(r io.Reader) *BufferedReadSeeker {\n\tconst defaultThreshold = 1 << 24 // 16MB threshold for switching to file buffering\n\n\tvar (\n\t\tbuf    *buffer.Buffer\n\t\tseeker io.Seeker\n\t)\n\n\tseeker = asSeeker(r)\n\tif seeker == nil {\n\t\tbuf = defaultBufferPool.Get()\n\t}\n\n\treturn &BufferedReadSeeker{\n\t\treader:    r,\n\t\tseeker:    seeker,\n\t\tbufPool:   defaultBufferPool,\n\t\tbuf:       buf,\n\t\tthreshold: defaultThreshold,\n\t}\n}\n\n// Read reads len(out) bytes from the reader starting at the current index.\n// It handles both seekable and non-seekable underlying readers efficiently.\nfunc (br *BufferedReadSeeker) Read(out []byte) (int, error) {\n\tif br.seeker != nil {\n\t\t// For seekable readers, read directly from the underlying reader.\n\t\tn, err := br.reader.Read(out)\n\t\tif n > 0 {\n\t\t\tbr.bytesRead += int64(n)\n\t\t}\n\t\treturn n, err\n\t}\n\n\t// If we have a temp file and the total size is known, we can read directly from it.\n\tif br.sizeKnown && br.tempFile != nil {\n\t\tif br.index >= br.totalSize {\n\t\t\treturn 0, io.EOF\n\t\t}\n\t\tif _, err := br.tempFile.Seek(br.index, io.SeekStart); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tn, err := br.tempFile.Read(out)\n\t\tbr.index += int64(n)\n\t\treturn n, err\n\t}\n\n\tif br.buf == nil {\n\t\tbr.buf = br.bufPool.Get()\n\t}\n\n\tvar (\n\t\ttotalBytesRead int\n\t\terr            error\n\t)\n\n\t// If the current read position is within the in-memory buffer.\n\tif br.index < int64(br.buf.Len()) {\n\t\ttotalBytesRead = copy(out, br.buf.Bytes()[br.index:])\n\t\tbr.index += int64(totalBytesRead)\n\t\tif totalBytesRead == len(out) {\n\t\t\treturn totalBytesRead, nil\n\t\t}\n\t\tout = out[totalBytesRead:]\n\t}\n\n\t// If we've exceeded the in-memory threshold and have a temp file.\n\tif br.tempFile != nil && br.index < br.diskBufferSize {\n\t\tif _, err := br.tempFile.Seek(br.index-int64(br.buf.Len()), io.SeekStart); err != nil {\n\t\t\treturn totalBytesRead, err\n\t\t}\n\t\tm, err := br.tempFile.Read(out)\n\t\ttotalBytesRead += m\n\t\tbr.index += int64(m)\n\t\tif err != nil && !errors.Is(err, io.EOF) {\n\t\t\treturn totalBytesRead, err\n\t\t}\n\t\tif totalBytesRead == len(out) {\n\t\t\treturn totalBytesRead, nil\n\t\t}\n\t\tout = out[totalBytesRead:]\n\t}\n\n\tif len(out) == 0 {\n\t\treturn totalBytesRead, nil\n\t}\n\n\t// If we still need to read more data.\n\tvar readerBytes int\n\treaderBytes, err = br.reader.Read(out)\n\ttotalBytesRead += readerBytes\n\tbr.index += int64(readerBytes)\n\n\tif writeErr := br.writeData(out[:readerBytes]); writeErr != nil {\n\t\treturn totalBytesRead, writeErr\n\t}\n\n\tif errors.Is(err, io.EOF) {\n\t\tbr.totalSize = br.bytesRead\n\t\tbr.sizeKnown = true\n\t}\n\n\treturn totalBytesRead, err\n}\n\n// Seek sets the offset for the next Read or Write to offset.\n// It supports both seekable and non-seekable underlying readers.\nfunc (br *BufferedReadSeeker) Seek(offset int64, whence int) (int64, error) {\n\tif br.seeker != nil {\n\t\t// Use the underlying Seeker if available.\n\t\treturn br.seeker.Seek(offset, whence)\n\t}\n\n\t// Manual seeking for non-seekable readers.\n\tnewIndex := br.index\n\n\tswitch whence {\n\tcase io.SeekStart:\n\t\tnewIndex = offset\n\tcase io.SeekCurrent:\n\t\tnewIndex += offset\n\tcase io.SeekEnd:\n\t\t// If we already know the total size, we can use it directly.\n\t\tif !br.sizeKnown {\n\t\t\tif err := br.readToEnd(); err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t}\n\t\tnewIndex = br.totalSize + offset\n\tdefault:\n\t\treturn 0, errors.New(\"invalid whence value\")\n\t}\n\n\tif newIndex < 0 {\n\t\treturn 0, errors.New(\"can not seek to before start of reader\")\n\t}\n\n\t// For non-seekable readers, we need to ensure we've read up to the new index.\n\tif br.seeker == nil && newIndex > br.bytesRead {\n\t\tif err := br.readUntil(newIndex); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\n\tbr.index = newIndex\n\n\t// Update bytesRead only if we've moved beyond what we've read so far.\n\tif br.index > br.bytesRead {\n\t\tbr.bytesRead = br.index\n\t}\n\n\treturn newIndex, nil\n}\n\nfunc (br *BufferedReadSeeker) readToEnd() error {\n\tbuf := br.bufPool.Get()\n\tdefer br.bufPool.Put(buf)\n\n\tfor {\n\t\tn, err := io.CopyN(buf, br.reader, defaultBufferSize)\n\t\tif n > 0 {\n\t\t\t// Write the data from the buffer.\n\t\t\tif writeErr := br.writeData(buf.Bytes()[:n]); writeErr != nil {\n\t\t\t\treturn writeErr\n\t\t\t}\n\t\t}\n\t\t// Reset the buffer for the next iteration.\n\t\tbuf.Reset()\n\t\tif errors.Is(err, io.EOF) {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// If a temporary file exists and the buffer contains data,\n\t// flush the buffer to the file. This allows future operations\n\t// to utilize the temporary file exclusively, simplifying\n\t// management by avoiding separate handling of the buffer and file.\n\tif br.tempFile != nil && br.buf.Len() > 0 {\n\t\tif err := br.flushBufferToDisk(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tbr.totalSize = br.bytesRead\n\tbr.sizeKnown = true\n\n\treturn nil\n}\n\nfunc (br *BufferedReadSeeker) writeData(data []byte) error {\n\tif br.buf == nil {\n\t\tbr.buf = br.bufPool.Get()\n\t}\n\n\t_, err := br.buf.Write(data)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbr.bytesRead += int64(len(data))\n\n\t// Check if we've reached or exceeded the threshold.\n\tif br.buf.Len() < int(br.threshold) {\n\t\treturn nil\n\t}\n\n\tif br.tempFile == nil {\n\t\tif err := br.createTempFile(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Flush the buffer to disk.\n\treturn br.flushBufferToDisk()\n}\n\nfunc (br *BufferedReadSeeker) readUntil(index int64) error {\n\tbuf := br.bufPool.Get()\n\tdefer br.bufPool.Put(buf)\n\n\tfor br.bytesRead < index {\n\t\tremaining := index - br.bytesRead\n\t\tbufSize := int64(defaultBufferSize)\n\t\tif remaining < bufSize {\n\t\t\tbufSize = remaining\n\t\t}\n\n\t\tn, err := io.CopyN(buf, br, bufSize)\n\t\tif err != nil && !errors.Is(err, io.EOF) {\n\t\t\treturn err\n\t\t}\n\n\t\tif n == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tbuf.Reset()\n\t}\n\n\treturn nil\n}\n\nfunc (br *BufferedReadSeeker) createTempFile() error {\n\ttempFile, err := os.CreateTemp(os.TempDir(), cleantemp.MkFilename())\n\tif err != nil {\n\t\treturn err\n\t}\n\tbr.tempFile = tempFile\n\tbr.tempFileName = tempFile.Name()\n\n\treturn nil\n}\n\nfunc (br *BufferedReadSeeker) flushBufferToDisk() error {\n\tn, err := br.buf.WriteTo(br.tempFile)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbr.diskBufferSize += n\n\n\treturn err\n}\n\n// ReadAt reads len(out) bytes into out starting at offset off in the underlying input source.\n// It uses Seek and Read to implement random access reading.\nfunc (br *BufferedReadSeeker) ReadAt(out []byte, offset int64) (int, error) {\n\tif br.seeker != nil {\n\t\t// Use the underlying Seeker if available.\n\t\t_, err := br.Seek(offset, io.SeekStart)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\treturn br.Read(out)\n\t}\n\n\tcurrentIndex := br.index\n\n\tif _, err := br.Seek(offset, io.SeekStart); err != nil {\n\t\treturn 0, err\n\t}\n\n\tn, err := br.Read(out)\n\tif err != nil {\n\t\treturn n, err\n\t}\n\n\t// Seek back to the original position.\n\tif _, err = br.Seek(currentIndex, io.SeekStart); err != nil {\n\t\treturn n, err\n\t}\n\n\treturn n, err\n}\n\n// Close closes the BufferedReadSeeker and releases any resources used.\n// It closes the temporary file if one was created and removes it from disk and\n// returns the buffer to the pool.\nfunc (br *BufferedReadSeeker) Close() error {\n\tif br.buf != nil {\n\t\tbr.bufPool.Put(br.buf)\n\t}\n\n\tif br.tempFile != nil {\n\t\tbr.tempFile.Close()\n\t\treturn os.Remove(br.tempFileName)\n\t}\n\treturn nil\n}\n\n// Size returns the total size of the reader.\nfunc (br *BufferedReadSeeker) Size() (int64, error) {\n\tif br.sizeKnown {\n\t\treturn br.totalSize, nil\n\t}\n\n\tcurrentPos, err := br.Seek(0, io.SeekCurrent)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"failed to get current position: %w\", err)\n\t}\n\n\tendPos, err := br.Seek(0, io.SeekEnd)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"failed to seek to end: %w\", err)\n\t}\n\n\tif _, err = br.Seek(currentPos, io.SeekStart); err != nil {\n\t\treturn 0, fmt.Errorf(\"failed to restore position: %w\", err)\n\t}\n\n\tbr.totalSize = endPos\n\tbr.sizeKnown = true\n\n\treturn br.totalSize, nil\n}\n"
  },
  {
    "path": "pkg/iobuf/bufferedreaderseeker_test.go",
    "content": "package iobuf\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestBufferedReaderSeekerRead(t *testing.T) {\n\ttests := []struct {\n\t\tname              string\n\t\treader            io.Reader\n\t\treads             []int\n\t\texpectedReads     []int\n\t\texpectedBytes     [][]byte\n\t\texpectedBytesRead int64\n\t\texpectedIndex     int64\n\t\texpectedBuffer    []byte\n\t\texpectedError     error\n\t}{\n\t\t{\n\t\t\tname:              \"read from seekable reader\",\n\t\t\treader:            strings.NewReader(\"test data\"),\n\t\t\treads:             []int{4},\n\t\t\texpectedReads:     []int{4},\n\t\t\texpectedBytes:     [][]byte{[]byte(\"test\")},\n\t\t\texpectedBytesRead: 4,\n\t\t\texpectedIndex:     4,\n\t\t},\n\t\t{\n\t\t\tname:              \"read from non-seekable reader with buffering\",\n\t\t\treader:            bytes.NewBufferString(\"test data\"),\n\t\t\treads:             []int{4},\n\t\t\texpectedReads:     []int{4},\n\t\t\texpectedBytes:     [][]byte{[]byte(\"test\")},\n\t\t\texpectedBytesRead: 4,\n\t\t\texpectedIndex:     4,\n\t\t\texpectedBuffer:    []byte(\"test\"),\n\t\t},\n\t\t{\n\t\t\tname:              \"read from non-seekable reader without buffering\",\n\t\t\treader:            bytes.NewBufferString(\"test data\"),\n\t\t\treads:             []int{4},\n\t\t\texpectedReads:     []int{4},\n\t\t\texpectedBytes:     [][]byte{[]byte(\"test\")},\n\t\t\texpectedBytesRead: 4,\n\t\t\texpectedIndex:     4,\n\t\t},\n\t\t{\n\t\t\tname:              \"read beyond buffer\",\n\t\t\treader:            strings.NewReader(\"test data\"),\n\t\t\treads:             []int{10},\n\t\t\texpectedReads:     []int{9},\n\t\t\texpectedBytes:     [][]byte{[]byte(\"test data\")},\n\t\t\texpectedBytesRead: 9,\n\t\t\texpectedIndex:     9,\n\t\t},\n\t\t{\n\t\t\tname:              \"read with empty reader\",\n\t\t\treader:            strings.NewReader(\"\"),\n\t\t\treads:             []int{4},\n\t\t\texpectedReads:     []int{0},\n\t\t\texpectedBytes:     [][]byte{[]byte(\"\")},\n\t\t\texpectedBytesRead: 0,\n\t\t\texpectedIndex:     0,\n\t\t\texpectedError:     io.EOF,\n\t\t},\n\t\t{\n\t\t\tname:              \"read exact buffer size\",\n\t\t\treader:            strings.NewReader(\"test\"),\n\t\t\treads:             []int{4},\n\t\t\texpectedReads:     []int{4},\n\t\t\texpectedBytes:     [][]byte{[]byte(\"test\")},\n\t\t\texpectedBytesRead: 4,\n\t\t\texpectedIndex:     4,\n\t\t},\n\t\t{\n\t\t\tname:              \"read less than buffer size\",\n\t\t\treader:            strings.NewReader(\"te\"),\n\t\t\treads:             []int{4},\n\t\t\texpectedReads:     []int{2},\n\t\t\texpectedBytes:     [][]byte{[]byte(\"te\")},\n\t\t\texpectedBytesRead: 2,\n\t\t\texpectedIndex:     2,\n\t\t},\n\t\t{\n\t\t\tname:              \"read more than buffer size without buffering\",\n\t\t\treader:            bytes.NewBufferString(\"test data\"),\n\t\t\treads:             []int{4},\n\t\t\texpectedReads:     []int{4},\n\t\t\texpectedBytes:     [][]byte{[]byte(\"test\")},\n\t\t\texpectedBytesRead: 4,\n\t\t\texpectedIndex:     4,\n\t\t},\n\t\t{\n\t\t\tname:              \"multiple reads with buffering\",\n\t\t\treader:            bytes.NewBufferString(\"test data\"),\n\t\t\treads:             []int{4, 5},\n\t\t\texpectedReads:     []int{4, 5},\n\t\t\texpectedBytes:     [][]byte{[]byte(\"test\"), []byte(\" data\")},\n\t\t\texpectedBytesRead: 9,\n\t\t\texpectedIndex:     9,\n\t\t\texpectedBuffer:    []byte(\"test data\"),\n\t\t},\n\t\t{\n\t\t\tname:              \"multiple reads without buffering\",\n\t\t\treader:            bytes.NewBufferString(\"test data\"),\n\t\t\treads:             []int{4, 5},\n\t\t\texpectedReads:     []int{4, 5},\n\t\t\texpectedBytes:     [][]byte{[]byte(\"test\"), []byte(\" data\")},\n\t\t\texpectedBytesRead: 9,\n\t\t\texpectedIndex:     9,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tbrs := NewBufferedReaderSeeker(tt.reader)\n\n\t\t\tfor i, readSize := range tt.reads {\n\t\t\t\tbuf := make([]byte, readSize)\n\t\t\t\tn, err := brs.Read(buf)\n\n\t\t\t\tassert.Equal(t, tt.expectedReads[i], n, \"read %d: unexpected number of bytes read\", i+1)\n\t\t\t\tassert.Equal(t, tt.expectedBytes[i], buf[:n], \"read %d: unexpected bytes\", i+1)\n\n\t\t\t\tif i == len(tt.reads)-1 {\n\t\t\t\t\tif tt.expectedError != nil {\n\t\t\t\t\t\tassert.ErrorIs(t, err, tt.expectedError)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tassert.NoError(t, err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tassert.Equal(t, tt.expectedBytesRead, brs.bytesRead)\n\t\t\tif brs.seeker == nil {\n\t\t\t\tassert.Equal(t, tt.expectedIndex, brs.index)\n\t\t\t}\n\n\t\t\tif brs.buf != nil && len(tt.expectedBuffer) > 0 {\n\t\t\t\tassert.Equal(t, tt.expectedBuffer, brs.buf.Bytes())\n\t\t\t} else {\n\t\t\t\tassert.Nil(t, tt.expectedBuffer)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferedReaderSeekerSeek(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\treader       io.Reader\n\t\toffset       int64\n\t\twhence       int\n\t\texpectedPos  int64\n\t\texpectedErr  bool\n\t\texpectedRead []byte\n\t}{\n\t\t{\n\t\t\tname:         \"seek on seekable reader with SeekStart\",\n\t\t\treader:       strings.NewReader(\"test data\"),\n\t\t\toffset:       4,\n\t\t\twhence:       io.SeekStart,\n\t\t\texpectedPos:  4,\n\t\t\texpectedErr:  false,\n\t\t\texpectedRead: []byte(\" dat\"),\n\t\t},\n\t\t{\n\t\t\tname:         \"seek on seekable reader with SeekCurrent\",\n\t\t\treader:       strings.NewReader(\"test data\"),\n\t\t\toffset:       4,\n\t\t\twhence:       io.SeekCurrent,\n\t\t\texpectedPos:  4,\n\t\t\texpectedErr:  false,\n\t\t\texpectedRead: []byte(\" dat\"),\n\t\t},\n\t\t{\n\t\t\tname:         \"seek on seekable reader with SeekEnd\",\n\t\t\treader:       strings.NewReader(\"test data\"),\n\t\t\toffset:       -4,\n\t\t\twhence:       io.SeekEnd,\n\t\t\texpectedPos:  5,\n\t\t\texpectedErr:  false,\n\t\t\texpectedRead: []byte(\"data\"),\n\t\t},\n\t\t{\n\t\t\tname:         \"seek on non-seekable reader with SeekStart\",\n\t\t\treader:       bytes.NewBufferString(\"test data\"),\n\t\t\toffset:       4,\n\t\t\twhence:       io.SeekStart,\n\t\t\texpectedPos:  4,\n\t\t\texpectedErr:  false,\n\t\t\texpectedRead: []byte{},\n\t\t},\n\t\t{\n\t\t\tname:         \"seek on non-seekable reader with SeekCurrent\",\n\t\t\treader:       bytes.NewBufferString(\"test data\"),\n\t\t\toffset:       4,\n\t\t\twhence:       io.SeekCurrent,\n\t\t\texpectedPos:  4,\n\t\t\texpectedErr:  false,\n\t\t\texpectedRead: []byte{},\n\t\t},\n\t\t{\n\t\t\tname:         \"seek on non-seekable reader with SeekEnd\",\n\t\t\treader:       bytes.NewBufferString(\"test data\"),\n\t\t\toffset:       -4,\n\t\t\twhence:       io.SeekEnd,\n\t\t\texpectedPos:  5,\n\t\t\texpectedErr:  false,\n\t\t\texpectedRead: []byte{},\n\t\t},\n\t\t{\n\t\t\tname:         \"seek to negative position\",\n\t\t\treader:       strings.NewReader(\"test data\"),\n\t\t\toffset:       -1,\n\t\t\twhence:       io.SeekStart,\n\t\t\texpectedPos:  0,\n\t\t\texpectedErr:  true,\n\t\t\texpectedRead: nil,\n\t\t},\n\t\t{\n\t\t\tname:         \"seek beyond EOF on non-seekable reader\",\n\t\t\treader:       bytes.NewBufferString(\"test data\"),\n\t\t\toffset:       20,\n\t\t\twhence:       io.SeekEnd,\n\t\t\texpectedPos:  29,\n\t\t\texpectedErr:  false,\n\t\t\texpectedRead: []byte{},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tbrs := NewBufferedReaderSeeker(tt.reader)\n\t\t\tpos, err := brs.Seek(tt.offset, tt.whence)\n\t\t\tif tt.expectedErr {\n\t\t\t\tassert.Error(t, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Equal(t, tt.expectedPos, pos)\n\t\t\tif len(tt.expectedRead) > 0 {\n\t\t\t\tbuf := make([]byte, len(tt.expectedRead))\n\t\t\t\tnn, err := brs.Read(buf)\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.Equal(t, len(tt.expectedRead), nn)\n\t\t\t\tassert.Equal(t, tt.expectedRead, buf[:nn])\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestBufferedReaderSeekerReadAt(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\treader      io.Reader\n\t\toffset      int64\n\t\tlength      int\n\t\texpectedN   int\n\t\texpectErr   bool\n\t\texpectedOut []byte\n\t}{\n\t\t{\n\t\t\tname:        \"read within buffer on seekable reader\",\n\t\t\treader:      strings.NewReader(\"test data\"),\n\t\t\toffset:      5,\n\t\t\tlength:      4,\n\t\t\texpectedN:   4,\n\t\t\texpectedOut: []byte(\"data\"),\n\t\t},\n\t\t{\n\t\t\tname:        \"read within buffer on non-seekable reader\",\n\t\t\treader:      bytes.NewBufferString(\"test data\"),\n\t\t\toffset:      5,\n\t\t\tlength:      4,\n\t\t\texpectedN:   4,\n\t\t\texpectedOut: []byte(\"data\"),\n\t\t},\n\t\t{\n\t\t\tname:        \"read beyond buffer\",\n\t\t\treader:      strings.NewReader(\"test data\"),\n\t\t\toffset:      9,\n\t\t\tlength:      1,\n\t\t\texpectedN:   0,\n\t\t\texpectErr:   true,\n\t\t\texpectedOut: []byte{},\n\t\t},\n\t\t{\n\t\t\tname:        \"read at start\",\n\t\t\treader:      strings.NewReader(\"test data\"),\n\t\t\toffset:      0,\n\t\t\tlength:      4,\n\t\t\texpectedN:   4,\n\t\t\texpectedOut: []byte(\"test\"),\n\t\t},\n\t\t{\n\t\t\tname:        \"read with zero length\",\n\t\t\treader:      strings.NewReader(\"test data\"),\n\t\t\toffset:      0,\n\t\t\tlength:      0,\n\t\t\texpectedN:   0,\n\t\t\texpectedOut: []byte{},\n\t\t},\n\t\t{\n\t\t\tname:        \"read negative offset\",\n\t\t\treader:      strings.NewReader(\"test data\"),\n\t\t\toffset:      -1,\n\t\t\tlength:      4,\n\t\t\texpectedN:   0,\n\t\t\texpectErr:   true,\n\t\t\texpectedOut: []byte{},\n\t\t},\n\t\t{\n\t\t\tname:        \"read beyond end on non-seekable reader\",\n\t\t\treader:      bytes.NewBufferString(\"test data\"),\n\t\t\toffset:      20,\n\t\t\tlength:      4,\n\t\t\texpectedN:   0,\n\t\t\texpectErr:   true,\n\t\t\texpectedOut: []byte{},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tbrs := NewBufferedReaderSeeker(tt.reader)\n\n\t\t\tout := make([]byte, tt.length)\n\t\t\tn, err := brs.ReadAt(out, tt.offset)\n\t\t\tif tt.expectErr {\n\t\t\t\tassert.Error(t, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Equal(t, tt.expectedN, n)\n\t\t\tassert.Equal(t, tt.expectedOut, out[:n])\n\t\t})\n\t}\n}\n\n// TestBufferedReadSeekerSize tests the Size method of BufferedReadSeeker.\nfunc TestBufferedReadSeekerSize(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\treader         io.Reader\n\t\tsetup          func(*BufferedReadSeeker)\n\t\texpectedSize   int64\n\t\texpectError    bool\n\t\tverifyPosition func(*BufferedReadSeeker, int64)\n\t}{\n\t\t{\n\t\t\tname:         \"size of seekable reader\",\n\t\t\treader:       strings.NewReader(\"Hello, World!\"),\n\t\t\texpectedSize: 13,\n\t\t},\n\t\t{\n\t\t\tname:         \"size of non-seekable reader\",\n\t\t\treader:       bytes.NewBufferString(\"Hello, World!\"),\n\t\t\texpectedSize: 13,\n\t\t},\n\t\t{\n\t\t\tname:         \"size of empty seekable reader\",\n\t\t\treader:       strings.NewReader(\"\"),\n\t\t\texpectedSize: 0,\n\t\t},\n\t\t{\n\t\t\tname:         \"size of empty non-seekable reader\",\n\t\t\treader:       bytes.NewBufferString(\"\"),\n\t\t\texpectedSize: 0,\n\t\t},\n\t\t{\n\t\t\tname:   \"size of non-seekable reader after partial read\",\n\t\t\treader: bytes.NewBufferString(\"Partial read data\"),\n\t\t\tsetup: func(brs *BufferedReadSeeker) {\n\t\t\t\t// Read first 7 bytes (\"Partial\").\n\t\t\t\tbuf := make([]byte, 7)\n\t\t\t\t_, _ = brs.Read(buf)\n\t\t\t},\n\t\t\texpectedSize: 17, // \"Partial read data\" is 16 bytes\n\t\t\texpectError:  false,\n\t\t\tverifyPosition: func(brs *BufferedReadSeeker, expectedSize int64) {\n\t\t\t\t// After Size is called, the read position should remain at 7\n\t\t\t\tcurrentPos, err := brs.Seek(0, io.SeekCurrent)\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.Equal(t, int64(7), currentPos)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:         \"repeated Size calls\",\n\t\t\treader:       strings.NewReader(\"Repeated Size Calls Test\"),\n\t\t\texpectedSize: 24,\n\t\t\texpectError:  false,\n\t\t\tsetup: func(brs *BufferedReadSeeker) {\n\t\t\t\t// Call Size multiple times.\n\t\t\t\tsize1, err1 := brs.Size()\n\t\t\t\tassert.NoError(t, err1)\n\t\t\t\tassert.Equal(t, int64(24), size1)\n\n\t\t\t\tsize2, err2 := brs.Size()\n\t\t\t\tassert.NoError(t, err2)\n\t\t\t\tassert.Equal(t, int64(24), size2)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"size with error during reading\",\n\t\t\treader: &errorReader{\n\t\t\t\tdata:       \"Data before error\",\n\t\t\t\terrorAfter: 5, // Return error after reading 5 bytes\n\t\t\t},\n\t\t\texpectedSize: 0,\n\t\t\texpectError:  true,\n\t\t},\n\t\t{\n\t\t\tname:         \"size with limited reader simulating EOF\",\n\t\t\treader:       io.LimitReader(strings.NewReader(\"Limited data\"), 7),\n\t\t\texpectedSize: 7,\n\t\t\texpectError:  false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tbrs := NewBufferedReaderSeeker(tt.reader)\n\n\t\t\tif tt.setup != nil {\n\t\t\t\ttt.setup(brs)\n\t\t\t}\n\n\t\t\tsize, err := brs.Size()\n\t\t\tif tt.expectError {\n\t\t\t\tassert.Error(t, err)\n\t\t\t} else {\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.Equal(t, tt.expectedSize, size)\n\t\t\t}\n\n\t\t\tif tt.verifyPosition != nil {\n\t\t\t\ttt.verifyPosition(brs, tt.expectedSize)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// errorReader is an io.Reader that returns an error after reading a specified number of bytes.\n// It's used to simulate non-EOF errors during read operations.\ntype errorReader struct {\n\tdata       string\n\terrorAfter int // Number of bytes to read before returning an error\n\treadBytes  int\n}\n\nfunc (er *errorReader) Read(p []byte) (int, error) {\n\tif er.readBytes >= er.errorAfter {\n\t\treturn 0, errors.New(\"simulated read error\")\n\t}\n\tremaining := er.errorAfter - er.readBytes\n\ttoRead := len(p)\n\tif toRead > remaining {\n\t\ttoRead = remaining\n\t}\n\tcopy(p, er.data[er.readBytes:er.readBytes+toRead])\n\ter.readBytes += toRead\n\treturn toRead, nil\n}\n"
  },
  {
    "path": "pkg/log/dynamic_redactor.go",
    "content": "package log\n\nimport (\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n)\n\ntype dynamicRedactor struct {\n\tdenySet   map[string]struct{}\n\tdenySlice []string\n\tdenyMu    sync.Mutex\n\n\treplacer atomic.Pointer[strings.Replacer]\n}\n\nvar globalRedactor *dynamicRedactor\n\nfunc init() {\n\tglobalRedactor = &dynamicRedactor{denySet: make(map[string]struct{})}\n\tglobalRedactor.replacer.CompareAndSwap(nil, strings.NewReplacer())\n}\n\n// RedactGlobally configures the global log redactor to redact the provided value during log emission. The value will be\n// redacted in log messages and values that are strings, but not in log keys or values of other types.\nfunc RedactGlobally(sensitiveValue string) {\n\tglobalRedactor.configureForRedaction(sensitiveValue)\n}\n\nfunc (r *dynamicRedactor) configureForRedaction(sensitiveValue string) {\n\tif sensitiveValue == \"\" {\n\t\treturn\n\t}\n\n\tr.denyMu.Lock()\n\tdefer r.denyMu.Unlock()\n\n\tif _, ok := r.denySet[sensitiveValue]; ok {\n\t\treturn\n\t}\n\n\tr.denySet[sensitiveValue] = struct{}{}\n\tr.denySlice = append(r.denySlice, sensitiveValue, \"*****\")\n\n\tr.replacer.Store(strings.NewReplacer(r.denySlice...))\n}\n\nfunc (r *dynamicRedactor) redact(s string) string {\n\treturn r.replacer.Load().Replace(s)\n}\n"
  },
  {
    "path": "pkg/log/level.go",
    "content": "package log\n\nimport (\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\nvar (\n\t// Global, default log level control.\n\tglobalLogLevel levelSetter = zap.NewAtomicLevel()\n)\n\ntype levelSetter interface {\n\tzapcore.LevelEnabler\n\tSetLevel(zapcore.Level)\n\tLevel() zapcore.Level\n}\n\n// SetLevel sets the log level for loggers created with the default level\n// controller.\nfunc SetLevel(level int8) {\n\tSetLevelForControl(globalLogLevel, level)\n}\n\n// SetLevelForControl sets the log level for a given control.\nfunc SetLevelForControl(control levelSetter, level int8) {\n\t// Zap's levels get more verbose as the number gets smaller, as explained\n\t// by zapr here: https://github.com/go-logr/zapr#increasing-verbosity\n\t// For example setting the level to -2 below, means log.V(2) will be enabled.\n\tcontrol.SetLevel(zapcore.Level(-level))\n}\n"
  },
  {
    "path": "pkg/log/log.go",
    "content": "package log\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"log/slog\"\n\t\"time\"\n\n\t\"github.com/TheZeroSlave/zapsentry\"\n\t\"github.com/getsentry/sentry-go\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/go-logr/zapr\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\ntype SyncFunc func() error\n\ntype SinkOption func(*sinkConfig)\n\n// New creates a new log object with the provided sinks. If no sinks are\n// provided, a no-op sink will be used. Returns the logger and a cleanup\n// function that should be executed before the program exits.\nfunc New(service string, cores ...zapcore.Core) (logr.Logger, SyncFunc) {\n\treturn NewWithCaller(service, false, cores...)\n}\n\n// NewWithCaller creates a new logger named after the specified service with\n// the provided sinks. If addCaller is true, call site information will be\n// attached to each emitted log message. (This behavior can be disabled on a\n// per-sink basis using WithSuppressCaller.)\nfunc NewWithCaller(service string, addCaller bool, cores ...zapcore.Core) (logr.Logger, SyncFunc) {\n\t// create logger\n\tzapLogger := zap.New(zapcore.NewTee(cores...), zap.WithCaller(addCaller))\n\tlogger := zapr.NewLogger(zapLogger).WithName(service)\n\n\treturn logger, zapLogger.Sync\n}\n\n// WithSentry adds sentry integration to the logger.\nfunc WithSentry(client *sentry.Client, tags map[string]string) zapcore.Core {\n\t// create sentry core\n\tcfg := zapsentry.Configuration{\n\t\tTags:              tags,\n\t\tLevel:             zapcore.ErrorLevel,\n\t\tEnableBreadcrumbs: true,\n\t\tBreadcrumbLevel:   zapcore.InfoLevel,\n\t}\n\tcore, err := zapsentry.NewCore(cfg, zapsentry.NewSentryClientFromClient(client))\n\tif err != nil {\n\t\t// NewCore should never fail because NewSentryClientFromClient\n\t\t// never returns an error and NewCore only returns an error if\n\t\t// the zapsentry.Configuration is invalid, which would indicate\n\t\t// a programmer error.\n\t\tpanic(err)\n\t}\n\n\treturn core\n}\n\ntype sinkConfig struct {\n\tencoder        zapcore.Encoder\n\tsink           zapcore.WriteSyncer\n\tlevel          levelSetter\n\tredactor       *dynamicRedactor\n\tsuppressCaller bool\n}\n\n// WithJSONSink adds a JSON encoded output to the logger.\nfunc WithJSONSink(sink io.Writer, opts ...SinkOption) zapcore.Core {\n\treturn newCore(\n\t\tzapcore.NewJSONEncoder(defaultEncoderConfig()),\n\t\tzapcore.Lock(zapcore.AddSync(sink)),\n\t\tglobalLogLevel,\n\t\topts...,\n\t)\n}\n\n// WithConsoleSink adds a console-style output to the logger.\nfunc WithConsoleSink(sink io.Writer, opts ...SinkOption) zapcore.Core {\n\treturn newCore(\n\t\tzapcore.NewConsoleEncoder(defaultEncoderConfig()),\n\t\tzapcore.Lock(zapcore.AddSync(sink)),\n\t\tglobalLogLevel,\n\t\topts...,\n\t)\n}\n\nfunc defaultEncoderConfig() zapcore.EncoderConfig {\n\tconf := zap.NewProductionEncoderConfig()\n\t// Use more human-readable time format.\n\tconf.EncodeTime = zapcore.TimeEncoderOfLayout(time.RFC3339)\n\tconf.EncodeLevel = func(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {\n\t\tif level == zapcore.ErrorLevel {\n\t\t\tenc.AppendString(\"error\")\n\t\t\treturn\n\t\t}\n\t\tenc.AppendString(fmt.Sprintf(\"info-%d\", -int8(level)))\n\t}\n\treturn conf\n}\n\n// AddSentry initializes a sentry client and extends an existing\n// logr.Logger with the hook.\nfunc AddSentry(l logr.Logger, client *sentry.Client, tags map[string]string) (logr.Logger, SyncFunc, error) {\n\treturn AddSink(l, WithSentry(client, tags))\n}\n\n// AddSink extends an existing logr.Logger with a new sink. It returns the new logr.Logger, a cleanup function, and an\n// error.\n//\n// The new sink will not inherit any of the existing logger's key-value pairs. Key-value pairs can be added to the new\n// sink specifically by passing them to this function.\nfunc AddSink(l logr.Logger, core zapcore.Core, keysAndValues ...any) (logr.Logger, SyncFunc, error) {\n\t// New key-value pairs cannot be ergonomically added directly to cores. logr has code to do it, but that code is not\n\t// exported. Rather than replicating it ourselves, we indirectly use it by creating a temporary logger for the new\n\t// core, adding the key-value pairs to the temporary logger, and then extracting the temporary logger's modified\n\t// core.\n\tnewSinkLogger := zapr.NewLogger(zap.New(core))\n\tnewSinkLogger = newSinkLogger.WithValues(keysAndValues...)\n\tnewCoreLogger, err := getZapLogger(newSinkLogger)\n\tif err != nil {\n\t\treturn l, nil, fmt.Errorf(\"error setting up new key-value pairs: %w\", err)\n\t}\n\tnewSinkCore := newCoreLogger.Core()\n\n\tzapLogger, err := getZapLogger(l)\n\tif err != nil {\n\t\treturn l, nil, errors.New(\"unsupported logr implementation\")\n\t}\n\n\tnewLoggerOptions := []zap.Option{\n\t\t// Tee the new core together with the original core\n\t\tzap.WrapCore(func(core zapcore.Core) zapcore.Core { return zapcore.NewTee(core, newSinkCore) }),\n\n\t\t// CMR: zapr.NewLogger, for whatever reason, assumes that the passed-in logger doesn't have its caller frame\n\t\t// adjustment already set up, so it adds a frame skip of 2. However, that assumption doesn't hold here because\n\t\t// we're adding a core to an existing logger rather than creating a new one. I can't figure out a way to disable\n\t\t// this automatic frame adjustment, so we compensate for it with the hamfisted kludge of a compensating offset.\n\t\tzap.AddCallerSkip(-2),\n\t}\n\n\tzapLogger = zapLogger.WithOptions(newLoggerOptions...)\n\tnewLogger := zapr.NewLogger(zapLogger)\n\treturn newLogger, zapLogger.Sync, nil\n}\n\n// getZapLogger is a helper function that gets the underlying zap logger from a\n// logr.Logger interface.\nfunc getZapLogger(l logr.Logger) (*zap.Logger, error) {\n\tif u, ok := l.GetSink().(zapr.Underlier); ok {\n\t\treturn u.GetUnderlying(), nil\n\t}\n\treturn nil, errors.New(\"not a zapr logger\")\n}\n\n// WithLevel sets the sink's level to a static level. This option prevents\n// changing the log level for this sink later on.\nfunc WithLevel(level int8) SinkOption {\n\treturn WithLeveler(\n\t\t// Zap's levels get more verbose as the number gets smaller, as explained\n\t\t// by zapr here: https://github.com/go-logr/zapr#increasing-verbosity\n\t\t// For example setting the level to -2 below, means log.V(2) will be enabled.\n\t\tzap.NewAtomicLevelAt(zapcore.Level(-level)),\n\t)\n}\n\n// WithLeveler sets the sink's level enabler to leveler.\nfunc WithLeveler(leveler levelSetter) SinkOption {\n\treturn func(conf *sinkConfig) {\n\t\tconf.level = leveler\n\t}\n}\n\n// WithGlobalRedaction adds values to be redacted from logs.\nfunc WithGlobalRedaction() SinkOption {\n\treturn func(conf *sinkConfig) {\n\t\tconf.redactor = globalRedactor\n\t}\n}\n\n// WithSuppressCaller prevents the sink being configured from logging any caller information, irrespective of any other\n// logger settings.\nfunc WithSuppressCaller() SinkOption {\n\treturn func(conf *sinkConfig) {\n\t\tconf.suppressCaller = true\n\t}\n}\n\n// ToLogger converts the logr.Logger into a legacy *log.Logger.\nfunc ToLogger(l logr.Logger) *log.Logger {\n\treturn slog.NewLogLogger(logr.ToSlogHandler(l), slog.LevelInfo)\n}\n\n// ToSlogger converts the logr.Logger into a *slog.Logger.\nfunc ToSlogger(l logr.Logger) *slog.Logger {\n\treturn slog.New(logr.ToSlogHandler(l))\n}\n\n// newCore is a helper function that creates a default sinkConfig,\n// applies the options, then creates a zapcore.Core.\nfunc newCore(\n\tdefaultEncoder zapcore.Encoder,\n\tdefaultSink zapcore.WriteSyncer,\n\tdefaultLevel levelSetter,\n\topts ...SinkOption,\n) zapcore.Core {\n\tconf := sinkConfig{\n\t\tencoder: defaultEncoder,\n\t\tsink:    defaultSink,\n\t\tlevel:   defaultLevel,\n\t}\n\tfor _, f := range opts {\n\t\tf(&conf)\n\t}\n\tcore := zapcore.NewCore(\n\t\tconf.encoder,\n\t\tconf.sink,\n\t\tconf.level,\n\t)\n\n\tif conf.redactor != nil {\n\t\tcore = NewRedactionCore(core, conf.redactor)\n\t}\n\n\tif conf.suppressCaller {\n\t\tcore = &suppressCallerCore{Core: core}\n\t}\n\n\treturn core\n}\n"
  },
  {
    "path": "pkg/log/log_cores_test.go",
    "content": "package log\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/go-logr/zapr\"\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/stretchr/testify/suite\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\n// ignoreEverythingCore is a zap core that rejects all log entries. It is used to test that our custom cores respect\n// wrapped core check logic.\ntype ignoreEverythingCore struct {\n\tzapcore.Core\n}\n\nfunc (t *ignoreEverythingCore) With(fields []zapcore.Field) zapcore.Core {\n\treturn &ignoreEverythingCore{t.Core.With(fields)}\n}\n\nfunc (t *ignoreEverythingCore) Check(entry zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {\n\treturn ce\n}\n\nvar _ zapcore.Core = (*ignoreEverythingCore)(nil)\n\ntype TestSuite struct {\n\tsuite.Suite\n\n\toldRedactor *dynamicRedactor\n}\n\nfunc (ts *TestSuite) SetupTest() {\n\tts.oldRedactor = globalRedactor\n\tglobalRedactor = &dynamicRedactor{\n\t\tdenySet: make(map[string]struct{}),\n\t}\n\tglobalRedactor.replacer.Store(strings.NewReplacer())\n}\n\nfunc (ts *TestSuite) TearDownTest() {\n\tglobalRedactor = ts.oldRedactor\n}\n\nfunc TestCustomCores(t *testing.T) {\n\tsuite.Run(t, new(TestSuite))\n}\n\n// This test confirms that when our custom redaction core wraps our custom caller suppression core, both redaction and\n// caller suppression occur.\nfunc (ts *TestSuite) TestCoreComposition_RedactionWrappingCallerSuppression() {\n\t// Arrange: Create a testable log sink\n\tvar buf bytes.Buffer\n\tbaseCore := zapcore.NewCore(\n\t\tzapcore.NewJSONEncoder(defaultEncoderConfig()),\n\t\tzapcore.Lock(zapcore.AddSync(&buf)),\n\t\tglobalLogLevel)\n\n\t// Arrange: Create a core stack and set up redaction\n\tcore := NewRedactionCore(&suppressCallerCore{baseCore}, globalRedactor)\n\tRedactGlobally(\"sensitive\")\n\n\t// Arrange: Create a logger\n\tlogger := zapr.NewLogger(zap.New(core, zap.AddCaller()))\n\n\t// Act\n\tlogger.Info(\"sensitive message\")\n\n\t// Assert that redaction executed correctly\n\tmsg := buf.String()\n\tts.Assert().Contains(msg, \"message\")\n\tts.Assert().NotContains(msg, \"sensitive\")\n\tts.Assert().NotContains(msg, \"caller\")\n}\n\n// This test confirms that when our custom caller suppression core wraps our custom redaction core, both redaction and\n// caller suppression occur.\nfunc (ts *TestSuite) TestCoreComposition_CallerSuppressionWrappingRedaction() {\n\t// Arrange: Create a testable log sink\n\tvar buf bytes.Buffer\n\tbaseCore := zapcore.NewCore(\n\t\tzapcore.NewJSONEncoder(defaultEncoderConfig()),\n\t\tzapcore.Lock(zapcore.AddSync(&buf)),\n\t\tglobalLogLevel)\n\n\t// Arrange: Create a core stack and set up redaction\n\tcore := &suppressCallerCore{NewRedactionCore(baseCore, globalRedactor)}\n\tRedactGlobally(\"sensitive\")\n\n\t// Arrange: Create a logger\n\tlogger := zapr.NewLogger(zap.New(core, zap.AddCaller()))\n\n\t// Act\n\tlogger.Info(\"sensitive message\")\n\n\t// Assert that redaction executed correctly\n\tmsg := buf.String()\n\tts.Assert().Contains(msg, \"message\")\n\tts.Assert().NotContains(msg, \"sensitive\")\n\tts.Assert().NotContains(msg, \"caller\")\n}\n\n// This test confirms that the redaction logic executes correctly for console sinks.\nfunc (ts *TestSuite) TestGlobalRedaction_Console() {\n\tvar buf bytes.Buffer\n\tlogger, flush := New(\"console-redaction-test\",\n\t\tWithConsoleSink(&buf, WithGlobalRedaction()),\n\t)\n\tRedactGlobally(\"foo\")\n\tRedactGlobally(\"bar\")\n\n\tlogger.Info(\"this foo is :bar\",\n\t\t\"foo\", \"bar\",\n\t\t\"array\", []string{\"foo\", \"bar\", \"baz\"},\n\t\t\"object\", map[string]string{\"foo\": \"bar\"})\n\tts.Require().NoError(flush())\n\n\tgotParts := strings.Split(buf.String(), \"\\t\")[1:] // The first item is the timestamp\n\twantParts := []string{\n\t\t\"info-0\",\n\t\t\"console-redaction-test\",\n\t\t\"this ***** is :*****\",\n\t\t\"{\\\"foo\\\": \\\"*****\\\", \\\"array\\\": [\\\"foo\\\", \\\"bar\\\", \\\"baz\\\"], \\\"object\\\": {\\\"foo\\\":\\\"bar\\\"}}\\n\",\n\t}\n\tts.Assert().Equal(wantParts, gotParts)\n}\n\n// This test confirms that the redaction logic executes correctly for JSON sinks.\nfunc (ts *TestSuite) TestGlobalRedaction_JSON() {\n\tvar jsonBuffer bytes.Buffer\n\tlogger, flush := New(\"json-redaction-test\",\n\t\tWithJSONSink(&jsonBuffer, WithGlobalRedaction()),\n\t)\n\tRedactGlobally(\"foo\")\n\tRedactGlobally(\"bar\")\n\n\tlogger.Info(\"this foo is :bar\",\n\t\t\"foo\", \"bar\",\n\t\t\"array\", []string{\"foo\", \"bar\", \"baz\"},\n\t\t\"object\", map[string]string{\"foo\": \"bar\"})\n\tts.Require().NoError(flush())\n\n\tvar parsedJSON map[string]any\n\tts.Require().NoError(json.Unmarshal(jsonBuffer.Bytes(), &parsedJSON))\n\tts.Assert().NotEmpty(parsedJSON[\"ts\"])\n\tdelete(parsedJSON, \"ts\")\n\tts.Assert().Equal(\n\t\tmap[string]any{\n\t\t\t\"level\":  \"info-0\",\n\t\t\t\"logger\": \"json-redaction-test\",\n\t\t\t\"msg\":    \"this ***** is :*****\",\n\t\t\t\"foo\":    \"*****\",\n\t\t\t\"array\":  []any{\"foo\", \"bar\", \"baz\"},\n\t\t\t\"object\": map[string]interface{}{\"foo\": \"bar\"},\n\t\t},\n\t\tparsedJSON,\n\t)\n}\n\n// This test confirms that our custom redaction core respects the \"check\" logic of any cores it wraps.\nfunc (ts *TestSuite) TestRedactionCore_RespectsWrappedCheckLogic() {\n\t// Arrange: Create a testable log sink\n\tvar buf bytes.Buffer\n\tbaseCore := zapcore.NewCore(\n\t\tzapcore.NewConsoleEncoder(defaultEncoderConfig()),\n\t\tzapcore.Lock(zapcore.AddSync(&buf)),\n\t\tglobalLogLevel)\n\n\t// Arrange: Set up a core stack\n\tcore := NewRedactionCore(&ignoreEverythingCore{baseCore}, globalRedactor)\n\n\t// Arrange: Create a logger\n\tlogger := zapr.NewLogger(zap.New(core))\n\n\t// Act\n\tlogger.Info(\"message\")\n\n\t// Assert that the wrapped core's check logic was respected\n\tmsg := buf.String()\n\tts.Assert().Empty(msg)\n}\n\n// This test confirms that our custom caller suppression core respects the \"check\" logic of any cores it wraps.\nfunc (ts *TestSuite) TestSuppressCallerCore_RespectsWrappedCheckLogic() {\n\t// Arrange: Create a testable log sink\n\tvar buf bytes.Buffer\n\tbaseCore := zapcore.NewCore(\n\t\tzapcore.NewConsoleEncoder(defaultEncoderConfig()),\n\t\tzapcore.Lock(zapcore.AddSync(&buf)),\n\t\tglobalLogLevel)\n\n\t// Arrange: Set up a core stack\n\tcore := &suppressCallerCore{&ignoreEverythingCore{baseCore}}\n\n\t// Arrange: Create a logger\n\tlogger := zapr.NewLogger(zap.New(core))\n\n\t// Act\n\tlogger.Info(\"message\")\n\n\t// Assert that the wrapped core's check logic was respected\n\tmsg := buf.String()\n\tts.Assert().Empty(msg)\n}\n\n// This test confirms that our custom caller suppression core suppresses caller information.\nfunc (ts *TestSuite) TestSuppressCallerCore_SuppressesCaller() {\n\t// Arrange: Create a testable log sink\n\tvar buf bytes.Buffer\n\tbaseCore := zapcore.NewCore(\n\t\tzapcore.NewJSONEncoder(defaultEncoderConfig()),\n\t\tzapcore.Lock(zapcore.AddSync(&buf)),\n\t\tglobalLogLevel)\n\n\t// Arrange: Set up a core stack\n\tcore := &suppressCallerCore{baseCore}\n\n\t// Arrange: Create a logger\n\tlogger := zapr.NewLogger(zap.New(core, zap.AddCaller()))\n\n\t// Act\n\tlogger.Info(\"message\")\n\n\t// Assert that caller information was suppressed\n\tmsg := buf.String()\n\tts.Assert().NotContains(msg, \"caller\")\n}\n\nfunc BenchmarkLoggerRedact(b *testing.B) {\n\tmsg := \"this is a message with 'foo' in it\"\n\tlogKvps := []any{\"key\", \"value\", \"foo\", \"bar\", \"bar\", \"baz\", \"longval\", \"84hblnqwp97ewilbgoab8fhqlngahs6dl3i269haa\"}\n\tredactor := &dynamicRedactor{denySet: make(map[string]struct{})}\n\tredactor.replacer.CompareAndSwap(nil, strings.NewReplacer())\n\n\tb.Run(\"no redaction\", func(b *testing.B) {\n\t\tlogger, flush := New(\"redaction-benchmark\", WithJSONSink(\n\t\t\tio.Discard,\n\t\t\tfunc(conf *sinkConfig) { conf.redactor = redactor },\n\t\t))\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tlogger.Info(msg, logKvps...)\n\t\t}\n\t\trequire.NoError(b, flush())\n\t})\n\tb.Run(\"1 redaction\", func(b *testing.B) {\n\t\tlogger, flush := New(\"redaction-benchmark\", WithJSONSink(\n\t\t\tio.Discard,\n\t\t\tfunc(conf *sinkConfig) { conf.redactor = redactor },\n\t\t))\n\t\tredactor.configureForRedaction(\"84hblnqwp97ewilbgoab8fhqlngahs6dl3i269haa\")\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tlogger.Info(msg, logKvps...)\n\t\t}\n\t\trequire.NoError(b, flush())\n\t})\n\tb.Run(\"2 redactions\", func(b *testing.B) {\n\t\tlogger, flush := New(\"redaction-benchmark\", WithJSONSink(\n\t\t\tio.Discard,\n\t\t\tfunc(conf *sinkConfig) { conf.redactor = redactor },\n\t\t))\n\t\tredactor.configureForRedaction(\"84hblnqwp97ewilbgoab8fhqlngahs6dl3i269haa\")\n\t\tredactor.configureForRedaction(\"foo\")\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tlogger.Info(msg, logKvps...)\n\t\t}\n\t\trequire.NoError(b, flush())\n\t})\n\tb.Run(\"3 redactions\", func(b *testing.B) {\n\t\tlogger, flush := New(\"redaction-benchmark\", WithJSONSink(\n\t\t\tio.Discard,\n\t\t\tfunc(conf *sinkConfig) { conf.redactor = redactor },\n\t\t))\n\t\tredactor.configureForRedaction(\"84hblnqwp97ewilbgoab8fhqlngahs6dl3i269haa\")\n\t\tredactor.configureForRedaction(\"foo\")\n\t\tredactor.configureForRedaction(\"bar\")\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tlogger.Info(msg, logKvps...)\n\t\t}\n\t\trequire.NoError(b, flush())\n\t})\n}\n"
  },
  {
    "path": "pkg/log/log_test.go",
    "content": "package log\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/getsentry/sentry-go\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\n// testCore implement zapcore.Core with custom methods.\ntype testCore struct {\n\tcheck   func(zapcore.Entry, *zapcore.CheckedEntry) *zapcore.CheckedEntry\n\tenabled func(zapcore.Level) bool\n\tsync    func() error\n\twith    func([]zapcore.Field) zapcore.Core\n\twrite   func(zapcore.Entry, []zapcore.Field) error\n}\n\nfunc (t *testCore) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {\n\tif t.check != nil {\n\t\treturn t.check(e, ce)\n\t}\n\treturn ce\n}\nfunc (t *testCore) Enabled(l zapcore.Level) bool {\n\tif t.enabled != nil {\n\t\treturn t.enabled(l)\n\t}\n\treturn true\n}\nfunc (t *testCore) Sync() error {\n\tif t.sync != nil {\n\t\treturn t.sync()\n\t}\n\treturn nil\n}\nfunc (t *testCore) With(fields []zapcore.Field) zapcore.Core {\n\tif t.with != nil {\n\t\treturn t.with(fields)\n\t}\n\treturn t\n}\nfunc (t *testCore) Write(ent zapcore.Entry, fields []zapcore.Field) error {\n\tif t.write != nil {\n\t\treturn t.write(ent, fields)\n\t}\n\treturn nil\n}\n\nfunc TestNew(t *testing.T) {\n\tvar jsonBuffer, consoleBuffer bytes.Buffer\n\tlogger, flush := New(\"service-name\",\n\t\tWithJSONSink(&jsonBuffer, WithGlobalRedaction()),\n\t\tWithConsoleSink(&consoleBuffer, WithGlobalRedaction()),\n\t)\n\tlogger.Info(\"yay\")\n\tassert.Nil(t, flush())\n\n\tvar parsedJSON map[string]any\n\tassert.Nil(t, json.Unmarshal(jsonBuffer.Bytes(), &parsedJSON))\n\tassert.NotEmpty(t, parsedJSON[\"ts\"])\n\tdelete(parsedJSON, \"ts\")\n\tassert.Equal(t,\n\t\tmap[string]any{\n\t\t\t\"level\":  \"info-0\",\n\t\t\t\"logger\": \"service-name\",\n\t\t\t\"msg\":    \"yay\",\n\t\t},\n\t\tparsedJSON,\n\t)\n\tassert.Equal(t,\n\t\t[]string{\"info-0\\tservice-name\\tyay\"},\n\t\tsplitLines(consoleBuffer.String()),\n\t)\n}\n\nfunc TestSetLevel(t *testing.T) {\n\tvar buffer bytes.Buffer\n\tdefer SetLevel(0)\n\tlogger, _ := New(\"service-name\", WithConsoleSink(&buffer))\n\n\tassert.Equal(t, true, logger.GetSink().Enabled(0))\n\tassert.Equal(t, false, logger.GetSink().Enabled(1))\n\tassert.Equal(t, false, logger.GetSink().Enabled(2))\n\n\tSetLevel(1)\n\tassert.Equal(t, true, logger.GetSink().Enabled(0))\n\tassert.Equal(t, true, logger.GetSink().Enabled(1))\n\tassert.Equal(t, false, logger.GetSink().Enabled(2))\n\n\tSetLevel(2)\n\tassert.Equal(t, true, logger.GetSink().Enabled(0))\n\tassert.Equal(t, true, logger.GetSink().Enabled(1))\n\tassert.Equal(t, true, logger.GetSink().Enabled(2))\n}\n\nfunc TestAddSentry(t *testing.T) {\n\tvar buffer bytes.Buffer\n\tvar sentryMessage string\n\tclient, err := sentry.NewClient(sentry.ClientOptions{\n\t\tBeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {\n\t\t\tsentryMessage = event.Message\n\t\t\treturn nil\n\t\t},\n\t})\n\tassert.Nil(t, err)\n\tlogger, _ := New(\"service-name\", WithConsoleSink(&buffer))\n\tlogger, flush, err := AddSentry(logger, client, nil)\n\tassert.Nil(t, err)\n\n\tlogger.Error(nil, \"oops\")\n\tlogger.Info(\"yay\")\n\tassert.Nil(t, flush())\n\n\tassert.Contains(t, buffer.String(), \"oops\")\n\tassert.Contains(t, buffer.String(), \"yay\")\n\tassert.Equal(t, \"oops\", sentryMessage)\n}\n\nfunc TestWithSentry(t *testing.T) {\n\tvar buffer bytes.Buffer\n\tvar sentryMessage string\n\tclient, err := sentry.NewClient(sentry.ClientOptions{\n\t\tBeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {\n\t\t\tsentryMessage = event.Message\n\t\t\treturn nil\n\t\t},\n\t})\n\tassert.Nil(t, err)\n\tlogger, flush := New(\"service-name\",\n\t\tWithConsoleSink(&buffer),\n\t\tWithSentry(client, nil),\n\t)\n\tlogger.Info(\"yay\")\n\tlogger.Error(nil, \"oops\")\n\tassert.Nil(t, flush())\n\n\tassert.Contains(t, buffer.String(), \"yay\")\n\tassert.Contains(t, buffer.String(), \"oops\")\n\tassert.Equal(t, \"oops\", sentryMessage)\n}\n\nfunc TestHumanReadableTimestamp(t *testing.T) {\n\tvar buffer bytes.Buffer\n\tlogger, flush := New(\"service-name\",\n\t\tWithConsoleSink(&buffer),\n\t)\n\tlogger.Info(\"yay\")\n\tassert.Nil(t, flush())\n\n\tts := strings.Split(buffer.String(), \"\\t\")[0]\n\tassert.NotContains(t, ts, \"e+09\")\n\n\tparsedTime, err := time.Parse(time.RFC3339, ts)\n\tassert.Nil(t, err)\n\tassert.Less(t, time.Since(parsedTime), 5*time.Second)\n}\n\nfunc TestAddSink(t *testing.T) {\n\tvar buf1, buf2 bytes.Buffer\n\tlogger, _ := New(\"service-name\",\n\t\tWithConsoleSink(&buf1),\n\t)\n\tlogger.Info(\"line 1\")\n\tlogger, flush, err := AddSink(logger, WithConsoleSink(&buf2))\n\tassert.Nil(t, err)\n\tlogger.Info(\"line 2\")\n\tassert.Nil(t, flush())\n\n\tassert.Contains(t, buf1.String(), \"line 1\")\n\tassert.Contains(t, buf1.String(), \"line 2\")\n\t// buf2 should only have \"line 2\"\n\tassert.NotContains(t, buf2.String(), \"line 1\")\n\tassert.Contains(t, buf2.String(), \"line 2\")\n}\n\nfunc TestAddSink_WithKeyValuePairs(t *testing.T) {\n\t// Arrange: Create a logger with a key-value pair\n\tvar buf1 bytes.Buffer\n\tlogger, cleanup := New(\"service-name\", WithConsoleSink(&buf1))\n\tt.Cleanup(func() { _ = cleanup })\n\tlogger = logger.WithValues(\"sink 1 key\", \"sink 1 value\")\n\n\t// Arrange: Add a second sink with a new key-value pair\n\tvar buf2 bytes.Buffer\n\tlogger, flush, err := AddSink(logger, WithConsoleSink(&buf2), \"sink 2 key\", \"sink 2 value\")\n\trequire.NoError(t, err)\n\n\t// Act\n\tlogger.Info(\"something\")\n\trequire.NoError(t, flush())\n\n\t// Assert: Confirm that each sink received only its own key-value pair\n\tassert.Contains(t, buf1.String(), \"sink 1\")\n\tassert.NotContains(t, buf1.String(), \"sink 2\")\n\tassert.Contains(t, buf2.String(), \"sink 2\")\n\tassert.NotContains(t, buf2.String(), \"sink 1\")\n}\n\nfunc TestStaticLevelSink(t *testing.T) {\n\tvar buf1, buf2 bytes.Buffer\n\tl1 := zap.NewAtomicLevel()\n\tlogger, flush := New(\n\t\t\"service-name\",\n\t\tWithConsoleSink(&buf1, WithLeveler(l1)),\n\t\tWithConsoleSink(&buf2, WithLevel(0)),\n\t)\n\n\tlogger.Info(\"line 1\")\n\tSetLevelForControl(l1, 1)\n\tlogger.V(1).Info(\"line 2\")\n\tassert.Nil(t, flush())\n\n\t// buf1 should have both lines\n\tassert.Contains(t, buf1.String(), \"line 1\")\n\tassert.Contains(t, buf1.String(), \"line 2\")\n\n\t// buf2 should only have \"line 1\"\n\tassert.Contains(t, buf2.String(), \"line 1\")\n\tassert.NotContains(t, buf2.String(), \"line 2\")\n}\n\nfunc TestWithLeveler(t *testing.T) {\n\tvar buf1, buf2 bytes.Buffer\n\tl1, l2 := zap.NewAtomicLevel(), zap.NewAtomicLevel()\n\tlogger, flush := New(\n\t\t\"service-name\",\n\t\tWithConsoleSink(&buf1, WithLeveler(l1)),\n\t\tWithConsoleSink(&buf2, WithLeveler(l2)),\n\t)\n\n\tSetLevelForControl(l1, 1)\n\tSetLevelForControl(l2, 2)\n\n\tlogger.V(0).Info(\"line 1\")\n\tlogger.V(1).Info(\"line 2\")\n\tlogger.V(2).Info(\"line 3\")\n\tassert.Nil(t, flush())\n\n\t// buf1 should have lines 1 and 2\n\tassert.Contains(t, buf1.String(), \"line 1\")\n\tassert.Contains(t, buf1.String(), \"line 2\")\n\tassert.NotContains(t, buf1.String(), \"line 3\")\n\n\t// buf2 should have all lines\n\tassert.Contains(t, buf2.String(), \"line 1\")\n\tassert.Contains(t, buf2.String(), \"line 2\")\n\tassert.Contains(t, buf2.String(), \"line 3\")\n}\n\nfunc splitLines(s string) []string {\n\tlines := strings.Split(strings.TrimSpace(s), \"\\n\")\n\tlogLines := make([]string, len(lines))\n\tfor i, logLine := range lines {\n\t\t// remove timestamp\n\t\tlogLines[i] = strings.TrimSpace(logLine[strings.Index(logLine, \"\\t\")+1:])\n\t}\n\treturn logLines\n}\n\nfunc TestToLogger(t *testing.T) {\n\tvar jsonBuffer bytes.Buffer\n\tl, flush := New(\"service-name\",\n\t\tWithJSONSink(&jsonBuffer),\n\t)\n\tlogger := ToLogger(l)\n\tlogger.Println(\"yay\")\n\tassert.Nil(t, flush())\n\n\tvar parsedJSON map[string]any\n\tassert.Nil(t, json.Unmarshal(jsonBuffer.Bytes(), &parsedJSON))\n\tassert.NotEmpty(t, parsedJSON[\"ts\"])\n\tdelete(parsedJSON, \"ts\")\n\tdelete(parsedJSON, \"caller\") // log.Logger adds a \"caller\" field\n\tassert.Equal(t,\n\t\tmap[string]any{\n\t\t\t\"level\":  \"info-0\",\n\t\t\t\"logger\": \"service-name\",\n\t\t\t\"msg\":    \"yay\",\n\t\t},\n\t\tparsedJSON,\n\t)\n}\n\nfunc TestToSlogger(t *testing.T) {\n\tvar jsonBuffer bytes.Buffer\n\tl, flush := New(\"service-name\", WithJSONSink(&jsonBuffer))\n\tlogger := ToSlogger(l)\n\tlogger.Info(\"yay\")\n\tassert.Nil(t, flush())\n\n\tvar parsedJSON map[string]any\n\tassert.Nil(t, json.Unmarshal(jsonBuffer.Bytes(), &parsedJSON))\n\tassert.NotEmpty(t, parsedJSON[\"ts\"])\n\tdelete(parsedJSON, \"ts\")\n\tdelete(parsedJSON, \"caller\") // slog.Logger adds a \"caller\" field\n\tassert.Equal(t,\n\t\tmap[string]any{\n\t\t\t\"level\":  \"info-0\",\n\t\t\t\"logger\": \"service-name\",\n\t\t\t\"msg\":    \"yay\",\n\t\t},\n\t\tparsedJSON,\n\t)\n}\n\nfunc TestSyncFunc(t *testing.T) {\n\tvar syncCalled bool\n\tcore := testCore{\n\t\tsync: func() error {\n\t\t\tsyncCalled = true\n\t\t\treturn nil\n\t\t},\n\t}\n\t_, flush := New(\"service-name\", &core)\n\tassert.NoError(t, flush())\n\tassert.True(t, syncCalled)\n}\n\nfunc TestAddSinkStillSyncs(t *testing.T) {\n\tvar syncCalled [2]bool\n\tcore := testCore{\n\t\tsync: func() error {\n\t\t\tsyncCalled[0] = true\n\t\t\treturn nil\n\t\t},\n\t}\n\tl, _ := New(\"service-name\", &core)\n\t_, flush, err := AddSink(l, &testCore{\n\t\tsync: func() error {\n\t\t\tsyncCalled[1] = true\n\t\t\treturn nil\n\t\t},\n\t})\n\tassert.NoError(t, err)\n\tassert.NoError(t, flush())\n\tassert.True(t, syncCalled[0])\n\tassert.True(t, syncCalled[1])\n}\n"
  },
  {
    "path": "pkg/log/redaction_core.go",
    "content": "package log\n\nimport (\n\t\"go.uber.org/zap/zapcore\"\n)\n\n// redactionCore wraps a zapcore.Core to perform redaction of log messages in\n// the message and field values.\ntype redactionCore struct {\n\tzapcore.Core\n\tredactor *dynamicRedactor\n}\n\n// NewRedactionCore creates a zapcore.Core that performs redaction of logs in\n// the message and field values.\nfunc NewRedactionCore(core zapcore.Core, redactor *dynamicRedactor) zapcore.Core {\n\treturn &redactionCore{core, redactor}\n}\n\n// Check determines whether the supplied Entry should be logged and, if it should, adds the core to the entry.\nfunc (c *redactionCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {\n\tif !c.Enabled(ent.Level) {\n\t\treturn ce\n\t}\n\n\t// Check to see whether the wrapped core would write, and if so, add this core to the CheckedEntry. We do not pass\n\t// the CheckedEntry directly to the wrapped core, because if we do, the wrapped core will probably add itself as a\n\t// side effect, which would result in the wrapped core executing its own writes, which would be both duplicative and\n\t// unredacted.\n\tif wrapped := c.Core.Check(ent, nil); wrapped != nil {\n\t\treturn ce.AddCore(ent, c)\n\t}\n\n\treturn ce\n}\n\n// With adds structured context to the Core. It does not do anything interesting and is only implemented at all because\n// of the way zap works.\nfunc (c *redactionCore) With(fields []zapcore.Field) zapcore.Core {\n\treturn NewRedactionCore(c.Core.With(fields), c.redactor)\n}\n\n// Write overrides the embedded zapcore.Core Write() method to redact the message and fields before passing them to be\n// written. Only message and string values are redacted; keys and non-string values (e.g. those inside of arrays and\n// structured objects) are not redacted.\nfunc (c *redactionCore) Write(ent zapcore.Entry, fields []zapcore.Field) error {\n\tent.Message = c.redactor.redact(ent.Message)\n\tfor i := range fields {\n\t\tfields[i].String = c.redactor.redact(fields[i].String)\n\t}\n\treturn c.Core.Write(ent, fields)\n}\n"
  },
  {
    "path": "pkg/log/suppress_caller_core.go",
    "content": "package log\n\nimport \"go.uber.org/zap/zapcore\"\n\n// suppressCallerCore is a zap core that removes caller information from zap entries before writing. It is intended to\n// be used as a wrapper around particular zap cores that you do not want to write caller information to.\n//\n// If you want to exclude caller information from the entire logger, don't use this core - instead, do not enable caller\n// information at the logger level. (That will be much faster than using this core, because zap will refrain from\n// generating caller information in the first place, rather than generating it and then throwing it away.)\ntype suppressCallerCore struct {\n\tzapcore.Core\n}\n\n// Check determines whether the supplied Entry should be logged and, if it should, adds the core to the entry.\nfunc (c *suppressCallerCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {\n\tif !c.Enabled(ent.Level) {\n\t\treturn ce\n\t}\n\n\t// Check to see whether the wrapped core would write, and if so, add this core to the CheckedEntry. We do not pass\n\t// the CheckedEntry directly to the wrapped core, because if we do, the wrapped core will probably add itself as a\n\t// side effect, which would result in the wrapped core executing its own writes, which would be both duplicative and\n\t// without caller suppression.\n\tif wrapped := c.Core.Check(ent, nil); wrapped != nil {\n\t\t// If we add the wrapped core, then that core will generate its own writes - which won't have caller information\n\t\t// suppressed! Instead, we only add this core, which will delegate its (caller-suppressed) writes to the wrapped\n\t\t// core.\n\t\treturn ce.AddCore(ent, c)\n\t}\n\n\treturn ce\n}\n\n// With adds structured context to the Core. It does not do anything interesting and is only implemented at all because\n// of the way zap works.\nfunc (c *suppressCallerCore) With(fields []zapcore.Field) zapcore.Core {\n\treturn &suppressCallerCore{Core: c.Core.With(fields)}\n}\n\n// Write removes caller information from a zap entry and then passes it to the wrapped core for writing.\nfunc (c *suppressCallerCore) Write(ent zapcore.Entry, fields []zapcore.Field) error {\n\tent.Caller = zapcore.EntryCaller{}\n\tent.Stack = \"\"\n\treturn c.Core.Write(ent, fields)\n}\n"
  },
  {
    "path": "pkg/output/github_actions.go",
    "content": "package output\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n)\n\nvar dedupeCache = make(map[string]struct{})\n\n// GitHubActionsPrinter is a printer that prints results in GitHub Actions format.\ntype GitHubActionsPrinter struct{ mu sync.Mutex }\n\nfunc (p *GitHubActionsPrinter) Print(_ context.Context, r *detectors.ResultWithMetadata) error {\n\tout := gitHubActionsOutputFormat{\n\t\tDetectorType:        r.Result.DetectorType.String(),\n\t\tDetectorDescription: r.DetectorDescription,\n\t\tDecoderType:         r.DecoderType.String(),\n\t\tVerified:            r.Result.Verified,\n\t}\n\n\tmeta, err := structToMap(r.SourceMetadata.Data)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not marshal result: %w\", err)\n\t}\n\n\tfor _, data := range meta {\n\t\tfor k, v := range data {\n\t\t\tif k == \"line\" {\n\t\t\t\tif line, ok := v.(float64); ok {\n\t\t\t\t\tout.StartLine = int64(line)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif k == \"file\" {\n\t\t\t\tif filename, ok := v.(string); ok {\n\t\t\t\t\tout.Filename = filename\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tverifiedStatus := \"unverified\"\n\tif out.Verified {\n\t\tverifiedStatus = \"verified\"\n\t}\n\n\tkey := fmt.Sprintf(\"%s:%s:%s:%s:%d\", out.DecoderType, out.DetectorType, verifiedStatus, out.Filename, out.StartLine)\n\th := sha256.New()\n\th.Write([]byte(key))\n\tkey = hex.EncodeToString(h.Sum(nil))\n\tp.mu.Lock()\n\tdefer p.mu.Unlock()\n\tif _, ok := dedupeCache[key]; ok {\n\t\treturn nil\n\t}\n\tdedupeCache[key] = struct{}{}\n\n\tname := \"\"\n\tif nameValue, ok := r.Result.ExtraData[\"name\"]; ok {\n\t\tname = fmt.Sprintf(\" (%s)\", nameValue)\n\t}\n\n\tmessage := fmt.Sprintf(\"Found %s %s%s result 🐷🔑\\n\", verifiedStatus, out.DetectorType, name)\n\tif r.DecoderType != detectorspb.DecoderType_PLAIN {\n\t\tmessage = fmt.Sprintf(\"Found %s %s%s result with %s encoding 🐷🔑\\n\", verifiedStatus, out.DetectorType, name, out.DecoderType)\n\t}\n\n\tfmt.Printf(\"::warning file=%s,line=%d,endLine=%d::%s\",\n\t\tout.Filename, out.StartLine, out.StartLine, message)\n\n\treturn nil\n}\n\ntype gitHubActionsOutputFormat struct {\n\tDetectorType        string\n\tDetectorDescription string\n\tDecoderType         string\n\tVerified            bool\n\tStartLine           int64\n\tFilename            string\n}\n"
  },
  {
    "path": "pkg/output/json.go",
    "content": "package output\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\n// JSONPrinter is a printer that prints results in JSON format.\ntype JSONPrinter struct{ mu sync.Mutex }\n\nfunc (p *JSONPrinter) Print(_ context.Context, r *detectors.ResultWithMetadata) error {\n\tverificationErr := func(err error) string {\n\t\tif err != nil {\n\t\t\treturn err.Error()\n\t\t}\n\t\treturn \"\"\n\t}(r.VerificationError())\n\n\tv := &struct {\n\t\t// SourceMetadata contains source-specific contextual information.\n\t\tSourceMetadata *source_metadatapb.MetaData\n\t\t// SourceID is the ID of the source that the API uses to map secrets to specific sources.\n\t\tSourceID sources.SourceID\n\t\t// SourceType is the type of Source.\n\t\tSourceType sourcespb.SourceType\n\t\t// SourceName is the name of the Source.\n\t\tSourceName string\n\t\t// DetectorType is the type of Detector.\n\t\tDetectorType detectorspb.DetectorType\n\t\t// DetectorName is the string name of the DetectorType.\n\t\tDetectorName string\n\t\t// DetectorDescription is the description of the Detector.\n\t\tDetectorDescription string\n\t\t// DecoderName is the string name of the DecoderType.\n\t\tDecoderName           string\n\t\tVerified              bool\n\t\tVerificationError     string `json:\",omitempty\"`\n\t\tVerificationFromCache bool\n\t\t// Raw contains the raw secret data.\n\t\tRaw string\n\t\t// RawV2 contains the raw secret identifier that is a combination of both the ID and the secret.\n\t\t// This is used for secrets that are multi part and could have the same ID. Ex: AWS credentials\n\t\tRawV2 string\n\t\t// Redacted contains the redacted version of the raw secret identification data for display purposes.\n\t\t// A secret ID should be used if available.\n\t\tRedacted       string\n\t\tExtraData      map[string]string\n\t\tStructuredData *detectorspb.StructuredData\n\t}{\n\t\tSourceMetadata:        r.SourceMetadata,\n\t\tSourceID:              r.SourceID,\n\t\tSourceType:            r.SourceType,\n\t\tSourceName:            r.SourceName,\n\t\tDetectorType:          r.DetectorType,\n\t\tDetectorName:          r.DetectorType.String(),\n\t\tDetectorDescription:   r.DetectorDescription,\n\t\tDecoderName:           r.DecoderType.String(),\n\t\tVerified:              r.Verified,\n\t\tVerificationError:     verificationErr,\n\t\tVerificationFromCache: r.VerificationFromCache,\n\t\tRaw:                   string(r.Raw),\n\t\tRawV2:                 string(r.RawV2),\n\t\tRedacted:              r.Redacted,\n\t\tExtraData:             r.ExtraData,\n\t\tStructuredData:        r.StructuredData,\n\t}\n\tout, err := json.Marshal(v)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not marshal result: %w\", err)\n\t}\n\n\tp.mu.Lock()\n\tfmt.Println(string(out))\n\tp.mu.Unlock()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/output/legacy_json.go",
    "content": "package output\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\n\tgogit \"github.com/go-git/go-git/v5\"\n\t\"github.com/go-git/go-git/v5/plumbing\"\n\t\"github.com/go-git/go-git/v5/plumbing/object\"\n\t\"github.com/sergi/go-diff/diffmatchpatch\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n)\n\n// LegacyJSONPrinter is a printer that prints results in legacy JSON format for backwards compatibility.\ntype LegacyJSONPrinter struct{ mu sync.Mutex }\n\nfunc (p *LegacyJSONPrinter) Print(ctx context.Context, r *detectors.ResultWithMetadata) error {\n\tvar repoPath string\n\tswitch r.SourceType {\n\tcase sourcespb.SourceType_SOURCE_TYPE_GIT:\n\t\trepoPath = r.SourceMetadata.GetGit().RepositoryLocalPath\n\tcase sourcespb.SourceType_SOURCE_TYPE_GITHUB:\n\t\trepoPath = r.SourceMetadata.GetGithub().RepositoryLocalPath\n\tcase sourcespb.SourceType_SOURCE_TYPE_GITLAB:\n\t\trepoPath = r.SourceMetadata.GetGitlab().RepositoryLocalPath\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported source type for legacy json output: %s\", r.SourceType)\n\t}\n\n\tlegacy, err := convertToLegacyJSON(r, repoPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not convert to legacy JSON: %w\", err)\n\t}\n\tout, err := json.Marshal(legacy)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not marshal result: %w\", err)\n\t}\n\n\tp.mu.Lock()\n\tfmt.Println(string(out))\n\tp.mu.Unlock()\n\treturn nil\n}\n\nfunc convertToLegacyJSON(r *detectors.ResultWithMetadata, repoPath string) (*LegacyJSONOutput, error) {\n\tvar source LegacyJSONCompatibleSource\n\tswitch r.SourceType {\n\tcase sourcespb.SourceType_SOURCE_TYPE_GIT:\n\t\tsource = r.SourceMetadata.GetGit()\n\tcase sourcespb.SourceType_SOURCE_TYPE_GITHUB:\n\t\tsource = r.SourceMetadata.GetGithub()\n\tcase sourcespb.SourceType_SOURCE_TYPE_GITLAB:\n\t\tsource = r.SourceMetadata.GetGitlab()\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"legacy JSON output can not be used with this source: %s\", r.SourceName)\n\t}\n\n\toptions := &gogit.PlainOpenOptions{\n\t\tDetectDotGit:          true,\n\t\tEnableDotGitCommonDir: true,\n\t}\n\n\t// The repo will be needed to gather info needed for the legacy output that\n\t// isn't included in the new output format.\n\trepo, err := gogit.PlainOpenWithOptions(repoPath, options)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not open repo %q: %w\", repoPath, err)\n\t}\n\n\tfileName := source.GetFile()\n\tcommitHash := plumbing.NewHash(source.GetCommit())\n\tcommit, err := repo.CommitObject(commitHash)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tdiff := GenerateDiff(commit, fileName)\n\n\tfoundString := string(r.Result.Raw)\n\n\t// Add highlighting to the offending bit of string.\n\tprintableDiff := strings.ReplaceAll(diff, foundString, fmt.Sprintf(\"\\u001b[93m%s\\u001b[0m\", foundString))\n\n\t// Load up the struct to match the old JSON format\n\toutput := &LegacyJSONOutput{\n\t\tBranch:       FindBranch(commit, repo),\n\t\tCommit:       commit.Message,\n\t\tCommitHash:   commitHash.String(),\n\t\tDate:         commit.Committer.When.Format(\"2006-01-02 15:04:05\"),\n\t\tDiff:         diff,\n\t\tPath:         fileName,\n\t\tPrintDiff:    printableDiff,\n\t\tReason:       r.Result.DetectorType.String(),\n\t\tStringsFound: []string{foundString},\n\t}\n\treturn output, nil\n}\n\n// BranchHeads creates a map of branch names to their head commit. This can be used to find if a commit is an ancestor\n// of a branch head.\nfunc BranchHeads(repo *gogit.Repository) (map[string]*object.Commit, error) {\n\tbranches := map[string]*object.Commit{}\n\tbranchIter, err := repo.Branches()\n\tif err != nil {\n\t\treturn branches, err\n\t}\n\n\tlogger := context.Background().Logger()\n\terr = branchIter.ForEach(func(branchRef *plumbing.Reference) error {\n\t\tbranchName := branchRef.Name().String()\n\t\theadHash, err := repo.ResolveRevision(plumbing.Revision(branchName))\n\t\tif err != nil {\n\t\t\tlogger.Error(err, \"unable to resolve head of branch\", \"branch\", branchRef.Name().String())\n\t\t\treturn nil\n\t\t}\n\t\theadCommit, err := repo.CommitObject(*headHash)\n\t\tif err != nil {\n\t\t\tlogger.Error(err, \"unable to get commit\", \"head_hash\", headHash.String())\n\t\t\treturn nil\n\t\t}\n\t\tbranches[branchName] = headCommit\n\t\treturn nil\n\t})\n\treturn branches, err\n}\n\n// FindBranch returns the first branch a commit is a part of. Not the most accurate, but it should work similar to pre v3.0.\nfunc FindBranch(commit *object.Commit, repo *gogit.Repository) string {\n\tlogger := context.Background().Logger()\n\tbranches, err := BranchHeads(repo)\n\tif err != nil {\n\t\tlogger.Error(err, \"could not list branches\")\n\t\tos.Exit(1)\n\t}\n\n\tfor name, head := range branches {\n\t\tisAncestor, err := commit.IsAncestor(head)\n\t\tif err != nil {\n\t\t\tlogger.Error(err, fmt.Sprintf(\"could not determine if %s is an ancestor of %s\", commit.Hash.String(), head.Hash.String()))\n\t\t\tcontinue\n\t\t}\n\t\tif isAncestor {\n\t\t\treturn name\n\t\t}\n\t}\n\treturn \"\"\n}\n\n// GenerateDiff will take a commit and create a string diff between the commit and its first parent.\nfunc GenerateDiff(commit *object.Commit, fileName string) string {\n\tvar diff string\n\tlogger := context.Background().Logger().WithValues(\"file\", fileName)\n\n\t// First grab the first parent of the commit. If there are none, we are at the first commit and should diff against\n\t// an empty file.\n\tparent, err := commit.Parent(0)\n\tif !errors.Is(err, object.ErrParentNotFound) && err != nil {\n\t\tlogger.Error(err, \"could not find parent\", \"commit\", commit.Hash.String())\n\t}\n\n\t// Now get the files from the commit and its parent.\n\tvar parentFile *object.File\n\tif parent != nil {\n\t\tparentFile, err = parent.File(fileName)\n\t\tif err != nil && !errors.Is(err, object.ErrFileNotFound) {\n\t\t\tlogger.Error(err, \"could not get previous version of file\")\n\t\t\treturn diff\n\t\t}\n\t}\n\tcommitFile, err := commit.File(fileName)\n\tif err != nil {\n\t\tlogger.Error(err, \"could not get current version of file\")\n\t\treturn diff\n\t}\n\n\t// go-git doesn't support creating a diff for just one file in a commit, so another package is needed to generate\n\t// the diff.\n\tdmp := diffmatchpatch.New()\n\tvar oldContent, newContent string\n\tif parentFile != nil {\n\t\toldContent, err = parentFile.Contents()\n\t\tif err != nil {\n\t\t\tlogger.Error(err, \"could not get contents of previous version of file\")\n\t\t}\n\t}\n\t// commitFile should never be nil at this point, but double-checking so we don't get a nil error.\n\tif commitFile != nil {\n\t\tnewContent, _ = commitFile.Contents()\n\t\tif err != nil {\n\t\t\tlogger.Error(err, \"could not get contents of current version of file\")\n\t\t}\n\t}\n\n\t// If anything has gone wrong here, we'll just be diffing two empty files.\n\tdiffs := dmp.DiffMain(oldContent, newContent, false)\n\tpatches := dmp.PatchMake(diffs)\n\n\t// Put all the pieces of the diff together into one string.\n\tfor _, patch := range patches {\n\t\t// The String() method URL escapes the diff, so it needs to be undone.\n\t\tpatchDiff, err := url.QueryUnescape(patch.String())\n\t\tif err != nil {\n\t\t\tlogger.Error(err, \"unable to unescape diff\")\n\t\t}\n\t\tdiff += patchDiff\n\t}\n\treturn diff\n}\n\ntype LegacyJSONOutput struct {\n\tBranch       string   `json:\"branch\"`\n\tCommit       string   `json:\"commit\"`\n\tCommitHash   string   `json:\"commitHash\"`\n\tDate         string   `json:\"date\"`\n\tDiff         string   `json:\"diff\"`\n\tPath         string   `json:\"path\"`\n\tPrintDiff    string   `json:\"printDiff\"`\n\tReason       string   `json:\"reason\"`\n\tStringsFound []string `json:\"stringsFound\"`\n}\n\ntype LegacyJSONCompatibleSource interface {\n\tGetCommit() string\n\tGetFile() string\n}\n"
  },
  {
    "path": "pkg/output/plain.go",
    "content": "package output\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/fatih/color\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/detectors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n)\n\nvar (\n\tyellowPrinter    = color.New(color.FgYellow)\n\tgreenPrinter     = color.New(color.FgHiGreen)\n\tboldGreenPrinter = color.New(color.Bold, color.FgHiGreen)\n\twhitePrinter     = color.New(color.FgWhite)\n\tboldWhitePrinter = color.New(color.Bold, color.FgWhite)\n\tcyanPrinter      = color.New(color.FgCyan)\n)\n\n// PlainPrinter is a printer that prints results in plain text format.\ntype PlainPrinter struct{ mu sync.Mutex }\n\nfunc (p *PlainPrinter) Print(_ context.Context, r *detectors.ResultWithMetadata) error {\n\tout := outputFormat{\n\t\tDetectorType:        r.Result.DetectorType.String(),\n\t\tDecoderType:         r.DecoderType.String(),\n\t\tVerified:            r.Result.Verified,\n\t\tVerificationError:   r.Result.VerificationError(),\n\t\tMetaData:            r.SourceMetadata,\n\t\tRaw:                 strings.TrimSpace(string(r.Result.Raw)),\n\t\tDetectorDescription: r.DetectorDescription,\n\t}\n\n\tmeta, err := structToMap(out.MetaData.Data)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not marshal result: %w\", err)\n\t}\n\n\tprinter := greenPrinter\n\tp.mu.Lock()\n\tdefer p.mu.Unlock()\n\n\tif out.Verified {\n\t\tboldGreenPrinter.Print(\"✅ Found verified result 🐷🔑\\n\")\n\t} else {\n\t\tprinter = whitePrinter\n\t\tboldWhitePrinter.Print(\"Found unverified result 🐷🔑❓\\n\")\n\t\tif out.VerificationError != nil {\n\t\t\tyellowPrinter.Printf(\"Verification issue: %s\\n\", out.VerificationError)\n\t\t}\n\t}\n\tif r.VerificationFromCache {\n\t\tcyanPrinter.Print(\"(Verification info cached)\\n\")\n\t}\n\tprinter.Printf(\"Detector Type: %s\\n\", out.DetectorType)\n\tprinter.Printf(\"Decoder Type: %s\\n\", out.DecoderType)\n\tprinter.Printf(\"Raw result: %s\\n\", whitePrinter.Sprint(out.Raw))\n\n\tfor k, v := range r.Result.ExtraData {\n\t\tprinter.Printf(\n\t\t\t\"%s: %v\\n\",\n\t\t\tcases.Title(language.AmericanEnglish).String(k),\n\t\t\tv)\n\t}\n\n\tif r.Result.StructuredData != nil {\n\t\tfor idx, v := range r.Result.StructuredData.GithubSshKey {\n\t\t\tprinter.Printf(\"GithubSshKey %d User: %s\\n\", idx, v.User)\n\n\t\t\tif v.PublicKeyFingerprint != \"\" {\n\t\t\t\tprinter.Printf(\"GithubSshKey %d Fingerprint: %s\\n\", idx, v.PublicKeyFingerprint)\n\t\t\t}\n\t\t}\n\n\t\tfor idx, v := range r.Result.StructuredData.TlsPrivateKey {\n\t\t\tprinter.Printf(\"TlsPrivateKey %d Fingerprint: %s\\n\", idx, v.CertificateFingerprint)\n\t\t\tprinter.Printf(\"TlsPrivateKey %d Verification URL: %s\\n\", idx, v.VerificationUrl)\n\t\t\tprinter.Printf(\"TlsPrivateKey %d Expiration: %d\\n\", idx, v.ExpirationTimestamp)\n\t\t}\n\t}\n\n\taggregateData := make(map[string]any)\n\tvar aggregateDataKeys []string\n\n\tfor _, data := range meta {\n\t\tfor k, v := range data {\n\t\t\taggregateDataKeys = append(aggregateDataKeys, k)\n\t\t\taggregateData[k] = v\n\t\t}\n\t}\n\tsort.Strings(aggregateDataKeys)\n\tfor _, k := range aggregateDataKeys {\n\t\tprinter.Printf(\"%s: %v\\n\", cases.Title(language.AmericanEnglish).String(k), aggregateData[k])\n\t}\n\n\t// if analysis info is not nil, means the detector added key for analyzer and result is verified\n\tif r.Result.AnalysisInfo != nil && r.Result.Verified {\n\t\tprinter.Printf(\"Analyze: Run `trufflehog analyze` to analyze this key's permissions\\n\")\n\t}\n\n\tfmt.Println(\"\")\n\treturn nil\n}\n\nfunc structToMap(obj any) (m map[string]map[string]any, err error) {\n\tdata, err := json.Marshal(obj)\n\tif err != nil {\n\t\treturn\n\t}\n\terr = json.Unmarshal(data, &m)\n\t// Due to PostmanLocationType protobuf field being an enum, we want to be able to assign the string value of the enum to the field without needing to create another Protobuf field.\n\t// To have the \"UNKNOWN_POSTMAN = 0\" value be assigned correctly to the field, we need to check if the Postman workspace ID or collection ID is filled since every secret\n\t// in the Postman source should have a valid workspace ID or collection ID and the 0 value is considered nil for integers.\n\tif m[\"Postman\"][\"workspace_uuid\"] != nil || m[\"Postman\"][\"collection_id\"] != nil {\n\t\tif m[\"Postman\"][\"location_type\"] == nil {\n\t\t\tm[\"Postman\"][\"location_type\"] = source_metadatapb.PostmanLocationType_UNKNOWN_POSTMAN.String()\n\t\t} else {\n\t\t\tm[\"Postman\"][\"location_type\"] = obj.(*source_metadatapb.MetaData_Postman).Postman.LocationType.String()\n\t\t}\n\t}\n\treturn\n}\n\ntype outputFormat struct {\n\tDetectorType,\n\tDecoderType string\n\tVerified          bool\n\tVerificationError error\n\tRaw               string\n\t*source_metadatapb.MetaData\n\tDetectorDescription string\n}\n"
  },
  {
    "path": "pkg/pb/configpb/config.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.33.0\n// \tprotoc        v4.25.3\n// source: config.proto\n\npackage configpb\n\nimport (\n\tcustom_detectorspb \"github.com/trufflesecurity/trufflehog/v3/pkg/pb/custom_detectorspb\"\n\tsourcespb \"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Config struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSources   []*sourcespb.LocalSource          `protobuf:\"bytes,9,rep,name=sources,proto3\" json:\"sources,omitempty\"`\n\tDetectors []*custom_detectorspb.CustomRegex `protobuf:\"bytes,13,rep,name=detectors,proto3\" json:\"detectors,omitempty\"`\n}\n\nfunc (x *Config) Reset() {\n\t*x = Config{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_config_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Config) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Config) ProtoMessage() {}\n\nfunc (x *Config) ProtoReflect() protoreflect.Message {\n\tmi := &file_config_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Config.ProtoReflect.Descriptor instead.\nfunc (*Config) Descriptor() ([]byte, []int) {\n\treturn file_config_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Config) GetSources() []*sourcespb.LocalSource {\n\tif x != nil {\n\t\treturn x.Sources\n\t}\n\treturn nil\n}\n\nfunc (x *Config) GetDetectors() []*custom_detectorspb.CustomRegex {\n\tif x != nil {\n\t\treturn x.Detectors\n\t}\n\treturn nil\n}\n\nvar File_config_proto protoreflect.FileDescriptor\n\nvar file_config_proto_rawDesc = []byte{\n\t0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06,\n\t0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e,\n\t0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x16, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x64, 0x65,\n\t0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x75, 0x0a,\n\t0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63,\n\t0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63,\n\t0x65, 0x73, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x07,\n\t0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3b, 0x0a, 0x09, 0x64, 0x65, 0x74, 0x65, 0x63,\n\t0x74, 0x6f, 0x72, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x75, 0x73,\n\t0x74, 0x6f, 0x6d, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x2e, 0x43, 0x75,\n\t0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x67, 0x65, 0x78, 0x52, 0x09, 0x64, 0x65, 0x74, 0x65, 0x63,\n\t0x74, 0x6f, 0x72, 0x73, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,\n\t0x6f, 0x6d, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69,\n\t0x74, 0x79, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x68, 0x6f, 0x67, 0x2f, 0x76, 0x33,\n\t0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x70, 0x62,\n\t0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_config_proto_rawDescOnce sync.Once\n\tfile_config_proto_rawDescData = file_config_proto_rawDesc\n)\n\nfunc file_config_proto_rawDescGZIP() []byte {\n\tfile_config_proto_rawDescOnce.Do(func() {\n\t\tfile_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_config_proto_rawDescData)\n\t})\n\treturn file_config_proto_rawDescData\n}\n\nvar file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_config_proto_goTypes = []interface{}{\n\t(*Config)(nil),                         // 0: config.Config\n\t(*sourcespb.LocalSource)(nil),          // 1: sources.LocalSource\n\t(*custom_detectorspb.CustomRegex)(nil), // 2: custom_detectors.CustomRegex\n}\nvar file_config_proto_depIdxs = []int32{\n\t1, // 0: config.Config.sources:type_name -> sources.LocalSource\n\t2, // 1: config.Config.detectors:type_name -> custom_detectors.CustomRegex\n\t2, // [2:2] is the sub-list for method output_type\n\t2, // [2:2] is the sub-list for method input_type\n\t2, // [2:2] is the sub-list for extension type_name\n\t2, // [2:2] is the sub-list for extension extendee\n\t0, // [0:2] is the sub-list for field type_name\n}\n\nfunc init() { file_config_proto_init() }\nfunc file_config_proto_init() {\n\tif File_config_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Config); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_config_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_config_proto_goTypes,\n\t\tDependencyIndexes: file_config_proto_depIdxs,\n\t\tMessageInfos:      file_config_proto_msgTypes,\n\t}.Build()\n\tFile_config_proto = out.File\n\tfile_config_proto_rawDesc = nil\n\tfile_config_proto_goTypes = nil\n\tfile_config_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pkg/pb/configpb/config.pb.validate.go",
    "content": "// Code generated by protoc-gen-validate. DO NOT EDIT.\n// source: config.proto\n\npackage configpb\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/mail\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode/utf8\"\n\n\t\"google.golang.org/protobuf/types/known/anypb\"\n)\n\n// ensure the imports are used\nvar (\n\t_ = bytes.MinRead\n\t_ = errors.New(\"\")\n\t_ = fmt.Print\n\t_ = utf8.UTFMax\n\t_ = (*regexp.Regexp)(nil)\n\t_ = (*strings.Reader)(nil)\n\t_ = net.IPv4len\n\t_ = time.Duration(0)\n\t_ = (*url.URL)(nil)\n\t_ = (*mail.Address)(nil)\n\t_ = anypb.Any{}\n\t_ = sort.Sort\n)\n\n// Validate checks the field values on Config with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Config) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Config with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in ConfigMultiError, or nil if none found.\nfunc (m *Config) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Config) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tfor idx, item := range m.GetSources() {\n\t\t_, _ = idx, item\n\n\t\tif all {\n\t\t\tswitch v := interface{}(item).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, ConfigValidationError{\n\t\t\t\t\t\tfield:  fmt.Sprintf(\"Sources[%v]\", idx),\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, ConfigValidationError{\n\t\t\t\t\t\tfield:  fmt.Sprintf(\"Sources[%v]\", idx),\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(item).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn ConfigValidationError{\n\t\t\t\t\tfield:  fmt.Sprintf(\"Sources[%v]\", idx),\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\n\tfor idx, item := range m.GetDetectors() {\n\t\t_, _ = idx, item\n\n\t\tif all {\n\t\t\tswitch v := interface{}(item).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, ConfigValidationError{\n\t\t\t\t\t\tfield:  fmt.Sprintf(\"Detectors[%v]\", idx),\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, ConfigValidationError{\n\t\t\t\t\t\tfield:  fmt.Sprintf(\"Detectors[%v]\", idx),\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(item).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn ConfigValidationError{\n\t\t\t\t\tfield:  fmt.Sprintf(\"Detectors[%v]\", idx),\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn ConfigMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// ConfigMultiError is an error wrapping multiple validation errors returned by\n// Config.ValidateAll() if the designated constraints aren't met.\ntype ConfigMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m ConfigMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m ConfigMultiError) AllErrors() []error { return m }\n\n// ConfigValidationError is the validation error returned by Config.Validate if\n// the designated constraints aren't met.\ntype ConfigValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e ConfigValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e ConfigValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e ConfigValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e ConfigValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e ConfigValidationError) ErrorName() string { return \"ConfigValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e ConfigValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sConfig.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = ConfigValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = ConfigValidationError{}\n"
  },
  {
    "path": "pkg/pb/credentialspb/credentials.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.33.0\n// \tprotoc        v4.25.3\n// source: credentials.proto\n\npackage credentialspb\n\nimport (\n\t_ \"github.com/envoyproxy/protoc-gen-validate/validate\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Unauthenticated struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *Unauthenticated) Reset() {\n\t*x = Unauthenticated{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_credentials_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Unauthenticated) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Unauthenticated) ProtoMessage() {}\n\nfunc (x *Unauthenticated) ProtoReflect() protoreflect.Message {\n\tmi := &file_credentials_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Unauthenticated.ProtoReflect.Descriptor instead.\nfunc (*Unauthenticated) Descriptor() ([]byte, []int) {\n\treturn file_credentials_proto_rawDescGZIP(), []int{0}\n}\n\ntype SSHAuth struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *SSHAuth) Reset() {\n\t*x = SSHAuth{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_credentials_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SSHAuth) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SSHAuth) ProtoMessage() {}\n\nfunc (x *SSHAuth) ProtoReflect() protoreflect.Message {\n\tmi := &file_credentials_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SSHAuth.ProtoReflect.Descriptor instead.\nfunc (*SSHAuth) Descriptor() ([]byte, []int) {\n\treturn file_credentials_proto_rawDescGZIP(), []int{1}\n}\n\ntype CloudEnvironment struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *CloudEnvironment) Reset() {\n\t*x = CloudEnvironment{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_credentials_proto_msgTypes[2]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CloudEnvironment) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CloudEnvironment) ProtoMessage() {}\n\nfunc (x *CloudEnvironment) ProtoReflect() protoreflect.Message {\n\tmi := &file_credentials_proto_msgTypes[2]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CloudEnvironment.ProtoReflect.Descriptor instead.\nfunc (*CloudEnvironment) Descriptor() ([]byte, []int) {\n\treturn file_credentials_proto_rawDescGZIP(), []int{2}\n}\n\ntype BasicAuth struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUsername string `protobuf:\"bytes,1,opt,name=username,proto3\" json:\"username,omitempty\"`\n\tPassword string `protobuf:\"bytes,2,opt,name=password,proto3\" json:\"password,omitempty\"`\n}\n\nfunc (x *BasicAuth) Reset() {\n\t*x = BasicAuth{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_credentials_proto_msgTypes[3]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *BasicAuth) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*BasicAuth) ProtoMessage() {}\n\nfunc (x *BasicAuth) ProtoReflect() protoreflect.Message {\n\tmi := &file_credentials_proto_msgTypes[3]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use BasicAuth.ProtoReflect.Descriptor instead.\nfunc (*BasicAuth) Descriptor() ([]byte, []int) {\n\treturn file_credentials_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *BasicAuth) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *BasicAuth) GetPassword() string {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn \"\"\n}\n\ntype Header struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tKey   string `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tValue string `protobuf:\"bytes,2,opt,name=value,proto3\" json:\"value,omitempty\"`\n}\n\nfunc (x *Header) Reset() {\n\t*x = Header{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_credentials_proto_msgTypes[4]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Header) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Header) ProtoMessage() {}\n\nfunc (x *Header) ProtoReflect() protoreflect.Message {\n\tmi := &file_credentials_proto_msgTypes[4]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Header.ProtoReflect.Descriptor instead.\nfunc (*Header) Descriptor() ([]byte, []int) {\n\treturn file_credentials_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *Header) GetKey() string {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn \"\"\n}\n\nfunc (x *Header) GetValue() string {\n\tif x != nil {\n\t\treturn x.Value\n\t}\n\treturn \"\"\n}\n\ntype ClientCredentials struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tTenantId     string `protobuf:\"bytes,1,opt,name=tenant_id,json=tenantId,proto3\" json:\"tenant_id,omitempty\"`\n\tClientId     string `protobuf:\"bytes,2,opt,name=client_id,json=clientId,proto3\" json:\"client_id,omitempty\"`\n\tClientSecret string `protobuf:\"bytes,3,opt,name=client_secret,json=clientSecret,proto3\" json:\"client_secret,omitempty\"`\n}\n\nfunc (x *ClientCredentials) Reset() {\n\t*x = ClientCredentials{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_credentials_proto_msgTypes[5]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ClientCredentials) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ClientCredentials) ProtoMessage() {}\n\nfunc (x *ClientCredentials) ProtoReflect() protoreflect.Message {\n\tmi := &file_credentials_proto_msgTypes[5]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ClientCredentials.ProtoReflect.Descriptor instead.\nfunc (*ClientCredentials) Descriptor() ([]byte, []int) {\n\treturn file_credentials_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *ClientCredentials) GetTenantId() string {\n\tif x != nil {\n\t\treturn x.TenantId\n\t}\n\treturn \"\"\n}\n\nfunc (x *ClientCredentials) GetClientId() string {\n\tif x != nil {\n\t\treturn x.ClientId\n\t}\n\treturn \"\"\n}\n\nfunc (x *ClientCredentials) GetClientSecret() string {\n\tif x != nil {\n\t\treturn x.ClientSecret\n\t}\n\treturn \"\"\n}\n\ntype ClientCertificate struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tTenantId            string `protobuf:\"bytes,1,opt,name=tenant_id,json=tenantId,proto3\" json:\"tenant_id,omitempty\"`\n\tClientId            string `protobuf:\"bytes,2,opt,name=client_id,json=clientId,proto3\" json:\"client_id,omitempty\"`\n\tCertificatePath     string `protobuf:\"bytes,3,opt,name=certificate_path,json=certificatePath,proto3\" json:\"certificate_path,omitempty\"`\n\tCertificatePassword string `protobuf:\"bytes,4,opt,name=certificate_password,json=certificatePassword,proto3\" json:\"certificate_password,omitempty\"`\n}\n\nfunc (x *ClientCertificate) Reset() {\n\t*x = ClientCertificate{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_credentials_proto_msgTypes[6]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ClientCertificate) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ClientCertificate) ProtoMessage() {}\n\nfunc (x *ClientCertificate) ProtoReflect() protoreflect.Message {\n\tmi := &file_credentials_proto_msgTypes[6]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ClientCertificate.ProtoReflect.Descriptor instead.\nfunc (*ClientCertificate) Descriptor() ([]byte, []int) {\n\treturn file_credentials_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *ClientCertificate) GetTenantId() string {\n\tif x != nil {\n\t\treturn x.TenantId\n\t}\n\treturn \"\"\n}\n\nfunc (x *ClientCertificate) GetClientId() string {\n\tif x != nil {\n\t\treturn x.ClientId\n\t}\n\treturn \"\"\n}\n\nfunc (x *ClientCertificate) GetCertificatePath() string {\n\tif x != nil {\n\t\treturn x.CertificatePath\n\t}\n\treturn \"\"\n}\n\nfunc (x *ClientCertificate) GetCertificatePassword() string {\n\tif x != nil {\n\t\treturn x.CertificatePassword\n\t}\n\treturn \"\"\n}\n\ntype Oauth2 struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tRefreshToken string `protobuf:\"bytes,1,opt,name=refresh_token,json=refreshToken,proto3\" json:\"refresh_token,omitempty\"`\n\tClientId     string `protobuf:\"bytes,2,opt,name=client_id,json=clientId,proto3\" json:\"client_id,omitempty\"`\n\tClientSecret string `protobuf:\"bytes,3,opt,name=client_secret,json=clientSecret,proto3\" json:\"client_secret,omitempty\"`\n\tAccessToken  string `protobuf:\"bytes,4,opt,name=access_token,json=accessToken,proto3\" json:\"access_token,omitempty\"`\n\tRedirectUri  string `protobuf:\"bytes,5,opt,name=redirect_uri,json=redirectUri,proto3\" json:\"redirect_uri,omitempty\"`\n}\n\nfunc (x *Oauth2) Reset() {\n\t*x = Oauth2{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_credentials_proto_msgTypes[7]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Oauth2) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Oauth2) ProtoMessage() {}\n\nfunc (x *Oauth2) ProtoReflect() protoreflect.Message {\n\tmi := &file_credentials_proto_msgTypes[7]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Oauth2.ProtoReflect.Descriptor instead.\nfunc (*Oauth2) Descriptor() ([]byte, []int) {\n\treturn file_credentials_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *Oauth2) GetRefreshToken() string {\n\tif x != nil {\n\t\treturn x.RefreshToken\n\t}\n\treturn \"\"\n}\n\nfunc (x *Oauth2) GetClientId() string {\n\tif x != nil {\n\t\treturn x.ClientId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Oauth2) GetClientSecret() string {\n\tif x != nil {\n\t\treturn x.ClientSecret\n\t}\n\treturn \"\"\n}\n\nfunc (x *Oauth2) GetAccessToken() string {\n\tif x != nil {\n\t\treturn x.AccessToken\n\t}\n\treturn \"\"\n}\n\nfunc (x *Oauth2) GetRedirectUri() string {\n\tif x != nil {\n\t\treturn x.RedirectUri\n\t}\n\treturn \"\"\n}\n\ntype KeySecret struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tKey    string `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tSecret string `protobuf:\"bytes,2,opt,name=secret,proto3\" json:\"secret,omitempty\"`\n}\n\nfunc (x *KeySecret) Reset() {\n\t*x = KeySecret{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_credentials_proto_msgTypes[8]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *KeySecret) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*KeySecret) ProtoMessage() {}\n\nfunc (x *KeySecret) ProtoReflect() protoreflect.Message {\n\tmi := &file_credentials_proto_msgTypes[8]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use KeySecret.ProtoReflect.Descriptor instead.\nfunc (*KeySecret) Descriptor() ([]byte, []int) {\n\treturn file_credentials_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *KeySecret) GetKey() string {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn \"\"\n}\n\nfunc (x *KeySecret) GetSecret() string {\n\tif x != nil {\n\t\treturn x.Secret\n\t}\n\treturn \"\"\n}\n\ntype AWSSessionTokenSecret struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tKey          string `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tSecret       string `protobuf:\"bytes,2,opt,name=secret,proto3\" json:\"secret,omitempty\"`\n\tSessionToken string `protobuf:\"bytes,3,opt,name=session_token,json=sessionToken,proto3\" json:\"session_token,omitempty\"`\n}\n\nfunc (x *AWSSessionTokenSecret) Reset() {\n\t*x = AWSSessionTokenSecret{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_credentials_proto_msgTypes[9]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AWSSessionTokenSecret) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AWSSessionTokenSecret) ProtoMessage() {}\n\nfunc (x *AWSSessionTokenSecret) ProtoReflect() protoreflect.Message {\n\tmi := &file_credentials_proto_msgTypes[9]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AWSSessionTokenSecret.ProtoReflect.Descriptor instead.\nfunc (*AWSSessionTokenSecret) Descriptor() ([]byte, []int) {\n\treturn file_credentials_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *AWSSessionTokenSecret) GetKey() string {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn \"\"\n}\n\nfunc (x *AWSSessionTokenSecret) GetSecret() string {\n\tif x != nil {\n\t\treturn x.Secret\n\t}\n\treturn \"\"\n}\n\nfunc (x *AWSSessionTokenSecret) GetSessionToken() string {\n\tif x != nil {\n\t\treturn x.SessionToken\n\t}\n\treturn \"\"\n}\n\ntype AWS struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tKey    string `protobuf:\"bytes,1,opt,name=key,proto3\" json:\"key,omitempty\"`\n\tSecret string `protobuf:\"bytes,2,opt,name=secret,proto3\" json:\"secret,omitempty\"`\n\tRegion string `protobuf:\"bytes,3,opt,name=region,proto3\" json:\"region,omitempty\"`\n}\n\nfunc (x *AWS) Reset() {\n\t*x = AWS{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_credentials_proto_msgTypes[10]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AWS) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AWS) ProtoMessage() {}\n\nfunc (x *AWS) ProtoReflect() protoreflect.Message {\n\tmi := &file_credentials_proto_msgTypes[10]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AWS.ProtoReflect.Descriptor instead.\nfunc (*AWS) Descriptor() ([]byte, []int) {\n\treturn file_credentials_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (x *AWS) GetKey() string {\n\tif x != nil {\n\t\treturn x.Key\n\t}\n\treturn \"\"\n}\n\nfunc (x *AWS) GetSecret() string {\n\tif x != nil {\n\t\treturn x.Secret\n\t}\n\treturn \"\"\n}\n\nfunc (x *AWS) GetRegion() string {\n\tif x != nil {\n\t\treturn x.Region\n\t}\n\treturn \"\"\n}\n\ntype SES struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCreds      *AWS     `protobuf:\"bytes,1,opt,name=creds,proto3\" json:\"creds,omitempty\"`\n\tSender     string   `protobuf:\"bytes,2,opt,name=sender,proto3\" json:\"sender,omitempty\"`\n\tRecipients []string `protobuf:\"bytes,3,rep,name=recipients,proto3\" json:\"recipients,omitempty\"`\n}\n\nfunc (x *SES) Reset() {\n\t*x = SES{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_credentials_proto_msgTypes[11]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SES) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SES) ProtoMessage() {}\n\nfunc (x *SES) ProtoReflect() protoreflect.Message {\n\tmi := &file_credentials_proto_msgTypes[11]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SES.ProtoReflect.Descriptor instead.\nfunc (*SES) Descriptor() ([]byte, []int) {\n\treturn file_credentials_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (x *SES) GetCreds() *AWS {\n\tif x != nil {\n\t\treturn x.Creds\n\t}\n\treturn nil\n}\n\nfunc (x *SES) GetSender() string {\n\tif x != nil {\n\t\treturn x.Sender\n\t}\n\treturn \"\"\n}\n\nfunc (x *SES) GetRecipients() []string {\n\tif x != nil {\n\t\treturn x.Recipients\n\t}\n\treturn nil\n}\n\ntype GitHubApp struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tPrivateKey     string `protobuf:\"bytes,1,opt,name=private_key,json=privateKey,proto3\" json:\"private_key,omitempty\"`\n\tInstallationId string `protobuf:\"bytes,2,opt,name=installation_id,json=installationId,proto3\" json:\"installation_id,omitempty\"`\n\tAppId          string `protobuf:\"bytes,3,opt,name=app_id,json=appId,proto3\" json:\"app_id,omitempty\"`\n}\n\nfunc (x *GitHubApp) Reset() {\n\t*x = GitHubApp{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_credentials_proto_msgTypes[12]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GitHubApp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GitHubApp) ProtoMessage() {}\n\nfunc (x *GitHubApp) ProtoReflect() protoreflect.Message {\n\tmi := &file_credentials_proto_msgTypes[12]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GitHubApp.ProtoReflect.Descriptor instead.\nfunc (*GitHubApp) Descriptor() ([]byte, []int) {\n\treturn file_credentials_proto_rawDescGZIP(), []int{12}\n}\n\nfunc (x *GitHubApp) GetPrivateKey() string {\n\tif x != nil {\n\t\treturn x.PrivateKey\n\t}\n\treturn \"\"\n}\n\nfunc (x *GitHubApp) GetInstallationId() string {\n\tif x != nil {\n\t\treturn x.InstallationId\n\t}\n\treturn \"\"\n}\n\nfunc (x *GitHubApp) GetAppId() string {\n\tif x != nil {\n\t\treturn x.AppId\n\t}\n\treturn \"\"\n}\n\ntype SlackTokens struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tAppToken    string `protobuf:\"bytes,1,opt,name=app_token,json=appToken,proto3\" json:\"app_token,omitempty\"`\n\tBotToken    string `protobuf:\"bytes,2,opt,name=bot_token,json=botToken,proto3\" json:\"bot_token,omitempty\"`\n\tClientToken string `protobuf:\"bytes,3,opt,name=client_token,json=clientToken,proto3\" json:\"client_token,omitempty\"`\n}\n\nfunc (x *SlackTokens) Reset() {\n\t*x = SlackTokens{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_credentials_proto_msgTypes[13]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SlackTokens) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SlackTokens) ProtoMessage() {}\n\nfunc (x *SlackTokens) ProtoReflect() protoreflect.Message {\n\tmi := &file_credentials_proto_msgTypes[13]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SlackTokens.ProtoReflect.Descriptor instead.\nfunc (*SlackTokens) Descriptor() ([]byte, []int) {\n\treturn file_credentials_proto_rawDescGZIP(), []int{13}\n}\n\nfunc (x *SlackTokens) GetAppToken() string {\n\tif x != nil {\n\t\treturn x.AppToken\n\t}\n\treturn \"\"\n}\n\nfunc (x *SlackTokens) GetBotToken() string {\n\tif x != nil {\n\t\treturn x.BotToken\n\t}\n\treturn \"\"\n}\n\nfunc (x *SlackTokens) GetClientToken() string {\n\tif x != nil {\n\t\treturn x.ClientToken\n\t}\n\treturn \"\"\n}\n\ntype GoogleDriveDWD struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tServiceAccountJson string   `protobuf:\"bytes,1,opt,name=service_account_json,json=serviceAccountJson,proto3\" json:\"service_account_json,omitempty\"`\n\tAdminEmail         string   `protobuf:\"bytes,2,opt,name=admin_email,json=adminEmail,proto3\" json:\"admin_email,omitempty\"`\n\tIncludeUsers       []string `protobuf:\"bytes,3,rep,name=include_users,json=includeUsers,proto3\" json:\"include_users,omitempty\"`\n\tExcludeUsers       []string `protobuf:\"bytes,4,rep,name=exclude_users,json=excludeUsers,proto3\" json:\"exclude_users,omitempty\"`\n}\n\nfunc (x *GoogleDriveDWD) Reset() {\n\t*x = GoogleDriveDWD{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_credentials_proto_msgTypes[14]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GoogleDriveDWD) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GoogleDriveDWD) ProtoMessage() {}\n\nfunc (x *GoogleDriveDWD) ProtoReflect() protoreflect.Message {\n\tmi := &file_credentials_proto_msgTypes[14]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GoogleDriveDWD.ProtoReflect.Descriptor instead.\nfunc (*GoogleDriveDWD) Descriptor() ([]byte, []int) {\n\treturn file_credentials_proto_rawDescGZIP(), []int{14}\n}\n\nfunc (x *GoogleDriveDWD) GetServiceAccountJson() string {\n\tif x != nil {\n\t\treturn x.ServiceAccountJson\n\t}\n\treturn \"\"\n}\n\nfunc (x *GoogleDriveDWD) GetAdminEmail() string {\n\tif x != nil {\n\t\treturn x.AdminEmail\n\t}\n\treturn \"\"\n}\n\nfunc (x *GoogleDriveDWD) GetIncludeUsers() []string {\n\tif x != nil {\n\t\treturn x.IncludeUsers\n\t}\n\treturn nil\n}\n\nfunc (x *GoogleDriveDWD) GetExcludeUsers() []string {\n\tif x != nil {\n\t\treturn x.ExcludeUsers\n\t}\n\treturn nil\n}\n\nvar File_credentials_proto protoreflect.FileDescriptor\n\nvar file_credentials_proto_rawDesc = []byte{\n\t0x0a, 0x11, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73,\n\t0x1a, 0x17, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64,\n\t0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x11, 0x0a, 0x0f, 0x55, 0x6e, 0x61,\n\t0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x22, 0x09, 0x0a, 0x07,\n\t0x53, 0x53, 0x48, 0x41, 0x75, 0x74, 0x68, 0x22, 0x12, 0x0a, 0x10, 0x43, 0x6c, 0x6f, 0x75, 0x64,\n\t0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x43, 0x0a, 0x09, 0x42,\n\t0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72,\n\t0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72,\n\t0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,\n\t0x22, 0x30, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,\n\t0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05,\n\t0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c,\n\t0x75, 0x65, 0x22, 0x72, 0x0a, 0x11, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x64,\n\t0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e,\n\t0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61,\n\t0x6e, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69,\n\t0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49,\n\t0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72,\n\t0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,\n\t0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x22, 0xab, 0x01, 0x0a, 0x11, 0x43, 0x6c, 0x69, 0x65, 0x6e,\n\t0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09,\n\t0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69,\n\t0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c,\n\t0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66,\n\t0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x0f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x61, 0x74,\n\t0x68, 0x12, 0x31, 0x0a, 0x14, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65,\n\t0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x13, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73,\n\t0x77, 0x6f, 0x72, 0x64, 0x22, 0xb5, 0x01, 0x0a, 0x06, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x12,\n\t0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54,\n\t0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69,\n\t0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49,\n\t0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72,\n\t0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,\n\t0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73,\n\t0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63,\n\t0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x64,\n\t0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x0b, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x69, 0x22, 0x35, 0x0a, 0x09,\n\t0x4b, 0x65, 0x79, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73,\n\t0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x63,\n\t0x72, 0x65, 0x74, 0x22, 0x81, 0x01, 0x0a, 0x15, 0x41, 0x57, 0x53, 0x53, 0x65, 0x73, 0x73, 0x69,\n\t0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x19, 0x0a,\n\t0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72,\n\t0x02, 0x10, 0x01, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72,\n\t0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10,\n\t0x01, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x2c, 0x0a, 0x0d, 0x73, 0x65, 0x73,\n\t0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,\n\t0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x0c, 0x73, 0x65, 0x73, 0x73, 0x69,\n\t0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x59, 0x0a, 0x03, 0x41, 0x57, 0x53, 0x12, 0x19,\n\t0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04,\n\t0x72, 0x02, 0x10, 0x01, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x06, 0x73, 0x65, 0x63,\n\t0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02,\n\t0x10, 0x01, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65,\n\t0x67, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69,\n\t0x6f, 0x6e, 0x22, 0x65, 0x0a, 0x03, 0x53, 0x45, 0x53, 0x12, 0x26, 0x0a, 0x05, 0x63, 0x72, 0x65,\n\t0x64, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65,\n\t0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x41, 0x57, 0x53, 0x52, 0x05, 0x63, 0x72, 0x65, 0x64,\n\t0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x63,\n\t0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72,\n\t0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x6c, 0x0a, 0x09, 0x47, 0x69, 0x74,\n\t0x48, 0x75, 0x62, 0x41, 0x70, 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74,\n\t0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x69,\n\t0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x73, 0x74, 0x61,\n\t0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x0e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64,\n\t0x12, 0x15, 0x0a, 0x06, 0x61, 0x70, 0x70, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x05, 0x61, 0x70, 0x70, 0x49, 0x64, 0x22, 0x6a, 0x0a, 0x0b, 0x53, 0x6c, 0x61, 0x63, 0x6b,\n\t0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, 0x74, 0x6f,\n\t0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x70, 0x70, 0x54, 0x6f,\n\t0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x6f, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x62, 0x6f, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e,\n\t0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e,\n\t0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x54, 0x6f,\n\t0x6b, 0x65, 0x6e, 0x22, 0xad, 0x01, 0x0a, 0x0e, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x44, 0x72,\n\t0x69, 0x76, 0x65, 0x44, 0x57, 0x44, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,\n\t0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63,\n\t0x6f, 0x75, 0x6e, 0x74, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x64, 0x6d, 0x69,\n\t0x6e, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61,\n\t0x64, 0x6d, 0x69, 0x6e, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x63,\n\t0x6c, 0x75, 0x64, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09,\n\t0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x23,\n\t0x0a, 0x0d, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18,\n\t0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x55, 0x73,\n\t0x65, 0x72, 0x73, 0x42, 0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,\n\t0x6d, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74,\n\t0x79, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x68, 0x6f, 0x67, 0x2f, 0x76, 0x33, 0x2f,\n\t0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61,\n\t0x6c, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_credentials_proto_rawDescOnce sync.Once\n\tfile_credentials_proto_rawDescData = file_credentials_proto_rawDesc\n)\n\nfunc file_credentials_proto_rawDescGZIP() []byte {\n\tfile_credentials_proto_rawDescOnce.Do(func() {\n\t\tfile_credentials_proto_rawDescData = protoimpl.X.CompressGZIP(file_credentials_proto_rawDescData)\n\t})\n\treturn file_credentials_proto_rawDescData\n}\n\nvar file_credentials_proto_msgTypes = make([]protoimpl.MessageInfo, 15)\nvar file_credentials_proto_goTypes = []interface{}{\n\t(*Unauthenticated)(nil),       // 0: credentials.Unauthenticated\n\t(*SSHAuth)(nil),               // 1: credentials.SSHAuth\n\t(*CloudEnvironment)(nil),      // 2: credentials.CloudEnvironment\n\t(*BasicAuth)(nil),             // 3: credentials.BasicAuth\n\t(*Header)(nil),                // 4: credentials.Header\n\t(*ClientCredentials)(nil),     // 5: credentials.ClientCredentials\n\t(*ClientCertificate)(nil),     // 6: credentials.ClientCertificate\n\t(*Oauth2)(nil),                // 7: credentials.Oauth2\n\t(*KeySecret)(nil),             // 8: credentials.KeySecret\n\t(*AWSSessionTokenSecret)(nil), // 9: credentials.AWSSessionTokenSecret\n\t(*AWS)(nil),                   // 10: credentials.AWS\n\t(*SES)(nil),                   // 11: credentials.SES\n\t(*GitHubApp)(nil),             // 12: credentials.GitHubApp\n\t(*SlackTokens)(nil),           // 13: credentials.SlackTokens\n\t(*GoogleDriveDWD)(nil),        // 14: credentials.GoogleDriveDWD\n}\nvar file_credentials_proto_depIdxs = []int32{\n\t10, // 0: credentials.SES.creds:type_name -> credentials.AWS\n\t1,  // [1:1] is the sub-list for method output_type\n\t1,  // [1:1] is the sub-list for method input_type\n\t1,  // [1:1] is the sub-list for extension type_name\n\t1,  // [1:1] is the sub-list for extension extendee\n\t0,  // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_credentials_proto_init() }\nfunc file_credentials_proto_init() {\n\tif File_credentials_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_credentials_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Unauthenticated); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_credentials_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SSHAuth); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_credentials_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CloudEnvironment); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_credentials_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*BasicAuth); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_credentials_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Header); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_credentials_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ClientCredentials); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_credentials_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ClientCertificate); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_credentials_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Oauth2); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_credentials_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*KeySecret); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_credentials_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*AWSSessionTokenSecret); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_credentials_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*AWS); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_credentials_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SES); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_credentials_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GitHubApp); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_credentials_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SlackTokens); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_credentials_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GoogleDriveDWD); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_credentials_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   15,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_credentials_proto_goTypes,\n\t\tDependencyIndexes: file_credentials_proto_depIdxs,\n\t\tMessageInfos:      file_credentials_proto_msgTypes,\n\t}.Build()\n\tFile_credentials_proto = out.File\n\tfile_credentials_proto_rawDesc = nil\n\tfile_credentials_proto_goTypes = nil\n\tfile_credentials_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pkg/pb/credentialspb/credentials.pb.validate.go",
    "content": "// Code generated by protoc-gen-validate. DO NOT EDIT.\n// source: credentials.proto\n\npackage credentialspb\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/mail\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode/utf8\"\n\n\t\"google.golang.org/protobuf/types/known/anypb\"\n)\n\n// ensure the imports are used\nvar (\n\t_ = bytes.MinRead\n\t_ = errors.New(\"\")\n\t_ = fmt.Print\n\t_ = utf8.UTFMax\n\t_ = (*regexp.Regexp)(nil)\n\t_ = (*strings.Reader)(nil)\n\t_ = net.IPv4len\n\t_ = time.Duration(0)\n\t_ = (*url.URL)(nil)\n\t_ = (*mail.Address)(nil)\n\t_ = anypb.Any{}\n\t_ = sort.Sort\n)\n\n// Validate checks the field values on Unauthenticated with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// first error encountered is returned, or nil if there are no violations.\nfunc (m *Unauthenticated) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Unauthenticated with the rules\n// defined in the proto definition for this message. If any rules are\n// violated, the result is a list of violation errors wrapped in\n// UnauthenticatedMultiError, or nil if none found.\nfunc (m *Unauthenticated) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Unauthenticated) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif len(errors) > 0 {\n\t\treturn UnauthenticatedMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// UnauthenticatedMultiError is an error wrapping multiple validation errors\n// returned by Unauthenticated.ValidateAll() if the designated constraints\n// aren't met.\ntype UnauthenticatedMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m UnauthenticatedMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m UnauthenticatedMultiError) AllErrors() []error { return m }\n\n// UnauthenticatedValidationError is the validation error returned by\n// Unauthenticated.Validate if the designated constraints aren't met.\ntype UnauthenticatedValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e UnauthenticatedValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e UnauthenticatedValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e UnauthenticatedValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e UnauthenticatedValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e UnauthenticatedValidationError) ErrorName() string { return \"UnauthenticatedValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e UnauthenticatedValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sUnauthenticated.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = UnauthenticatedValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = UnauthenticatedValidationError{}\n\n// Validate checks the field values on SSHAuth with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *SSHAuth) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on SSHAuth with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in SSHAuthMultiError, or nil if none found.\nfunc (m *SSHAuth) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *SSHAuth) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif len(errors) > 0 {\n\t\treturn SSHAuthMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// SSHAuthMultiError is an error wrapping multiple validation errors returned\n// by SSHAuth.ValidateAll() if the designated constraints aren't met.\ntype SSHAuthMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m SSHAuthMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m SSHAuthMultiError) AllErrors() []error { return m }\n\n// SSHAuthValidationError is the validation error returned by SSHAuth.Validate\n// if the designated constraints aren't met.\ntype SSHAuthValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e SSHAuthValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e SSHAuthValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e SSHAuthValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e SSHAuthValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e SSHAuthValidationError) ErrorName() string { return \"SSHAuthValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e SSHAuthValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sSSHAuth.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = SSHAuthValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = SSHAuthValidationError{}\n\n// Validate checks the field values on CloudEnvironment with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// first error encountered is returned, or nil if there are no violations.\nfunc (m *CloudEnvironment) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on CloudEnvironment with the rules\n// defined in the proto definition for this message. If any rules are\n// violated, the result is a list of violation errors wrapped in\n// CloudEnvironmentMultiError, or nil if none found.\nfunc (m *CloudEnvironment) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *CloudEnvironment) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif len(errors) > 0 {\n\t\treturn CloudEnvironmentMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// CloudEnvironmentMultiError is an error wrapping multiple validation errors\n// returned by CloudEnvironment.ValidateAll() if the designated constraints\n// aren't met.\ntype CloudEnvironmentMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m CloudEnvironmentMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m CloudEnvironmentMultiError) AllErrors() []error { return m }\n\n// CloudEnvironmentValidationError is the validation error returned by\n// CloudEnvironment.Validate if the designated constraints aren't met.\ntype CloudEnvironmentValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e CloudEnvironmentValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e CloudEnvironmentValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e CloudEnvironmentValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e CloudEnvironmentValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e CloudEnvironmentValidationError) ErrorName() string { return \"CloudEnvironmentValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e CloudEnvironmentValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sCloudEnvironment.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = CloudEnvironmentValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = CloudEnvironmentValidationError{}\n\n// Validate checks the field values on BasicAuth with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *BasicAuth) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on BasicAuth with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in BasicAuthMultiError, or nil\n// if none found.\nfunc (m *BasicAuth) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *BasicAuth) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Username\n\n\t// no validation rules for Password\n\n\tif len(errors) > 0 {\n\t\treturn BasicAuthMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// BasicAuthMultiError is an error wrapping multiple validation errors returned\n// by BasicAuth.ValidateAll() if the designated constraints aren't met.\ntype BasicAuthMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m BasicAuthMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m BasicAuthMultiError) AllErrors() []error { return m }\n\n// BasicAuthValidationError is the validation error returned by\n// BasicAuth.Validate if the designated constraints aren't met.\ntype BasicAuthValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e BasicAuthValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e BasicAuthValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e BasicAuthValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e BasicAuthValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e BasicAuthValidationError) ErrorName() string { return \"BasicAuthValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e BasicAuthValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sBasicAuth.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = BasicAuthValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = BasicAuthValidationError{}\n\n// Validate checks the field values on Header with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Header) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Header with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in HeaderMultiError, or nil if none found.\nfunc (m *Header) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Header) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Key\n\n\t// no validation rules for Value\n\n\tif len(errors) > 0 {\n\t\treturn HeaderMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// HeaderMultiError is an error wrapping multiple validation errors returned by\n// Header.ValidateAll() if the designated constraints aren't met.\ntype HeaderMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m HeaderMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m HeaderMultiError) AllErrors() []error { return m }\n\n// HeaderValidationError is the validation error returned by Header.Validate if\n// the designated constraints aren't met.\ntype HeaderValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e HeaderValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e HeaderValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e HeaderValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e HeaderValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e HeaderValidationError) ErrorName() string { return \"HeaderValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e HeaderValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sHeader.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = HeaderValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = HeaderValidationError{}\n\n// Validate checks the field values on ClientCredentials with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// first error encountered is returned, or nil if there are no violations.\nfunc (m *ClientCredentials) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on ClientCredentials with the rules\n// defined in the proto definition for this message. If any rules are\n// violated, the result is a list of violation errors wrapped in\n// ClientCredentialsMultiError, or nil if none found.\nfunc (m *ClientCredentials) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *ClientCredentials) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for TenantId\n\n\t// no validation rules for ClientId\n\n\t// no validation rules for ClientSecret\n\n\tif len(errors) > 0 {\n\t\treturn ClientCredentialsMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// ClientCredentialsMultiError is an error wrapping multiple validation errors\n// returned by ClientCredentials.ValidateAll() if the designated constraints\n// aren't met.\ntype ClientCredentialsMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m ClientCredentialsMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m ClientCredentialsMultiError) AllErrors() []error { return m }\n\n// ClientCredentialsValidationError is the validation error returned by\n// ClientCredentials.Validate if the designated constraints aren't met.\ntype ClientCredentialsValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e ClientCredentialsValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e ClientCredentialsValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e ClientCredentialsValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e ClientCredentialsValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e ClientCredentialsValidationError) ErrorName() string {\n\treturn \"ClientCredentialsValidationError\"\n}\n\n// Error satisfies the builtin error interface\nfunc (e ClientCredentialsValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sClientCredentials.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = ClientCredentialsValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = ClientCredentialsValidationError{}\n\n// Validate checks the field values on ClientCertificate with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// first error encountered is returned, or nil if there are no violations.\nfunc (m *ClientCertificate) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on ClientCertificate with the rules\n// defined in the proto definition for this message. If any rules are\n// violated, the result is a list of violation errors wrapped in\n// ClientCertificateMultiError, or nil if none found.\nfunc (m *ClientCertificate) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *ClientCertificate) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for TenantId\n\n\t// no validation rules for ClientId\n\n\t// no validation rules for CertificatePath\n\n\t// no validation rules for CertificatePassword\n\n\tif len(errors) > 0 {\n\t\treturn ClientCertificateMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// ClientCertificateMultiError is an error wrapping multiple validation errors\n// returned by ClientCertificate.ValidateAll() if the designated constraints\n// aren't met.\ntype ClientCertificateMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m ClientCertificateMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m ClientCertificateMultiError) AllErrors() []error { return m }\n\n// ClientCertificateValidationError is the validation error returned by\n// ClientCertificate.Validate if the designated constraints aren't met.\ntype ClientCertificateValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e ClientCertificateValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e ClientCertificateValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e ClientCertificateValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e ClientCertificateValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e ClientCertificateValidationError) ErrorName() string {\n\treturn \"ClientCertificateValidationError\"\n}\n\n// Error satisfies the builtin error interface\nfunc (e ClientCertificateValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sClientCertificate.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = ClientCertificateValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = ClientCertificateValidationError{}\n\n// Validate checks the field values on Oauth2 with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Oauth2) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Oauth2 with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in Oauth2MultiError, or nil if none found.\nfunc (m *Oauth2) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Oauth2) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for RefreshToken\n\n\t// no validation rules for ClientId\n\n\t// no validation rules for ClientSecret\n\n\t// no validation rules for AccessToken\n\n\t// no validation rules for RedirectUri\n\n\tif len(errors) > 0 {\n\t\treturn Oauth2MultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// Oauth2MultiError is an error wrapping multiple validation errors returned by\n// Oauth2.ValidateAll() if the designated constraints aren't met.\ntype Oauth2MultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m Oauth2MultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m Oauth2MultiError) AllErrors() []error { return m }\n\n// Oauth2ValidationError is the validation error returned by Oauth2.Validate if\n// the designated constraints aren't met.\ntype Oauth2ValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e Oauth2ValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e Oauth2ValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e Oauth2ValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e Oauth2ValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e Oauth2ValidationError) ErrorName() string { return \"Oauth2ValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e Oauth2ValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sOauth2.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = Oauth2ValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = Oauth2ValidationError{}\n\n// Validate checks the field values on KeySecret with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *KeySecret) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on KeySecret with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in KeySecretMultiError, or nil\n// if none found.\nfunc (m *KeySecret) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *KeySecret) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Key\n\n\t// no validation rules for Secret\n\n\tif len(errors) > 0 {\n\t\treturn KeySecretMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// KeySecretMultiError is an error wrapping multiple validation errors returned\n// by KeySecret.ValidateAll() if the designated constraints aren't met.\ntype KeySecretMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m KeySecretMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m KeySecretMultiError) AllErrors() []error { return m }\n\n// KeySecretValidationError is the validation error returned by\n// KeySecret.Validate if the designated constraints aren't met.\ntype KeySecretValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e KeySecretValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e KeySecretValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e KeySecretValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e KeySecretValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e KeySecretValidationError) ErrorName() string { return \"KeySecretValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e KeySecretValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sKeySecret.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = KeySecretValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = KeySecretValidationError{}\n\n// Validate checks the field values on AWSSessionTokenSecret with the rules\n// defined in the proto definition for this message. If any rules are\n// violated, the first error encountered is returned, or nil if there are no violations.\nfunc (m *AWSSessionTokenSecret) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on AWSSessionTokenSecret with the rules\n// defined in the proto definition for this message. If any rules are\n// violated, the result is a list of violation errors wrapped in\n// AWSSessionTokenSecretMultiError, or nil if none found.\nfunc (m *AWSSessionTokenSecret) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *AWSSessionTokenSecret) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif utf8.RuneCountInString(m.GetKey()) < 1 {\n\t\terr := AWSSessionTokenSecretValidationError{\n\t\t\tfield:  \"Key\",\n\t\t\treason: \"value length must be at least 1 runes\",\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\tif utf8.RuneCountInString(m.GetSecret()) < 1 {\n\t\terr := AWSSessionTokenSecretValidationError{\n\t\t\tfield:  \"Secret\",\n\t\t\treason: \"value length must be at least 1 runes\",\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\tif utf8.RuneCountInString(m.GetSessionToken()) < 1 {\n\t\terr := AWSSessionTokenSecretValidationError{\n\t\t\tfield:  \"SessionToken\",\n\t\t\treason: \"value length must be at least 1 runes\",\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn AWSSessionTokenSecretMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// AWSSessionTokenSecretMultiError is an error wrapping multiple validation\n// errors returned by AWSSessionTokenSecret.ValidateAll() if the designated\n// constraints aren't met.\ntype AWSSessionTokenSecretMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m AWSSessionTokenSecretMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m AWSSessionTokenSecretMultiError) AllErrors() []error { return m }\n\n// AWSSessionTokenSecretValidationError is the validation error returned by\n// AWSSessionTokenSecret.Validate if the designated constraints aren't met.\ntype AWSSessionTokenSecretValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e AWSSessionTokenSecretValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e AWSSessionTokenSecretValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e AWSSessionTokenSecretValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e AWSSessionTokenSecretValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e AWSSessionTokenSecretValidationError) ErrorName() string {\n\treturn \"AWSSessionTokenSecretValidationError\"\n}\n\n// Error satisfies the builtin error interface\nfunc (e AWSSessionTokenSecretValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sAWSSessionTokenSecret.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = AWSSessionTokenSecretValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = AWSSessionTokenSecretValidationError{}\n\n// Validate checks the field values on AWS with the rules defined in the proto\n// definition for this message. If any rules are violated, the first error\n// encountered is returned, or nil if there are no violations.\nfunc (m *AWS) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on AWS with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in AWSMultiError, or nil if none found.\nfunc (m *AWS) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *AWS) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif utf8.RuneCountInString(m.GetKey()) < 1 {\n\t\terr := AWSValidationError{\n\t\t\tfield:  \"Key\",\n\t\t\treason: \"value length must be at least 1 runes\",\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\tif utf8.RuneCountInString(m.GetSecret()) < 1 {\n\t\terr := AWSValidationError{\n\t\t\tfield:  \"Secret\",\n\t\t\treason: \"value length must be at least 1 runes\",\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\t// no validation rules for Region\n\n\tif len(errors) > 0 {\n\t\treturn AWSMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// AWSMultiError is an error wrapping multiple validation errors returned by\n// AWS.ValidateAll() if the designated constraints aren't met.\ntype AWSMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m AWSMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m AWSMultiError) AllErrors() []error { return m }\n\n// AWSValidationError is the validation error returned by AWS.Validate if the\n// designated constraints aren't met.\ntype AWSValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e AWSValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e AWSValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e AWSValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e AWSValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e AWSValidationError) ErrorName() string { return \"AWSValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e AWSValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sAWS.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = AWSValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = AWSValidationError{}\n\n// Validate checks the field values on SES with the rules defined in the proto\n// definition for this message. If any rules are violated, the first error\n// encountered is returned, or nil if there are no violations.\nfunc (m *SES) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on SES with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in SESMultiError, or nil if none found.\nfunc (m *SES) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *SES) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif all {\n\t\tswitch v := interface{}(m.GetCreds()).(type) {\n\t\tcase interface{ ValidateAll() error }:\n\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\terrors = append(errors, SESValidationError{\n\t\t\t\t\tfield:  \"Creds\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t})\n\t\t\t}\n\t\tcase interface{ Validate() error }:\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\terrors = append(errors, SESValidationError{\n\t\t\t\t\tfield:  \"Creds\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t} else if v, ok := interface{}(m.GetCreds()).(interface{ Validate() error }); ok {\n\t\tif err := v.Validate(); err != nil {\n\t\t\treturn SESValidationError{\n\t\t\t\tfield:  \"Creds\",\n\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\tcause:  err,\n\t\t\t}\n\t\t}\n\t}\n\n\t// no validation rules for Sender\n\n\tif len(errors) > 0 {\n\t\treturn SESMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// SESMultiError is an error wrapping multiple validation errors returned by\n// SES.ValidateAll() if the designated constraints aren't met.\ntype SESMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m SESMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m SESMultiError) AllErrors() []error { return m }\n\n// SESValidationError is the validation error returned by SES.Validate if the\n// designated constraints aren't met.\ntype SESValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e SESValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e SESValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e SESValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e SESValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e SESValidationError) ErrorName() string { return \"SESValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e SESValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sSES.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = SESValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = SESValidationError{}\n\n// Validate checks the field values on GitHubApp with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *GitHubApp) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on GitHubApp with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in GitHubAppMultiError, or nil\n// if none found.\nfunc (m *GitHubApp) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *GitHubApp) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for PrivateKey\n\n\t// no validation rules for InstallationId\n\n\t// no validation rules for AppId\n\n\tif len(errors) > 0 {\n\t\treturn GitHubAppMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// GitHubAppMultiError is an error wrapping multiple validation errors returned\n// by GitHubApp.ValidateAll() if the designated constraints aren't met.\ntype GitHubAppMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m GitHubAppMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m GitHubAppMultiError) AllErrors() []error { return m }\n\n// GitHubAppValidationError is the validation error returned by\n// GitHubApp.Validate if the designated constraints aren't met.\ntype GitHubAppValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e GitHubAppValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e GitHubAppValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e GitHubAppValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e GitHubAppValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e GitHubAppValidationError) ErrorName() string { return \"GitHubAppValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e GitHubAppValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sGitHubApp.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = GitHubAppValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = GitHubAppValidationError{}\n\n// Validate checks the field values on SlackTokens with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *SlackTokens) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on SlackTokens with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in SlackTokensMultiError, or\n// nil if none found.\nfunc (m *SlackTokens) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *SlackTokens) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for AppToken\n\n\t// no validation rules for BotToken\n\n\t// no validation rules for ClientToken\n\n\tif len(errors) > 0 {\n\t\treturn SlackTokensMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// SlackTokensMultiError is an error wrapping multiple validation errors\n// returned by SlackTokens.ValidateAll() if the designated constraints aren't met.\ntype SlackTokensMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m SlackTokensMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m SlackTokensMultiError) AllErrors() []error { return m }\n\n// SlackTokensValidationError is the validation error returned by\n// SlackTokens.Validate if the designated constraints aren't met.\ntype SlackTokensValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e SlackTokensValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e SlackTokensValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e SlackTokensValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e SlackTokensValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e SlackTokensValidationError) ErrorName() string { return \"SlackTokensValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e SlackTokensValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sSlackTokens.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = SlackTokensValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = SlackTokensValidationError{}\n\n// Validate checks the field values on GoogleDriveDWD with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *GoogleDriveDWD) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on GoogleDriveDWD with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in GoogleDriveDWDMultiError,\n// or nil if none found.\nfunc (m *GoogleDriveDWD) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *GoogleDriveDWD) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for ServiceAccountJson\n\n\t// no validation rules for AdminEmail\n\n\tif len(errors) > 0 {\n\t\treturn GoogleDriveDWDMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// GoogleDriveDWDMultiError is an error wrapping multiple validation errors\n// returned by GoogleDriveDWD.ValidateAll() if the designated constraints\n// aren't met.\ntype GoogleDriveDWDMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m GoogleDriveDWDMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m GoogleDriveDWDMultiError) AllErrors() []error { return m }\n\n// GoogleDriveDWDValidationError is the validation error returned by\n// GoogleDriveDWD.Validate if the designated constraints aren't met.\ntype GoogleDriveDWDValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e GoogleDriveDWDValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e GoogleDriveDWDValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e GoogleDriveDWDValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e GoogleDriveDWDValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e GoogleDriveDWDValidationError) ErrorName() string { return \"GoogleDriveDWDValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e GoogleDriveDWDValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sGoogleDriveDWD.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = GoogleDriveDWDValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = GoogleDriveDWDValidationError{}\n"
  },
  {
    "path": "pkg/pb/custom_detectorspb/custom_detectors.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.33.0\n// \tprotoc        v4.25.3\n// source: custom_detectors.proto\n\npackage custom_detectorspb\n\nimport (\n\t_ \"github.com/envoyproxy/protoc-gen-validate/validate\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype CustomDetectors struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tDetectors []*CustomRegex `protobuf:\"bytes,1,rep,name=detectors,proto3\" json:\"detectors,omitempty\"`\n}\n\nfunc (x *CustomDetectors) Reset() {\n\t*x = CustomDetectors{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_custom_detectors_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CustomDetectors) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CustomDetectors) ProtoMessage() {}\n\nfunc (x *CustomDetectors) ProtoReflect() protoreflect.Message {\n\tmi := &file_custom_detectors_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CustomDetectors.ProtoReflect.Descriptor instead.\nfunc (*CustomDetectors) Descriptor() ([]byte, []int) {\n\treturn file_custom_detectors_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *CustomDetectors) GetDetectors() []*CustomRegex {\n\tif x != nil {\n\t\treturn x.Detectors\n\t}\n\treturn nil\n}\n\ntype CustomRegex struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tName                  string                       `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tKeywords              []string                     `protobuf:\"bytes,2,rep,name=keywords,proto3\" json:\"keywords,omitempty\"`\n\tRegex                 map[string]string            `protobuf:\"bytes,3,rep,name=regex,proto3\" json:\"regex,omitempty\" protobuf_key:\"bytes,1,opt,name=key,proto3\" protobuf_val:\"bytes,2,opt,name=value,proto3\"`\n\tVerify                []*VerifierConfig            `protobuf:\"bytes,4,rep,name=verify,proto3\" json:\"verify,omitempty\"`\n\tDescription           string                       `protobuf:\"bytes,5,opt,name=description,proto3\" json:\"description,omitempty\"`\n\tExcludeRegexesCapture []string                     `protobuf:\"bytes,6,rep,name=exclude_regexes_capture,json=excludeRegexesCapture,proto3\" json:\"exclude_regexes_capture,omitempty\"`\n\tExcludeWords          []string                     `protobuf:\"bytes,7,rep,name=exclude_words,json=excludeWords,proto3\" json:\"exclude_words,omitempty\"`\n\tEntropy               float32                      `protobuf:\"fixed32,8,opt,name=entropy,proto3\" json:\"entropy,omitempty\"`\n\tExcludeRegexesMatch   []string                     `protobuf:\"bytes,9,rep,name=exclude_regexes_match,json=excludeRegexesMatch,proto3\" json:\"exclude_regexes_match,omitempty\"`\n\tPrimaryRegexName      string                       `protobuf:\"bytes,10,opt,name=primary_regex_name,json=primaryRegexName,proto3\" json:\"primary_regex_name,omitempty\"`\n\tValidations           map[string]*ValidationConfig `protobuf:\"bytes,11,rep,name=validations,proto3\" json:\"validations,omitempty\" protobuf_key:\"bytes,1,opt,name=key,proto3\" protobuf_val:\"bytes,2,opt,name=value,proto3\"`\n}\n\nfunc (x *CustomRegex) Reset() {\n\t*x = CustomRegex{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_custom_detectors_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CustomRegex) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CustomRegex) ProtoMessage() {}\n\nfunc (x *CustomRegex) ProtoReflect() protoreflect.Message {\n\tmi := &file_custom_detectors_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CustomRegex.ProtoReflect.Descriptor instead.\nfunc (*CustomRegex) Descriptor() ([]byte, []int) {\n\treturn file_custom_detectors_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *CustomRegex) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *CustomRegex) GetKeywords() []string {\n\tif x != nil {\n\t\treturn x.Keywords\n\t}\n\treturn nil\n}\n\nfunc (x *CustomRegex) GetRegex() map[string]string {\n\tif x != nil {\n\t\treturn x.Regex\n\t}\n\treturn nil\n}\n\nfunc (x *CustomRegex) GetVerify() []*VerifierConfig {\n\tif x != nil {\n\t\treturn x.Verify\n\t}\n\treturn nil\n}\n\nfunc (x *CustomRegex) GetDescription() string {\n\tif x != nil {\n\t\treturn x.Description\n\t}\n\treturn \"\"\n}\n\nfunc (x *CustomRegex) GetExcludeRegexesCapture() []string {\n\tif x != nil {\n\t\treturn x.ExcludeRegexesCapture\n\t}\n\treturn nil\n}\n\nfunc (x *CustomRegex) GetExcludeWords() []string {\n\tif x != nil {\n\t\treturn x.ExcludeWords\n\t}\n\treturn nil\n}\n\nfunc (x *CustomRegex) GetEntropy() float32 {\n\tif x != nil {\n\t\treturn x.Entropy\n\t}\n\treturn 0\n}\n\nfunc (x *CustomRegex) GetExcludeRegexesMatch() []string {\n\tif x != nil {\n\t\treturn x.ExcludeRegexesMatch\n\t}\n\treturn nil\n}\n\nfunc (x *CustomRegex) GetPrimaryRegexName() string {\n\tif x != nil {\n\t\treturn x.PrimaryRegexName\n\t}\n\treturn \"\"\n}\n\nfunc (x *CustomRegex) GetValidations() map[string]*ValidationConfig {\n\tif x != nil {\n\t\treturn x.Validations\n\t}\n\treturn nil\n}\n\ntype VerifierConfig struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEndpoint      string   `protobuf:\"bytes,1,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\tUnsafe        bool     `protobuf:\"varint,2,opt,name=unsafe,proto3\" json:\"unsafe,omitempty\"`\n\tHeaders       []string `protobuf:\"bytes,3,rep,name=headers,proto3\" json:\"headers,omitempty\"`\n\tSuccessRanges []string `protobuf:\"bytes,4,rep,name=successRanges,proto3\" json:\"successRanges,omitempty\"`\n}\n\nfunc (x *VerifierConfig) Reset() {\n\t*x = VerifierConfig{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_custom_detectors_proto_msgTypes[2]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *VerifierConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VerifierConfig) ProtoMessage() {}\n\nfunc (x *VerifierConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_custom_detectors_proto_msgTypes[2]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VerifierConfig.ProtoReflect.Descriptor instead.\nfunc (*VerifierConfig) Descriptor() ([]byte, []int) {\n\treturn file_custom_detectors_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *VerifierConfig) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (x *VerifierConfig) GetUnsafe() bool {\n\tif x != nil {\n\t\treturn x.Unsafe\n\t}\n\treturn false\n}\n\nfunc (x *VerifierConfig) GetHeaders() []string {\n\tif x != nil {\n\t\treturn x.Headers\n\t}\n\treturn nil\n}\n\nfunc (x *VerifierConfig) GetSuccessRanges() []string {\n\tif x != nil {\n\t\treturn x.SuccessRanges\n\t}\n\treturn nil\n}\n\ntype ValidationConfig struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tContainsDigit       bool `protobuf:\"varint,1,opt,name=contains_digit,json=containsDigit,proto3\" json:\"contains_digit,omitempty\"`\n\tContainsLowercase   bool `protobuf:\"varint,2,opt,name=contains_lowercase,json=containsLowercase,proto3\" json:\"contains_lowercase,omitempty\"`\n\tContainsUppercase   bool `protobuf:\"varint,3,opt,name=contains_uppercase,json=containsUppercase,proto3\" json:\"contains_uppercase,omitempty\"`\n\tContainsSpecialChar bool `protobuf:\"varint,4,opt,name=contains_special_char,json=containsSpecialChar,proto3\" json:\"contains_special_char,omitempty\"`\n}\n\nfunc (x *ValidationConfig) Reset() {\n\t*x = ValidationConfig{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_custom_detectors_proto_msgTypes[3]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ValidationConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ValidationConfig) ProtoMessage() {}\n\nfunc (x *ValidationConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_custom_detectors_proto_msgTypes[3]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ValidationConfig.ProtoReflect.Descriptor instead.\nfunc (*ValidationConfig) Descriptor() ([]byte, []int) {\n\treturn file_custom_detectors_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *ValidationConfig) GetContainsDigit() bool {\n\tif x != nil {\n\t\treturn x.ContainsDigit\n\t}\n\treturn false\n}\n\nfunc (x *ValidationConfig) GetContainsLowercase() bool {\n\tif x != nil {\n\t\treturn x.ContainsLowercase\n\t}\n\treturn false\n}\n\nfunc (x *ValidationConfig) GetContainsUppercase() bool {\n\tif x != nil {\n\t\treturn x.ContainsUppercase\n\t}\n\treturn false\n}\n\nfunc (x *ValidationConfig) GetContainsSpecialChar() bool {\n\tif x != nil {\n\t\treturn x.ContainsSpecialChar\n\t}\n\treturn false\n}\n\nvar File_custom_detectors_proto protoreflect.FileDescriptor\n\nvar file_custom_detectors_proto_rawDesc = []byte{\n\t0x0a, 0x16, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f,\n\t0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,\n\t0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x1a, 0x17, 0x76, 0x61, 0x6c, 0x69,\n\t0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x22, 0x4e, 0x0a, 0x0f, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x44, 0x65, 0x74,\n\t0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x3b, 0x0a, 0x09, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74,\n\t0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x75, 0x73, 0x74,\n\t0x6f, 0x6d, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x2e, 0x43, 0x75, 0x73,\n\t0x74, 0x6f, 0x6d, 0x52, 0x65, 0x67, 0x65, 0x78, 0x52, 0x09, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74,\n\t0x6f, 0x72, 0x73, 0x22, 0xa2, 0x05, 0x0a, 0x0b, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65,\n\t0x67, 0x65, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x77, 0x6f,\n\t0x72, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x77, 0x6f,\n\t0x72, 0x64, 0x73, 0x12, 0x3e, 0x0a, 0x05, 0x72, 0x65, 0x67, 0x65, 0x78, 0x18, 0x03, 0x20, 0x03,\n\t0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x64, 0x65, 0x74, 0x65,\n\t0x63, 0x74, 0x6f, 0x72, 0x73, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x67, 0x65,\n\t0x78, 0x2e, 0x52, 0x65, 0x67, 0x65, 0x78, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x72, 0x65,\n\t0x67, 0x65, 0x78, 0x12, 0x38, 0x0a, 0x06, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x18, 0x04, 0x20,\n\t0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x64, 0x65, 0x74,\n\t0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, 0x43,\n\t0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x20, 0x0a,\n\t0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12,\n\t0x36, 0x0a, 0x17, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78,\n\t0x65, 0x73, 0x5f, 0x63, 0x61, 0x70, 0x74, 0x75, 0x72, 0x65, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09,\n\t0x52, 0x15, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x52, 0x65, 0x67, 0x65, 0x78, 0x65, 0x73,\n\t0x43, 0x61, 0x70, 0x74, 0x75, 0x72, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x63, 0x6c, 0x75,\n\t0x64, 0x65, 0x5f, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c,\n\t0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x57, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x18, 0x0a, 0x07,\n\t0x65, 0x6e, 0x74, 0x72, 0x6f, 0x70, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x02, 0x52, 0x07, 0x65,\n\t0x6e, 0x74, 0x72, 0x6f, 0x70, 0x79, 0x12, 0x32, 0x0a, 0x15, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64,\n\t0x65, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, 0x65, 0x73, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18,\n\t0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x52, 0x65,\n\t0x67, 0x65, 0x78, 0x65, 0x73, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x72,\n\t0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, 0x5f, 0x6e, 0x61, 0x6d, 0x65,\n\t0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x52,\n\t0x65, 0x67, 0x65, 0x78, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x50, 0x0a, 0x0b, 0x76, 0x61, 0x6c, 0x69,\n\t0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e,\n\t0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73,\n\t0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x67, 0x65, 0x78, 0x2e, 0x56, 0x61, 0x6c,\n\t0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x76,\n\t0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x38, 0x0a, 0x0a, 0x52, 0x65,\n\t0x67, 0x65, 0x78, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,\n\t0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,\n\t0x3a, 0x02, 0x38, 0x01, 0x1a, 0x62, 0x0a, 0x10, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x38, 0x0a, 0x05, 0x76, 0x61,\n\t0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x75, 0x73, 0x74,\n\t0x6f, 0x6d, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x2e, 0x56, 0x61, 0x6c,\n\t0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x05, 0x76,\n\t0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x8e, 0x01, 0x0a, 0x0e, 0x56, 0x65, 0x72,\n\t0x69, 0x66, 0x69, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x08, 0x65,\n\t0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa,\n\t0x42, 0x05, 0x72, 0x03, 0x90, 0x01, 0x01, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e,\n\t0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x6e, 0x73, 0x61, 0x66, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x08, 0x52, 0x06, 0x75, 0x6e, 0x73, 0x61, 0x66, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x61,\n\t0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64,\n\t0x65, 0x72, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x61,\n\t0x6e, 0x67, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x75, 0x63, 0x63,\n\t0x65, 0x73, 0x73, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x22, 0xcb, 0x01, 0x0a, 0x10, 0x56, 0x61,\n\t0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25,\n\t0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x5f, 0x64, 0x69, 0x67, 0x69, 0x74,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73,\n\t0x44, 0x69, 0x67, 0x69, 0x74, 0x12, 0x2d, 0x0a, 0x12, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e,\n\t0x73, 0x5f, 0x6c, 0x6f, 0x77, 0x65, 0x72, 0x63, 0x61, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x08, 0x52, 0x11, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x4c, 0x6f, 0x77, 0x65, 0x72,\n\t0x63, 0x61, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73,\n\t0x5f, 0x75, 0x70, 0x70, 0x65, 0x72, 0x63, 0x61, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08,\n\t0x52, 0x11, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x55, 0x70, 0x70, 0x65, 0x72, 0x63,\n\t0x61, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x5f,\n\t0x73, 0x70, 0x65, 0x63, 0x69, 0x61, 0x6c, 0x5f, 0x63, 0x68, 0x61, 0x72, 0x18, 0x04, 0x20, 0x01,\n\t0x28, 0x08, 0x52, 0x13, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x53, 0x70, 0x65, 0x63,\n\t0x69, 0x61, 0x6c, 0x43, 0x68, 0x61, 0x72, 0x42, 0x44, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75,\n\t0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x73, 0x65, 0x63,\n\t0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x68, 0x6f, 0x67,\n\t0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f,\n\t0x6d, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_custom_detectors_proto_rawDescOnce sync.Once\n\tfile_custom_detectors_proto_rawDescData = file_custom_detectors_proto_rawDesc\n)\n\nfunc file_custom_detectors_proto_rawDescGZIP() []byte {\n\tfile_custom_detectors_proto_rawDescOnce.Do(func() {\n\t\tfile_custom_detectors_proto_rawDescData = protoimpl.X.CompressGZIP(file_custom_detectors_proto_rawDescData)\n\t})\n\treturn file_custom_detectors_proto_rawDescData\n}\n\nvar file_custom_detectors_proto_msgTypes = make([]protoimpl.MessageInfo, 6)\nvar file_custom_detectors_proto_goTypes = []interface{}{\n\t(*CustomDetectors)(nil),  // 0: custom_detectors.CustomDetectors\n\t(*CustomRegex)(nil),      // 1: custom_detectors.CustomRegex\n\t(*VerifierConfig)(nil),   // 2: custom_detectors.VerifierConfig\n\t(*ValidationConfig)(nil), // 3: custom_detectors.ValidationConfig\n\tnil,                      // 4: custom_detectors.CustomRegex.RegexEntry\n\tnil,                      // 5: custom_detectors.CustomRegex.ValidationsEntry\n}\nvar file_custom_detectors_proto_depIdxs = []int32{\n\t1, // 0: custom_detectors.CustomDetectors.detectors:type_name -> custom_detectors.CustomRegex\n\t4, // 1: custom_detectors.CustomRegex.regex:type_name -> custom_detectors.CustomRegex.RegexEntry\n\t2, // 2: custom_detectors.CustomRegex.verify:type_name -> custom_detectors.VerifierConfig\n\t5, // 3: custom_detectors.CustomRegex.validations:type_name -> custom_detectors.CustomRegex.ValidationsEntry\n\t3, // 4: custom_detectors.CustomRegex.ValidationsEntry.value:type_name -> custom_detectors.ValidationConfig\n\t5, // [5:5] is the sub-list for method output_type\n\t5, // [5:5] is the sub-list for method input_type\n\t5, // [5:5] is the sub-list for extension type_name\n\t5, // [5:5] is the sub-list for extension extendee\n\t0, // [0:5] is the sub-list for field type_name\n}\n\nfunc init() { file_custom_detectors_proto_init() }\nfunc file_custom_detectors_proto_init() {\n\tif File_custom_detectors_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_custom_detectors_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CustomDetectors); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_custom_detectors_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CustomRegex); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_custom_detectors_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*VerifierConfig); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_custom_detectors_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ValidationConfig); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_custom_detectors_proto_rawDesc,\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   6,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_custom_detectors_proto_goTypes,\n\t\tDependencyIndexes: file_custom_detectors_proto_depIdxs,\n\t\tMessageInfos:      file_custom_detectors_proto_msgTypes,\n\t}.Build()\n\tFile_custom_detectors_proto = out.File\n\tfile_custom_detectors_proto_rawDesc = nil\n\tfile_custom_detectors_proto_goTypes = nil\n\tfile_custom_detectors_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pkg/pb/custom_detectorspb/custom_detectors.pb.validate.go",
    "content": "// Code generated by protoc-gen-validate. DO NOT EDIT.\n// source: custom_detectors.proto\n\npackage custom_detectorspb\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/mail\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode/utf8\"\n\n\t\"google.golang.org/protobuf/types/known/anypb\"\n)\n\n// ensure the imports are used\nvar (\n\t_ = bytes.MinRead\n\t_ = errors.New(\"\")\n\t_ = fmt.Print\n\t_ = utf8.UTFMax\n\t_ = (*regexp.Regexp)(nil)\n\t_ = (*strings.Reader)(nil)\n\t_ = net.IPv4len\n\t_ = time.Duration(0)\n\t_ = (*url.URL)(nil)\n\t_ = (*mail.Address)(nil)\n\t_ = anypb.Any{}\n\t_ = sort.Sort\n)\n\n// Validate checks the field values on CustomDetectors with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// first error encountered is returned, or nil if there are no violations.\nfunc (m *CustomDetectors) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on CustomDetectors with the rules\n// defined in the proto definition for this message. If any rules are\n// violated, the result is a list of violation errors wrapped in\n// CustomDetectorsMultiError, or nil if none found.\nfunc (m *CustomDetectors) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *CustomDetectors) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tfor idx, item := range m.GetDetectors() {\n\t\t_, _ = idx, item\n\n\t\tif all {\n\t\t\tswitch v := interface{}(item).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, CustomDetectorsValidationError{\n\t\t\t\t\t\tfield:  fmt.Sprintf(\"Detectors[%v]\", idx),\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, CustomDetectorsValidationError{\n\t\t\t\t\t\tfield:  fmt.Sprintf(\"Detectors[%v]\", idx),\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(item).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn CustomDetectorsValidationError{\n\t\t\t\t\tfield:  fmt.Sprintf(\"Detectors[%v]\", idx),\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn CustomDetectorsMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// CustomDetectorsMultiError is an error wrapping multiple validation errors\n// returned by CustomDetectors.ValidateAll() if the designated constraints\n// aren't met.\ntype CustomDetectorsMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m CustomDetectorsMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m CustomDetectorsMultiError) AllErrors() []error { return m }\n\n// CustomDetectorsValidationError is the validation error returned by\n// CustomDetectors.Validate if the designated constraints aren't met.\ntype CustomDetectorsValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e CustomDetectorsValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e CustomDetectorsValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e CustomDetectorsValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e CustomDetectorsValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e CustomDetectorsValidationError) ErrorName() string { return \"CustomDetectorsValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e CustomDetectorsValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sCustomDetectors.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = CustomDetectorsValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = CustomDetectorsValidationError{}\n\n// Validate checks the field values on CustomRegex with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *CustomRegex) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on CustomRegex with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in CustomRegexMultiError, or\n// nil if none found.\nfunc (m *CustomRegex) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *CustomRegex) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Name\n\n\t// no validation rules for Regex\n\n\tfor idx, item := range m.GetVerify() {\n\t\t_, _ = idx, item\n\n\t\tif all {\n\t\t\tswitch v := interface{}(item).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, CustomRegexValidationError{\n\t\t\t\t\t\tfield:  fmt.Sprintf(\"Verify[%v]\", idx),\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, CustomRegexValidationError{\n\t\t\t\t\t\tfield:  fmt.Sprintf(\"Verify[%v]\", idx),\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(item).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn CustomRegexValidationError{\n\t\t\t\t\tfield:  fmt.Sprintf(\"Verify[%v]\", idx),\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\n\t// no validation rules for Description\n\n\t// no validation rules for Entropy\n\n\t// no validation rules for PrimaryRegexName\n\n\t{\n\t\tsorted_keys := make([]string, len(m.GetValidations()))\n\t\ti := 0\n\t\tfor key := range m.GetValidations() {\n\t\t\tsorted_keys[i] = key\n\t\t\ti++\n\t\t}\n\t\tsort.Slice(sorted_keys, func(i, j int) bool { return sorted_keys[i] < sorted_keys[j] })\n\t\tfor _, key := range sorted_keys {\n\t\t\tval := m.GetValidations()[key]\n\t\t\t_ = val\n\n\t\t\t// no validation rules for Validations[key]\n\n\t\t\tif all {\n\t\t\t\tswitch v := interface{}(val).(type) {\n\t\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\t\terrors = append(errors, CustomRegexValidationError{\n\t\t\t\t\t\t\tfield:  fmt.Sprintf(\"Validations[%v]\", key),\n\t\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\tcase interface{ Validate() error }:\n\t\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\t\terrors = append(errors, CustomRegexValidationError{\n\t\t\t\t\t\t\tfield:  fmt.Sprintf(\"Validations[%v]\", key),\n\t\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if v, ok := interface{}(val).(interface{ Validate() error }); ok {\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\treturn CustomRegexValidationError{\n\t\t\t\t\t\tfield:  fmt.Sprintf(\"Validations[%v]\", key),\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn CustomRegexMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// CustomRegexMultiError is an error wrapping multiple validation errors\n// returned by CustomRegex.ValidateAll() if the designated constraints aren't met.\ntype CustomRegexMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m CustomRegexMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m CustomRegexMultiError) AllErrors() []error { return m }\n\n// CustomRegexValidationError is the validation error returned by\n// CustomRegex.Validate if the designated constraints aren't met.\ntype CustomRegexValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e CustomRegexValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e CustomRegexValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e CustomRegexValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e CustomRegexValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e CustomRegexValidationError) ErrorName() string { return \"CustomRegexValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e CustomRegexValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sCustomRegex.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = CustomRegexValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = CustomRegexValidationError{}\n\n// Validate checks the field values on VerifierConfig with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *VerifierConfig) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on VerifierConfig with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in VerifierConfigMultiError,\n// or nil if none found.\nfunc (m *VerifierConfig) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *VerifierConfig) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif _, err := url.Parse(m.GetEndpoint()); err != nil {\n\t\terr = VerifierConfigValidationError{\n\t\t\tfield:  \"Endpoint\",\n\t\t\treason: \"value must be a valid URI\",\n\t\t\tcause:  err,\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\t// no validation rules for Unsafe\n\n\tif len(errors) > 0 {\n\t\treturn VerifierConfigMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// VerifierConfigMultiError is an error wrapping multiple validation errors\n// returned by VerifierConfig.ValidateAll() if the designated constraints\n// aren't met.\ntype VerifierConfigMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m VerifierConfigMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m VerifierConfigMultiError) AllErrors() []error { return m }\n\n// VerifierConfigValidationError is the validation error returned by\n// VerifierConfig.Validate if the designated constraints aren't met.\ntype VerifierConfigValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e VerifierConfigValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e VerifierConfigValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e VerifierConfigValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e VerifierConfigValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e VerifierConfigValidationError) ErrorName() string { return \"VerifierConfigValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e VerifierConfigValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sVerifierConfig.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = VerifierConfigValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = VerifierConfigValidationError{}\n\n// Validate checks the field values on ValidationConfig with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// first error encountered is returned, or nil if there are no violations.\nfunc (m *ValidationConfig) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on ValidationConfig with the rules\n// defined in the proto definition for this message. If any rules are\n// violated, the result is a list of violation errors wrapped in\n// ValidationConfigMultiError, or nil if none found.\nfunc (m *ValidationConfig) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *ValidationConfig) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for ContainsDigit\n\n\t// no validation rules for ContainsLowercase\n\n\t// no validation rules for ContainsUppercase\n\n\t// no validation rules for ContainsSpecialChar\n\n\tif len(errors) > 0 {\n\t\treturn ValidationConfigMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// ValidationConfigMultiError is an error wrapping multiple validation errors\n// returned by ValidationConfig.ValidateAll() if the designated constraints\n// aren't met.\ntype ValidationConfigMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m ValidationConfigMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m ValidationConfigMultiError) AllErrors() []error { return m }\n\n// ValidationConfigValidationError is the validation error returned by\n// ValidationConfig.Validate if the designated constraints aren't met.\ntype ValidationConfigValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e ValidationConfigValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e ValidationConfigValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e ValidationConfigValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e ValidationConfigValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e ValidationConfigValidationError) ErrorName() string { return \"ValidationConfigValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e ValidationConfigValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sValidationConfig.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = ValidationConfigValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = ValidationConfigValidationError{}\n"
  },
  {
    "path": "pkg/pb/detectorspb/detectors.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.33.0\n// \tprotoc        v4.25.3\n// source: detectors.proto\n\npackage detectorspb\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype DecoderType int32\n\nconst (\n\tDecoderType_UNKNOWN         DecoderType = 0\n\tDecoderType_PLAIN           DecoderType = 1\n\tDecoderType_BASE64          DecoderType = 2\n\tDecoderType_UTF16           DecoderType = 3\n\tDecoderType_ESCAPED_UNICODE DecoderType = 4\n)\n\n// Enum value maps for DecoderType.\nvar (\n\tDecoderType_name = map[int32]string{\n\t\t0: \"UNKNOWN\",\n\t\t1: \"PLAIN\",\n\t\t2: \"BASE64\",\n\t\t3: \"UTF16\",\n\t\t4: \"ESCAPED_UNICODE\",\n\t}\n\tDecoderType_value = map[string]int32{\n\t\t\"UNKNOWN\":         0,\n\t\t\"PLAIN\":           1,\n\t\t\"BASE64\":          2,\n\t\t\"UTF16\":           3,\n\t\t\"ESCAPED_UNICODE\": 4,\n\t}\n)\n\nfunc (x DecoderType) Enum() *DecoderType {\n\tp := new(DecoderType)\n\t*p = x\n\treturn p\n}\n\nfunc (x DecoderType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (DecoderType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_detectors_proto_enumTypes[0].Descriptor()\n}\n\nfunc (DecoderType) Type() protoreflect.EnumType {\n\treturn &file_detectors_proto_enumTypes[0]\n}\n\nfunc (x DecoderType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use DecoderType.Descriptor instead.\nfunc (DecoderType) EnumDescriptor() ([]byte, []int) {\n\treturn file_detectors_proto_rawDescGZIP(), []int{0}\n}\n\ntype DetectorType int32\n\nconst (\n\tDetectorType_Alibaba                  DetectorType = 0\n\tDetectorType_AMQP                     DetectorType = 1 // Not yet implemented\n\tDetectorType_AWS                      DetectorType = 2\n\tDetectorType_Azure                    DetectorType = 3\n\tDetectorType_Circle                   DetectorType = 4\n\tDetectorType_Coinbase                 DetectorType = 5\n\tDetectorType_GCP                      DetectorType = 6\n\tDetectorType_Generic                  DetectorType = 7\n\tDetectorType_Github                   DetectorType = 8\n\tDetectorType_Gitlab                   DetectorType = 9\n\tDetectorType_JDBC                     DetectorType = 10\n\tDetectorType_RazorPay                 DetectorType = 11\n\tDetectorType_SendGrid                 DetectorType = 12\n\tDetectorType_Slack                    DetectorType = 13\n\tDetectorType_Square                   DetectorType = 14\n\tDetectorType_PrivateKey               DetectorType = 15\n\tDetectorType_Stripe                   DetectorType = 16\n\tDetectorType_URI                      DetectorType = 17\n\tDetectorType_Dropbox                  DetectorType = 18\n\tDetectorType_Heroku                   DetectorType = 19\n\tDetectorType_Mailchimp                DetectorType = 20\n\tDetectorType_Okta                     DetectorType = 21\n\tDetectorType_OneLogin                 DetectorType = 22\n\tDetectorType_PivotalTracker           DetectorType = 23\n\tDetectorType_SquareApp                DetectorType = 25\n\tDetectorType_Twilio                   DetectorType = 26\n\tDetectorType_Test                     DetectorType = 27\n\tDetectorType_TravisCI                 DetectorType = 29\n\tDetectorType_SlackWebhook             DetectorType = 30\n\tDetectorType_PaypalOauth              DetectorType = 31\n\tDetectorType_PagerDutyApiKey          DetectorType = 32\n\tDetectorType_Firebase                 DetectorType = 33 // Not yet implemented\n\tDetectorType_Mailgun                  DetectorType = 34\n\tDetectorType_HubSpot                  DetectorType = 35\n\tDetectorType_GitHubApp                DetectorType = 36\n\tDetectorType_CircleCI                 DetectorType = 37 // Not yet implemented\n\tDetectorType_WpEngine                 DetectorType = 38 // Not yet implemented\n\tDetectorType_DatadogToken             DetectorType = 39\n\tDetectorType_FacebookOAuth            DetectorType = 40\n\tDetectorType_AsanaPersonalAccessToken DetectorType = 41\n\tDetectorType_AmplitudeApiKey          DetectorType = 42\n\tDetectorType_BitLyAccessToken         DetectorType = 43\n\tDetectorType_CalendlyApiKey           DetectorType = 44\n\tDetectorType_ZapierWebhook            DetectorType = 45\n\tDetectorType_YoutubeApiKey            DetectorType = 46\n\tDetectorType_SalesforceOauth2         DetectorType = 47 // Not yet implemented\n\tDetectorType_TwitterApiSecret         DetectorType = 48 // Not yet implemented\n\tDetectorType_NpmToken                 DetectorType = 49\n\tDetectorType_NewRelicPersonalApiKey   DetectorType = 50\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_AirtableApiKey         DetectorType = 51\n\tDetectorType_AkamaiToken            DetectorType = 52 // Not yet implemented\n\tDetectorType_AmazonMWS              DetectorType = 53 // Not yet implemented\n\tDetectorType_KubeConfig             DetectorType = 54 // Not yet implemented\n\tDetectorType_Auth0oauth             DetectorType = 55\n\tDetectorType_Bitfinex               DetectorType = 56\n\tDetectorType_Clarifai               DetectorType = 57\n\tDetectorType_CloudflareGlobalApiKey DetectorType = 58\n\tDetectorType_CloudflareCaKey        DetectorType = 59\n\tDetectorType_Confluent              DetectorType = 60\n\tDetectorType_ContentfulDelivery     DetectorType = 61 // Not yet implemented\n\tDetectorType_DatabricksToken        DetectorType = 62\n\tDetectorType_DigitalOceanSpaces     DetectorType = 63 // Not yet implemented\n\tDetectorType_DigitalOceanToken      DetectorType = 64\n\tDetectorType_DiscordBotToken        DetectorType = 65\n\tDetectorType_DiscordWebhook         DetectorType = 66\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_EtsyApiKey                    DetectorType = 67\n\tDetectorType_FastlyPersonalToken           DetectorType = 68\n\tDetectorType_GoogleOauth2                  DetectorType = 69 // Not yet implemented\n\tDetectorType_ReCAPTCHA                     DetectorType = 70 // Not yet implemented\n\tDetectorType_GoogleApiKey                  DetectorType = 71 // Not yet implemented\n\tDetectorType_Hunter                        DetectorType = 72\n\tDetectorType_IbmCloudUserKey               DetectorType = 73\n\tDetectorType_Netlify                       DetectorType = 74\n\tDetectorType_Vonage                        DetectorType = 75 // Not yet implemented\n\tDetectorType_EquinixOauth                  DetectorType = 76 // Not yet implemented\n\tDetectorType_Paystack                      DetectorType = 77\n\tDetectorType_PlaidToken                    DetectorType = 78 // Not yet implemented\n\tDetectorType_PlaidKey                      DetectorType = 79\n\tDetectorType_Plivo                         DetectorType = 80\n\tDetectorType_Postmark                      DetectorType = 81\n\tDetectorType_PubNubPublishKey              DetectorType = 82\n\tDetectorType_PubNubSubscriptionKey         DetectorType = 83\n\tDetectorType_PusherChannelKey              DetectorType = 84\n\tDetectorType_ScalewayKey                   DetectorType = 85\n\tDetectorType_SendinBlueV2                  DetectorType = 86\n\tDetectorType_SentryToken                   DetectorType = 87\n\tDetectorType_ShodanKey                     DetectorType = 88\n\tDetectorType_SnykKey                       DetectorType = 89\n\tDetectorType_SpotifyKey                    DetectorType = 90\n\tDetectorType_TelegramBotToken              DetectorType = 91\n\tDetectorType_TencentCloudKey               DetectorType = 92 // Not yet implemented\n\tDetectorType_TerraformCloudPersonalToken   DetectorType = 93\n\tDetectorType_TrelloApiKey                  DetectorType = 94\n\tDetectorType_ZendeskApi                    DetectorType = 95\n\tDetectorType_MaxMindLicense                DetectorType = 96\n\tDetectorType_AirtableMetadataApiKey        DetectorType = 97 // Not yet implemented\n\tDetectorType_AsanaOauth                    DetectorType = 98\n\tDetectorType_RapidApi                      DetectorType = 99\n\tDetectorType_CloudflareApiToken            DetectorType = 100\n\tDetectorType_Webex                         DetectorType = 101\n\tDetectorType_FirebaseCloudMessaging        DetectorType = 102 // Not yet implemented\n\tDetectorType_ContentfulPersonalAccessToken DetectorType = 103\n\tDetectorType_MapBox                        DetectorType = 104\n\tDetectorType_MailJetBasicAuth              DetectorType = 105\n\tDetectorType_MailJetSMS                    DetectorType = 106\n\tDetectorType_HubSpotApiKey                 DetectorType = 107\n\tDetectorType_HubSpotOauth                  DetectorType = 108 // Not yet implemented\n\tDetectorType_SslMate                       DetectorType = 109\n\tDetectorType_Auth0ManagementApiToken       DetectorType = 110\n\tDetectorType_MessageBird                   DetectorType = 111\n\tDetectorType_ElasticEmail                  DetectorType = 112\n\tDetectorType_FigmaPersonalAccessToken      DetectorType = 113\n\tDetectorType_MicrosoftTeamsWebhook         DetectorType = 114\n\tDetectorType_GitHubOld                     DetectorType = 115 // Not yet implemented\n\tDetectorType_VultrApiKey                   DetectorType = 116\n\tDetectorType_Pepipost                      DetectorType = 117\n\tDetectorType_Postman                       DetectorType = 118\n\tDetectorType_CloudsightKey                 DetectorType = 119 // Not yet implemented\n\tDetectorType_JiraToken                     DetectorType = 120\n\tDetectorType_NexmoApiKey                   DetectorType = 121\n\tDetectorType_SegmentApiKey                 DetectorType = 122\n\tDetectorType_SumoLogicKey                  DetectorType = 123\n\tDetectorType_PushBulletApiKey              DetectorType = 124\n\tDetectorType_AirbrakeProjectKey            DetectorType = 125\n\tDetectorType_AirbrakeUserKey               DetectorType = 126\n\tDetectorType_PendoIntegrationKey           DetectorType = 127 // Not yet implemented\n\tDetectorType_SplunkOberservabilityToken    DetectorType = 128\n\tDetectorType_LokaliseToken                 DetectorType = 129\n\tDetectorType_Calendarific                  DetectorType = 130\n\tDetectorType_Jumpcloud                     DetectorType = 131\n\tDetectorType_IpStack                       DetectorType = 133\n\tDetectorType_Notion                        DetectorType = 134\n\tDetectorType_DroneCI                       DetectorType = 135\n\tDetectorType_AdobeIO                       DetectorType = 136\n\tDetectorType_TwelveData                    DetectorType = 137\n\tDetectorType_D7Network                     DetectorType = 138\n\tDetectorType_ScrapingBee                   DetectorType = 139\n\tDetectorType_KeenIO                        DetectorType = 140\n\tDetectorType_Wakatime                      DetectorType = 141 // Not yet implemented\n\tDetectorType_Buildkite                     DetectorType = 142\n\tDetectorType_Verimail                      DetectorType = 143\n\tDetectorType_Zerobounce                    DetectorType = 144\n\tDetectorType_Mailboxlayer                  DetectorType = 145\n\tDetectorType_Fastspring                    DetectorType = 146 // Not yet implemented\n\tDetectorType_Paddle                        DetectorType = 147 // Not yet implemented\n\tDetectorType_Sellfy                        DetectorType = 148 // Not yet implemented\n\tDetectorType_FixerIO                       DetectorType = 149\n\tDetectorType_ButterCMS                     DetectorType = 150\n\tDetectorType_Taxjar                        DetectorType = 151\n\tDetectorType_Avalara                       DetectorType = 152 // Not yet implemented\n\tDetectorType_Helpscout                     DetectorType = 153\n\tDetectorType_ElasticPath                   DetectorType = 154 // Not yet implemented\n\tDetectorType_Zeplin                        DetectorType = 155\n\tDetectorType_Intercom                      DetectorType = 156\n\tDetectorType_Mailmodo                      DetectorType = 157\n\tDetectorType_CannyIo                       DetectorType = 158\n\tDetectorType_Pipedrive                     DetectorType = 159\n\tDetectorType_Vercel                        DetectorType = 160\n\tDetectorType_PosthogApp                    DetectorType = 161\n\tDetectorType_SinchMessage                  DetectorType = 162\n\tDetectorType_Ayrshare                      DetectorType = 163\n\tDetectorType_HelpCrunch                    DetectorType = 164\n\tDetectorType_LiveAgent                     DetectorType = 165\n\tDetectorType_Beamer                        DetectorType = 166\n\tDetectorType_WeChatAppKey                  DetectorType = 167 // Not yet implemented\n\tDetectorType_LineMessaging                 DetectorType = 168\n\tDetectorType_UberServerToken               DetectorType = 169 // Not yet implemented\n\tDetectorType_AlgoliaAdminKey               DetectorType = 170\n\tDetectorType_FullContact                   DetectorType = 171 // Not yet implemented\n\tDetectorType_Mandrill                      DetectorType = 172\n\tDetectorType_Flutterwave                   DetectorType = 173\n\tDetectorType_MattermostPersonalToken       DetectorType = 174\n\tDetectorType_Cloudant                      DetectorType = 175 // Not yet implemented\n\tDetectorType_LineNotify                    DetectorType = 176\n\tDetectorType_LinearAPI                     DetectorType = 177\n\tDetectorType_Ubidots                       DetectorType = 178\n\tDetectorType_Anypoint                      DetectorType = 179\n\tDetectorType_Dwolla                        DetectorType = 180\n\tDetectorType_ArtifactoryAccessToken        DetectorType = 181\n\tDetectorType_Surge                         DetectorType = 182 // Not yet implemented\n\tDetectorType_Sparkpost                     DetectorType = 183\n\tDetectorType_GoCardless                    DetectorType = 184\n\tDetectorType_Codacy                        DetectorType = 185\n\tDetectorType_Kraken                        DetectorType = 186\n\tDetectorType_Checkout                      DetectorType = 187\n\tDetectorType_Kairos                        DetectorType = 188 // Not yet implemented\n\tDetectorType_ClockworkSMS                  DetectorType = 189\n\tDetectorType_Atlassian                     DetectorType = 190 // Not yet implemented\n\tDetectorType_LaunchDarkly                  DetectorType = 191\n\tDetectorType_Coveralls                     DetectorType = 192\n\tDetectorType_Linode                        DetectorType = 193 // Not yet implemented\n\tDetectorType_WePay                         DetectorType = 194\n\tDetectorType_PlanetScale                   DetectorType = 195\n\tDetectorType_Doppler                       DetectorType = 196\n\tDetectorType_Agora                         DetectorType = 197\n\tDetectorType_Samsara                       DetectorType = 198 // Not yet implemented\n\tDetectorType_FrameIO                       DetectorType = 199\n\tDetectorType_RubyGems                      DetectorType = 200\n\tDetectorType_OpenAI                        DetectorType = 201\n\tDetectorType_SurveySparrow                 DetectorType = 202\n\tDetectorType_Simvoly                       DetectorType = 203\n\tDetectorType_Survicate                     DetectorType = 204\n\tDetectorType_Omnisend                      DetectorType = 205\n\tDetectorType_Groovehq                      DetectorType = 206\n\tDetectorType_Newsapi                       DetectorType = 207\n\tDetectorType_Chatbot                       DetectorType = 208\n\tDetectorType_ClickSendsms                  DetectorType = 209\n\tDetectorType_Getgist                       DetectorType = 210\n\tDetectorType_CustomerIO                    DetectorType = 211\n\tDetectorType_ApiDeck                       DetectorType = 212\n\tDetectorType_Nftport                       DetectorType = 213\n\tDetectorType_Copper                        DetectorType = 214\n\tDetectorType_Close                         DetectorType = 215\n\tDetectorType_Myfreshworks                  DetectorType = 216\n\tDetectorType_Salesflare                    DetectorType = 217\n\tDetectorType_Webflow                       DetectorType = 218\n\tDetectorType_Duda                          DetectorType = 219 // Not yet implemented\n\tDetectorType_Yext                          DetectorType = 220 // Not yet implemented\n\tDetectorType_ContentStack                  DetectorType = 221 // Not yet implemented\n\tDetectorType_StoryblokAccessToken          DetectorType = 222\n\tDetectorType_GraphCMS                      DetectorType = 223\n\tDetectorType_Checkmarket                   DetectorType = 224 // Not yet implemented\n\tDetectorType_Convertkit                    DetectorType = 225\n\tDetectorType_CustomerGuru                  DetectorType = 226\n\tDetectorType_Kaleyra                       DetectorType = 227 // Not yet implemented\n\tDetectorType_Mailerlite                    DetectorType = 228\n\tDetectorType_Qualaroo                      DetectorType = 229\n\tDetectorType_SatismeterProjectkey          DetectorType = 230\n\tDetectorType_SatismeterWritekey            DetectorType = 231\n\tDetectorType_Simplesat                     DetectorType = 232\n\tDetectorType_SurveyAnyplace                DetectorType = 233\n\tDetectorType_SurveyBot                     DetectorType = 234\n\tDetectorType_Webengage                     DetectorType = 235 // Not yet implemented\n\tDetectorType_ZonkaFeedback                 DetectorType = 236\n\tDetectorType_Delighted                     DetectorType = 237\n\tDetectorType_Feedier                       DetectorType = 238\n\tDetectorType_Abyssale                      DetectorType = 239\n\tDetectorType_Magnetic                      DetectorType = 240\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Nytimes DetectorType = 241\n\tDetectorType_Polygon DetectorType = 242\n\tDetectorType_Powrbot DetectorType = 243\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_ProspectIO  DetectorType = 244\n\tDetectorType_Skrappio    DetectorType = 245\n\tDetectorType_Monday      DetectorType = 246\n\tDetectorType_Smartsheets DetectorType = 247\n\tDetectorType_Wrike       DetectorType = 248\n\tDetectorType_Float       DetectorType = 249\n\tDetectorType_Imagekit    DetectorType = 250\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Integromat          DetectorType = 251\n\tDetectorType_Salesblink          DetectorType = 252\n\tDetectorType_Bored               DetectorType = 253 // Not yet implemented\n\tDetectorType_Campayn             DetectorType = 254\n\tDetectorType_Clinchpad           DetectorType = 255\n\tDetectorType_CompanyHub          DetectorType = 256\n\tDetectorType_Debounce            DetectorType = 257\n\tDetectorType_Dyspatch            DetectorType = 258\n\tDetectorType_Guardianapi         DetectorType = 259\n\tDetectorType_Harvest             DetectorType = 260\n\tDetectorType_Moosend             DetectorType = 261\n\tDetectorType_OpenWeather         DetectorType = 262\n\tDetectorType_Siteleaf            DetectorType = 263\n\tDetectorType_Squarespace         DetectorType = 264\n\tDetectorType_FlowFlu             DetectorType = 265\n\tDetectorType_Nimble              DetectorType = 266\n\tDetectorType_LessAnnoyingCRM     DetectorType = 267\n\tDetectorType_Nethunt             DetectorType = 268\n\tDetectorType_Apptivo             DetectorType = 269\n\tDetectorType_CapsuleCRM          DetectorType = 270\n\tDetectorType_Insightly           DetectorType = 271\n\tDetectorType_Kylas               DetectorType = 272\n\tDetectorType_OnepageCRM          DetectorType = 273\n\tDetectorType_User                DetectorType = 274\n\tDetectorType_ProspectCRM         DetectorType = 275\n\tDetectorType_ReallySimpleSystems DetectorType = 276\n\tDetectorType_Airship             DetectorType = 277\n\tDetectorType_Artsy               DetectorType = 278\n\tDetectorType_Yandex              DetectorType = 279\n\tDetectorType_Clockify            DetectorType = 280\n\tDetectorType_Dnscheck            DetectorType = 281\n\tDetectorType_EasyInsight         DetectorType = 282\n\tDetectorType_Ethplorer           DetectorType = 283\n\tDetectorType_Everhour            DetectorType = 284\n\tDetectorType_Fulcrum             DetectorType = 285\n\tDetectorType_GeoIpifi            DetectorType = 286\n\tDetectorType_Jotform             DetectorType = 287\n\tDetectorType_Refiner             DetectorType = 288\n\tDetectorType_Timezoneapi         DetectorType = 289\n\tDetectorType_TogglTrack          DetectorType = 290\n\tDetectorType_Vpnapi              DetectorType = 291\n\tDetectorType_Workstack           DetectorType = 292\n\tDetectorType_Apollo              DetectorType = 293\n\tDetectorType_Eversign            DetectorType = 294 // Not yet implemented\n\tDetectorType_Juro                DetectorType = 295\n\tDetectorType_KarmaCRM            DetectorType = 296\n\tDetectorType_Metrilo             DetectorType = 297\n\tDetectorType_Pandadoc            DetectorType = 298\n\tDetectorType_RevampCRM           DetectorType = 299\n\tDetectorType_Salescookie         DetectorType = 300\n\tDetectorType_Alconost            DetectorType = 301\n\tDetectorType_Blogger             DetectorType = 302\n\tDetectorType_Accuweather         DetectorType = 303\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Opengraphr         DetectorType = 304\n\tDetectorType_Rawg               DetectorType = 305\n\tDetectorType_Riotgames          DetectorType = 306 // Not yet implemented\n\tDetectorType_Clientary          DetectorType = 307\n\tDetectorType_Stormglass         DetectorType = 308\n\tDetectorType_Tomtom             DetectorType = 309\n\tDetectorType_Twitch             DetectorType = 310\n\tDetectorType_Documo             DetectorType = 311\n\tDetectorType_Cloudways          DetectorType = 312 // Not yet implemented\n\tDetectorType_Veevavault         DetectorType = 313 // Not yet implemented\n\tDetectorType_KiteConnect        DetectorType = 314 // Not yet implemented\n\tDetectorType_ShopeeOpenPlatform DetectorType = 315 // Not yet implemented\n\tDetectorType_TeamViewer         DetectorType = 316 // Not yet implemented\n\tDetectorType_Bulbul             DetectorType = 317\n\tDetectorType_CentralStationCRM  DetectorType = 318\n\tDetectorType_Teamgate           DetectorType = 319\n\tDetectorType_Axonaut            DetectorType = 320\n\tDetectorType_Tyntec             DetectorType = 321\n\tDetectorType_Appcues            DetectorType = 322\n\tDetectorType_Autoklose          DetectorType = 323\n\tDetectorType_Cloudplan          DetectorType = 324\n\tDetectorType_Dotdigital         DetectorType = 325\n\tDetectorType_GetEmail           DetectorType = 326\n\tDetectorType_GetEmails          DetectorType = 327\n\tDetectorType_Kontent            DetectorType = 328\n\tDetectorType_Leadfeeder         DetectorType = 329\n\tDetectorType_Raven              DetectorType = 330\n\tDetectorType_RocketReach        DetectorType = 331\n\tDetectorType_Uplead             DetectorType = 332\n\tDetectorType_Brandfetch         DetectorType = 333\n\tDetectorType_Clearbit           DetectorType = 334\n\tDetectorType_Crowdin            DetectorType = 335\n\tDetectorType_Mapquest           DetectorType = 336\n\tDetectorType_Noticeable         DetectorType = 337\n\tDetectorType_Onbuka             DetectorType = 338 // Not yet implemented\n\tDetectorType_Todoist            DetectorType = 339\n\tDetectorType_Storychief         DetectorType = 340\n\tDetectorType_LinkedIn           DetectorType = 341 // Not yet implemented\n\tDetectorType_YouSign            DetectorType = 342\n\tDetectorType_Docker             DetectorType = 343\n\tDetectorType_Telesign           DetectorType = 344 // Not yet implemented\n\tDetectorType_Spoonacular        DetectorType = 345\n\tDetectorType_Aerisweather       DetectorType = 346 // Not yet implemented\n\tDetectorType_Alphavantage       DetectorType = 347 // Not yet implemented\n\tDetectorType_Imgur              DetectorType = 348 // Not yet implemented\n\tDetectorType_Imagga             DetectorType = 349\n\tDetectorType_SMSApi             DetectorType = 350 // Not yet implemented\n\tDetectorType_Distribusion       DetectorType = 351 // Not yet implemented\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Blablabus            DetectorType = 352\n\tDetectorType_WordsApi             DetectorType = 353 // Not yet implemented\n\tDetectorType_Currencylayer        DetectorType = 354\n\tDetectorType_Html2Pdf             DetectorType = 355\n\tDetectorType_IPGeolocation        DetectorType = 356\n\tDetectorType_Owlbot               DetectorType = 357\n\tDetectorType_Cloudmersive         DetectorType = 358\n\tDetectorType_Dynalist             DetectorType = 359\n\tDetectorType_ExchangeRateAPI      DetectorType = 360\n\tDetectorType_HolidayAPI           DetectorType = 361\n\tDetectorType_Ipapi                DetectorType = 362\n\tDetectorType_Marketstack          DetectorType = 363\n\tDetectorType_Nutritionix          DetectorType = 364\n\tDetectorType_Swell                DetectorType = 365\n\tDetectorType_ClickupPersonalToken DetectorType = 366\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Nitro                 DetectorType = 367\n\tDetectorType_Rev                   DetectorType = 368\n\tDetectorType_RunRunIt              DetectorType = 369\n\tDetectorType_Typeform              DetectorType = 370\n\tDetectorType_Mixpanel              DetectorType = 371\n\tDetectorType_Tradier               DetectorType = 372\n\tDetectorType_Verifier              DetectorType = 373\n\tDetectorType_Vouchery              DetectorType = 374\n\tDetectorType_Alegra                DetectorType = 375\n\tDetectorType_Audd                  DetectorType = 376\n\tDetectorType_Baremetrics           DetectorType = 377\n\tDetectorType_Coinlib               DetectorType = 378\n\tDetectorType_ExchangeRatesAPI      DetectorType = 379\n\tDetectorType_CurrencyScoop         DetectorType = 380\n\tDetectorType_FXMarket              DetectorType = 381\n\tDetectorType_CurrencyCloud         DetectorType = 382\n\tDetectorType_GetGeoAPI             DetectorType = 383\n\tDetectorType_Abstract              DetectorType = 384\n\tDetectorType_Billomat              DetectorType = 385\n\tDetectorType_Dovico                DetectorType = 386\n\tDetectorType_Bitbar                DetectorType = 387\n\tDetectorType_Bugsnag               DetectorType = 388\n\tDetectorType_AssemblyAI            DetectorType = 389\n\tDetectorType_AdafruitIO            DetectorType = 390\n\tDetectorType_Apify                 DetectorType = 391\n\tDetectorType_CoinGecko             DetectorType = 392 // Not yet implemented\n\tDetectorType_CryptoCompare         DetectorType = 393\n\tDetectorType_Fullstory             DetectorType = 394\n\tDetectorType_HelloSign             DetectorType = 395\n\tDetectorType_Loyverse              DetectorType = 396\n\tDetectorType_NetCore               DetectorType = 397 // Not yet implemented\n\tDetectorType_SauceLabs             DetectorType = 398\n\tDetectorType_AlienVault            DetectorType = 399\n\tDetectorType_Apiflash              DetectorType = 401\n\tDetectorType_Coinlayer             DetectorType = 402\n\tDetectorType_CurrentsAPI           DetectorType = 403\n\tDetectorType_DataGov               DetectorType = 404\n\tDetectorType_Enigma                DetectorType = 405\n\tDetectorType_FinancialModelingPrep DetectorType = 406\n\tDetectorType_Geocodio              DetectorType = 407\n\tDetectorType_HereAPI               DetectorType = 408\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Macaddress     DetectorType = 409\n\tDetectorType_OOPSpam        DetectorType = 410\n\tDetectorType_ProtocolsIO    DetectorType = 411\n\tDetectorType_ScraperAPI     DetectorType = 412\n\tDetectorType_SecurityTrails DetectorType = 413\n\tDetectorType_TomorrowIO     DetectorType = 414\n\tDetectorType_WorldCoinIndex DetectorType = 415\n\tDetectorType_FacePlusPlus   DetectorType = 416\n\tDetectorType_Voicegain      DetectorType = 417\n\tDetectorType_Deepgram       DetectorType = 418\n\tDetectorType_VisualCrossing DetectorType = 419\n\tDetectorType_Finnhub        DetectorType = 420\n\tDetectorType_Tiingo         DetectorType = 421\n\tDetectorType_RingCentral    DetectorType = 422\n\tDetectorType_Finage         DetectorType = 423\n\tDetectorType_Edamam         DetectorType = 424\n\tDetectorType_HypeAuditor    DetectorType = 425 // Not yet implemented\n\tDetectorType_Gengo          DetectorType = 426\n\tDetectorType_Front          DetectorType = 427\n\tDetectorType_Fleetbase      DetectorType = 428\n\tDetectorType_Bubble         DetectorType = 429 // Not yet implemented\n\tDetectorType_Bannerbear     DetectorType = 430\n\tDetectorType_Adzuna         DetectorType = 431\n\tDetectorType_BitcoinAverage DetectorType = 432\n\tDetectorType_CommerceJS     DetectorType = 433\n\tDetectorType_DetectLanguage DetectorType = 434\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_FakeJSON    DetectorType = 435\n\tDetectorType_Graphhopper DetectorType = 436\n\tDetectorType_Lexigram    DetectorType = 437\n\tDetectorType_LinkPreview DetectorType = 438\n\tDetectorType_Numverify   DetectorType = 439\n\tDetectorType_ProxyCrawl  DetectorType = 440\n\tDetectorType_ZipCodeAPI  DetectorType = 441\n\tDetectorType_Cometchat   DetectorType = 442 // Not yet implemented\n\tDetectorType_Keygen      DetectorType = 443 // Not yet implemented\n\tDetectorType_Mixcloud    DetectorType = 444 // Not yet implemented\n\tDetectorType_TatumIO     DetectorType = 445\n\tDetectorType_Tmetric     DetectorType = 446\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Lastfm        DetectorType = 447\n\tDetectorType_Browshot      DetectorType = 448\n\tDetectorType_JSONbin       DetectorType = 449 // Not yet implemented\n\tDetectorType_LocationIQ    DetectorType = 450\n\tDetectorType_ScreenshotAPI DetectorType = 451\n\tDetectorType_WeatherStack  DetectorType = 452\n\tDetectorType_Amadeus       DetectorType = 453\n\tDetectorType_FourSquare    DetectorType = 454\n\tDetectorType_Flickr        DetectorType = 455\n\tDetectorType_ClickHelp     DetectorType = 456\n\tDetectorType_Ambee         DetectorType = 457\n\tDetectorType_Api2Cart      DetectorType = 458\n\tDetectorType_Hypertrack    DetectorType = 459\n\tDetectorType_KakaoTalk     DetectorType = 460 // Not yet implemented\n\tDetectorType_RiteKit       DetectorType = 461\n\tDetectorType_Shutterstock  DetectorType = 462\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Text2Data      DetectorType = 463\n\tDetectorType_YouNeedABudget DetectorType = 464\n\tDetectorType_Cricket        DetectorType = 465 // Not yet implemented\n\tDetectorType_Filestack      DetectorType = 466 // Not yet implemented\n\tDetectorType_Gyazo          DetectorType = 467\n\tDetectorType_Mavenlink      DetectorType = 468\n\tDetectorType_Sheety         DetectorType = 469\n\tDetectorType_Sportsmonk     DetectorType = 470\n\tDetectorType_Stockdata      DetectorType = 471\n\tDetectorType_Unsplash       DetectorType = 472\n\tDetectorType_Allsports      DetectorType = 473\n\tDetectorType_CalorieNinja   DetectorType = 474\n\tDetectorType_WalkScore      DetectorType = 475\n\tDetectorType_Strava         DetectorType = 476\n\tDetectorType_Cicero         DetectorType = 477\n\tDetectorType_IPQuality      DetectorType = 478\n\tDetectorType_ParallelDots   DetectorType = 479\n\tDetectorType_Roaring        DetectorType = 480\n\tDetectorType_Mailsac        DetectorType = 481\n\tDetectorType_Whoxy          DetectorType = 482\n\tDetectorType_WorldWeather   DetectorType = 483\n\tDetectorType_ApiFonica      DetectorType = 484\n\tDetectorType_Aylien         DetectorType = 485\n\tDetectorType_Geocode        DetectorType = 486\n\tDetectorType_IconFinder     DetectorType = 487\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Ipify         DetectorType = 488\n\tDetectorType_LanguageLayer DetectorType = 489\n\tDetectorType_Lob           DetectorType = 490\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_OnWaterIO             DetectorType = 491\n\tDetectorType_Pastebin              DetectorType = 492\n\tDetectorType_PdfLayer              DetectorType = 493\n\tDetectorType_Pixabay               DetectorType = 494\n\tDetectorType_ReadMe                DetectorType = 495\n\tDetectorType_VatLayer              DetectorType = 496\n\tDetectorType_VirusTotal            DetectorType = 497\n\tDetectorType_AirVisual             DetectorType = 498\n\tDetectorType_Currencyfreaks        DetectorType = 499\n\tDetectorType_Duffel                DetectorType = 500 // Not yet implemented\n\tDetectorType_FlatIO                DetectorType = 501\n\tDetectorType_M3o                   DetectorType = 502\n\tDetectorType_Mesibo                DetectorType = 503\n\tDetectorType_Openuv                DetectorType = 504\n\tDetectorType_Snipcart              DetectorType = 505\n\tDetectorType_Besttime              DetectorType = 506\n\tDetectorType_Happyscribe           DetectorType = 507\n\tDetectorType_Humanity              DetectorType = 508\n\tDetectorType_Impala                DetectorType = 509\n\tDetectorType_Loginradius           DetectorType = 510\n\tDetectorType_AutoPilot             DetectorType = 511\n\tDetectorType_Bitmex                DetectorType = 512\n\tDetectorType_ClustDoc              DetectorType = 513\n\tDetectorType_Messari               DetectorType = 514 // Not yet implemented\n\tDetectorType_PdfShift              DetectorType = 515\n\tDetectorType_Poloniex              DetectorType = 516\n\tDetectorType_RestpackHtmlToPdfAPI  DetectorType = 517\n\tDetectorType_RestpackScreenshotAPI DetectorType = 518\n\tDetectorType_ShutterstockOAuth     DetectorType = 519\n\tDetectorType_SkyBiometry           DetectorType = 520\n\tDetectorType_AbuseIPDB             DetectorType = 521\n\tDetectorType_AletheiaApi           DetectorType = 522\n\tDetectorType_BlitApp               DetectorType = 523\n\tDetectorType_Censys                DetectorType = 524\n\tDetectorType_Cloverly              DetectorType = 525\n\tDetectorType_CountryLayer          DetectorType = 526\n\tDetectorType_FileIO                DetectorType = 527\n\tDetectorType_FlightApi             DetectorType = 528\n\tDetectorType_Geoapify              DetectorType = 529\n\tDetectorType_IPinfoDB              DetectorType = 530\n\tDetectorType_MediaStack            DetectorType = 531\n\tDetectorType_NasdaqDataLink        DetectorType = 532\n\tDetectorType_OpenCageData          DetectorType = 533\n\tDetectorType_Paymongo              DetectorType = 534\n\tDetectorType_PositionStack         DetectorType = 535\n\tDetectorType_Rebrandly             DetectorType = 536\n\tDetectorType_ScreenshotLayer       DetectorType = 537\n\tDetectorType_Stytch                DetectorType = 538\n\tDetectorType_Unplugg               DetectorType = 539\n\tDetectorType_UPCDatabase           DetectorType = 540\n\tDetectorType_UserStack             DetectorType = 541\n\tDetectorType_Geocodify             DetectorType = 542\n\tDetectorType_Newscatcher           DetectorType = 543\n\tDetectorType_Nicereply             DetectorType = 544\n\tDetectorType_Partnerstack          DetectorType = 545\n\tDetectorType_Route4me              DetectorType = 546\n\tDetectorType_Scrapeowl             DetectorType = 547\n\tDetectorType_ScrapingDog           DetectorType = 548 // Not yet implemented\n\tDetectorType_Streak                DetectorType = 549\n\tDetectorType_Veriphone             DetectorType = 550\n\tDetectorType_Webscraping           DetectorType = 551\n\tDetectorType_Zenscrape             DetectorType = 552\n\tDetectorType_Zenserp               DetectorType = 553\n\tDetectorType_CoinApi               DetectorType = 554\n\tDetectorType_Gitter                DetectorType = 555\n\tDetectorType_Host                  DetectorType = 556\n\tDetectorType_Iexcloud              DetectorType = 557\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Restpack      DetectorType = 558\n\tDetectorType_ScraperBox    DetectorType = 559\n\tDetectorType_ScrapingAnt   DetectorType = 560\n\tDetectorType_SerpStack     DetectorType = 561\n\tDetectorType_SmartyStreets DetectorType = 562\n\tDetectorType_TicketMaster  DetectorType = 563\n\tDetectorType_AviationStack DetectorType = 564\n\tDetectorType_BombBomb      DetectorType = 565\n\tDetectorType_Commodities   DetectorType = 566\n\tDetectorType_Dfuse         DetectorType = 567\n\tDetectorType_EdenAI        DetectorType = 568\n\tDetectorType_Glassnode     DetectorType = 569\n\tDetectorType_Guru          DetectorType = 570\n\tDetectorType_Hive          DetectorType = 571\n\tDetectorType_Hiveage       DetectorType = 572\n\tDetectorType_Kickbox       DetectorType = 573\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Passbase        DetectorType = 574\n\tDetectorType_PostageApp      DetectorType = 575\n\tDetectorType_PureStake       DetectorType = 576\n\tDetectorType_Qubole          DetectorType = 577\n\tDetectorType_CarbonInterface DetectorType = 578\n\tDetectorType_Intrinio        DetectorType = 579\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_QuickMetrics         DetectorType = 580\n\tDetectorType_ScrapeStack          DetectorType = 581\n\tDetectorType_TechnicalAnalysisApi DetectorType = 582\n\tDetectorType_Urlscan              DetectorType = 583\n\tDetectorType_BaseApiIO            DetectorType = 584 // Not yet implemented\n\tDetectorType_DailyCO              DetectorType = 585\n\tDetectorType_TLy                  DetectorType = 586\n\tDetectorType_Shortcut             DetectorType = 587\n\tDetectorType_Appfollow            DetectorType = 588\n\tDetectorType_Thinkific            DetectorType = 589\n\tDetectorType_Feedly               DetectorType = 590 // Not yet implemented\n\tDetectorType_Stitchdata           DetectorType = 591\n\tDetectorType_Fetchrss             DetectorType = 592\n\tDetectorType_Signupgenius         DetectorType = 593\n\tDetectorType_Signaturit           DetectorType = 594\n\tDetectorType_Optimizely           DetectorType = 595\n\tDetectorType_OcrSpace             DetectorType = 596 // Not yet implemented\n\tDetectorType_WeatherBit           DetectorType = 597\n\tDetectorType_BuddyNS              DetectorType = 598\n\tDetectorType_ZipAPI               DetectorType = 599\n\tDetectorType_ZipBooks             DetectorType = 600\n\tDetectorType_Onedesk              DetectorType = 601\n\tDetectorType_Bugherd              DetectorType = 602\n\tDetectorType_Blazemeter           DetectorType = 603\n\tDetectorType_Autodesk             DetectorType = 604\n\tDetectorType_Tru                  DetectorType = 605\n\tDetectorType_UnifyID              DetectorType = 606\n\tDetectorType_Trimble              DetectorType = 607 // Not yet implemented\n\tDetectorType_Smooch               DetectorType = 608\n\tDetectorType_Semaphore            DetectorType = 609\n\tDetectorType_Telnyx               DetectorType = 610\n\tDetectorType_Signalwire           DetectorType = 611\n\tDetectorType_Textmagic            DetectorType = 612\n\tDetectorType_Serphouse            DetectorType = 613\n\tDetectorType_Planyo               DetectorType = 614\n\tDetectorType_Simplybook           DetectorType = 615 // Not yet implemented\n\tDetectorType_Vyte                 DetectorType = 616\n\tDetectorType_Nylas                DetectorType = 617\n\tDetectorType_Squareup             DetectorType = 618\n\tDetectorType_Dandelion            DetectorType = 619\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_DataFire     DetectorType = 620\n\tDetectorType_DeepAI       DetectorType = 621\n\tDetectorType_MeaningCloud DetectorType = 622\n\tDetectorType_NeutrinoApi  DetectorType = 623\n\tDetectorType_Storecove    DetectorType = 624\n\tDetectorType_Shipday      DetectorType = 625\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Sentiment           DetectorType = 626\n\tDetectorType_StreamChatMessaging DetectorType = 627 // Not yet implemented\n\tDetectorType_TeamworkCRM         DetectorType = 628\n\tDetectorType_TeamworkDesk        DetectorType = 629\n\tDetectorType_TeamworkSpaces      DetectorType = 630\n\tDetectorType_TheOddsApi          DetectorType = 631\n\tDetectorType_Apacta              DetectorType = 632\n\tDetectorType_GetSandbox          DetectorType = 633\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Happi            DetectorType = 634\n\tDetectorType_Oanda            DetectorType = 635\n\tDetectorType_FastForex        DetectorType = 636\n\tDetectorType_APIMatic         DetectorType = 637\n\tDetectorType_VersionEye       DetectorType = 638\n\tDetectorType_EagleEyeNetworks DetectorType = 639\n\tDetectorType_ThousandEyes     DetectorType = 640\n\tDetectorType_SelectPDF        DetectorType = 641\n\tDetectorType_Flightstats      DetectorType = 642\n\tDetectorType_ChecIO           DetectorType = 643\n\tDetectorType_Manifest         DetectorType = 644\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_ApiScience     DetectorType = 645\n\tDetectorType_AppSynergy     DetectorType = 646\n\tDetectorType_Caflou         DetectorType = 647\n\tDetectorType_Caspio         DetectorType = 648\n\tDetectorType_ChecklyHQ      DetectorType = 649\n\tDetectorType_CloudElements  DetectorType = 650\n\tDetectorType_DronaHQ        DetectorType = 651\n\tDetectorType_Enablex        DetectorType = 652\n\tDetectorType_Fmfw           DetectorType = 653\n\tDetectorType_GoodDay        DetectorType = 654\n\tDetectorType_Luno           DetectorType = 655\n\tDetectorType_Meistertask    DetectorType = 656\n\tDetectorType_Mindmeister    DetectorType = 657\n\tDetectorType_PeopleDataLabs DetectorType = 658\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_ScraperSite   DetectorType = 659\n\tDetectorType_Scrapfly      DetectorType = 660\n\tDetectorType_SimplyNoted   DetectorType = 661\n\tDetectorType_TravelPayouts DetectorType = 662\n\tDetectorType_WebScraper    DetectorType = 663\n\tDetectorType_Convier       DetectorType = 664\n\tDetectorType_Courier       DetectorType = 665\n\tDetectorType_Ditto         DetectorType = 666\n\tDetectorType_Findl         DetectorType = 667\n\tDetectorType_Lendflow      DetectorType = 668\n\tDetectorType_Moderation    DetectorType = 669\n\tDetectorType_Opendatasoft  DetectorType = 670 // Not yet implemented\n\tDetectorType_Podio         DetectorType = 671\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Rockset      DetectorType = 672\n\tDetectorType_Rownd        DetectorType = 673\n\tDetectorType_Shotstack    DetectorType = 674\n\tDetectorType_Swiftype     DetectorType = 675\n\tDetectorType_Twitter      DetectorType = 676\n\tDetectorType_Honey        DetectorType = 677\n\tDetectorType_Freshdesk    DetectorType = 678\n\tDetectorType_Upwave       DetectorType = 679\n\tDetectorType_Fountain     DetectorType = 680 // Not yet implemented\n\tDetectorType_Freshbooks   DetectorType = 681\n\tDetectorType_Mite         DetectorType = 682\n\tDetectorType_Deputy       DetectorType = 683\n\tDetectorType_Beebole      DetectorType = 684\n\tDetectorType_Cashboard    DetectorType = 685\n\tDetectorType_Kanban       DetectorType = 686\n\tDetectorType_Worksnaps    DetectorType = 687\n\tDetectorType_MyIntervals  DetectorType = 688\n\tDetectorType_InvoiceOcean DetectorType = 689\n\tDetectorType_Sherpadesk   DetectorType = 690\n\tDetectorType_Mrticktock   DetectorType = 691\n\tDetectorType_Chatfule     DetectorType = 692\n\tDetectorType_Aeroworkflow DetectorType = 693\n\tDetectorType_Emailoctopus DetectorType = 694 // Not yet implemented\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Fusebill                 DetectorType = 695\n\tDetectorType_Geckoboard               DetectorType = 696\n\tDetectorType_Gosquared                DetectorType = 697 // Not yet implemented\n\tDetectorType_Moonclerk                DetectorType = 698\n\tDetectorType_Paymoapp                 DetectorType = 699\n\tDetectorType_Mixmax                   DetectorType = 700\n\tDetectorType_Processst                DetectorType = 701 // Not yet implemented\n\tDetectorType_Repairshopr              DetectorType = 702\n\tDetectorType_Goshippo                 DetectorType = 703 // Not yet implemented\n\tDetectorType_Sigopt                   DetectorType = 704\n\tDetectorType_Sugester                 DetectorType = 705\n\tDetectorType_Viewneo                  DetectorType = 706\n\tDetectorType_BoostNote                DetectorType = 707\n\tDetectorType_CaptainData              DetectorType = 708\n\tDetectorType_Checkvist                DetectorType = 709\n\tDetectorType_Cliengo                  DetectorType = 710\n\tDetectorType_Cloze                    DetectorType = 711\n\tDetectorType_FormIO                   DetectorType = 712\n\tDetectorType_FormBucket               DetectorType = 713\n\tDetectorType_GoCanvas                 DetectorType = 714\n\tDetectorType_MadKudu                  DetectorType = 715\n\tDetectorType_NozbeTeams               DetectorType = 716\n\tDetectorType_Papyrs                   DetectorType = 717 // Not yet implemented\n\tDetectorType_SuperNotesAPI            DetectorType = 718\n\tDetectorType_Tallyfy                  DetectorType = 719\n\tDetectorType_ZenkitAPI                DetectorType = 720\n\tDetectorType_CloudImage               DetectorType = 721\n\tDetectorType_UploadCare               DetectorType = 722\n\tDetectorType_Borgbase                 DetectorType = 723\n\tDetectorType_Pipedream                DetectorType = 724\n\tDetectorType_Sirv                     DetectorType = 725\n\tDetectorType_Diffbot                  DetectorType = 726\n\tDetectorType_EightxEight              DetectorType = 727\n\tDetectorType_Sendoso                  DetectorType = 728 // Not yet implemented\n\tDetectorType_Printfection             DetectorType = 729 // Not yet implemented\n\tDetectorType_Authorize                DetectorType = 730 // Not yet implemented\n\tDetectorType_PandaScore               DetectorType = 731\n\tDetectorType_Paymo                    DetectorType = 732\n\tDetectorType_AvazaPersonalAccessToken DetectorType = 733\n\tDetectorType_PlanviewLeanKit          DetectorType = 734\n\tDetectorType_Livestorm                DetectorType = 735\n\tDetectorType_KuCoin                   DetectorType = 736\n\tDetectorType_MetaAPI                  DetectorType = 737\n\tDetectorType_NiceHash                 DetectorType = 738 // Not yet implemented\n\tDetectorType_CexIO                    DetectorType = 739\n\tDetectorType_Klipfolio                DetectorType = 740\n\tDetectorType_Dynatrace                DetectorType = 741 // Not yet implemented\n\tDetectorType_MollieAPIKey             DetectorType = 742 // Not yet implemented\n\tDetectorType_MollieAccessToken        DetectorType = 743 // Not yet implemented\n\tDetectorType_BasisTheory              DetectorType = 744 // Not yet implemented\n\tDetectorType_Nordigen                 DetectorType = 745 // Not yet implemented\n\tDetectorType_FlagsmithEnvironmentKey  DetectorType = 746 // Not yet implemented\n\tDetectorType_FlagsmithToken           DetectorType = 747 // Not yet implemented\n\tDetectorType_Mux                      DetectorType = 748\n\tDetectorType_Column                   DetectorType = 749\n\tDetectorType_Sendbird                 DetectorType = 750\n\tDetectorType_SendbirdOrganizationAPI  DetectorType = 751\n\tDetectorType_Midise                   DetectorType = 752 // Not yet implemented\n\tDetectorType_Mockaroo                 DetectorType = 753\n\tDetectorType_Image4                   DetectorType = 754 // Not yet implemented\n\tDetectorType_Pinata                   DetectorType = 755\n\tDetectorType_BrowserStack             DetectorType = 756\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_CrossBrowserTesting DetectorType = 757\n\tDetectorType_Loadmill            DetectorType = 758\n\tDetectorType_TestingBot          DetectorType = 759\n\tDetectorType_KnapsackPro         DetectorType = 760\n\tDetectorType_Qase                DetectorType = 761\n\tDetectorType_Dareboost           DetectorType = 762\n\tDetectorType_GTMetrix            DetectorType = 763\n\tDetectorType_Holistic            DetectorType = 764\n\tDetectorType_Parsers             DetectorType = 765\n\tDetectorType_ScrutinizerCi       DetectorType = 766\n\tDetectorType_SonarCloud          DetectorType = 767\n\tDetectorType_APITemplate         DetectorType = 768\n\tDetectorType_ConversionTools     DetectorType = 769\n\tDetectorType_CraftMyPDF          DetectorType = 770\n\tDetectorType_ExportSDK           DetectorType = 771\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_GlitterlyAPI DetectorType = 772\n\tDetectorType_Hybiscus     DetectorType = 773\n\tDetectorType_Miro         DetectorType = 774\n\tDetectorType_Statuspage   DetectorType = 775\n\tDetectorType_Statuspal    DetectorType = 776\n\tDetectorType_Teletype     DetectorType = 777\n\tDetectorType_TimeCamp     DetectorType = 778\n\tDetectorType_Userflow     DetectorType = 779\n\tDetectorType_Wistia       DetectorType = 780\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_SportRadar    DetectorType = 781\n\tDetectorType_UptimeRobot   DetectorType = 782\n\tDetectorType_Codequiry     DetectorType = 783\n\tDetectorType_ExtractorAPI  DetectorType = 784\n\tDetectorType_Signable      DetectorType = 785\n\tDetectorType_MagicBell     DetectorType = 786\n\tDetectorType_Stormboard    DetectorType = 787\n\tDetectorType_Apilayer      DetectorType = 788\n\tDetectorType_Disqus        DetectorType = 789\n\tDetectorType_Woopra        DetectorType = 790 // Not yet implemented\n\tDetectorType_Paperform     DetectorType = 791\n\tDetectorType_Gumroad       DetectorType = 792\n\tDetectorType_Paydirtapp    DetectorType = 793\n\tDetectorType_Detectify     DetectorType = 794\n\tDetectorType_Statuscake    DetectorType = 795\n\tDetectorType_Jumpseller    DetectorType = 796 // Not yet implemented\n\tDetectorType_LunchMoney    DetectorType = 797\n\tDetectorType_Rosette       DetectorType = 798 // Not yet implemented\n\tDetectorType_Yelp          DetectorType = 799\n\tDetectorType_Atera         DetectorType = 800\n\tDetectorType_EcoStruxureIT DetectorType = 801\n\tDetectorType_Aha           DetectorType = 802\n\tDetectorType_Parsehub      DetectorType = 803\n\tDetectorType_PackageCloud  DetectorType = 804\n\tDetectorType_Cloudsmith    DetectorType = 805\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Flowdash DetectorType = 806\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Flowdock          DetectorType = 807\n\tDetectorType_Fibery            DetectorType = 808\n\tDetectorType_Typetalk          DetectorType = 809\n\tDetectorType_VoodooSMS         DetectorType = 810\n\tDetectorType_ZulipChat         DetectorType = 811\n\tDetectorType_Formcraft         DetectorType = 812\n\tDetectorType_Iexapis           DetectorType = 813\n\tDetectorType_Reachmail         DetectorType = 814\n\tDetectorType_Chartmogul        DetectorType = 815\n\tDetectorType_Appointedd        DetectorType = 816\n\tDetectorType_Wit               DetectorType = 817\n\tDetectorType_RechargePayments  DetectorType = 818\n\tDetectorType_Diggernaut        DetectorType = 819\n\tDetectorType_MonkeyLearn       DetectorType = 820\n\tDetectorType_Duply             DetectorType = 821\n\tDetectorType_Postbacks         DetectorType = 822\n\tDetectorType_Collect2          DetectorType = 823\n\tDetectorType_ZenRows           DetectorType = 824\n\tDetectorType_Zipcodebase       DetectorType = 825\n\tDetectorType_Tefter            DetectorType = 826\n\tDetectorType_Twist             DetectorType = 827\n\tDetectorType_BraintreePayments DetectorType = 828\n\tDetectorType_CloudConvert      DetectorType = 829\n\tDetectorType_Grafana           DetectorType = 830\n\tDetectorType_ConvertApi        DetectorType = 831\n\tDetectorType_Transferwise      DetectorType = 832\n\tDetectorType_Bulksms           DetectorType = 833\n\tDetectorType_Databox           DetectorType = 834\n\tDetectorType_Onesignal         DetectorType = 835\n\tDetectorType_Rentman           DetectorType = 836\n\tDetectorType_Parseur           DetectorType = 837\n\tDetectorType_Docparser         DetectorType = 838\n\tDetectorType_Formsite          DetectorType = 839\n\tDetectorType_Tickettailor      DetectorType = 840\n\tDetectorType_Lemlist           DetectorType = 841\n\tDetectorType_Prodpad           DetectorType = 842\n\tDetectorType_Formstack         DetectorType = 843 // Not yet implemented\n\tDetectorType_Codeclimate       DetectorType = 844\n\tDetectorType_Codemagic         DetectorType = 845\n\tDetectorType_Vbout             DetectorType = 846\n\tDetectorType_Nightfall         DetectorType = 847\n\tDetectorType_FlightLabs        DetectorType = 848\n\tDetectorType_SpeechTextAI      DetectorType = 849\n\tDetectorType_PollsAPI          DetectorType = 850\n\tDetectorType_SimFin            DetectorType = 851\n\tDetectorType_Scalr             DetectorType = 852\n\tDetectorType_Kanbantool        DetectorType = 853\n\tDetectorType_Brightlocal       DetectorType = 854 // Not yet implemented\n\tDetectorType_Hotwire           DetectorType = 855 // Not yet implemented\n\tDetectorType_Instabot          DetectorType = 856\n\tDetectorType_Timekit           DetectorType = 857 // Not yet implemented\n\tDetectorType_Interseller       DetectorType = 858\n\tDetectorType_Mojohelpdesk      DetectorType = 859 // Not yet implemented\n\tDetectorType_Createsend        DetectorType = 860 // Not yet implemented\n\tDetectorType_Getresponse       DetectorType = 861\n\tDetectorType_Dynadot           DetectorType = 862 // Not yet implemented\n\tDetectorType_Demio             DetectorType = 863\n\tDetectorType_Tokeet            DetectorType = 864\n\tDetectorType_Myexperiment      DetectorType = 865 // Not yet implemented\n\tDetectorType_Copyscape         DetectorType = 866 // Not yet implemented\n\tDetectorType_Besnappy          DetectorType = 867\n\tDetectorType_Salesmate         DetectorType = 868\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_Heatmapapi      DetectorType = 869\n\tDetectorType_Websitepulse    DetectorType = 870\n\tDetectorType_Uclassify       DetectorType = 871\n\tDetectorType_Convert         DetectorType = 872\n\tDetectorType_PDFmyURL        DetectorType = 873 // Not yet implemented\n\tDetectorType_Api2Convert     DetectorType = 874 // Not yet implemented\n\tDetectorType_Opsgenie        DetectorType = 875\n\tDetectorType_Gemini          DetectorType = 876\n\tDetectorType_Honeycomb       DetectorType = 877\n\tDetectorType_KalturaAppToken DetectorType = 878 // Not yet implemented\n\tDetectorType_KalturaSession  DetectorType = 879 // Not yet implemented\n\tDetectorType_BitGo           DetectorType = 880 // Not yet implemented\n\tDetectorType_Optidash        DetectorType = 881 // Not yet implemented\n\tDetectorType_Imgix           DetectorType = 882 // Not yet implemented\n\tDetectorType_ImageToText     DetectorType = 883 // Not yet implemented\n\tDetectorType_Page2Images     DetectorType = 884 // Not yet implemented\n\tDetectorType_Quickbase       DetectorType = 885 // Not yet implemented\n\tDetectorType_Redbooth        DetectorType = 886 // Not yet implemented\n\tDetectorType_Nubela          DetectorType = 887 // Not yet implemented\n\tDetectorType_Infobip         DetectorType = 888 // Not yet implemented\n\tDetectorType_Uproc           DetectorType = 889 // Not yet implemented\n\tDetectorType_Supportbee      DetectorType = 890 // Not yet implemented\n\tDetectorType_Aftership       DetectorType = 891 // Not yet implemented\n\tDetectorType_Edusign         DetectorType = 892 // Not yet implemented\n\tDetectorType_Teamup          DetectorType = 893 // Not yet implemented\n\tDetectorType_Workday         DetectorType = 894 // Not yet implemented\n\tDetectorType_MongoDB         DetectorType = 895\n\tDetectorType_NGC             DetectorType = 896\n\tDetectorType_DigitalOceanV2  DetectorType = 897\n\tDetectorType_SQLServer       DetectorType = 898\n\tDetectorType_FTP             DetectorType = 899\n\tDetectorType_Redis           DetectorType = 900\n\tDetectorType_LDAP            DetectorType = 901\n\tDetectorType_Shopify         DetectorType = 902\n\tDetectorType_RabbitMQ        DetectorType = 903\n\tDetectorType_CustomRegex     DetectorType = 904\n\tDetectorType_Etherscan       DetectorType = 905\n\tDetectorType_Infura          DetectorType = 906\n\tDetectorType_Alchemy         DetectorType = 907\n\tDetectorType_BlockNative     DetectorType = 908\n\tDetectorType_Moralis         DetectorType = 909\n\tDetectorType_BscScan         DetectorType = 910\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_CoinMarketCap             DetectorType = 911\n\tDetectorType_Percy                     DetectorType = 912\n\tDetectorType_TinesWebhook              DetectorType = 913\n\tDetectorType_Pulumi                    DetectorType = 914\n\tDetectorType_SupabaseToken             DetectorType = 915\n\tDetectorType_NuGetApiKey               DetectorType = 916\n\tDetectorType_Aiven                     DetectorType = 917\n\tDetectorType_Prefect                   DetectorType = 918\n\tDetectorType_Docusign                  DetectorType = 919\n\tDetectorType_Couchbase                 DetectorType = 920\n\tDetectorType_Dockerhub                 DetectorType = 921\n\tDetectorType_TrufflehogEnterprise      DetectorType = 922\n\tDetectorType_EnvoyApiKey               DetectorType = 923\n\tDetectorType_GitHubOauth2              DetectorType = 924\n\tDetectorType_Salesforce                DetectorType = 925\n\tDetectorType_HuggingFace               DetectorType = 926\n\tDetectorType_Snowflake                 DetectorType = 927\n\tDetectorType_Sourcegraph               DetectorType = 928\n\tDetectorType_Tailscale                 DetectorType = 929\n\tDetectorType_Web3Storage               DetectorType = 930\n\tDetectorType_AzureStorage              DetectorType = 931\n\tDetectorType_PlanetScaleDb             DetectorType = 932\n\tDetectorType_Anthropic                 DetectorType = 933\n\tDetectorType_Ramp                      DetectorType = 934\n\tDetectorType_Klaviyo                   DetectorType = 935\n\tDetectorType_SourcegraphCody           DetectorType = 936\n\tDetectorType_Voiceflow                 DetectorType = 937\n\tDetectorType_Privacy                   DetectorType = 938\n\tDetectorType_IPInfo                    DetectorType = 939\n\tDetectorType_Ip2location               DetectorType = 940\n\tDetectorType_Instamojo                 DetectorType = 941\n\tDetectorType_Portainer                 DetectorType = 942\n\tDetectorType_PortainerToken            DetectorType = 943\n\tDetectorType_Loggly                    DetectorType = 944\n\tDetectorType_OpenVpn                   DetectorType = 945\n\tDetectorType_VagrantCloudPersonalToken DetectorType = 946\n\tDetectorType_BetterStack               DetectorType = 947\n\tDetectorType_ZeroTier                  DetectorType = 948\n\tDetectorType_AppOptics                 DetectorType = 949\n\tDetectorType_Metabase                  DetectorType = 950\n\t// Deprecated: Marked as deprecated in detectors.proto.\n\tDetectorType_CoinbaseWaaS                            DetectorType = 951\n\tDetectorType_LemonSqueezy                            DetectorType = 952\n\tDetectorType_Budibase                                DetectorType = 953\n\tDetectorType_DenoDeploy                              DetectorType = 954\n\tDetectorType_Stripo                                  DetectorType = 955\n\tDetectorType_ReplyIO                                 DetectorType = 956\n\tDetectorType_AzureBatch                              DetectorType = 957\n\tDetectorType_AzureContainerRegistry                  DetectorType = 958\n\tDetectorType_AWSSessionKey                           DetectorType = 959\n\tDetectorType_Coda                                    DetectorType = 960\n\tDetectorType_LogzIO                                  DetectorType = 961\n\tDetectorType_Eventbrite                              DetectorType = 962\n\tDetectorType_GrafanaServiceAccount                   DetectorType = 963\n\tDetectorType_RequestFinance                          DetectorType = 964\n\tDetectorType_Overloop                                DetectorType = 965\n\tDetectorType_Ngrok                                   DetectorType = 966\n\tDetectorType_Replicate                               DetectorType = 967\n\tDetectorType_Postgres                                DetectorType = 968\n\tDetectorType_AzureActiveDirectoryApplicationSecret   DetectorType = 969\n\tDetectorType_AzureCacheForRedisAccessKey             DetectorType = 970\n\tDetectorType_AzureCosmosDBKeyIdentifiable            DetectorType = 971\n\tDetectorType_AzureDevopsPersonalAccessToken          DetectorType = 972\n\tDetectorType_AzureFunctionKey                        DetectorType = 973\n\tDetectorType_AzureMLWebServiceClassicIdentifiableKey DetectorType = 974\n\tDetectorType_AzureSasToken                           DetectorType = 975\n\tDetectorType_AzureSearchAdminKey                     DetectorType = 976\n\tDetectorType_AzureSearchQueryKey                     DetectorType = 977\n\tDetectorType_AzureManagementCertificate              DetectorType = 978\n\tDetectorType_AzureSQL                                DetectorType = 979\n\tDetectorType_FlyIO                                   DetectorType = 980\n\tDetectorType_BuiltWith                               DetectorType = 981\n\tDetectorType_JupiterOne                              DetectorType = 982\n\tDetectorType_GCPApplicationDefaultCredentials        DetectorType = 983\n\tDetectorType_Wiz                                     DetectorType = 984\n\tDetectorType_Pagarme                                 DetectorType = 985\n\tDetectorType_Onfleet                                 DetectorType = 986\n\tDetectorType_Intra42                                 DetectorType = 987\n\tDetectorType_Groq                                    DetectorType = 988\n\tDetectorType_TwitterConsumerkey                      DetectorType = 989\n\tDetectorType_Eraser                                  DetectorType = 990\n\tDetectorType_LarkSuite                               DetectorType = 991\n\tDetectorType_LarkSuiteApiKey                         DetectorType = 992\n\tDetectorType_EndorLabs                               DetectorType = 993\n\tDetectorType_ElevenLabs                              DetectorType = 994\n\tDetectorType_Netsuite                                DetectorType = 995\n\tDetectorType_RobinhoodCrypto                         DetectorType = 996\n\tDetectorType_NVAPI                                   DetectorType = 997\n\tDetectorType_PyPI                                    DetectorType = 998\n\tDetectorType_RailwayApp                              DetectorType = 999\n\tDetectorType_Meraki                                  DetectorType = 1000\n\tDetectorType_SaladCloudApiKey                        DetectorType = 1001\n\tDetectorType_Box                                     DetectorType = 1002\n\tDetectorType_BoxOauth                                DetectorType = 1003\n\tDetectorType_ApiMetrics                              DetectorType = 1004\n\tDetectorType_WeightsAndBiases                        DetectorType = 1005\n\tDetectorType_ZohoCRM                                 DetectorType = 1006\n\tDetectorType_AzureOpenAI                             DetectorType = 1007\n\tDetectorType_GoDaddy                                 DetectorType = 1008\n\tDetectorType_Flexport                                DetectorType = 1009\n\tDetectorType_TwitchAccessToken                       DetectorType = 1010\n\tDetectorType_TwilioApiKey                            DetectorType = 1011\n\tDetectorType_Sanity                                  DetectorType = 1012\n\tDetectorType_AzureRefreshToken                       DetectorType = 1013\n\tDetectorType_AirtableOAuth                           DetectorType = 1014\n\tDetectorType_AirtablePersonalAccessToken             DetectorType = 1015\n\tDetectorType_StoryblokPersonalAccessToken            DetectorType = 1016\n\tDetectorType_SentryOrgToken                          DetectorType = 1017\n\tDetectorType_AzureApiManagementRepositoryKey         DetectorType = 1018\n\tDetectorType_AzureAPIManagementSubscriptionKey       DetectorType = 1019\n\tDetectorType_Harness                                 DetectorType = 1020\n\tDetectorType_Langfuse                                DetectorType = 1021\n\tDetectorType_BingSubscriptionKey                     DetectorType = 1022\n\tDetectorType_XAI                                     DetectorType = 1023\n\tDetectorType_AzureDirectManagementKey                DetectorType = 1024\n\tDetectorType_AzureAppConfigConnectionString          DetectorType = 1025\n\tDetectorType_DeepSeek                                DetectorType = 1026\n\tDetectorType_StripePaymentIntent                     DetectorType = 1027\n\tDetectorType_LangSmith                               DetectorType = 1028\n\tDetectorType_BitbucketAppPassword                    DetectorType = 1029\n\tDetectorType_Hasura                                  DetectorType = 1030\n\tDetectorType_SalesforceRefreshToken                  DetectorType = 1031\n\tDetectorType_AnypointOAuth2                          DetectorType = 1032\n\tDetectorType_WebexBot                                DetectorType = 1033\n\tDetectorType_TableauPersonalAccessToken              DetectorType = 1034\n\tDetectorType_Rootly                                  DetectorType = 1035\n\tDetectorType_HashiCorpVaultAuth                      DetectorType = 1036\n\tDetectorType_PhraseAccessToken                       DetectorType = 1037\n\tDetectorType_Photoroom                               DetectorType = 1038\n\tDetectorType_JWT                                     DetectorType = 1039\n\tDetectorType_OpenAIAdmin                             DetectorType = 1040\n\tDetectorType_GoogleGeminiAPIKey                      DetectorType = 1041\n\tDetectorType_ArtifactoryReferenceToken               DetectorType = 1042\n\tDetectorType_DatadogApikey                           DetectorType = 1043\n)\n\n// Enum value maps for DetectorType.\nvar (\n\tDetectorType_name = map[int32]string{\n\t\t0:    \"Alibaba\",\n\t\t1:    \"AMQP\",\n\t\t2:    \"AWS\",\n\t\t3:    \"Azure\",\n\t\t4:    \"Circle\",\n\t\t5:    \"Coinbase\",\n\t\t6:    \"GCP\",\n\t\t7:    \"Generic\",\n\t\t8:    \"Github\",\n\t\t9:    \"Gitlab\",\n\t\t10:   \"JDBC\",\n\t\t11:   \"RazorPay\",\n\t\t12:   \"SendGrid\",\n\t\t13:   \"Slack\",\n\t\t14:   \"Square\",\n\t\t15:   \"PrivateKey\",\n\t\t16:   \"Stripe\",\n\t\t17:   \"URI\",\n\t\t18:   \"Dropbox\",\n\t\t19:   \"Heroku\",\n\t\t20:   \"Mailchimp\",\n\t\t21:   \"Okta\",\n\t\t22:   \"OneLogin\",\n\t\t23:   \"PivotalTracker\",\n\t\t25:   \"SquareApp\",\n\t\t26:   \"Twilio\",\n\t\t27:   \"Test\",\n\t\t29:   \"TravisCI\",\n\t\t30:   \"SlackWebhook\",\n\t\t31:   \"PaypalOauth\",\n\t\t32:   \"PagerDutyApiKey\",\n\t\t33:   \"Firebase\",\n\t\t34:   \"Mailgun\",\n\t\t35:   \"HubSpot\",\n\t\t36:   \"GitHubApp\",\n\t\t37:   \"CircleCI\",\n\t\t38:   \"WpEngine\",\n\t\t39:   \"DatadogToken\",\n\t\t40:   \"FacebookOAuth\",\n\t\t41:   \"AsanaPersonalAccessToken\",\n\t\t42:   \"AmplitudeApiKey\",\n\t\t43:   \"BitLyAccessToken\",\n\t\t44:   \"CalendlyApiKey\",\n\t\t45:   \"ZapierWebhook\",\n\t\t46:   \"YoutubeApiKey\",\n\t\t47:   \"SalesforceOauth2\",\n\t\t48:   \"TwitterApiSecret\",\n\t\t49:   \"NpmToken\",\n\t\t50:   \"NewRelicPersonalApiKey\",\n\t\t51:   \"AirtableApiKey\",\n\t\t52:   \"AkamaiToken\",\n\t\t53:   \"AmazonMWS\",\n\t\t54:   \"KubeConfig\",\n\t\t55:   \"Auth0oauth\",\n\t\t56:   \"Bitfinex\",\n\t\t57:   \"Clarifai\",\n\t\t58:   \"CloudflareGlobalApiKey\",\n\t\t59:   \"CloudflareCaKey\",\n\t\t60:   \"Confluent\",\n\t\t61:   \"ContentfulDelivery\",\n\t\t62:   \"DatabricksToken\",\n\t\t63:   \"DigitalOceanSpaces\",\n\t\t64:   \"DigitalOceanToken\",\n\t\t65:   \"DiscordBotToken\",\n\t\t66:   \"DiscordWebhook\",\n\t\t67:   \"EtsyApiKey\",\n\t\t68:   \"FastlyPersonalToken\",\n\t\t69:   \"GoogleOauth2\",\n\t\t70:   \"ReCAPTCHA\",\n\t\t71:   \"GoogleApiKey\",\n\t\t72:   \"Hunter\",\n\t\t73:   \"IbmCloudUserKey\",\n\t\t74:   \"Netlify\",\n\t\t75:   \"Vonage\",\n\t\t76:   \"EquinixOauth\",\n\t\t77:   \"Paystack\",\n\t\t78:   \"PlaidToken\",\n\t\t79:   \"PlaidKey\",\n\t\t80:   \"Plivo\",\n\t\t81:   \"Postmark\",\n\t\t82:   \"PubNubPublishKey\",\n\t\t83:   \"PubNubSubscriptionKey\",\n\t\t84:   \"PusherChannelKey\",\n\t\t85:   \"ScalewayKey\",\n\t\t86:   \"SendinBlueV2\",\n\t\t87:   \"SentryToken\",\n\t\t88:   \"ShodanKey\",\n\t\t89:   \"SnykKey\",\n\t\t90:   \"SpotifyKey\",\n\t\t91:   \"TelegramBotToken\",\n\t\t92:   \"TencentCloudKey\",\n\t\t93:   \"TerraformCloudPersonalToken\",\n\t\t94:   \"TrelloApiKey\",\n\t\t95:   \"ZendeskApi\",\n\t\t96:   \"MaxMindLicense\",\n\t\t97:   \"AirtableMetadataApiKey\",\n\t\t98:   \"AsanaOauth\",\n\t\t99:   \"RapidApi\",\n\t\t100:  \"CloudflareApiToken\",\n\t\t101:  \"Webex\",\n\t\t102:  \"FirebaseCloudMessaging\",\n\t\t103:  \"ContentfulPersonalAccessToken\",\n\t\t104:  \"MapBox\",\n\t\t105:  \"MailJetBasicAuth\",\n\t\t106:  \"MailJetSMS\",\n\t\t107:  \"HubSpotApiKey\",\n\t\t108:  \"HubSpotOauth\",\n\t\t109:  \"SslMate\",\n\t\t110:  \"Auth0ManagementApiToken\",\n\t\t111:  \"MessageBird\",\n\t\t112:  \"ElasticEmail\",\n\t\t113:  \"FigmaPersonalAccessToken\",\n\t\t114:  \"MicrosoftTeamsWebhook\",\n\t\t115:  \"GitHubOld\",\n\t\t116:  \"VultrApiKey\",\n\t\t117:  \"Pepipost\",\n\t\t118:  \"Postman\",\n\t\t119:  \"CloudsightKey\",\n\t\t120:  \"JiraToken\",\n\t\t121:  \"NexmoApiKey\",\n\t\t122:  \"SegmentApiKey\",\n\t\t123:  \"SumoLogicKey\",\n\t\t124:  \"PushBulletApiKey\",\n\t\t125:  \"AirbrakeProjectKey\",\n\t\t126:  \"AirbrakeUserKey\",\n\t\t127:  \"PendoIntegrationKey\",\n\t\t128:  \"SplunkOberservabilityToken\",\n\t\t129:  \"LokaliseToken\",\n\t\t130:  \"Calendarific\",\n\t\t131:  \"Jumpcloud\",\n\t\t133:  \"IpStack\",\n\t\t134:  \"Notion\",\n\t\t135:  \"DroneCI\",\n\t\t136:  \"AdobeIO\",\n\t\t137:  \"TwelveData\",\n\t\t138:  \"D7Network\",\n\t\t139:  \"ScrapingBee\",\n\t\t140:  \"KeenIO\",\n\t\t141:  \"Wakatime\",\n\t\t142:  \"Buildkite\",\n\t\t143:  \"Verimail\",\n\t\t144:  \"Zerobounce\",\n\t\t145:  \"Mailboxlayer\",\n\t\t146:  \"Fastspring\",\n\t\t147:  \"Paddle\",\n\t\t148:  \"Sellfy\",\n\t\t149:  \"FixerIO\",\n\t\t150:  \"ButterCMS\",\n\t\t151:  \"Taxjar\",\n\t\t152:  \"Avalara\",\n\t\t153:  \"Helpscout\",\n\t\t154:  \"ElasticPath\",\n\t\t155:  \"Zeplin\",\n\t\t156:  \"Intercom\",\n\t\t157:  \"Mailmodo\",\n\t\t158:  \"CannyIo\",\n\t\t159:  \"Pipedrive\",\n\t\t160:  \"Vercel\",\n\t\t161:  \"PosthogApp\",\n\t\t162:  \"SinchMessage\",\n\t\t163:  \"Ayrshare\",\n\t\t164:  \"HelpCrunch\",\n\t\t165:  \"LiveAgent\",\n\t\t166:  \"Beamer\",\n\t\t167:  \"WeChatAppKey\",\n\t\t168:  \"LineMessaging\",\n\t\t169:  \"UberServerToken\",\n\t\t170:  \"AlgoliaAdminKey\",\n\t\t171:  \"FullContact\",\n\t\t172:  \"Mandrill\",\n\t\t173:  \"Flutterwave\",\n\t\t174:  \"MattermostPersonalToken\",\n\t\t175:  \"Cloudant\",\n\t\t176:  \"LineNotify\",\n\t\t177:  \"LinearAPI\",\n\t\t178:  \"Ubidots\",\n\t\t179:  \"Anypoint\",\n\t\t180:  \"Dwolla\",\n\t\t181:  \"ArtifactoryAccessToken\",\n\t\t182:  \"Surge\",\n\t\t183:  \"Sparkpost\",\n\t\t184:  \"GoCardless\",\n\t\t185:  \"Codacy\",\n\t\t186:  \"Kraken\",\n\t\t187:  \"Checkout\",\n\t\t188:  \"Kairos\",\n\t\t189:  \"ClockworkSMS\",\n\t\t190:  \"Atlassian\",\n\t\t191:  \"LaunchDarkly\",\n\t\t192:  \"Coveralls\",\n\t\t193:  \"Linode\",\n\t\t194:  \"WePay\",\n\t\t195:  \"PlanetScale\",\n\t\t196:  \"Doppler\",\n\t\t197:  \"Agora\",\n\t\t198:  \"Samsara\",\n\t\t199:  \"FrameIO\",\n\t\t200:  \"RubyGems\",\n\t\t201:  \"OpenAI\",\n\t\t202:  \"SurveySparrow\",\n\t\t203:  \"Simvoly\",\n\t\t204:  \"Survicate\",\n\t\t205:  \"Omnisend\",\n\t\t206:  \"Groovehq\",\n\t\t207:  \"Newsapi\",\n\t\t208:  \"Chatbot\",\n\t\t209:  \"ClickSendsms\",\n\t\t210:  \"Getgist\",\n\t\t211:  \"CustomerIO\",\n\t\t212:  \"ApiDeck\",\n\t\t213:  \"Nftport\",\n\t\t214:  \"Copper\",\n\t\t215:  \"Close\",\n\t\t216:  \"Myfreshworks\",\n\t\t217:  \"Salesflare\",\n\t\t218:  \"Webflow\",\n\t\t219:  \"Duda\",\n\t\t220:  \"Yext\",\n\t\t221:  \"ContentStack\",\n\t\t222:  \"StoryblokAccessToken\",\n\t\t223:  \"GraphCMS\",\n\t\t224:  \"Checkmarket\",\n\t\t225:  \"Convertkit\",\n\t\t226:  \"CustomerGuru\",\n\t\t227:  \"Kaleyra\",\n\t\t228:  \"Mailerlite\",\n\t\t229:  \"Qualaroo\",\n\t\t230:  \"SatismeterProjectkey\",\n\t\t231:  \"SatismeterWritekey\",\n\t\t232:  \"Simplesat\",\n\t\t233:  \"SurveyAnyplace\",\n\t\t234:  \"SurveyBot\",\n\t\t235:  \"Webengage\",\n\t\t236:  \"ZonkaFeedback\",\n\t\t237:  \"Delighted\",\n\t\t238:  \"Feedier\",\n\t\t239:  \"Abyssale\",\n\t\t240:  \"Magnetic\",\n\t\t241:  \"Nytimes\",\n\t\t242:  \"Polygon\",\n\t\t243:  \"Powrbot\",\n\t\t244:  \"ProspectIO\",\n\t\t245:  \"Skrappio\",\n\t\t246:  \"Monday\",\n\t\t247:  \"Smartsheets\",\n\t\t248:  \"Wrike\",\n\t\t249:  \"Float\",\n\t\t250:  \"Imagekit\",\n\t\t251:  \"Integromat\",\n\t\t252:  \"Salesblink\",\n\t\t253:  \"Bored\",\n\t\t254:  \"Campayn\",\n\t\t255:  \"Clinchpad\",\n\t\t256:  \"CompanyHub\",\n\t\t257:  \"Debounce\",\n\t\t258:  \"Dyspatch\",\n\t\t259:  \"Guardianapi\",\n\t\t260:  \"Harvest\",\n\t\t261:  \"Moosend\",\n\t\t262:  \"OpenWeather\",\n\t\t263:  \"Siteleaf\",\n\t\t264:  \"Squarespace\",\n\t\t265:  \"FlowFlu\",\n\t\t266:  \"Nimble\",\n\t\t267:  \"LessAnnoyingCRM\",\n\t\t268:  \"Nethunt\",\n\t\t269:  \"Apptivo\",\n\t\t270:  \"CapsuleCRM\",\n\t\t271:  \"Insightly\",\n\t\t272:  \"Kylas\",\n\t\t273:  \"OnepageCRM\",\n\t\t274:  \"User\",\n\t\t275:  \"ProspectCRM\",\n\t\t276:  \"ReallySimpleSystems\",\n\t\t277:  \"Airship\",\n\t\t278:  \"Artsy\",\n\t\t279:  \"Yandex\",\n\t\t280:  \"Clockify\",\n\t\t281:  \"Dnscheck\",\n\t\t282:  \"EasyInsight\",\n\t\t283:  \"Ethplorer\",\n\t\t284:  \"Everhour\",\n\t\t285:  \"Fulcrum\",\n\t\t286:  \"GeoIpifi\",\n\t\t287:  \"Jotform\",\n\t\t288:  \"Refiner\",\n\t\t289:  \"Timezoneapi\",\n\t\t290:  \"TogglTrack\",\n\t\t291:  \"Vpnapi\",\n\t\t292:  \"Workstack\",\n\t\t293:  \"Apollo\",\n\t\t294:  \"Eversign\",\n\t\t295:  \"Juro\",\n\t\t296:  \"KarmaCRM\",\n\t\t297:  \"Metrilo\",\n\t\t298:  \"Pandadoc\",\n\t\t299:  \"RevampCRM\",\n\t\t300:  \"Salescookie\",\n\t\t301:  \"Alconost\",\n\t\t302:  \"Blogger\",\n\t\t303:  \"Accuweather\",\n\t\t304:  \"Opengraphr\",\n\t\t305:  \"Rawg\",\n\t\t306:  \"Riotgames\",\n\t\t307:  \"Clientary\",\n\t\t308:  \"Stormglass\",\n\t\t309:  \"Tomtom\",\n\t\t310:  \"Twitch\",\n\t\t311:  \"Documo\",\n\t\t312:  \"Cloudways\",\n\t\t313:  \"Veevavault\",\n\t\t314:  \"KiteConnect\",\n\t\t315:  \"ShopeeOpenPlatform\",\n\t\t316:  \"TeamViewer\",\n\t\t317:  \"Bulbul\",\n\t\t318:  \"CentralStationCRM\",\n\t\t319:  \"Teamgate\",\n\t\t320:  \"Axonaut\",\n\t\t321:  \"Tyntec\",\n\t\t322:  \"Appcues\",\n\t\t323:  \"Autoklose\",\n\t\t324:  \"Cloudplan\",\n\t\t325:  \"Dotdigital\",\n\t\t326:  \"GetEmail\",\n\t\t327:  \"GetEmails\",\n\t\t328:  \"Kontent\",\n\t\t329:  \"Leadfeeder\",\n\t\t330:  \"Raven\",\n\t\t331:  \"RocketReach\",\n\t\t332:  \"Uplead\",\n\t\t333:  \"Brandfetch\",\n\t\t334:  \"Clearbit\",\n\t\t335:  \"Crowdin\",\n\t\t336:  \"Mapquest\",\n\t\t337:  \"Noticeable\",\n\t\t338:  \"Onbuka\",\n\t\t339:  \"Todoist\",\n\t\t340:  \"Storychief\",\n\t\t341:  \"LinkedIn\",\n\t\t342:  \"YouSign\",\n\t\t343:  \"Docker\",\n\t\t344:  \"Telesign\",\n\t\t345:  \"Spoonacular\",\n\t\t346:  \"Aerisweather\",\n\t\t347:  \"Alphavantage\",\n\t\t348:  \"Imgur\",\n\t\t349:  \"Imagga\",\n\t\t350:  \"SMSApi\",\n\t\t351:  \"Distribusion\",\n\t\t352:  \"Blablabus\",\n\t\t353:  \"WordsApi\",\n\t\t354:  \"Currencylayer\",\n\t\t355:  \"Html2Pdf\",\n\t\t356:  \"IPGeolocation\",\n\t\t357:  \"Owlbot\",\n\t\t358:  \"Cloudmersive\",\n\t\t359:  \"Dynalist\",\n\t\t360:  \"ExchangeRateAPI\",\n\t\t361:  \"HolidayAPI\",\n\t\t362:  \"Ipapi\",\n\t\t363:  \"Marketstack\",\n\t\t364:  \"Nutritionix\",\n\t\t365:  \"Swell\",\n\t\t366:  \"ClickupPersonalToken\",\n\t\t367:  \"Nitro\",\n\t\t368:  \"Rev\",\n\t\t369:  \"RunRunIt\",\n\t\t370:  \"Typeform\",\n\t\t371:  \"Mixpanel\",\n\t\t372:  \"Tradier\",\n\t\t373:  \"Verifier\",\n\t\t374:  \"Vouchery\",\n\t\t375:  \"Alegra\",\n\t\t376:  \"Audd\",\n\t\t377:  \"Baremetrics\",\n\t\t378:  \"Coinlib\",\n\t\t379:  \"ExchangeRatesAPI\",\n\t\t380:  \"CurrencyScoop\",\n\t\t381:  \"FXMarket\",\n\t\t382:  \"CurrencyCloud\",\n\t\t383:  \"GetGeoAPI\",\n\t\t384:  \"Abstract\",\n\t\t385:  \"Billomat\",\n\t\t386:  \"Dovico\",\n\t\t387:  \"Bitbar\",\n\t\t388:  \"Bugsnag\",\n\t\t389:  \"AssemblyAI\",\n\t\t390:  \"AdafruitIO\",\n\t\t391:  \"Apify\",\n\t\t392:  \"CoinGecko\",\n\t\t393:  \"CryptoCompare\",\n\t\t394:  \"Fullstory\",\n\t\t395:  \"HelloSign\",\n\t\t396:  \"Loyverse\",\n\t\t397:  \"NetCore\",\n\t\t398:  \"SauceLabs\",\n\t\t399:  \"AlienVault\",\n\t\t401:  \"Apiflash\",\n\t\t402:  \"Coinlayer\",\n\t\t403:  \"CurrentsAPI\",\n\t\t404:  \"DataGov\",\n\t\t405:  \"Enigma\",\n\t\t406:  \"FinancialModelingPrep\",\n\t\t407:  \"Geocodio\",\n\t\t408:  \"HereAPI\",\n\t\t409:  \"Macaddress\",\n\t\t410:  \"OOPSpam\",\n\t\t411:  \"ProtocolsIO\",\n\t\t412:  \"ScraperAPI\",\n\t\t413:  \"SecurityTrails\",\n\t\t414:  \"TomorrowIO\",\n\t\t415:  \"WorldCoinIndex\",\n\t\t416:  \"FacePlusPlus\",\n\t\t417:  \"Voicegain\",\n\t\t418:  \"Deepgram\",\n\t\t419:  \"VisualCrossing\",\n\t\t420:  \"Finnhub\",\n\t\t421:  \"Tiingo\",\n\t\t422:  \"RingCentral\",\n\t\t423:  \"Finage\",\n\t\t424:  \"Edamam\",\n\t\t425:  \"HypeAuditor\",\n\t\t426:  \"Gengo\",\n\t\t427:  \"Front\",\n\t\t428:  \"Fleetbase\",\n\t\t429:  \"Bubble\",\n\t\t430:  \"Bannerbear\",\n\t\t431:  \"Adzuna\",\n\t\t432:  \"BitcoinAverage\",\n\t\t433:  \"CommerceJS\",\n\t\t434:  \"DetectLanguage\",\n\t\t435:  \"FakeJSON\",\n\t\t436:  \"Graphhopper\",\n\t\t437:  \"Lexigram\",\n\t\t438:  \"LinkPreview\",\n\t\t439:  \"Numverify\",\n\t\t440:  \"ProxyCrawl\",\n\t\t441:  \"ZipCodeAPI\",\n\t\t442:  \"Cometchat\",\n\t\t443:  \"Keygen\",\n\t\t444:  \"Mixcloud\",\n\t\t445:  \"TatumIO\",\n\t\t446:  \"Tmetric\",\n\t\t447:  \"Lastfm\",\n\t\t448:  \"Browshot\",\n\t\t449:  \"JSONbin\",\n\t\t450:  \"LocationIQ\",\n\t\t451:  \"ScreenshotAPI\",\n\t\t452:  \"WeatherStack\",\n\t\t453:  \"Amadeus\",\n\t\t454:  \"FourSquare\",\n\t\t455:  \"Flickr\",\n\t\t456:  \"ClickHelp\",\n\t\t457:  \"Ambee\",\n\t\t458:  \"Api2Cart\",\n\t\t459:  \"Hypertrack\",\n\t\t460:  \"KakaoTalk\",\n\t\t461:  \"RiteKit\",\n\t\t462:  \"Shutterstock\",\n\t\t463:  \"Text2Data\",\n\t\t464:  \"YouNeedABudget\",\n\t\t465:  \"Cricket\",\n\t\t466:  \"Filestack\",\n\t\t467:  \"Gyazo\",\n\t\t468:  \"Mavenlink\",\n\t\t469:  \"Sheety\",\n\t\t470:  \"Sportsmonk\",\n\t\t471:  \"Stockdata\",\n\t\t472:  \"Unsplash\",\n\t\t473:  \"Allsports\",\n\t\t474:  \"CalorieNinja\",\n\t\t475:  \"WalkScore\",\n\t\t476:  \"Strava\",\n\t\t477:  \"Cicero\",\n\t\t478:  \"IPQuality\",\n\t\t479:  \"ParallelDots\",\n\t\t480:  \"Roaring\",\n\t\t481:  \"Mailsac\",\n\t\t482:  \"Whoxy\",\n\t\t483:  \"WorldWeather\",\n\t\t484:  \"ApiFonica\",\n\t\t485:  \"Aylien\",\n\t\t486:  \"Geocode\",\n\t\t487:  \"IconFinder\",\n\t\t488:  \"Ipify\",\n\t\t489:  \"LanguageLayer\",\n\t\t490:  \"Lob\",\n\t\t491:  \"OnWaterIO\",\n\t\t492:  \"Pastebin\",\n\t\t493:  \"PdfLayer\",\n\t\t494:  \"Pixabay\",\n\t\t495:  \"ReadMe\",\n\t\t496:  \"VatLayer\",\n\t\t497:  \"VirusTotal\",\n\t\t498:  \"AirVisual\",\n\t\t499:  \"Currencyfreaks\",\n\t\t500:  \"Duffel\",\n\t\t501:  \"FlatIO\",\n\t\t502:  \"M3o\",\n\t\t503:  \"Mesibo\",\n\t\t504:  \"Openuv\",\n\t\t505:  \"Snipcart\",\n\t\t506:  \"Besttime\",\n\t\t507:  \"Happyscribe\",\n\t\t508:  \"Humanity\",\n\t\t509:  \"Impala\",\n\t\t510:  \"Loginradius\",\n\t\t511:  \"AutoPilot\",\n\t\t512:  \"Bitmex\",\n\t\t513:  \"ClustDoc\",\n\t\t514:  \"Messari\",\n\t\t515:  \"PdfShift\",\n\t\t516:  \"Poloniex\",\n\t\t517:  \"RestpackHtmlToPdfAPI\",\n\t\t518:  \"RestpackScreenshotAPI\",\n\t\t519:  \"ShutterstockOAuth\",\n\t\t520:  \"SkyBiometry\",\n\t\t521:  \"AbuseIPDB\",\n\t\t522:  \"AletheiaApi\",\n\t\t523:  \"BlitApp\",\n\t\t524:  \"Censys\",\n\t\t525:  \"Cloverly\",\n\t\t526:  \"CountryLayer\",\n\t\t527:  \"FileIO\",\n\t\t528:  \"FlightApi\",\n\t\t529:  \"Geoapify\",\n\t\t530:  \"IPinfoDB\",\n\t\t531:  \"MediaStack\",\n\t\t532:  \"NasdaqDataLink\",\n\t\t533:  \"OpenCageData\",\n\t\t534:  \"Paymongo\",\n\t\t535:  \"PositionStack\",\n\t\t536:  \"Rebrandly\",\n\t\t537:  \"ScreenshotLayer\",\n\t\t538:  \"Stytch\",\n\t\t539:  \"Unplugg\",\n\t\t540:  \"UPCDatabase\",\n\t\t541:  \"UserStack\",\n\t\t542:  \"Geocodify\",\n\t\t543:  \"Newscatcher\",\n\t\t544:  \"Nicereply\",\n\t\t545:  \"Partnerstack\",\n\t\t546:  \"Route4me\",\n\t\t547:  \"Scrapeowl\",\n\t\t548:  \"ScrapingDog\",\n\t\t549:  \"Streak\",\n\t\t550:  \"Veriphone\",\n\t\t551:  \"Webscraping\",\n\t\t552:  \"Zenscrape\",\n\t\t553:  \"Zenserp\",\n\t\t554:  \"CoinApi\",\n\t\t555:  \"Gitter\",\n\t\t556:  \"Host\",\n\t\t557:  \"Iexcloud\",\n\t\t558:  \"Restpack\",\n\t\t559:  \"ScraperBox\",\n\t\t560:  \"ScrapingAnt\",\n\t\t561:  \"SerpStack\",\n\t\t562:  \"SmartyStreets\",\n\t\t563:  \"TicketMaster\",\n\t\t564:  \"AviationStack\",\n\t\t565:  \"BombBomb\",\n\t\t566:  \"Commodities\",\n\t\t567:  \"Dfuse\",\n\t\t568:  \"EdenAI\",\n\t\t569:  \"Glassnode\",\n\t\t570:  \"Guru\",\n\t\t571:  \"Hive\",\n\t\t572:  \"Hiveage\",\n\t\t573:  \"Kickbox\",\n\t\t574:  \"Passbase\",\n\t\t575:  \"PostageApp\",\n\t\t576:  \"PureStake\",\n\t\t577:  \"Qubole\",\n\t\t578:  \"CarbonInterface\",\n\t\t579:  \"Intrinio\",\n\t\t580:  \"QuickMetrics\",\n\t\t581:  \"ScrapeStack\",\n\t\t582:  \"TechnicalAnalysisApi\",\n\t\t583:  \"Urlscan\",\n\t\t584:  \"BaseApiIO\",\n\t\t585:  \"DailyCO\",\n\t\t586:  \"TLy\",\n\t\t587:  \"Shortcut\",\n\t\t588:  \"Appfollow\",\n\t\t589:  \"Thinkific\",\n\t\t590:  \"Feedly\",\n\t\t591:  \"Stitchdata\",\n\t\t592:  \"Fetchrss\",\n\t\t593:  \"Signupgenius\",\n\t\t594:  \"Signaturit\",\n\t\t595:  \"Optimizely\",\n\t\t596:  \"OcrSpace\",\n\t\t597:  \"WeatherBit\",\n\t\t598:  \"BuddyNS\",\n\t\t599:  \"ZipAPI\",\n\t\t600:  \"ZipBooks\",\n\t\t601:  \"Onedesk\",\n\t\t602:  \"Bugherd\",\n\t\t603:  \"Blazemeter\",\n\t\t604:  \"Autodesk\",\n\t\t605:  \"Tru\",\n\t\t606:  \"UnifyID\",\n\t\t607:  \"Trimble\",\n\t\t608:  \"Smooch\",\n\t\t609:  \"Semaphore\",\n\t\t610:  \"Telnyx\",\n\t\t611:  \"Signalwire\",\n\t\t612:  \"Textmagic\",\n\t\t613:  \"Serphouse\",\n\t\t614:  \"Planyo\",\n\t\t615:  \"Simplybook\",\n\t\t616:  \"Vyte\",\n\t\t617:  \"Nylas\",\n\t\t618:  \"Squareup\",\n\t\t619:  \"Dandelion\",\n\t\t620:  \"DataFire\",\n\t\t621:  \"DeepAI\",\n\t\t622:  \"MeaningCloud\",\n\t\t623:  \"NeutrinoApi\",\n\t\t624:  \"Storecove\",\n\t\t625:  \"Shipday\",\n\t\t626:  \"Sentiment\",\n\t\t627:  \"StreamChatMessaging\",\n\t\t628:  \"TeamworkCRM\",\n\t\t629:  \"TeamworkDesk\",\n\t\t630:  \"TeamworkSpaces\",\n\t\t631:  \"TheOddsApi\",\n\t\t632:  \"Apacta\",\n\t\t633:  \"GetSandbox\",\n\t\t634:  \"Happi\",\n\t\t635:  \"Oanda\",\n\t\t636:  \"FastForex\",\n\t\t637:  \"APIMatic\",\n\t\t638:  \"VersionEye\",\n\t\t639:  \"EagleEyeNetworks\",\n\t\t640:  \"ThousandEyes\",\n\t\t641:  \"SelectPDF\",\n\t\t642:  \"Flightstats\",\n\t\t643:  \"ChecIO\",\n\t\t644:  \"Manifest\",\n\t\t645:  \"ApiScience\",\n\t\t646:  \"AppSynergy\",\n\t\t647:  \"Caflou\",\n\t\t648:  \"Caspio\",\n\t\t649:  \"ChecklyHQ\",\n\t\t650:  \"CloudElements\",\n\t\t651:  \"DronaHQ\",\n\t\t652:  \"Enablex\",\n\t\t653:  \"Fmfw\",\n\t\t654:  \"GoodDay\",\n\t\t655:  \"Luno\",\n\t\t656:  \"Meistertask\",\n\t\t657:  \"Mindmeister\",\n\t\t658:  \"PeopleDataLabs\",\n\t\t659:  \"ScraperSite\",\n\t\t660:  \"Scrapfly\",\n\t\t661:  \"SimplyNoted\",\n\t\t662:  \"TravelPayouts\",\n\t\t663:  \"WebScraper\",\n\t\t664:  \"Convier\",\n\t\t665:  \"Courier\",\n\t\t666:  \"Ditto\",\n\t\t667:  \"Findl\",\n\t\t668:  \"Lendflow\",\n\t\t669:  \"Moderation\",\n\t\t670:  \"Opendatasoft\",\n\t\t671:  \"Podio\",\n\t\t672:  \"Rockset\",\n\t\t673:  \"Rownd\",\n\t\t674:  \"Shotstack\",\n\t\t675:  \"Swiftype\",\n\t\t676:  \"Twitter\",\n\t\t677:  \"Honey\",\n\t\t678:  \"Freshdesk\",\n\t\t679:  \"Upwave\",\n\t\t680:  \"Fountain\",\n\t\t681:  \"Freshbooks\",\n\t\t682:  \"Mite\",\n\t\t683:  \"Deputy\",\n\t\t684:  \"Beebole\",\n\t\t685:  \"Cashboard\",\n\t\t686:  \"Kanban\",\n\t\t687:  \"Worksnaps\",\n\t\t688:  \"MyIntervals\",\n\t\t689:  \"InvoiceOcean\",\n\t\t690:  \"Sherpadesk\",\n\t\t691:  \"Mrticktock\",\n\t\t692:  \"Chatfule\",\n\t\t693:  \"Aeroworkflow\",\n\t\t694:  \"Emailoctopus\",\n\t\t695:  \"Fusebill\",\n\t\t696:  \"Geckoboard\",\n\t\t697:  \"Gosquared\",\n\t\t698:  \"Moonclerk\",\n\t\t699:  \"Paymoapp\",\n\t\t700:  \"Mixmax\",\n\t\t701:  \"Processst\",\n\t\t702:  \"Repairshopr\",\n\t\t703:  \"Goshippo\",\n\t\t704:  \"Sigopt\",\n\t\t705:  \"Sugester\",\n\t\t706:  \"Viewneo\",\n\t\t707:  \"BoostNote\",\n\t\t708:  \"CaptainData\",\n\t\t709:  \"Checkvist\",\n\t\t710:  \"Cliengo\",\n\t\t711:  \"Cloze\",\n\t\t712:  \"FormIO\",\n\t\t713:  \"FormBucket\",\n\t\t714:  \"GoCanvas\",\n\t\t715:  \"MadKudu\",\n\t\t716:  \"NozbeTeams\",\n\t\t717:  \"Papyrs\",\n\t\t718:  \"SuperNotesAPI\",\n\t\t719:  \"Tallyfy\",\n\t\t720:  \"ZenkitAPI\",\n\t\t721:  \"CloudImage\",\n\t\t722:  \"UploadCare\",\n\t\t723:  \"Borgbase\",\n\t\t724:  \"Pipedream\",\n\t\t725:  \"Sirv\",\n\t\t726:  \"Diffbot\",\n\t\t727:  \"EightxEight\",\n\t\t728:  \"Sendoso\",\n\t\t729:  \"Printfection\",\n\t\t730:  \"Authorize\",\n\t\t731:  \"PandaScore\",\n\t\t732:  \"Paymo\",\n\t\t733:  \"AvazaPersonalAccessToken\",\n\t\t734:  \"PlanviewLeanKit\",\n\t\t735:  \"Livestorm\",\n\t\t736:  \"KuCoin\",\n\t\t737:  \"MetaAPI\",\n\t\t738:  \"NiceHash\",\n\t\t739:  \"CexIO\",\n\t\t740:  \"Klipfolio\",\n\t\t741:  \"Dynatrace\",\n\t\t742:  \"MollieAPIKey\",\n\t\t743:  \"MollieAccessToken\",\n\t\t744:  \"BasisTheory\",\n\t\t745:  \"Nordigen\",\n\t\t746:  \"FlagsmithEnvironmentKey\",\n\t\t747:  \"FlagsmithToken\",\n\t\t748:  \"Mux\",\n\t\t749:  \"Column\",\n\t\t750:  \"Sendbird\",\n\t\t751:  \"SendbirdOrganizationAPI\",\n\t\t752:  \"Midise\",\n\t\t753:  \"Mockaroo\",\n\t\t754:  \"Image4\",\n\t\t755:  \"Pinata\",\n\t\t756:  \"BrowserStack\",\n\t\t757:  \"CrossBrowserTesting\",\n\t\t758:  \"Loadmill\",\n\t\t759:  \"TestingBot\",\n\t\t760:  \"KnapsackPro\",\n\t\t761:  \"Qase\",\n\t\t762:  \"Dareboost\",\n\t\t763:  \"GTMetrix\",\n\t\t764:  \"Holistic\",\n\t\t765:  \"Parsers\",\n\t\t766:  \"ScrutinizerCi\",\n\t\t767:  \"SonarCloud\",\n\t\t768:  \"APITemplate\",\n\t\t769:  \"ConversionTools\",\n\t\t770:  \"CraftMyPDF\",\n\t\t771:  \"ExportSDK\",\n\t\t772:  \"GlitterlyAPI\",\n\t\t773:  \"Hybiscus\",\n\t\t774:  \"Miro\",\n\t\t775:  \"Statuspage\",\n\t\t776:  \"Statuspal\",\n\t\t777:  \"Teletype\",\n\t\t778:  \"TimeCamp\",\n\t\t779:  \"Userflow\",\n\t\t780:  \"Wistia\",\n\t\t781:  \"SportRadar\",\n\t\t782:  \"UptimeRobot\",\n\t\t783:  \"Codequiry\",\n\t\t784:  \"ExtractorAPI\",\n\t\t785:  \"Signable\",\n\t\t786:  \"MagicBell\",\n\t\t787:  \"Stormboard\",\n\t\t788:  \"Apilayer\",\n\t\t789:  \"Disqus\",\n\t\t790:  \"Woopra\",\n\t\t791:  \"Paperform\",\n\t\t792:  \"Gumroad\",\n\t\t793:  \"Paydirtapp\",\n\t\t794:  \"Detectify\",\n\t\t795:  \"Statuscake\",\n\t\t796:  \"Jumpseller\",\n\t\t797:  \"LunchMoney\",\n\t\t798:  \"Rosette\",\n\t\t799:  \"Yelp\",\n\t\t800:  \"Atera\",\n\t\t801:  \"EcoStruxureIT\",\n\t\t802:  \"Aha\",\n\t\t803:  \"Parsehub\",\n\t\t804:  \"PackageCloud\",\n\t\t805:  \"Cloudsmith\",\n\t\t806:  \"Flowdash\",\n\t\t807:  \"Flowdock\",\n\t\t808:  \"Fibery\",\n\t\t809:  \"Typetalk\",\n\t\t810:  \"VoodooSMS\",\n\t\t811:  \"ZulipChat\",\n\t\t812:  \"Formcraft\",\n\t\t813:  \"Iexapis\",\n\t\t814:  \"Reachmail\",\n\t\t815:  \"Chartmogul\",\n\t\t816:  \"Appointedd\",\n\t\t817:  \"Wit\",\n\t\t818:  \"RechargePayments\",\n\t\t819:  \"Diggernaut\",\n\t\t820:  \"MonkeyLearn\",\n\t\t821:  \"Duply\",\n\t\t822:  \"Postbacks\",\n\t\t823:  \"Collect2\",\n\t\t824:  \"ZenRows\",\n\t\t825:  \"Zipcodebase\",\n\t\t826:  \"Tefter\",\n\t\t827:  \"Twist\",\n\t\t828:  \"BraintreePayments\",\n\t\t829:  \"CloudConvert\",\n\t\t830:  \"Grafana\",\n\t\t831:  \"ConvertApi\",\n\t\t832:  \"Transferwise\",\n\t\t833:  \"Bulksms\",\n\t\t834:  \"Databox\",\n\t\t835:  \"Onesignal\",\n\t\t836:  \"Rentman\",\n\t\t837:  \"Parseur\",\n\t\t838:  \"Docparser\",\n\t\t839:  \"Formsite\",\n\t\t840:  \"Tickettailor\",\n\t\t841:  \"Lemlist\",\n\t\t842:  \"Prodpad\",\n\t\t843:  \"Formstack\",\n\t\t844:  \"Codeclimate\",\n\t\t845:  \"Codemagic\",\n\t\t846:  \"Vbout\",\n\t\t847:  \"Nightfall\",\n\t\t848:  \"FlightLabs\",\n\t\t849:  \"SpeechTextAI\",\n\t\t850:  \"PollsAPI\",\n\t\t851:  \"SimFin\",\n\t\t852:  \"Scalr\",\n\t\t853:  \"Kanbantool\",\n\t\t854:  \"Brightlocal\",\n\t\t855:  \"Hotwire\",\n\t\t856:  \"Instabot\",\n\t\t857:  \"Timekit\",\n\t\t858:  \"Interseller\",\n\t\t859:  \"Mojohelpdesk\",\n\t\t860:  \"Createsend\",\n\t\t861:  \"Getresponse\",\n\t\t862:  \"Dynadot\",\n\t\t863:  \"Demio\",\n\t\t864:  \"Tokeet\",\n\t\t865:  \"Myexperiment\",\n\t\t866:  \"Copyscape\",\n\t\t867:  \"Besnappy\",\n\t\t868:  \"Salesmate\",\n\t\t869:  \"Heatmapapi\",\n\t\t870:  \"Websitepulse\",\n\t\t871:  \"Uclassify\",\n\t\t872:  \"Convert\",\n\t\t873:  \"PDFmyURL\",\n\t\t874:  \"Api2Convert\",\n\t\t875:  \"Opsgenie\",\n\t\t876:  \"Gemini\",\n\t\t877:  \"Honeycomb\",\n\t\t878:  \"KalturaAppToken\",\n\t\t879:  \"KalturaSession\",\n\t\t880:  \"BitGo\",\n\t\t881:  \"Optidash\",\n\t\t882:  \"Imgix\",\n\t\t883:  \"ImageToText\",\n\t\t884:  \"Page2Images\",\n\t\t885:  \"Quickbase\",\n\t\t886:  \"Redbooth\",\n\t\t887:  \"Nubela\",\n\t\t888:  \"Infobip\",\n\t\t889:  \"Uproc\",\n\t\t890:  \"Supportbee\",\n\t\t891:  \"Aftership\",\n\t\t892:  \"Edusign\",\n\t\t893:  \"Teamup\",\n\t\t894:  \"Workday\",\n\t\t895:  \"MongoDB\",\n\t\t896:  \"NGC\",\n\t\t897:  \"DigitalOceanV2\",\n\t\t898:  \"SQLServer\",\n\t\t899:  \"FTP\",\n\t\t900:  \"Redis\",\n\t\t901:  \"LDAP\",\n\t\t902:  \"Shopify\",\n\t\t903:  \"RabbitMQ\",\n\t\t904:  \"CustomRegex\",\n\t\t905:  \"Etherscan\",\n\t\t906:  \"Infura\",\n\t\t907:  \"Alchemy\",\n\t\t908:  \"BlockNative\",\n\t\t909:  \"Moralis\",\n\t\t910:  \"BscScan\",\n\t\t911:  \"CoinMarketCap\",\n\t\t912:  \"Percy\",\n\t\t913:  \"TinesWebhook\",\n\t\t914:  \"Pulumi\",\n\t\t915:  \"SupabaseToken\",\n\t\t916:  \"NuGetApiKey\",\n\t\t917:  \"Aiven\",\n\t\t918:  \"Prefect\",\n\t\t919:  \"Docusign\",\n\t\t920:  \"Couchbase\",\n\t\t921:  \"Dockerhub\",\n\t\t922:  \"TrufflehogEnterprise\",\n\t\t923:  \"EnvoyApiKey\",\n\t\t924:  \"GitHubOauth2\",\n\t\t925:  \"Salesforce\",\n\t\t926:  \"HuggingFace\",\n\t\t927:  \"Snowflake\",\n\t\t928:  \"Sourcegraph\",\n\t\t929:  \"Tailscale\",\n\t\t930:  \"Web3Storage\",\n\t\t931:  \"AzureStorage\",\n\t\t932:  \"PlanetScaleDb\",\n\t\t933:  \"Anthropic\",\n\t\t934:  \"Ramp\",\n\t\t935:  \"Klaviyo\",\n\t\t936:  \"SourcegraphCody\",\n\t\t937:  \"Voiceflow\",\n\t\t938:  \"Privacy\",\n\t\t939:  \"IPInfo\",\n\t\t940:  \"Ip2location\",\n\t\t941:  \"Instamojo\",\n\t\t942:  \"Portainer\",\n\t\t943:  \"PortainerToken\",\n\t\t944:  \"Loggly\",\n\t\t945:  \"OpenVpn\",\n\t\t946:  \"VagrantCloudPersonalToken\",\n\t\t947:  \"BetterStack\",\n\t\t948:  \"ZeroTier\",\n\t\t949:  \"AppOptics\",\n\t\t950:  \"Metabase\",\n\t\t951:  \"CoinbaseWaaS\",\n\t\t952:  \"LemonSqueezy\",\n\t\t953:  \"Budibase\",\n\t\t954:  \"DenoDeploy\",\n\t\t955:  \"Stripo\",\n\t\t956:  \"ReplyIO\",\n\t\t957:  \"AzureBatch\",\n\t\t958:  \"AzureContainerRegistry\",\n\t\t959:  \"AWSSessionKey\",\n\t\t960:  \"Coda\",\n\t\t961:  \"LogzIO\",\n\t\t962:  \"Eventbrite\",\n\t\t963:  \"GrafanaServiceAccount\",\n\t\t964:  \"RequestFinance\",\n\t\t965:  \"Overloop\",\n\t\t966:  \"Ngrok\",\n\t\t967:  \"Replicate\",\n\t\t968:  \"Postgres\",\n\t\t969:  \"AzureActiveDirectoryApplicationSecret\",\n\t\t970:  \"AzureCacheForRedisAccessKey\",\n\t\t971:  \"AzureCosmosDBKeyIdentifiable\",\n\t\t972:  \"AzureDevopsPersonalAccessToken\",\n\t\t973:  \"AzureFunctionKey\",\n\t\t974:  \"AzureMLWebServiceClassicIdentifiableKey\",\n\t\t975:  \"AzureSasToken\",\n\t\t976:  \"AzureSearchAdminKey\",\n\t\t977:  \"AzureSearchQueryKey\",\n\t\t978:  \"AzureManagementCertificate\",\n\t\t979:  \"AzureSQL\",\n\t\t980:  \"FlyIO\",\n\t\t981:  \"BuiltWith\",\n\t\t982:  \"JupiterOne\",\n\t\t983:  \"GCPApplicationDefaultCredentials\",\n\t\t984:  \"Wiz\",\n\t\t985:  \"Pagarme\",\n\t\t986:  \"Onfleet\",\n\t\t987:  \"Intra42\",\n\t\t988:  \"Groq\",\n\t\t989:  \"TwitterConsumerkey\",\n\t\t990:  \"Eraser\",\n\t\t991:  \"LarkSuite\",\n\t\t992:  \"LarkSuiteApiKey\",\n\t\t993:  \"EndorLabs\",\n\t\t994:  \"ElevenLabs\",\n\t\t995:  \"Netsuite\",\n\t\t996:  \"RobinhoodCrypto\",\n\t\t997:  \"NVAPI\",\n\t\t998:  \"PyPI\",\n\t\t999:  \"RailwayApp\",\n\t\t1000: \"Meraki\",\n\t\t1001: \"SaladCloudApiKey\",\n\t\t1002: \"Box\",\n\t\t1003: \"BoxOauth\",\n\t\t1004: \"ApiMetrics\",\n\t\t1005: \"WeightsAndBiases\",\n\t\t1006: \"ZohoCRM\",\n\t\t1007: \"AzureOpenAI\",\n\t\t1008: \"GoDaddy\",\n\t\t1009: \"Flexport\",\n\t\t1010: \"TwitchAccessToken\",\n\t\t1011: \"TwilioApiKey\",\n\t\t1012: \"Sanity\",\n\t\t1013: \"AzureRefreshToken\",\n\t\t1014: \"AirtableOAuth\",\n\t\t1015: \"AirtablePersonalAccessToken\",\n\t\t1016: \"StoryblokPersonalAccessToken\",\n\t\t1017: \"SentryOrgToken\",\n\t\t1018: \"AzureApiManagementRepositoryKey\",\n\t\t1019: \"AzureAPIManagementSubscriptionKey\",\n\t\t1020: \"Harness\",\n\t\t1021: \"Langfuse\",\n\t\t1022: \"BingSubscriptionKey\",\n\t\t1023: \"XAI\",\n\t\t1024: \"AzureDirectManagementKey\",\n\t\t1025: \"AzureAppConfigConnectionString\",\n\t\t1026: \"DeepSeek\",\n\t\t1027: \"StripePaymentIntent\",\n\t\t1028: \"LangSmith\",\n\t\t1029: \"BitbucketAppPassword\",\n\t\t1030: \"Hasura\",\n\t\t1031: \"SalesforceRefreshToken\",\n\t\t1032: \"AnypointOAuth2\",\n\t\t1033: \"WebexBot\",\n\t\t1034: \"TableauPersonalAccessToken\",\n\t\t1035: \"Rootly\",\n\t\t1036: \"HashiCorpVaultAuth\",\n\t\t1037: \"PhraseAccessToken\",\n\t\t1038: \"Photoroom\",\n\t\t1039: \"JWT\",\n\t\t1040: \"OpenAIAdmin\",\n\t\t1041: \"GoogleGeminiAPIKey\",\n\t\t1042: \"ArtifactoryReferenceToken\",\n\t\t1043: \"DatadogApikey\",\n\t}\n\tDetectorType_value = map[string]int32{\n\t\t\"Alibaba\":                               0,\n\t\t\"AMQP\":                                  1,\n\t\t\"AWS\":                                   2,\n\t\t\"Azure\":                                 3,\n\t\t\"Circle\":                                4,\n\t\t\"Coinbase\":                              5,\n\t\t\"GCP\":                                   6,\n\t\t\"Generic\":                               7,\n\t\t\"Github\":                                8,\n\t\t\"Gitlab\":                                9,\n\t\t\"JDBC\":                                  10,\n\t\t\"RazorPay\":                              11,\n\t\t\"SendGrid\":                              12,\n\t\t\"Slack\":                                 13,\n\t\t\"Square\":                                14,\n\t\t\"PrivateKey\":                            15,\n\t\t\"Stripe\":                                16,\n\t\t\"URI\":                                   17,\n\t\t\"Dropbox\":                               18,\n\t\t\"Heroku\":                                19,\n\t\t\"Mailchimp\":                             20,\n\t\t\"Okta\":                                  21,\n\t\t\"OneLogin\":                              22,\n\t\t\"PivotalTracker\":                        23,\n\t\t\"SquareApp\":                             25,\n\t\t\"Twilio\":                                26,\n\t\t\"Test\":                                  27,\n\t\t\"TravisCI\":                              29,\n\t\t\"SlackWebhook\":                          30,\n\t\t\"PaypalOauth\":                           31,\n\t\t\"PagerDutyApiKey\":                       32,\n\t\t\"Firebase\":                              33,\n\t\t\"Mailgun\":                               34,\n\t\t\"HubSpot\":                               35,\n\t\t\"GitHubApp\":                             36,\n\t\t\"CircleCI\":                              37,\n\t\t\"WpEngine\":                              38,\n\t\t\"DatadogToken\":                          39,\n\t\t\"FacebookOAuth\":                         40,\n\t\t\"AsanaPersonalAccessToken\":              41,\n\t\t\"AmplitudeApiKey\":                       42,\n\t\t\"BitLyAccessToken\":                      43,\n\t\t\"CalendlyApiKey\":                        44,\n\t\t\"ZapierWebhook\":                         45,\n\t\t\"YoutubeApiKey\":                         46,\n\t\t\"SalesforceOauth2\":                      47,\n\t\t\"TwitterApiSecret\":                      48,\n\t\t\"NpmToken\":                              49,\n\t\t\"NewRelicPersonalApiKey\":                50,\n\t\t\"AirtableApiKey\":                        51,\n\t\t\"AkamaiToken\":                           52,\n\t\t\"AmazonMWS\":                             53,\n\t\t\"KubeConfig\":                            54,\n\t\t\"Auth0oauth\":                            55,\n\t\t\"Bitfinex\":                              56,\n\t\t\"Clarifai\":                              57,\n\t\t\"CloudflareGlobalApiKey\":                58,\n\t\t\"CloudflareCaKey\":                       59,\n\t\t\"Confluent\":                             60,\n\t\t\"ContentfulDelivery\":                    61,\n\t\t\"DatabricksToken\":                       62,\n\t\t\"DigitalOceanSpaces\":                    63,\n\t\t\"DigitalOceanToken\":                     64,\n\t\t\"DiscordBotToken\":                       65,\n\t\t\"DiscordWebhook\":                        66,\n\t\t\"EtsyApiKey\":                            67,\n\t\t\"FastlyPersonalToken\":                   68,\n\t\t\"GoogleOauth2\":                          69,\n\t\t\"ReCAPTCHA\":                             70,\n\t\t\"GoogleApiKey\":                          71,\n\t\t\"Hunter\":                                72,\n\t\t\"IbmCloudUserKey\":                       73,\n\t\t\"Netlify\":                               74,\n\t\t\"Vonage\":                                75,\n\t\t\"EquinixOauth\":                          76,\n\t\t\"Paystack\":                              77,\n\t\t\"PlaidToken\":                            78,\n\t\t\"PlaidKey\":                              79,\n\t\t\"Plivo\":                                 80,\n\t\t\"Postmark\":                              81,\n\t\t\"PubNubPublishKey\":                      82,\n\t\t\"PubNubSubscriptionKey\":                 83,\n\t\t\"PusherChannelKey\":                      84,\n\t\t\"ScalewayKey\":                           85,\n\t\t\"SendinBlueV2\":                          86,\n\t\t\"SentryToken\":                           87,\n\t\t\"ShodanKey\":                             88,\n\t\t\"SnykKey\":                               89,\n\t\t\"SpotifyKey\":                            90,\n\t\t\"TelegramBotToken\":                      91,\n\t\t\"TencentCloudKey\":                       92,\n\t\t\"TerraformCloudPersonalToken\":           93,\n\t\t\"TrelloApiKey\":                          94,\n\t\t\"ZendeskApi\":                            95,\n\t\t\"MaxMindLicense\":                        96,\n\t\t\"AirtableMetadataApiKey\":                97,\n\t\t\"AsanaOauth\":                            98,\n\t\t\"RapidApi\":                              99,\n\t\t\"CloudflareApiToken\":                    100,\n\t\t\"Webex\":                                 101,\n\t\t\"FirebaseCloudMessaging\":                102,\n\t\t\"ContentfulPersonalAccessToken\":         103,\n\t\t\"MapBox\":                                104,\n\t\t\"MailJetBasicAuth\":                      105,\n\t\t\"MailJetSMS\":                            106,\n\t\t\"HubSpotApiKey\":                         107,\n\t\t\"HubSpotOauth\":                          108,\n\t\t\"SslMate\":                               109,\n\t\t\"Auth0ManagementApiToken\":               110,\n\t\t\"MessageBird\":                           111,\n\t\t\"ElasticEmail\":                          112,\n\t\t\"FigmaPersonalAccessToken\":              113,\n\t\t\"MicrosoftTeamsWebhook\":                 114,\n\t\t\"GitHubOld\":                             115,\n\t\t\"VultrApiKey\":                           116,\n\t\t\"Pepipost\":                              117,\n\t\t\"Postman\":                               118,\n\t\t\"CloudsightKey\":                         119,\n\t\t\"JiraToken\":                             120,\n\t\t\"NexmoApiKey\":                           121,\n\t\t\"SegmentApiKey\":                         122,\n\t\t\"SumoLogicKey\":                          123,\n\t\t\"PushBulletApiKey\":                      124,\n\t\t\"AirbrakeProjectKey\":                    125,\n\t\t\"AirbrakeUserKey\":                       126,\n\t\t\"PendoIntegrationKey\":                   127,\n\t\t\"SplunkOberservabilityToken\":            128,\n\t\t\"LokaliseToken\":                         129,\n\t\t\"Calendarific\":                          130,\n\t\t\"Jumpcloud\":                             131,\n\t\t\"IpStack\":                               133,\n\t\t\"Notion\":                                134,\n\t\t\"DroneCI\":                               135,\n\t\t\"AdobeIO\":                               136,\n\t\t\"TwelveData\":                            137,\n\t\t\"D7Network\":                             138,\n\t\t\"ScrapingBee\":                           139,\n\t\t\"KeenIO\":                                140,\n\t\t\"Wakatime\":                              141,\n\t\t\"Buildkite\":                             142,\n\t\t\"Verimail\":                              143,\n\t\t\"Zerobounce\":                            144,\n\t\t\"Mailboxlayer\":                          145,\n\t\t\"Fastspring\":                            146,\n\t\t\"Paddle\":                                147,\n\t\t\"Sellfy\":                                148,\n\t\t\"FixerIO\":                               149,\n\t\t\"ButterCMS\":                             150,\n\t\t\"Taxjar\":                                151,\n\t\t\"Avalara\":                               152,\n\t\t\"Helpscout\":                             153,\n\t\t\"ElasticPath\":                           154,\n\t\t\"Zeplin\":                                155,\n\t\t\"Intercom\":                              156,\n\t\t\"Mailmodo\":                              157,\n\t\t\"CannyIo\":                               158,\n\t\t\"Pipedrive\":                             159,\n\t\t\"Vercel\":                                160,\n\t\t\"PosthogApp\":                            161,\n\t\t\"SinchMessage\":                          162,\n\t\t\"Ayrshare\":                              163,\n\t\t\"HelpCrunch\":                            164,\n\t\t\"LiveAgent\":                             165,\n\t\t\"Beamer\":                                166,\n\t\t\"WeChatAppKey\":                          167,\n\t\t\"LineMessaging\":                         168,\n\t\t\"UberServerToken\":                       169,\n\t\t\"AlgoliaAdminKey\":                       170,\n\t\t\"FullContact\":                           171,\n\t\t\"Mandrill\":                              172,\n\t\t\"Flutterwave\":                           173,\n\t\t\"MattermostPersonalToken\":               174,\n\t\t\"Cloudant\":                              175,\n\t\t\"LineNotify\":                            176,\n\t\t\"LinearAPI\":                             177,\n\t\t\"Ubidots\":                               178,\n\t\t\"Anypoint\":                              179,\n\t\t\"Dwolla\":                                180,\n\t\t\"ArtifactoryAccessToken\":                181,\n\t\t\"Surge\":                                 182,\n\t\t\"Sparkpost\":                             183,\n\t\t\"GoCardless\":                            184,\n\t\t\"Codacy\":                                185,\n\t\t\"Kraken\":                                186,\n\t\t\"Checkout\":                              187,\n\t\t\"Kairos\":                                188,\n\t\t\"ClockworkSMS\":                          189,\n\t\t\"Atlassian\":                             190,\n\t\t\"LaunchDarkly\":                          191,\n\t\t\"Coveralls\":                             192,\n\t\t\"Linode\":                                193,\n\t\t\"WePay\":                                 194,\n\t\t\"PlanetScale\":                           195,\n\t\t\"Doppler\":                               196,\n\t\t\"Agora\":                                 197,\n\t\t\"Samsara\":                               198,\n\t\t\"FrameIO\":                               199,\n\t\t\"RubyGems\":                              200,\n\t\t\"OpenAI\":                                201,\n\t\t\"SurveySparrow\":                         202,\n\t\t\"Simvoly\":                               203,\n\t\t\"Survicate\":                             204,\n\t\t\"Omnisend\":                              205,\n\t\t\"Groovehq\":                              206,\n\t\t\"Newsapi\":                               207,\n\t\t\"Chatbot\":                               208,\n\t\t\"ClickSendsms\":                          209,\n\t\t\"Getgist\":                               210,\n\t\t\"CustomerIO\":                            211,\n\t\t\"ApiDeck\":                               212,\n\t\t\"Nftport\":                               213,\n\t\t\"Copper\":                                214,\n\t\t\"Close\":                                 215,\n\t\t\"Myfreshworks\":                          216,\n\t\t\"Salesflare\":                            217,\n\t\t\"Webflow\":                               218,\n\t\t\"Duda\":                                  219,\n\t\t\"Yext\":                                  220,\n\t\t\"ContentStack\":                          221,\n\t\t\"StoryblokAccessToken\":                  222,\n\t\t\"GraphCMS\":                              223,\n\t\t\"Checkmarket\":                           224,\n\t\t\"Convertkit\":                            225,\n\t\t\"CustomerGuru\":                          226,\n\t\t\"Kaleyra\":                               227,\n\t\t\"Mailerlite\":                            228,\n\t\t\"Qualaroo\":                              229,\n\t\t\"SatismeterProjectkey\":                  230,\n\t\t\"SatismeterWritekey\":                    231,\n\t\t\"Simplesat\":                             232,\n\t\t\"SurveyAnyplace\":                        233,\n\t\t\"SurveyBot\":                             234,\n\t\t\"Webengage\":                             235,\n\t\t\"ZonkaFeedback\":                         236,\n\t\t\"Delighted\":                             237,\n\t\t\"Feedier\":                               238,\n\t\t\"Abyssale\":                              239,\n\t\t\"Magnetic\":                              240,\n\t\t\"Nytimes\":                               241,\n\t\t\"Polygon\":                               242,\n\t\t\"Powrbot\":                               243,\n\t\t\"ProspectIO\":                            244,\n\t\t\"Skrappio\":                              245,\n\t\t\"Monday\":                                246,\n\t\t\"Smartsheets\":                           247,\n\t\t\"Wrike\":                                 248,\n\t\t\"Float\":                                 249,\n\t\t\"Imagekit\":                              250,\n\t\t\"Integromat\":                            251,\n\t\t\"Salesblink\":                            252,\n\t\t\"Bored\":                                 253,\n\t\t\"Campayn\":                               254,\n\t\t\"Clinchpad\":                             255,\n\t\t\"CompanyHub\":                            256,\n\t\t\"Debounce\":                              257,\n\t\t\"Dyspatch\":                              258,\n\t\t\"Guardianapi\":                           259,\n\t\t\"Harvest\":                               260,\n\t\t\"Moosend\":                               261,\n\t\t\"OpenWeather\":                           262,\n\t\t\"Siteleaf\":                              263,\n\t\t\"Squarespace\":                           264,\n\t\t\"FlowFlu\":                               265,\n\t\t\"Nimble\":                                266,\n\t\t\"LessAnnoyingCRM\":                       267,\n\t\t\"Nethunt\":                               268,\n\t\t\"Apptivo\":                               269,\n\t\t\"CapsuleCRM\":                            270,\n\t\t\"Insightly\":                             271,\n\t\t\"Kylas\":                                 272,\n\t\t\"OnepageCRM\":                            273,\n\t\t\"User\":                                  274,\n\t\t\"ProspectCRM\":                           275,\n\t\t\"ReallySimpleSystems\":                   276,\n\t\t\"Airship\":                               277,\n\t\t\"Artsy\":                                 278,\n\t\t\"Yandex\":                                279,\n\t\t\"Clockify\":                              280,\n\t\t\"Dnscheck\":                              281,\n\t\t\"EasyInsight\":                           282,\n\t\t\"Ethplorer\":                             283,\n\t\t\"Everhour\":                              284,\n\t\t\"Fulcrum\":                               285,\n\t\t\"GeoIpifi\":                              286,\n\t\t\"Jotform\":                               287,\n\t\t\"Refiner\":                               288,\n\t\t\"Timezoneapi\":                           289,\n\t\t\"TogglTrack\":                            290,\n\t\t\"Vpnapi\":                                291,\n\t\t\"Workstack\":                             292,\n\t\t\"Apollo\":                                293,\n\t\t\"Eversign\":                              294,\n\t\t\"Juro\":                                  295,\n\t\t\"KarmaCRM\":                              296,\n\t\t\"Metrilo\":                               297,\n\t\t\"Pandadoc\":                              298,\n\t\t\"RevampCRM\":                             299,\n\t\t\"Salescookie\":                           300,\n\t\t\"Alconost\":                              301,\n\t\t\"Blogger\":                               302,\n\t\t\"Accuweather\":                           303,\n\t\t\"Opengraphr\":                            304,\n\t\t\"Rawg\":                                  305,\n\t\t\"Riotgames\":                             306,\n\t\t\"Clientary\":                             307,\n\t\t\"Stormglass\":                            308,\n\t\t\"Tomtom\":                                309,\n\t\t\"Twitch\":                                310,\n\t\t\"Documo\":                                311,\n\t\t\"Cloudways\":                             312,\n\t\t\"Veevavault\":                            313,\n\t\t\"KiteConnect\":                           314,\n\t\t\"ShopeeOpenPlatform\":                    315,\n\t\t\"TeamViewer\":                            316,\n\t\t\"Bulbul\":                                317,\n\t\t\"CentralStationCRM\":                     318,\n\t\t\"Teamgate\":                              319,\n\t\t\"Axonaut\":                               320,\n\t\t\"Tyntec\":                                321,\n\t\t\"Appcues\":                               322,\n\t\t\"Autoklose\":                             323,\n\t\t\"Cloudplan\":                             324,\n\t\t\"Dotdigital\":                            325,\n\t\t\"GetEmail\":                              326,\n\t\t\"GetEmails\":                             327,\n\t\t\"Kontent\":                               328,\n\t\t\"Leadfeeder\":                            329,\n\t\t\"Raven\":                                 330,\n\t\t\"RocketReach\":                           331,\n\t\t\"Uplead\":                                332,\n\t\t\"Brandfetch\":                            333,\n\t\t\"Clearbit\":                              334,\n\t\t\"Crowdin\":                               335,\n\t\t\"Mapquest\":                              336,\n\t\t\"Noticeable\":                            337,\n\t\t\"Onbuka\":                                338,\n\t\t\"Todoist\":                               339,\n\t\t\"Storychief\":                            340,\n\t\t\"LinkedIn\":                              341,\n\t\t\"YouSign\":                               342,\n\t\t\"Docker\":                                343,\n\t\t\"Telesign\":                              344,\n\t\t\"Spoonacular\":                           345,\n\t\t\"Aerisweather\":                          346,\n\t\t\"Alphavantage\":                          347,\n\t\t\"Imgur\":                                 348,\n\t\t\"Imagga\":                                349,\n\t\t\"SMSApi\":                                350,\n\t\t\"Distribusion\":                          351,\n\t\t\"Blablabus\":                             352,\n\t\t\"WordsApi\":                              353,\n\t\t\"Currencylayer\":                         354,\n\t\t\"Html2Pdf\":                              355,\n\t\t\"IPGeolocation\":                         356,\n\t\t\"Owlbot\":                                357,\n\t\t\"Cloudmersive\":                          358,\n\t\t\"Dynalist\":                              359,\n\t\t\"ExchangeRateAPI\":                       360,\n\t\t\"HolidayAPI\":                            361,\n\t\t\"Ipapi\":                                 362,\n\t\t\"Marketstack\":                           363,\n\t\t\"Nutritionix\":                           364,\n\t\t\"Swell\":                                 365,\n\t\t\"ClickupPersonalToken\":                  366,\n\t\t\"Nitro\":                                 367,\n\t\t\"Rev\":                                   368,\n\t\t\"RunRunIt\":                              369,\n\t\t\"Typeform\":                              370,\n\t\t\"Mixpanel\":                              371,\n\t\t\"Tradier\":                               372,\n\t\t\"Verifier\":                              373,\n\t\t\"Vouchery\":                              374,\n\t\t\"Alegra\":                                375,\n\t\t\"Audd\":                                  376,\n\t\t\"Baremetrics\":                           377,\n\t\t\"Coinlib\":                               378,\n\t\t\"ExchangeRatesAPI\":                      379,\n\t\t\"CurrencyScoop\":                         380,\n\t\t\"FXMarket\":                              381,\n\t\t\"CurrencyCloud\":                         382,\n\t\t\"GetGeoAPI\":                             383,\n\t\t\"Abstract\":                              384,\n\t\t\"Billomat\":                              385,\n\t\t\"Dovico\":                                386,\n\t\t\"Bitbar\":                                387,\n\t\t\"Bugsnag\":                               388,\n\t\t\"AssemblyAI\":                            389,\n\t\t\"AdafruitIO\":                            390,\n\t\t\"Apify\":                                 391,\n\t\t\"CoinGecko\":                             392,\n\t\t\"CryptoCompare\":                         393,\n\t\t\"Fullstory\":                             394,\n\t\t\"HelloSign\":                             395,\n\t\t\"Loyverse\":                              396,\n\t\t\"NetCore\":                               397,\n\t\t\"SauceLabs\":                             398,\n\t\t\"AlienVault\":                            399,\n\t\t\"Apiflash\":                              401,\n\t\t\"Coinlayer\":                             402,\n\t\t\"CurrentsAPI\":                           403,\n\t\t\"DataGov\":                               404,\n\t\t\"Enigma\":                                405,\n\t\t\"FinancialModelingPrep\":                 406,\n\t\t\"Geocodio\":                              407,\n\t\t\"HereAPI\":                               408,\n\t\t\"Macaddress\":                            409,\n\t\t\"OOPSpam\":                               410,\n\t\t\"ProtocolsIO\":                           411,\n\t\t\"ScraperAPI\":                            412,\n\t\t\"SecurityTrails\":                        413,\n\t\t\"TomorrowIO\":                            414,\n\t\t\"WorldCoinIndex\":                        415,\n\t\t\"FacePlusPlus\":                          416,\n\t\t\"Voicegain\":                             417,\n\t\t\"Deepgram\":                              418,\n\t\t\"VisualCrossing\":                        419,\n\t\t\"Finnhub\":                               420,\n\t\t\"Tiingo\":                                421,\n\t\t\"RingCentral\":                           422,\n\t\t\"Finage\":                                423,\n\t\t\"Edamam\":                                424,\n\t\t\"HypeAuditor\":                           425,\n\t\t\"Gengo\":                                 426,\n\t\t\"Front\":                                 427,\n\t\t\"Fleetbase\":                             428,\n\t\t\"Bubble\":                                429,\n\t\t\"Bannerbear\":                            430,\n\t\t\"Adzuna\":                                431,\n\t\t\"BitcoinAverage\":                        432,\n\t\t\"CommerceJS\":                            433,\n\t\t\"DetectLanguage\":                        434,\n\t\t\"FakeJSON\":                              435,\n\t\t\"Graphhopper\":                           436,\n\t\t\"Lexigram\":                              437,\n\t\t\"LinkPreview\":                           438,\n\t\t\"Numverify\":                             439,\n\t\t\"ProxyCrawl\":                            440,\n\t\t\"ZipCodeAPI\":                            441,\n\t\t\"Cometchat\":                             442,\n\t\t\"Keygen\":                                443,\n\t\t\"Mixcloud\":                              444,\n\t\t\"TatumIO\":                               445,\n\t\t\"Tmetric\":                               446,\n\t\t\"Lastfm\":                                447,\n\t\t\"Browshot\":                              448,\n\t\t\"JSONbin\":                               449,\n\t\t\"LocationIQ\":                            450,\n\t\t\"ScreenshotAPI\":                         451,\n\t\t\"WeatherStack\":                          452,\n\t\t\"Amadeus\":                               453,\n\t\t\"FourSquare\":                            454,\n\t\t\"Flickr\":                                455,\n\t\t\"ClickHelp\":                             456,\n\t\t\"Ambee\":                                 457,\n\t\t\"Api2Cart\":                              458,\n\t\t\"Hypertrack\":                            459,\n\t\t\"KakaoTalk\":                             460,\n\t\t\"RiteKit\":                               461,\n\t\t\"Shutterstock\":                          462,\n\t\t\"Text2Data\":                             463,\n\t\t\"YouNeedABudget\":                        464,\n\t\t\"Cricket\":                               465,\n\t\t\"Filestack\":                             466,\n\t\t\"Gyazo\":                                 467,\n\t\t\"Mavenlink\":                             468,\n\t\t\"Sheety\":                                469,\n\t\t\"Sportsmonk\":                            470,\n\t\t\"Stockdata\":                             471,\n\t\t\"Unsplash\":                              472,\n\t\t\"Allsports\":                             473,\n\t\t\"CalorieNinja\":                          474,\n\t\t\"WalkScore\":                             475,\n\t\t\"Strava\":                                476,\n\t\t\"Cicero\":                                477,\n\t\t\"IPQuality\":                             478,\n\t\t\"ParallelDots\":                          479,\n\t\t\"Roaring\":                               480,\n\t\t\"Mailsac\":                               481,\n\t\t\"Whoxy\":                                 482,\n\t\t\"WorldWeather\":                          483,\n\t\t\"ApiFonica\":                             484,\n\t\t\"Aylien\":                                485,\n\t\t\"Geocode\":                               486,\n\t\t\"IconFinder\":                            487,\n\t\t\"Ipify\":                                 488,\n\t\t\"LanguageLayer\":                         489,\n\t\t\"Lob\":                                   490,\n\t\t\"OnWaterIO\":                             491,\n\t\t\"Pastebin\":                              492,\n\t\t\"PdfLayer\":                              493,\n\t\t\"Pixabay\":                               494,\n\t\t\"ReadMe\":                                495,\n\t\t\"VatLayer\":                              496,\n\t\t\"VirusTotal\":                            497,\n\t\t\"AirVisual\":                             498,\n\t\t\"Currencyfreaks\":                        499,\n\t\t\"Duffel\":                                500,\n\t\t\"FlatIO\":                                501,\n\t\t\"M3o\":                                   502,\n\t\t\"Mesibo\":                                503,\n\t\t\"Openuv\":                                504,\n\t\t\"Snipcart\":                              505,\n\t\t\"Besttime\":                              506,\n\t\t\"Happyscribe\":                           507,\n\t\t\"Humanity\":                              508,\n\t\t\"Impala\":                                509,\n\t\t\"Loginradius\":                           510,\n\t\t\"AutoPilot\":                             511,\n\t\t\"Bitmex\":                                512,\n\t\t\"ClustDoc\":                              513,\n\t\t\"Messari\":                               514,\n\t\t\"PdfShift\":                              515,\n\t\t\"Poloniex\":                              516,\n\t\t\"RestpackHtmlToPdfAPI\":                  517,\n\t\t\"RestpackScreenshotAPI\":                 518,\n\t\t\"ShutterstockOAuth\":                     519,\n\t\t\"SkyBiometry\":                           520,\n\t\t\"AbuseIPDB\":                             521,\n\t\t\"AletheiaApi\":                           522,\n\t\t\"BlitApp\":                               523,\n\t\t\"Censys\":                                524,\n\t\t\"Cloverly\":                              525,\n\t\t\"CountryLayer\":                          526,\n\t\t\"FileIO\":                                527,\n\t\t\"FlightApi\":                             528,\n\t\t\"Geoapify\":                              529,\n\t\t\"IPinfoDB\":                              530,\n\t\t\"MediaStack\":                            531,\n\t\t\"NasdaqDataLink\":                        532,\n\t\t\"OpenCageData\":                          533,\n\t\t\"Paymongo\":                              534,\n\t\t\"PositionStack\":                         535,\n\t\t\"Rebrandly\":                             536,\n\t\t\"ScreenshotLayer\":                       537,\n\t\t\"Stytch\":                                538,\n\t\t\"Unplugg\":                               539,\n\t\t\"UPCDatabase\":                           540,\n\t\t\"UserStack\":                             541,\n\t\t\"Geocodify\":                             542,\n\t\t\"Newscatcher\":                           543,\n\t\t\"Nicereply\":                             544,\n\t\t\"Partnerstack\":                          545,\n\t\t\"Route4me\":                              546,\n\t\t\"Scrapeowl\":                             547,\n\t\t\"ScrapingDog\":                           548,\n\t\t\"Streak\":                                549,\n\t\t\"Veriphone\":                             550,\n\t\t\"Webscraping\":                           551,\n\t\t\"Zenscrape\":                             552,\n\t\t\"Zenserp\":                               553,\n\t\t\"CoinApi\":                               554,\n\t\t\"Gitter\":                                555,\n\t\t\"Host\":                                  556,\n\t\t\"Iexcloud\":                              557,\n\t\t\"Restpack\":                              558,\n\t\t\"ScraperBox\":                            559,\n\t\t\"ScrapingAnt\":                           560,\n\t\t\"SerpStack\":                             561,\n\t\t\"SmartyStreets\":                         562,\n\t\t\"TicketMaster\":                          563,\n\t\t\"AviationStack\":                         564,\n\t\t\"BombBomb\":                              565,\n\t\t\"Commodities\":                           566,\n\t\t\"Dfuse\":                                 567,\n\t\t\"EdenAI\":                                568,\n\t\t\"Glassnode\":                             569,\n\t\t\"Guru\":                                  570,\n\t\t\"Hive\":                                  571,\n\t\t\"Hiveage\":                               572,\n\t\t\"Kickbox\":                               573,\n\t\t\"Passbase\":                              574,\n\t\t\"PostageApp\":                            575,\n\t\t\"PureStake\":                             576,\n\t\t\"Qubole\":                                577,\n\t\t\"CarbonInterface\":                       578,\n\t\t\"Intrinio\":                              579,\n\t\t\"QuickMetrics\":                          580,\n\t\t\"ScrapeStack\":                           581,\n\t\t\"TechnicalAnalysisApi\":                  582,\n\t\t\"Urlscan\":                               583,\n\t\t\"BaseApiIO\":                             584,\n\t\t\"DailyCO\":                               585,\n\t\t\"TLy\":                                   586,\n\t\t\"Shortcut\":                              587,\n\t\t\"Appfollow\":                             588,\n\t\t\"Thinkific\":                             589,\n\t\t\"Feedly\":                                590,\n\t\t\"Stitchdata\":                            591,\n\t\t\"Fetchrss\":                              592,\n\t\t\"Signupgenius\":                          593,\n\t\t\"Signaturit\":                            594,\n\t\t\"Optimizely\":                            595,\n\t\t\"OcrSpace\":                              596,\n\t\t\"WeatherBit\":                            597,\n\t\t\"BuddyNS\":                               598,\n\t\t\"ZipAPI\":                                599,\n\t\t\"ZipBooks\":                              600,\n\t\t\"Onedesk\":                               601,\n\t\t\"Bugherd\":                               602,\n\t\t\"Blazemeter\":                            603,\n\t\t\"Autodesk\":                              604,\n\t\t\"Tru\":                                   605,\n\t\t\"UnifyID\":                               606,\n\t\t\"Trimble\":                               607,\n\t\t\"Smooch\":                                608,\n\t\t\"Semaphore\":                             609,\n\t\t\"Telnyx\":                                610,\n\t\t\"Signalwire\":                            611,\n\t\t\"Textmagic\":                             612,\n\t\t\"Serphouse\":                             613,\n\t\t\"Planyo\":                                614,\n\t\t\"Simplybook\":                            615,\n\t\t\"Vyte\":                                  616,\n\t\t\"Nylas\":                                 617,\n\t\t\"Squareup\":                              618,\n\t\t\"Dandelion\":                             619,\n\t\t\"DataFire\":                              620,\n\t\t\"DeepAI\":                                621,\n\t\t\"MeaningCloud\":                          622,\n\t\t\"NeutrinoApi\":                           623,\n\t\t\"Storecove\":                             624,\n\t\t\"Shipday\":                               625,\n\t\t\"Sentiment\":                             626,\n\t\t\"StreamChatMessaging\":                   627,\n\t\t\"TeamworkCRM\":                           628,\n\t\t\"TeamworkDesk\":                          629,\n\t\t\"TeamworkSpaces\":                        630,\n\t\t\"TheOddsApi\":                            631,\n\t\t\"Apacta\":                                632,\n\t\t\"GetSandbox\":                            633,\n\t\t\"Happi\":                                 634,\n\t\t\"Oanda\":                                 635,\n\t\t\"FastForex\":                             636,\n\t\t\"APIMatic\":                              637,\n\t\t\"VersionEye\":                            638,\n\t\t\"EagleEyeNetworks\":                      639,\n\t\t\"ThousandEyes\":                          640,\n\t\t\"SelectPDF\":                             641,\n\t\t\"Flightstats\":                           642,\n\t\t\"ChecIO\":                                643,\n\t\t\"Manifest\":                              644,\n\t\t\"ApiScience\":                            645,\n\t\t\"AppSynergy\":                            646,\n\t\t\"Caflou\":                                647,\n\t\t\"Caspio\":                                648,\n\t\t\"ChecklyHQ\":                             649,\n\t\t\"CloudElements\":                         650,\n\t\t\"DronaHQ\":                               651,\n\t\t\"Enablex\":                               652,\n\t\t\"Fmfw\":                                  653,\n\t\t\"GoodDay\":                               654,\n\t\t\"Luno\":                                  655,\n\t\t\"Meistertask\":                           656,\n\t\t\"Mindmeister\":                           657,\n\t\t\"PeopleDataLabs\":                        658,\n\t\t\"ScraperSite\":                           659,\n\t\t\"Scrapfly\":                              660,\n\t\t\"SimplyNoted\":                           661,\n\t\t\"TravelPayouts\":                         662,\n\t\t\"WebScraper\":                            663,\n\t\t\"Convier\":                               664,\n\t\t\"Courier\":                               665,\n\t\t\"Ditto\":                                 666,\n\t\t\"Findl\":                                 667,\n\t\t\"Lendflow\":                              668,\n\t\t\"Moderation\":                            669,\n\t\t\"Opendatasoft\":                          670,\n\t\t\"Podio\":                                 671,\n\t\t\"Rockset\":                               672,\n\t\t\"Rownd\":                                 673,\n\t\t\"Shotstack\":                             674,\n\t\t\"Swiftype\":                              675,\n\t\t\"Twitter\":                               676,\n\t\t\"Honey\":                                 677,\n\t\t\"Freshdesk\":                             678,\n\t\t\"Upwave\":                                679,\n\t\t\"Fountain\":                              680,\n\t\t\"Freshbooks\":                            681,\n\t\t\"Mite\":                                  682,\n\t\t\"Deputy\":                                683,\n\t\t\"Beebole\":                               684,\n\t\t\"Cashboard\":                             685,\n\t\t\"Kanban\":                                686,\n\t\t\"Worksnaps\":                             687,\n\t\t\"MyIntervals\":                           688,\n\t\t\"InvoiceOcean\":                          689,\n\t\t\"Sherpadesk\":                            690,\n\t\t\"Mrticktock\":                            691,\n\t\t\"Chatfule\":                              692,\n\t\t\"Aeroworkflow\":                          693,\n\t\t\"Emailoctopus\":                          694,\n\t\t\"Fusebill\":                              695,\n\t\t\"Geckoboard\":                            696,\n\t\t\"Gosquared\":                             697,\n\t\t\"Moonclerk\":                             698,\n\t\t\"Paymoapp\":                              699,\n\t\t\"Mixmax\":                                700,\n\t\t\"Processst\":                             701,\n\t\t\"Repairshopr\":                           702,\n\t\t\"Goshippo\":                              703,\n\t\t\"Sigopt\":                                704,\n\t\t\"Sugester\":                              705,\n\t\t\"Viewneo\":                               706,\n\t\t\"BoostNote\":                             707,\n\t\t\"CaptainData\":                           708,\n\t\t\"Checkvist\":                             709,\n\t\t\"Cliengo\":                               710,\n\t\t\"Cloze\":                                 711,\n\t\t\"FormIO\":                                712,\n\t\t\"FormBucket\":                            713,\n\t\t\"GoCanvas\":                              714,\n\t\t\"MadKudu\":                               715,\n\t\t\"NozbeTeams\":                            716,\n\t\t\"Papyrs\":                                717,\n\t\t\"SuperNotesAPI\":                         718,\n\t\t\"Tallyfy\":                               719,\n\t\t\"ZenkitAPI\":                             720,\n\t\t\"CloudImage\":                            721,\n\t\t\"UploadCare\":                            722,\n\t\t\"Borgbase\":                              723,\n\t\t\"Pipedream\":                             724,\n\t\t\"Sirv\":                                  725,\n\t\t\"Diffbot\":                               726,\n\t\t\"EightxEight\":                           727,\n\t\t\"Sendoso\":                               728,\n\t\t\"Printfection\":                          729,\n\t\t\"Authorize\":                             730,\n\t\t\"PandaScore\":                            731,\n\t\t\"Paymo\":                                 732,\n\t\t\"AvazaPersonalAccessToken\":              733,\n\t\t\"PlanviewLeanKit\":                       734,\n\t\t\"Livestorm\":                             735,\n\t\t\"KuCoin\":                                736,\n\t\t\"MetaAPI\":                               737,\n\t\t\"NiceHash\":                              738,\n\t\t\"CexIO\":                                 739,\n\t\t\"Klipfolio\":                             740,\n\t\t\"Dynatrace\":                             741,\n\t\t\"MollieAPIKey\":                          742,\n\t\t\"MollieAccessToken\":                     743,\n\t\t\"BasisTheory\":                           744,\n\t\t\"Nordigen\":                              745,\n\t\t\"FlagsmithEnvironmentKey\":               746,\n\t\t\"FlagsmithToken\":                        747,\n\t\t\"Mux\":                                   748,\n\t\t\"Column\":                                749,\n\t\t\"Sendbird\":                              750,\n\t\t\"SendbirdOrganizationAPI\":               751,\n\t\t\"Midise\":                                752,\n\t\t\"Mockaroo\":                              753,\n\t\t\"Image4\":                                754,\n\t\t\"Pinata\":                                755,\n\t\t\"BrowserStack\":                          756,\n\t\t\"CrossBrowserTesting\":                   757,\n\t\t\"Loadmill\":                              758,\n\t\t\"TestingBot\":                            759,\n\t\t\"KnapsackPro\":                           760,\n\t\t\"Qase\":                                  761,\n\t\t\"Dareboost\":                             762,\n\t\t\"GTMetrix\":                              763,\n\t\t\"Holistic\":                              764,\n\t\t\"Parsers\":                               765,\n\t\t\"ScrutinizerCi\":                         766,\n\t\t\"SonarCloud\":                            767,\n\t\t\"APITemplate\":                           768,\n\t\t\"ConversionTools\":                       769,\n\t\t\"CraftMyPDF\":                            770,\n\t\t\"ExportSDK\":                             771,\n\t\t\"GlitterlyAPI\":                          772,\n\t\t\"Hybiscus\":                              773,\n\t\t\"Miro\":                                  774,\n\t\t\"Statuspage\":                            775,\n\t\t\"Statuspal\":                             776,\n\t\t\"Teletype\":                              777,\n\t\t\"TimeCamp\":                              778,\n\t\t\"Userflow\":                              779,\n\t\t\"Wistia\":                                780,\n\t\t\"SportRadar\":                            781,\n\t\t\"UptimeRobot\":                           782,\n\t\t\"Codequiry\":                             783,\n\t\t\"ExtractorAPI\":                          784,\n\t\t\"Signable\":                              785,\n\t\t\"MagicBell\":                             786,\n\t\t\"Stormboard\":                            787,\n\t\t\"Apilayer\":                              788,\n\t\t\"Disqus\":                                789,\n\t\t\"Woopra\":                                790,\n\t\t\"Paperform\":                             791,\n\t\t\"Gumroad\":                               792,\n\t\t\"Paydirtapp\":                            793,\n\t\t\"Detectify\":                             794,\n\t\t\"Statuscake\":                            795,\n\t\t\"Jumpseller\":                            796,\n\t\t\"LunchMoney\":                            797,\n\t\t\"Rosette\":                               798,\n\t\t\"Yelp\":                                  799,\n\t\t\"Atera\":                                 800,\n\t\t\"EcoStruxureIT\":                         801,\n\t\t\"Aha\":                                   802,\n\t\t\"Parsehub\":                              803,\n\t\t\"PackageCloud\":                          804,\n\t\t\"Cloudsmith\":                            805,\n\t\t\"Flowdash\":                              806,\n\t\t\"Flowdock\":                              807,\n\t\t\"Fibery\":                                808,\n\t\t\"Typetalk\":                              809,\n\t\t\"VoodooSMS\":                             810,\n\t\t\"ZulipChat\":                             811,\n\t\t\"Formcraft\":                             812,\n\t\t\"Iexapis\":                               813,\n\t\t\"Reachmail\":                             814,\n\t\t\"Chartmogul\":                            815,\n\t\t\"Appointedd\":                            816,\n\t\t\"Wit\":                                   817,\n\t\t\"RechargePayments\":                      818,\n\t\t\"Diggernaut\":                            819,\n\t\t\"MonkeyLearn\":                           820,\n\t\t\"Duply\":                                 821,\n\t\t\"Postbacks\":                             822,\n\t\t\"Collect2\":                              823,\n\t\t\"ZenRows\":                               824,\n\t\t\"Zipcodebase\":                           825,\n\t\t\"Tefter\":                                826,\n\t\t\"Twist\":                                 827,\n\t\t\"BraintreePayments\":                     828,\n\t\t\"CloudConvert\":                          829,\n\t\t\"Grafana\":                               830,\n\t\t\"ConvertApi\":                            831,\n\t\t\"Transferwise\":                          832,\n\t\t\"Bulksms\":                               833,\n\t\t\"Databox\":                               834,\n\t\t\"Onesignal\":                             835,\n\t\t\"Rentman\":                               836,\n\t\t\"Parseur\":                               837,\n\t\t\"Docparser\":                             838,\n\t\t\"Formsite\":                              839,\n\t\t\"Tickettailor\":                          840,\n\t\t\"Lemlist\":                               841,\n\t\t\"Prodpad\":                               842,\n\t\t\"Formstack\":                             843,\n\t\t\"Codeclimate\":                           844,\n\t\t\"Codemagic\":                             845,\n\t\t\"Vbout\":                                 846,\n\t\t\"Nightfall\":                             847,\n\t\t\"FlightLabs\":                            848,\n\t\t\"SpeechTextAI\":                          849,\n\t\t\"PollsAPI\":                              850,\n\t\t\"SimFin\":                                851,\n\t\t\"Scalr\":                                 852,\n\t\t\"Kanbantool\":                            853,\n\t\t\"Brightlocal\":                           854,\n\t\t\"Hotwire\":                               855,\n\t\t\"Instabot\":                              856,\n\t\t\"Timekit\":                               857,\n\t\t\"Interseller\":                           858,\n\t\t\"Mojohelpdesk\":                          859,\n\t\t\"Createsend\":                            860,\n\t\t\"Getresponse\":                           861,\n\t\t\"Dynadot\":                               862,\n\t\t\"Demio\":                                 863,\n\t\t\"Tokeet\":                                864,\n\t\t\"Myexperiment\":                          865,\n\t\t\"Copyscape\":                             866,\n\t\t\"Besnappy\":                              867,\n\t\t\"Salesmate\":                             868,\n\t\t\"Heatmapapi\":                            869,\n\t\t\"Websitepulse\":                          870,\n\t\t\"Uclassify\":                             871,\n\t\t\"Convert\":                               872,\n\t\t\"PDFmyURL\":                              873,\n\t\t\"Api2Convert\":                           874,\n\t\t\"Opsgenie\":                              875,\n\t\t\"Gemini\":                                876,\n\t\t\"Honeycomb\":                             877,\n\t\t\"KalturaAppToken\":                       878,\n\t\t\"KalturaSession\":                        879,\n\t\t\"BitGo\":                                 880,\n\t\t\"Optidash\":                              881,\n\t\t\"Imgix\":                                 882,\n\t\t\"ImageToText\":                           883,\n\t\t\"Page2Images\":                           884,\n\t\t\"Quickbase\":                             885,\n\t\t\"Redbooth\":                              886,\n\t\t\"Nubela\":                                887,\n\t\t\"Infobip\":                               888,\n\t\t\"Uproc\":                                 889,\n\t\t\"Supportbee\":                            890,\n\t\t\"Aftership\":                             891,\n\t\t\"Edusign\":                               892,\n\t\t\"Teamup\":                                893,\n\t\t\"Workday\":                               894,\n\t\t\"MongoDB\":                               895,\n\t\t\"NGC\":                                   896,\n\t\t\"DigitalOceanV2\":                        897,\n\t\t\"SQLServer\":                             898,\n\t\t\"FTP\":                                   899,\n\t\t\"Redis\":                                 900,\n\t\t\"LDAP\":                                  901,\n\t\t\"Shopify\":                               902,\n\t\t\"RabbitMQ\":                              903,\n\t\t\"CustomRegex\":                           904,\n\t\t\"Etherscan\":                             905,\n\t\t\"Infura\":                                906,\n\t\t\"Alchemy\":                               907,\n\t\t\"BlockNative\":                           908,\n\t\t\"Moralis\":                               909,\n\t\t\"BscScan\":                               910,\n\t\t\"CoinMarketCap\":                         911,\n\t\t\"Percy\":                                 912,\n\t\t\"TinesWebhook\":                          913,\n\t\t\"Pulumi\":                                914,\n\t\t\"SupabaseToken\":                         915,\n\t\t\"NuGetApiKey\":                           916,\n\t\t\"Aiven\":                                 917,\n\t\t\"Prefect\":                               918,\n\t\t\"Docusign\":                              919,\n\t\t\"Couchbase\":                             920,\n\t\t\"Dockerhub\":                             921,\n\t\t\"TrufflehogEnterprise\":                  922,\n\t\t\"EnvoyApiKey\":                           923,\n\t\t\"GitHubOauth2\":                          924,\n\t\t\"Salesforce\":                            925,\n\t\t\"HuggingFace\":                           926,\n\t\t\"Snowflake\":                             927,\n\t\t\"Sourcegraph\":                           928,\n\t\t\"Tailscale\":                             929,\n\t\t\"Web3Storage\":                           930,\n\t\t\"AzureStorage\":                          931,\n\t\t\"PlanetScaleDb\":                         932,\n\t\t\"Anthropic\":                             933,\n\t\t\"Ramp\":                                  934,\n\t\t\"Klaviyo\":                               935,\n\t\t\"SourcegraphCody\":                       936,\n\t\t\"Voiceflow\":                             937,\n\t\t\"Privacy\":                               938,\n\t\t\"IPInfo\":                                939,\n\t\t\"Ip2location\":                           940,\n\t\t\"Instamojo\":                             941,\n\t\t\"Portainer\":                             942,\n\t\t\"PortainerToken\":                        943,\n\t\t\"Loggly\":                                944,\n\t\t\"OpenVpn\":                               945,\n\t\t\"VagrantCloudPersonalToken\":             946,\n\t\t\"BetterStack\":                           947,\n\t\t\"ZeroTier\":                              948,\n\t\t\"AppOptics\":                             949,\n\t\t\"Metabase\":                              950,\n\t\t\"CoinbaseWaaS\":                          951,\n\t\t\"LemonSqueezy\":                          952,\n\t\t\"Budibase\":                              953,\n\t\t\"DenoDeploy\":                            954,\n\t\t\"Stripo\":                                955,\n\t\t\"ReplyIO\":                               956,\n\t\t\"AzureBatch\":                            957,\n\t\t\"AzureContainerRegistry\":                958,\n\t\t\"AWSSessionKey\":                         959,\n\t\t\"Coda\":                                  960,\n\t\t\"LogzIO\":                                961,\n\t\t\"Eventbrite\":                            962,\n\t\t\"GrafanaServiceAccount\":                 963,\n\t\t\"RequestFinance\":                        964,\n\t\t\"Overloop\":                              965,\n\t\t\"Ngrok\":                                 966,\n\t\t\"Replicate\":                             967,\n\t\t\"Postgres\":                              968,\n\t\t\"AzureActiveDirectoryApplicationSecret\": 969,\n\t\t\"AzureCacheForRedisAccessKey\":           970,\n\t\t\"AzureCosmosDBKeyIdentifiable\":          971,\n\t\t\"AzureDevopsPersonalAccessToken\":        972,\n\t\t\"AzureFunctionKey\":                      973,\n\t\t\"AzureMLWebServiceClassicIdentifiableKey\": 974,\n\t\t\"AzureSasToken\":                     975,\n\t\t\"AzureSearchAdminKey\":               976,\n\t\t\"AzureSearchQueryKey\":               977,\n\t\t\"AzureManagementCertificate\":        978,\n\t\t\"AzureSQL\":                          979,\n\t\t\"FlyIO\":                             980,\n\t\t\"BuiltWith\":                         981,\n\t\t\"JupiterOne\":                        982,\n\t\t\"GCPApplicationDefaultCredentials\":  983,\n\t\t\"Wiz\":                               984,\n\t\t\"Pagarme\":                           985,\n\t\t\"Onfleet\":                           986,\n\t\t\"Intra42\":                           987,\n\t\t\"Groq\":                              988,\n\t\t\"TwitterConsumerkey\":                989,\n\t\t\"Eraser\":                            990,\n\t\t\"LarkSuite\":                         991,\n\t\t\"LarkSuiteApiKey\":                   992,\n\t\t\"EndorLabs\":                         993,\n\t\t\"ElevenLabs\":                        994,\n\t\t\"Netsuite\":                          995,\n\t\t\"RobinhoodCrypto\":                   996,\n\t\t\"NVAPI\":                             997,\n\t\t\"PyPI\":                              998,\n\t\t\"RailwayApp\":                        999,\n\t\t\"Meraki\":                            1000,\n\t\t\"SaladCloudApiKey\":                  1001,\n\t\t\"Box\":                               1002,\n\t\t\"BoxOauth\":                          1003,\n\t\t\"ApiMetrics\":                        1004,\n\t\t\"WeightsAndBiases\":                  1005,\n\t\t\"ZohoCRM\":                           1006,\n\t\t\"AzureOpenAI\":                       1007,\n\t\t\"GoDaddy\":                           1008,\n\t\t\"Flexport\":                          1009,\n\t\t\"TwitchAccessToken\":                 1010,\n\t\t\"TwilioApiKey\":                      1011,\n\t\t\"Sanity\":                            1012,\n\t\t\"AzureRefreshToken\":                 1013,\n\t\t\"AirtableOAuth\":                     1014,\n\t\t\"AirtablePersonalAccessToken\":       1015,\n\t\t\"StoryblokPersonalAccessToken\":      1016,\n\t\t\"SentryOrgToken\":                    1017,\n\t\t\"AzureApiManagementRepositoryKey\":   1018,\n\t\t\"AzureAPIManagementSubscriptionKey\": 1019,\n\t\t\"Harness\":                           1020,\n\t\t\"Langfuse\":                          1021,\n\t\t\"BingSubscriptionKey\":               1022,\n\t\t\"XAI\":                               1023,\n\t\t\"AzureDirectManagementKey\":          1024,\n\t\t\"AzureAppConfigConnectionString\":    1025,\n\t\t\"DeepSeek\":                          1026,\n\t\t\"StripePaymentIntent\":               1027,\n\t\t\"LangSmith\":                         1028,\n\t\t\"BitbucketAppPassword\":              1029,\n\t\t\"Hasura\":                            1030,\n\t\t\"SalesforceRefreshToken\":            1031,\n\t\t\"AnypointOAuth2\":                    1032,\n\t\t\"WebexBot\":                          1033,\n\t\t\"TableauPersonalAccessToken\":        1034,\n\t\t\"Rootly\":                            1035,\n\t\t\"HashiCorpVaultAuth\":                1036,\n\t\t\"PhraseAccessToken\":                 1037,\n\t\t\"Photoroom\":                         1038,\n\t\t\"JWT\":                               1039,\n\t\t\"OpenAIAdmin\":                       1040,\n\t\t\"GoogleGeminiAPIKey\":                1041,\n\t\t\"ArtifactoryReferenceToken\":         1042,\n\t\t\"DatadogApikey\":                     1043,\n\t}\n)\n\nfunc (x DetectorType) Enum() *DetectorType {\n\tp := new(DetectorType)\n\t*p = x\n\treturn p\n}\n\nfunc (x DetectorType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (DetectorType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_detectors_proto_enumTypes[1].Descriptor()\n}\n\nfunc (DetectorType) Type() protoreflect.EnumType {\n\treturn &file_detectors_proto_enumTypes[1]\n}\n\nfunc (x DetectorType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use DetectorType.Descriptor instead.\nfunc (DetectorType) EnumDescriptor() ([]byte, []int) {\n\treturn file_detectors_proto_rawDescGZIP(), []int{1}\n}\n\ntype Result struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tSourceId       int64             `protobuf:\"varint,2,opt,name=source_id,json=sourceId,proto3\" json:\"source_id,omitempty\"`\n\tRedacted       string            `protobuf:\"bytes,3,opt,name=redacted,proto3\" json:\"redacted,omitempty\"`\n\tVerified       bool              `protobuf:\"varint,4,opt,name=verified,proto3\" json:\"verified,omitempty\"`\n\tHash           string            `protobuf:\"bytes,5,opt,name=hash,proto3\" json:\"hash,omitempty\"`\n\tExtraData      map[string]string `protobuf:\"bytes,6,rep,name=extra_data,json=extraData,proto3\" json:\"extra_data,omitempty\" protobuf_key:\"bytes,1,opt,name=key,proto3\" protobuf_val:\"bytes,2,opt,name=value,proto3\"`\n\tStructuredData *StructuredData   `protobuf:\"bytes,7,opt,name=structured_data,json=structuredData,proto3\" json:\"structured_data,omitempty\"`\n\tHashV2         string            `protobuf:\"bytes,8,opt,name=hash_v2,json=hashV2,proto3\" json:\"hash_v2,omitempty\"`\n\tDecoderType    DecoderType       `protobuf:\"varint,9,opt,name=decoder_type,json=decoderType,proto3,enum=detectors.DecoderType\" json:\"decoder_type,omitempty\"`\n\t// This field should only be populated if the verification process itself failed in a way that provides no information\n\t// about the verification status of the candidate secret, such as if the verification request timed out.\n\tVerificationErrorMessage string             `protobuf:\"bytes,10,opt,name=verification_error_message,json=verificationErrorMessage,proto3\" json:\"verification_error_message,omitempty\"`\n\tFalsePositiveInfo        *FalsePositiveInfo `protobuf:\"bytes,11,opt,name=false_positive_info,json=falsePositiveInfo,proto3\" json:\"false_positive_info,omitempty\"`\n}\n\nfunc (x *Result) Reset() {\n\t*x = Result{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_detectors_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Result) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Result) ProtoMessage() {}\n\nfunc (x *Result) ProtoReflect() protoreflect.Message {\n\tmi := &file_detectors_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Result.ProtoReflect.Descriptor instead.\nfunc (*Result) Descriptor() ([]byte, []int) {\n\treturn file_detectors_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Result) GetSourceId() int64 {\n\tif x != nil {\n\t\treturn x.SourceId\n\t}\n\treturn 0\n}\n\nfunc (x *Result) GetRedacted() string {\n\tif x != nil {\n\t\treturn x.Redacted\n\t}\n\treturn \"\"\n}\n\nfunc (x *Result) GetVerified() bool {\n\tif x != nil {\n\t\treturn x.Verified\n\t}\n\treturn false\n}\n\nfunc (x *Result) GetHash() string {\n\tif x != nil {\n\t\treturn x.Hash\n\t}\n\treturn \"\"\n}\n\nfunc (x *Result) GetExtraData() map[string]string {\n\tif x != nil {\n\t\treturn x.ExtraData\n\t}\n\treturn nil\n}\n\nfunc (x *Result) GetStructuredData() *StructuredData {\n\tif x != nil {\n\t\treturn x.StructuredData\n\t}\n\treturn nil\n}\n\nfunc (x *Result) GetHashV2() string {\n\tif x != nil {\n\t\treturn x.HashV2\n\t}\n\treturn \"\"\n}\n\nfunc (x *Result) GetDecoderType() DecoderType {\n\tif x != nil {\n\t\treturn x.DecoderType\n\t}\n\treturn DecoderType_UNKNOWN\n}\n\nfunc (x *Result) GetVerificationErrorMessage() string {\n\tif x != nil {\n\t\treturn x.VerificationErrorMessage\n\t}\n\treturn \"\"\n}\n\nfunc (x *Result) GetFalsePositiveInfo() *FalsePositiveInfo {\n\tif x != nil {\n\t\treturn x.FalsePositiveInfo\n\t}\n\treturn nil\n}\n\ntype FalsePositiveInfo struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tWordMatch  bool `protobuf:\"varint,1,opt,name=word_match,json=wordMatch,proto3\" json:\"word_match,omitempty\"`\n\tLowEntropy bool `protobuf:\"varint,2,opt,name=low_entropy,json=lowEntropy,proto3\" json:\"low_entropy,omitempty\"`\n}\n\nfunc (x *FalsePositiveInfo) Reset() {\n\t*x = FalsePositiveInfo{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_detectors_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *FalsePositiveInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*FalsePositiveInfo) ProtoMessage() {}\n\nfunc (x *FalsePositiveInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_detectors_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use FalsePositiveInfo.ProtoReflect.Descriptor instead.\nfunc (*FalsePositiveInfo) Descriptor() ([]byte, []int) {\n\treturn file_detectors_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *FalsePositiveInfo) GetWordMatch() bool {\n\tif x != nil {\n\t\treturn x.WordMatch\n\t}\n\treturn false\n}\n\nfunc (x *FalsePositiveInfo) GetLowEntropy() bool {\n\tif x != nil {\n\t\treturn x.LowEntropy\n\t}\n\treturn false\n}\n\ntype StructuredData struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tTlsPrivateKey []*TlsPrivateKey `protobuf:\"bytes,1,rep,name=tls_private_key,json=tlsPrivateKey,proto3\" json:\"tls_private_key,omitempty\"`\n\tGithubSshKey  []*GitHubSSHKey  `protobuf:\"bytes,2,rep,name=github_ssh_key,json=githubSshKey,proto3\" json:\"github_ssh_key,omitempty\"`\n}\n\nfunc (x *StructuredData) Reset() {\n\t*x = StructuredData{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_detectors_proto_msgTypes[2]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *StructuredData) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*StructuredData) ProtoMessage() {}\n\nfunc (x *StructuredData) ProtoReflect() protoreflect.Message {\n\tmi := &file_detectors_proto_msgTypes[2]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use StructuredData.ProtoReflect.Descriptor instead.\nfunc (*StructuredData) Descriptor() ([]byte, []int) {\n\treturn file_detectors_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *StructuredData) GetTlsPrivateKey() []*TlsPrivateKey {\n\tif x != nil {\n\t\treturn x.TlsPrivateKey\n\t}\n\treturn nil\n}\n\nfunc (x *StructuredData) GetGithubSshKey() []*GitHubSSHKey {\n\tif x != nil {\n\t\treturn x.GithubSshKey\n\t}\n\treturn nil\n}\n\ntype TlsPrivateKey struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCertificateFingerprint string `protobuf:\"bytes,1,opt,name=certificate_fingerprint,json=certificateFingerprint,proto3\" json:\"certificate_fingerprint,omitempty\"`\n\tVerificationUrl        string `protobuf:\"bytes,2,opt,name=verification_url,json=verificationUrl,proto3\" json:\"verification_url,omitempty\"`\n\tExpirationTimestamp    int64  `protobuf:\"varint,3,opt,name=expiration_timestamp,json=expirationTimestamp,proto3\" json:\"expiration_timestamp,omitempty\"`\n}\n\nfunc (x *TlsPrivateKey) Reset() {\n\t*x = TlsPrivateKey{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_detectors_proto_msgTypes[3]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *TlsPrivateKey) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TlsPrivateKey) ProtoMessage() {}\n\nfunc (x *TlsPrivateKey) ProtoReflect() protoreflect.Message {\n\tmi := &file_detectors_proto_msgTypes[3]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TlsPrivateKey.ProtoReflect.Descriptor instead.\nfunc (*TlsPrivateKey) Descriptor() ([]byte, []int) {\n\treturn file_detectors_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *TlsPrivateKey) GetCertificateFingerprint() string {\n\tif x != nil {\n\t\treturn x.CertificateFingerprint\n\t}\n\treturn \"\"\n}\n\nfunc (x *TlsPrivateKey) GetVerificationUrl() string {\n\tif x != nil {\n\t\treturn x.VerificationUrl\n\t}\n\treturn \"\"\n}\n\nfunc (x *TlsPrivateKey) GetExpirationTimestamp() int64 {\n\tif x != nil {\n\t\treturn x.ExpirationTimestamp\n\t}\n\treturn 0\n}\n\ntype GitHubSSHKey struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUser                 string `protobuf:\"bytes,1,opt,name=user,proto3\" json:\"user,omitempty\"`\n\tPublicKeyFingerprint string `protobuf:\"bytes,2,opt,name=public_key_fingerprint,json=publicKeyFingerprint,proto3\" json:\"public_key_fingerprint,omitempty\"`\n}\n\nfunc (x *GitHubSSHKey) Reset() {\n\t*x = GitHubSSHKey{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_detectors_proto_msgTypes[4]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GitHubSSHKey) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GitHubSSHKey) ProtoMessage() {}\n\nfunc (x *GitHubSSHKey) ProtoReflect() protoreflect.Message {\n\tmi := &file_detectors_proto_msgTypes[4]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GitHubSSHKey.ProtoReflect.Descriptor instead.\nfunc (*GitHubSSHKey) Descriptor() ([]byte, []int) {\n\treturn file_detectors_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *GitHubSSHKey) GetUser() string {\n\tif x != nil {\n\t\treturn x.User\n\t}\n\treturn \"\"\n}\n\nfunc (x *GitHubSSHKey) GetPublicKeyFingerprint() string {\n\tif x != nil {\n\t\treturn x.PublicKeyFingerprint\n\t}\n\treturn \"\"\n}\n\nvar File_detectors_proto protoreflect.FileDescriptor\n\nvar file_detectors_proto_rawDesc = []byte{\n\t0x0a, 0x0f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x12, 0x09, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0x94, 0x04, 0x0a,\n\t0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x6f, 0x75, 0x72, 0x63,\n\t0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x6f, 0x75, 0x72,\n\t0x63, 0x65, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x64, 0x61, 0x63, 0x74, 0x65, 0x64,\n\t0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x64, 0x61, 0x63, 0x74, 0x65, 0x64,\n\t0x12, 0x1a, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01,\n\t0x28, 0x08, 0x52, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04,\n\t0x68, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68,\n\t0x12, 0x3f, 0x0a, 0x0a, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06,\n\t0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73,\n\t0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x45, 0x78, 0x74, 0x72, 0x61, 0x44, 0x61, 0x74,\n\t0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x65, 0x78, 0x74, 0x72, 0x61, 0x44, 0x61, 0x74,\n\t0x61, 0x12, 0x42, 0x0a, 0x0f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, 0x65, 0x64, 0x5f,\n\t0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x64, 0x65, 0x74,\n\t0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, 0x65,\n\t0x64, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, 0x65,\n\t0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x76, 0x32,\n\t0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x61, 0x73, 0x68, 0x56, 0x32, 0x12, 0x39,\n\t0x0a, 0x0c, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09,\n\t0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73,\n\t0x2e, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x64, 0x65,\n\t0x63, 0x6f, 0x64, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x1a, 0x76, 0x65, 0x72,\n\t0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f,\n\t0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x18, 0x76,\n\t0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72,\n\t0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x4c, 0x0a, 0x13, 0x66, 0x61, 0x6c, 0x73, 0x65,\n\t0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x0b,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73,\n\t0x2e, 0x46, 0x61, 0x6c, 0x73, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x49, 0x6e,\n\t0x66, 0x6f, 0x52, 0x11, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76,\n\t0x65, 0x49, 0x6e, 0x66, 0x6f, 0x1a, 0x3c, 0x0a, 0x0e, 0x45, 0x78, 0x74, 0x72, 0x61, 0x44, 0x61,\n\t0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,\n\t0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,\n\t0x02, 0x38, 0x01, 0x22, 0x53, 0x0a, 0x11, 0x46, 0x61, 0x6c, 0x73, 0x65, 0x50, 0x6f, 0x73, 0x69,\n\t0x74, 0x69, 0x76, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, 0x77, 0x6f, 0x72, 0x64,\n\t0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x77, 0x6f,\n\t0x72, 0x64, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x6f, 0x77, 0x5f, 0x65,\n\t0x6e, 0x74, 0x72, 0x6f, 0x70, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x6c, 0x6f,\n\t0x77, 0x45, 0x6e, 0x74, 0x72, 0x6f, 0x70, 0x79, 0x22, 0x91, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x72,\n\t0x75, 0x63, 0x74, 0x75, 0x72, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x40, 0x0a, 0x0f, 0x74,\n\t0x6c, 0x73, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01,\n\t0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73,\n\t0x2e, 0x54, 0x6c, 0x73, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x0d,\n\t0x74, 0x6c, 0x73, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x3d, 0x0a,\n\t0x0e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x5f, 0x73, 0x73, 0x68, 0x5f, 0x6b, 0x65, 0x79, 0x18,\n\t0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72,\n\t0x73, 0x2e, 0x47, 0x69, 0x74, 0x48, 0x75, 0x62, 0x53, 0x53, 0x48, 0x4b, 0x65, 0x79, 0x52, 0x0c,\n\t0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x53, 0x73, 0x68, 0x4b, 0x65, 0x79, 0x22, 0xa6, 0x01, 0x0a,\n\t0x0d, 0x54, 0x6c, 0x73, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x37,\n\t0x0a, 0x17, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x66, 0x69,\n\t0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x46, 0x69, 0x6e, 0x67,\n\t0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x76, 0x65, 0x72, 0x69, 0x66,\n\t0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x0f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55,\n\t0x72, 0x6c, 0x12, 0x31, 0x0a, 0x14, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,\n\t0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,\n\t0x52, 0x13, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65,\n\t0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x58, 0x0a, 0x0c, 0x47, 0x69, 0x74, 0x48, 0x75, 0x62, 0x53,\n\t0x53, 0x48, 0x4b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x34, 0x0a, 0x16, 0x70, 0x75, 0x62,\n\t0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72,\n\t0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x70, 0x75, 0x62, 0x6c, 0x69,\n\t0x63, 0x4b, 0x65, 0x79, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x2a,\n\t0x51, 0x0a, 0x0b, 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b,\n\t0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x50,\n\t0x4c, 0x41, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x41, 0x53, 0x45, 0x36, 0x34,\n\t0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x54, 0x46, 0x31, 0x36, 0x10, 0x03, 0x12, 0x13, 0x0a,\n\t0x0f, 0x45, 0x53, 0x43, 0x41, 0x50, 0x45, 0x44, 0x5f, 0x55, 0x4e, 0x49, 0x43, 0x4f, 0x44, 0x45,\n\t0x10, 0x04, 0x2a, 0xa4, 0x87, 0x01, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72,\n\t0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x6c, 0x69, 0x62, 0x61, 0x62, 0x61, 0x10,\n\t0x00, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x4d, 0x51, 0x50, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x41,\n\t0x57, 0x53, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x10, 0x03, 0x12,\n\t0x0a, 0x0a, 0x06, 0x43, 0x69, 0x72, 0x63, 0x6c, 0x65, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x43,\n\t0x6f, 0x69, 0x6e, 0x62, 0x61, 0x73, 0x65, 0x10, 0x05, 0x12, 0x07, 0x0a, 0x03, 0x47, 0x43, 0x50,\n\t0x10, 0x06, 0x12, 0x0b, 0x0a, 0x07, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x10, 0x07, 0x12,\n\t0x0a, 0x0a, 0x06, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x10, 0x08, 0x12, 0x0a, 0x0a, 0x06, 0x47,\n\t0x69, 0x74, 0x6c, 0x61, 0x62, 0x10, 0x09, 0x12, 0x08, 0x0a, 0x04, 0x4a, 0x44, 0x42, 0x43, 0x10,\n\t0x0a, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x61, 0x7a, 0x6f, 0x72, 0x50, 0x61, 0x79, 0x10, 0x0b, 0x12,\n\t0x0c, 0x0a, 0x08, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x72, 0x69, 0x64, 0x10, 0x0c, 0x12, 0x09, 0x0a,\n\t0x05, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x10, 0x0d, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x71, 0x75, 0x61,\n\t0x72, 0x65, 0x10, 0x0e, 0x12, 0x0e, 0x0a, 0x0a, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b,\n\t0x65, 0x79, 0x10, 0x0f, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x74, 0x72, 0x69, 0x70, 0x65, 0x10, 0x10,\n\t0x12, 0x07, 0x0a, 0x03, 0x55, 0x52, 0x49, 0x10, 0x11, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x72, 0x6f,\n\t0x70, 0x62, 0x6f, 0x78, 0x10, 0x12, 0x12, 0x0a, 0x0a, 0x06, 0x48, 0x65, 0x72, 0x6f, 0x6b, 0x75,\n\t0x10, 0x13, 0x12, 0x0d, 0x0a, 0x09, 0x4d, 0x61, 0x69, 0x6c, 0x63, 0x68, 0x69, 0x6d, 0x70, 0x10,\n\t0x14, 0x12, 0x08, 0x0a, 0x04, 0x4f, 0x6b, 0x74, 0x61, 0x10, 0x15, 0x12, 0x0c, 0x0a, 0x08, 0x4f,\n\t0x6e, 0x65, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x10, 0x16, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x69, 0x76,\n\t0x6f, 0x74, 0x61, 0x6c, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x72, 0x10, 0x17, 0x12, 0x0d, 0x0a,\n\t0x09, 0x53, 0x71, 0x75, 0x61, 0x72, 0x65, 0x41, 0x70, 0x70, 0x10, 0x19, 0x12, 0x0a, 0x0a, 0x06,\n\t0x54, 0x77, 0x69, 0x6c, 0x69, 0x6f, 0x10, 0x1a, 0x12, 0x08, 0x0a, 0x04, 0x54, 0x65, 0x73, 0x74,\n\t0x10, 0x1b, 0x12, 0x0c, 0x0a, 0x08, 0x54, 0x72, 0x61, 0x76, 0x69, 0x73, 0x43, 0x49, 0x10, 0x1d,\n\t0x12, 0x10, 0x0a, 0x0c, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b,\n\t0x10, 0x1e, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x61, 0x79, 0x70, 0x61, 0x6c, 0x4f, 0x61, 0x75, 0x74,\n\t0x68, 0x10, 0x1f, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x61, 0x67, 0x65, 0x72, 0x44, 0x75, 0x74, 0x79,\n\t0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0x20, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x69, 0x72, 0x65,\n\t0x62, 0x61, 0x73, 0x65, 0x10, 0x21, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x61, 0x69, 0x6c, 0x67, 0x75,\n\t0x6e, 0x10, 0x22, 0x12, 0x0b, 0x0a, 0x07, 0x48, 0x75, 0x62, 0x53, 0x70, 0x6f, 0x74, 0x10, 0x23,\n\t0x12, 0x0d, 0x0a, 0x09, 0x47, 0x69, 0x74, 0x48, 0x75, 0x62, 0x41, 0x70, 0x70, 0x10, 0x24, 0x12,\n\t0x0c, 0x0a, 0x08, 0x43, 0x69, 0x72, 0x63, 0x6c, 0x65, 0x43, 0x49, 0x10, 0x25, 0x12, 0x0c, 0x0a,\n\t0x08, 0x57, 0x70, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x10, 0x26, 0x12, 0x10, 0x0a, 0x0c, 0x44,\n\t0x61, 0x74, 0x61, 0x64, 0x6f, 0x67, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x27, 0x12, 0x11, 0x0a,\n\t0x0d, 0x46, 0x61, 0x63, 0x65, 0x62, 0x6f, 0x6f, 0x6b, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x10, 0x28,\n\t0x12, 0x1c, 0x0a, 0x18, 0x41, 0x73, 0x61, 0x6e, 0x61, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61,\n\t0x6c, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x29, 0x12, 0x13,\n\t0x0a, 0x0f, 0x41, 0x6d, 0x70, 0x6c, 0x69, 0x74, 0x75, 0x64, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65,\n\t0x79, 0x10, 0x2a, 0x12, 0x14, 0x0a, 0x10, 0x42, 0x69, 0x74, 0x4c, 0x79, 0x41, 0x63, 0x63, 0x65,\n\t0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x2b, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x61, 0x6c,\n\t0x65, 0x6e, 0x64, 0x6c, 0x79, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0x2c, 0x12, 0x11, 0x0a,\n\t0x0d, 0x5a, 0x61, 0x70, 0x69, 0x65, 0x72, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x10, 0x2d,\n\t0x12, 0x11, 0x0a, 0x0d, 0x59, 0x6f, 0x75, 0x74, 0x75, 0x62, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65,\n\t0x79, 0x10, 0x2e, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63,\n\t0x65, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x10, 0x2f, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x77, 0x69,\n\t0x74, 0x74, 0x65, 0x72, 0x41, 0x70, 0x69, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x10, 0x30, 0x12,\n\t0x0c, 0x0a, 0x08, 0x4e, 0x70, 0x6d, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x31, 0x12, 0x1a, 0x0a,\n\t0x16, 0x4e, 0x65, 0x77, 0x52, 0x65, 0x6c, 0x69, 0x63, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61,\n\t0x6c, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0x32, 0x12, 0x16, 0x0a, 0x0e, 0x41, 0x69, 0x72,\n\t0x74, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0x33, 0x1a, 0x02, 0x08,\n\t0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x6b, 0x61, 0x6d, 0x61, 0x69, 0x54, 0x6f, 0x6b, 0x65, 0x6e,\n\t0x10, 0x34, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x4d, 0x57, 0x53, 0x10,\n\t0x35, 0x12, 0x0e, 0x0a, 0x0a, 0x4b, 0x75, 0x62, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x10,\n\t0x36, 0x12, 0x0e, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x30, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x10,\n\t0x37, 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x69, 0x74, 0x66, 0x69, 0x6e, 0x65, 0x78, 0x10, 0x38, 0x12,\n\t0x0c, 0x0a, 0x08, 0x43, 0x6c, 0x61, 0x72, 0x69, 0x66, 0x61, 0x69, 0x10, 0x39, 0x12, 0x1a, 0x0a,\n\t0x16, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x66, 0x6c, 0x61, 0x72, 0x65, 0x47, 0x6c, 0x6f, 0x62, 0x61,\n\t0x6c, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0x3a, 0x12, 0x13, 0x0a, 0x0f, 0x43, 0x6c, 0x6f,\n\t0x75, 0x64, 0x66, 0x6c, 0x61, 0x72, 0x65, 0x43, 0x61, 0x4b, 0x65, 0x79, 0x10, 0x3b, 0x12, 0x0d,\n\t0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x75, 0x65, 0x6e, 0x74, 0x10, 0x3c, 0x12, 0x16, 0x0a,\n\t0x12, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x66, 0x75, 0x6c, 0x44, 0x65, 0x6c, 0x69, 0x76,\n\t0x65, 0x72, 0x79, 0x10, 0x3d, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x61, 0x74, 0x61, 0x62, 0x72, 0x69,\n\t0x63, 0x6b, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x3e, 0x12, 0x16, 0x0a, 0x12, 0x44, 0x69,\n\t0x67, 0x69, 0x74, 0x61, 0x6c, 0x4f, 0x63, 0x65, 0x61, 0x6e, 0x53, 0x70, 0x61, 0x63, 0x65, 0x73,\n\t0x10, 0x3f, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6c, 0x4f, 0x63, 0x65,\n\t0x61, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x40, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x69, 0x73,\n\t0x63, 0x6f, 0x72, 0x64, 0x42, 0x6f, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x41, 0x12, 0x12,\n\t0x0a, 0x0e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b,\n\t0x10, 0x42, 0x12, 0x12, 0x0a, 0x0a, 0x45, 0x74, 0x73, 0x79, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79,\n\t0x10, 0x43, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x61, 0x73, 0x74, 0x6c, 0x79,\n\t0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x44, 0x12,\n\t0x10, 0x0a, 0x0c, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x10,\n\t0x45, 0x12, 0x0d, 0x0a, 0x09, 0x52, 0x65, 0x43, 0x41, 0x50, 0x54, 0x43, 0x48, 0x41, 0x10, 0x46,\n\t0x12, 0x10, 0x0a, 0x0c, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79,\n\t0x10, 0x47, 0x12, 0x0a, 0x0a, 0x06, 0x48, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x10, 0x48, 0x12, 0x13,\n\t0x0a, 0x0f, 0x49, 0x62, 0x6d, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x55, 0x73, 0x65, 0x72, 0x4b, 0x65,\n\t0x79, 0x10, 0x49, 0x12, 0x0b, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x6c, 0x69, 0x66, 0x79, 0x10, 0x4a,\n\t0x12, 0x0a, 0x0a, 0x06, 0x56, 0x6f, 0x6e, 0x61, 0x67, 0x65, 0x10, 0x4b, 0x12, 0x10, 0x0a, 0x0c,\n\t0x45, 0x71, 0x75, 0x69, 0x6e, 0x69, 0x78, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x10, 0x4c, 0x12, 0x0c,\n\t0x0a, 0x08, 0x50, 0x61, 0x79, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x10, 0x4d, 0x12, 0x0e, 0x0a, 0x0a,\n\t0x50, 0x6c, 0x61, 0x69, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x4e, 0x12, 0x0c, 0x0a, 0x08,\n\t0x50, 0x6c, 0x61, 0x69, 0x64, 0x4b, 0x65, 0x79, 0x10, 0x4f, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x6c,\n\t0x69, 0x76, 0x6f, 0x10, 0x50, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x6f, 0x73, 0x74, 0x6d, 0x61, 0x72,\n\t0x6b, 0x10, 0x51, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x75, 0x62, 0x4e, 0x75, 0x62, 0x50, 0x75, 0x62,\n\t0x6c, 0x69, 0x73, 0x68, 0x4b, 0x65, 0x79, 0x10, 0x52, 0x12, 0x19, 0x0a, 0x15, 0x50, 0x75, 0x62,\n\t0x4e, 0x75, 0x62, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4b,\n\t0x65, 0x79, 0x10, 0x53, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x75, 0x73, 0x68, 0x65, 0x72, 0x43, 0x68,\n\t0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4b, 0x65, 0x79, 0x10, 0x54, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x63,\n\t0x61, 0x6c, 0x65, 0x77, 0x61, 0x79, 0x4b, 0x65, 0x79, 0x10, 0x55, 0x12, 0x10, 0x0a, 0x0c, 0x53,\n\t0x65, 0x6e, 0x64, 0x69, 0x6e, 0x42, 0x6c, 0x75, 0x65, 0x56, 0x32, 0x10, 0x56, 0x12, 0x0f, 0x0a,\n\t0x0b, 0x53, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x57, 0x12, 0x0d,\n\t0x0a, 0x09, 0x53, 0x68, 0x6f, 0x64, 0x61, 0x6e, 0x4b, 0x65, 0x79, 0x10, 0x58, 0x12, 0x0b, 0x0a,\n\t0x07, 0x53, 0x6e, 0x79, 0x6b, 0x4b, 0x65, 0x79, 0x10, 0x59, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x70,\n\t0x6f, 0x74, 0x69, 0x66, 0x79, 0x4b, 0x65, 0x79, 0x10, 0x5a, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x65,\n\t0x6c, 0x65, 0x67, 0x72, 0x61, 0x6d, 0x42, 0x6f, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x5b,\n\t0x12, 0x13, 0x0a, 0x0f, 0x54, 0x65, 0x6e, 0x63, 0x65, 0x6e, 0x74, 0x43, 0x6c, 0x6f, 0x75, 0x64,\n\t0x4b, 0x65, 0x79, 0x10, 0x5c, 0x12, 0x1f, 0x0a, 0x1b, 0x54, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f,\n\t0x72, 0x6d, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x54,\n\t0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x5d, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x72, 0x65, 0x6c, 0x6c, 0x6f,\n\t0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0x5e, 0x12, 0x0e, 0x0a, 0x0a, 0x5a, 0x65, 0x6e, 0x64,\n\t0x65, 0x73, 0x6b, 0x41, 0x70, 0x69, 0x10, 0x5f, 0x12, 0x12, 0x0a, 0x0e, 0x4d, 0x61, 0x78, 0x4d,\n\t0x69, 0x6e, 0x64, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x10, 0x60, 0x12, 0x1a, 0x0a, 0x16,\n\t0x41, 0x69, 0x72, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,\n\t0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0x61, 0x12, 0x0e, 0x0a, 0x0a, 0x41, 0x73, 0x61, 0x6e,\n\t0x61, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x10, 0x62, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x61, 0x70, 0x69,\n\t0x64, 0x41, 0x70, 0x69, 0x10, 0x63, 0x12, 0x16, 0x0a, 0x12, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x66,\n\t0x6c, 0x61, 0x72, 0x65, 0x41, 0x70, 0x69, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x64, 0x12, 0x09,\n\t0x0a, 0x05, 0x57, 0x65, 0x62, 0x65, 0x78, 0x10, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x69, 0x72,\n\t0x65, 0x62, 0x61, 0x73, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,\n\t0x69, 0x6e, 0x67, 0x10, 0x66, 0x12, 0x21, 0x0a, 0x1d, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,\n\t0x66, 0x75, 0x6c, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x73,\n\t0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x67, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x61, 0x70, 0x42,\n\t0x6f, 0x78, 0x10, 0x68, 0x12, 0x14, 0x0a, 0x10, 0x4d, 0x61, 0x69, 0x6c, 0x4a, 0x65, 0x74, 0x42,\n\t0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x10, 0x69, 0x12, 0x0e, 0x0a, 0x0a, 0x4d, 0x61,\n\t0x69, 0x6c, 0x4a, 0x65, 0x74, 0x53, 0x4d, 0x53, 0x10, 0x6a, 0x12, 0x11, 0x0a, 0x0d, 0x48, 0x75,\n\t0x62, 0x53, 0x70, 0x6f, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0x6b, 0x12, 0x10, 0x0a,\n\t0x0c, 0x48, 0x75, 0x62, 0x53, 0x70, 0x6f, 0x74, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x10, 0x6c, 0x12,\n\t0x0b, 0x0a, 0x07, 0x53, 0x73, 0x6c, 0x4d, 0x61, 0x74, 0x65, 0x10, 0x6d, 0x12, 0x1b, 0x0a, 0x17,\n\t0x41, 0x75, 0x74, 0x68, 0x30, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x41,\n\t0x70, 0x69, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x6e, 0x12, 0x0f, 0x0a, 0x0b, 0x4d, 0x65, 0x73,\n\t0x73, 0x61, 0x67, 0x65, 0x42, 0x69, 0x72, 0x64, 0x10, 0x6f, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x6c,\n\t0x61, 0x73, 0x74, 0x69, 0x63, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x10, 0x70, 0x12, 0x1c, 0x0a, 0x18,\n\t0x46, 0x69, 0x67, 0x6d, 0x61, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63,\n\t0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x71, 0x12, 0x19, 0x0a, 0x15, 0x4d, 0x69,\n\t0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x54, 0x65, 0x61, 0x6d, 0x73, 0x57, 0x65, 0x62, 0x68,\n\t0x6f, 0x6f, 0x6b, 0x10, 0x72, 0x12, 0x0d, 0x0a, 0x09, 0x47, 0x69, 0x74, 0x48, 0x75, 0x62, 0x4f,\n\t0x6c, 0x64, 0x10, 0x73, 0x12, 0x0f, 0x0a, 0x0b, 0x56, 0x75, 0x6c, 0x74, 0x72, 0x41, 0x70, 0x69,\n\t0x4b, 0x65, 0x79, 0x10, 0x74, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x65, 0x70, 0x69, 0x70, 0x6f, 0x73,\n\t0x74, 0x10, 0x75, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x6f, 0x73, 0x74, 0x6d, 0x61, 0x6e, 0x10, 0x76,\n\t0x12, 0x11, 0x0a, 0x0d, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x73, 0x69, 0x67, 0x68, 0x74, 0x4b, 0x65,\n\t0x79, 0x10, 0x77, 0x12, 0x0d, 0x0a, 0x09, 0x4a, 0x69, 0x72, 0x61, 0x54, 0x6f, 0x6b, 0x65, 0x6e,\n\t0x10, 0x78, 0x12, 0x0f, 0x0a, 0x0b, 0x4e, 0x65, 0x78, 0x6d, 0x6f, 0x41, 0x70, 0x69, 0x4b, 0x65,\n\t0x79, 0x10, 0x79, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x41, 0x70,\n\t0x69, 0x4b, 0x65, 0x79, 0x10, 0x7a, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x75, 0x6d, 0x6f, 0x4c, 0x6f,\n\t0x67, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x10, 0x7b, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x75, 0x73, 0x68,\n\t0x42, 0x75, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0x7c, 0x12, 0x16,\n\t0x0a, 0x12, 0x41, 0x69, 0x72, 0x62, 0x72, 0x61, 0x6b, 0x65, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63,\n\t0x74, 0x4b, 0x65, 0x79, 0x10, 0x7d, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x69, 0x72, 0x62, 0x72, 0x61,\n\t0x6b, 0x65, 0x55, 0x73, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x10, 0x7e, 0x12, 0x17, 0x0a, 0x13, 0x50,\n\t0x65, 0x6e, 0x64, 0x6f, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b,\n\t0x65, 0x79, 0x10, 0x7f, 0x12, 0x1f, 0x0a, 0x1a, 0x53, 0x70, 0x6c, 0x75, 0x6e, 0x6b, 0x4f, 0x62,\n\t0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x54, 0x6f, 0x6b,\n\t0x65, 0x6e, 0x10, 0x80, 0x01, 0x12, 0x12, 0x0a, 0x0d, 0x4c, 0x6f, 0x6b, 0x61, 0x6c, 0x69, 0x73,\n\t0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x81, 0x01, 0x12, 0x11, 0x0a, 0x0c, 0x43, 0x61, 0x6c,\n\t0x65, 0x6e, 0x64, 0x61, 0x72, 0x69, 0x66, 0x69, 0x63, 0x10, 0x82, 0x01, 0x12, 0x0e, 0x0a, 0x09,\n\t0x4a, 0x75, 0x6d, 0x70, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x10, 0x83, 0x01, 0x12, 0x0c, 0x0a, 0x07,\n\t0x49, 0x70, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x10, 0x85, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x4e, 0x6f,\n\t0x74, 0x69, 0x6f, 0x6e, 0x10, 0x86, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x44, 0x72, 0x6f, 0x6e, 0x65,\n\t0x43, 0x49, 0x10, 0x87, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x49, 0x4f,\n\t0x10, 0x88, 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x54, 0x77, 0x65, 0x6c, 0x76, 0x65, 0x44, 0x61, 0x74,\n\t0x61, 0x10, 0x89, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x44, 0x37, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,\n\t0x6b, 0x10, 0x8a, 0x01, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x63, 0x72, 0x61, 0x70, 0x69, 0x6e, 0x67,\n\t0x42, 0x65, 0x65, 0x10, 0x8b, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x4b, 0x65, 0x65, 0x6e, 0x49, 0x4f,\n\t0x10, 0x8c, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x57, 0x61, 0x6b, 0x61, 0x74, 0x69, 0x6d, 0x65, 0x10,\n\t0x8d, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x6b, 0x69, 0x74, 0x65, 0x10,\n\t0x8e, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x56, 0x65, 0x72, 0x69, 0x6d, 0x61, 0x69, 0x6c, 0x10, 0x8f,\n\t0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x5a, 0x65, 0x72, 0x6f, 0x62, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x10,\n\t0x90, 0x01, 0x12, 0x11, 0x0a, 0x0c, 0x4d, 0x61, 0x69, 0x6c, 0x62, 0x6f, 0x78, 0x6c, 0x61, 0x79,\n\t0x65, 0x72, 0x10, 0x91, 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x46, 0x61, 0x73, 0x74, 0x73, 0x70, 0x72,\n\t0x69, 0x6e, 0x67, 0x10, 0x92, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x50, 0x61, 0x64, 0x64, 0x6c, 0x65,\n\t0x10, 0x93, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x65, 0x6c, 0x6c, 0x66, 0x79, 0x10, 0x94, 0x01,\n\t0x12, 0x0c, 0x0a, 0x07, 0x46, 0x69, 0x78, 0x65, 0x72, 0x49, 0x4f, 0x10, 0x95, 0x01, 0x12, 0x0e,\n\t0x0a, 0x09, 0x42, 0x75, 0x74, 0x74, 0x65, 0x72, 0x43, 0x4d, 0x53, 0x10, 0x96, 0x01, 0x12, 0x0b,\n\t0x0a, 0x06, 0x54, 0x61, 0x78, 0x6a, 0x61, 0x72, 0x10, 0x97, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x41,\n\t0x76, 0x61, 0x6c, 0x61, 0x72, 0x61, 0x10, 0x98, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x48, 0x65, 0x6c,\n\t0x70, 0x73, 0x63, 0x6f, 0x75, 0x74, 0x10, 0x99, 0x01, 0x12, 0x10, 0x0a, 0x0b, 0x45, 0x6c, 0x61,\n\t0x73, 0x74, 0x69, 0x63, 0x50, 0x61, 0x74, 0x68, 0x10, 0x9a, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x5a,\n\t0x65, 0x70, 0x6c, 0x69, 0x6e, 0x10, 0x9b, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x49, 0x6e, 0x74, 0x65,\n\t0x72, 0x63, 0x6f, 0x6d, 0x10, 0x9c, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x4d, 0x61, 0x69, 0x6c, 0x6d,\n\t0x6f, 0x64, 0x6f, 0x10, 0x9d, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x43, 0x61, 0x6e, 0x6e, 0x79, 0x49,\n\t0x6f, 0x10, 0x9e, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x50, 0x69, 0x70, 0x65, 0x64, 0x72, 0x69, 0x76,\n\t0x65, 0x10, 0x9f, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x56, 0x65, 0x72, 0x63, 0x65, 0x6c, 0x10, 0xa0,\n\t0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x50, 0x6f, 0x73, 0x74, 0x68, 0x6f, 0x67, 0x41, 0x70, 0x70, 0x10,\n\t0xa1, 0x01, 0x12, 0x11, 0x0a, 0x0c, 0x53, 0x69, 0x6e, 0x63, 0x68, 0x4d, 0x65, 0x73, 0x73, 0x61,\n\t0x67, 0x65, 0x10, 0xa2, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x41, 0x79, 0x72, 0x73, 0x68, 0x61, 0x72,\n\t0x65, 0x10, 0xa3, 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x70, 0x43, 0x72, 0x75, 0x6e,\n\t0x63, 0x68, 0x10, 0xa4, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x4c, 0x69, 0x76, 0x65, 0x41, 0x67, 0x65,\n\t0x6e, 0x74, 0x10, 0xa5, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x42, 0x65, 0x61, 0x6d, 0x65, 0x72, 0x10,\n\t0xa6, 0x01, 0x12, 0x11, 0x0a, 0x0c, 0x57, 0x65, 0x43, 0x68, 0x61, 0x74, 0x41, 0x70, 0x70, 0x4b,\n\t0x65, 0x79, 0x10, 0xa7, 0x01, 0x12, 0x12, 0x0a, 0x0d, 0x4c, 0x69, 0x6e, 0x65, 0x4d, 0x65, 0x73,\n\t0x73, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x10, 0xa8, 0x01, 0x12, 0x14, 0x0a, 0x0f, 0x55, 0x62, 0x65,\n\t0x72, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xa9, 0x01, 0x12,\n\t0x14, 0x0a, 0x0f, 0x41, 0x6c, 0x67, 0x6f, 0x6c, 0x69, 0x61, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4b,\n\t0x65, 0x79, 0x10, 0xaa, 0x01, 0x12, 0x10, 0x0a, 0x0b, 0x46, 0x75, 0x6c, 0x6c, 0x43, 0x6f, 0x6e,\n\t0x74, 0x61, 0x63, 0x74, 0x10, 0xab, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x4d, 0x61, 0x6e, 0x64, 0x72,\n\t0x69, 0x6c, 0x6c, 0x10, 0xac, 0x01, 0x12, 0x10, 0x0a, 0x0b, 0x46, 0x6c, 0x75, 0x74, 0x74, 0x65,\n\t0x72, 0x77, 0x61, 0x76, 0x65, 0x10, 0xad, 0x01, 0x12, 0x1c, 0x0a, 0x17, 0x4d, 0x61, 0x74, 0x74,\n\t0x65, 0x72, 0x6d, 0x6f, 0x73, 0x74, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x6f,\n\t0x6b, 0x65, 0x6e, 0x10, 0xae, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x61,\n\t0x6e, 0x74, 0x10, 0xaf, 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x4c, 0x69, 0x6e, 0x65, 0x4e, 0x6f, 0x74,\n\t0x69, 0x66, 0x79, 0x10, 0xb0, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x4c, 0x69, 0x6e, 0x65, 0x61, 0x72,\n\t0x41, 0x50, 0x49, 0x10, 0xb1, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x55, 0x62, 0x69, 0x64, 0x6f, 0x74,\n\t0x73, 0x10, 0xb2, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x41, 0x6e, 0x79, 0x70, 0x6f, 0x69, 0x6e, 0x74,\n\t0x10, 0xb3, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x44, 0x77, 0x6f, 0x6c, 0x6c, 0x61, 0x10, 0xb4, 0x01,\n\t0x12, 0x1b, 0x0a, 0x16, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x41,\n\t0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xb5, 0x01, 0x12, 0x0a, 0x0a,\n\t0x05, 0x53, 0x75, 0x72, 0x67, 0x65, 0x10, 0xb6, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x70, 0x61,\n\t0x72, 0x6b, 0x70, 0x6f, 0x73, 0x74, 0x10, 0xb7, 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x47, 0x6f, 0x43,\n\t0x61, 0x72, 0x64, 0x6c, 0x65, 0x73, 0x73, 0x10, 0xb8, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x43, 0x6f,\n\t0x64, 0x61, 0x63, 0x79, 0x10, 0xb9, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x4b, 0x72, 0x61, 0x6b, 0x65,\n\t0x6e, 0x10, 0xba, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74,\n\t0x10, 0xbb, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x4b, 0x61, 0x69, 0x72, 0x6f, 0x73, 0x10, 0xbc, 0x01,\n\t0x12, 0x11, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x4d, 0x53,\n\t0x10, 0xbd, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x74, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x61, 0x6e,\n\t0x10, 0xbe, 0x01, 0x12, 0x11, 0x0a, 0x0c, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x44, 0x61, 0x72,\n\t0x6b, 0x6c, 0x79, 0x10, 0xbf, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6f, 0x76, 0x65, 0x72, 0x61,\n\t0x6c, 0x6c, 0x73, 0x10, 0xc0, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x4c, 0x69, 0x6e, 0x6f, 0x64, 0x65,\n\t0x10, 0xc1, 0x01, 0x12, 0x0a, 0x0a, 0x05, 0x57, 0x65, 0x50, 0x61, 0x79, 0x10, 0xc2, 0x01, 0x12,\n\t0x10, 0x0a, 0x0b, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x10, 0xc3,\n\t0x01, 0x12, 0x0c, 0x0a, 0x07, 0x44, 0x6f, 0x70, 0x70, 0x6c, 0x65, 0x72, 0x10, 0xc4, 0x01, 0x12,\n\t0x0a, 0x0a, 0x05, 0x41, 0x67, 0x6f, 0x72, 0x61, 0x10, 0xc5, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x53,\n\t0x61, 0x6d, 0x73, 0x61, 0x72, 0x61, 0x10, 0xc6, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x46, 0x72, 0x61,\n\t0x6d, 0x65, 0x49, 0x4f, 0x10, 0xc7, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x52, 0x75, 0x62, 0x79, 0x47,\n\t0x65, 0x6d, 0x73, 0x10, 0xc8, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x49,\n\t0x10, 0xc9, 0x01, 0x12, 0x12, 0x0a, 0x0d, 0x53, 0x75, 0x72, 0x76, 0x65, 0x79, 0x53, 0x70, 0x61,\n\t0x72, 0x72, 0x6f, 0x77, 0x10, 0xca, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x53, 0x69, 0x6d, 0x76, 0x6f,\n\t0x6c, 0x79, 0x10, 0xcb, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x75, 0x72, 0x76, 0x69, 0x63, 0x61,\n\t0x74, 0x65, 0x10, 0xcc, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x4f, 0x6d, 0x6e, 0x69, 0x73, 0x65, 0x6e,\n\t0x64, 0x10, 0xcd, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x47, 0x72, 0x6f, 0x6f, 0x76, 0x65, 0x68, 0x71,\n\t0x10, 0xce, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x4e, 0x65, 0x77, 0x73, 0x61, 0x70, 0x69, 0x10, 0xcf,\n\t0x01, 0x12, 0x0c, 0x0a, 0x07, 0x43, 0x68, 0x61, 0x74, 0x62, 0x6f, 0x74, 0x10, 0xd0, 0x01, 0x12,\n\t0x11, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x53, 0x65, 0x6e, 0x64, 0x73, 0x6d, 0x73, 0x10,\n\t0xd1, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x67, 0x69, 0x73, 0x74, 0x10, 0xd2, 0x01,\n\t0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x49, 0x4f, 0x10, 0xd3,\n\t0x01, 0x12, 0x0c, 0x0a, 0x07, 0x41, 0x70, 0x69, 0x44, 0x65, 0x63, 0x6b, 0x10, 0xd4, 0x01, 0x12,\n\t0x0c, 0x0a, 0x07, 0x4e, 0x66, 0x74, 0x70, 0x6f, 0x72, 0x74, 0x10, 0xd5, 0x01, 0x12, 0x0b, 0x0a,\n\t0x06, 0x43, 0x6f, 0x70, 0x70, 0x65, 0x72, 0x10, 0xd6, 0x01, 0x12, 0x0a, 0x0a, 0x05, 0x43, 0x6c,\n\t0x6f, 0x73, 0x65, 0x10, 0xd7, 0x01, 0x12, 0x11, 0x0a, 0x0c, 0x4d, 0x79, 0x66, 0x72, 0x65, 0x73,\n\t0x68, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x10, 0xd8, 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x61, 0x6c,\n\t0x65, 0x73, 0x66, 0x6c, 0x61, 0x72, 0x65, 0x10, 0xd9, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x57, 0x65,\n\t0x62, 0x66, 0x6c, 0x6f, 0x77, 0x10, 0xda, 0x01, 0x12, 0x09, 0x0a, 0x04, 0x44, 0x75, 0x64, 0x61,\n\t0x10, 0xdb, 0x01, 0x12, 0x09, 0x0a, 0x04, 0x59, 0x65, 0x78, 0x74, 0x10, 0xdc, 0x01, 0x12, 0x11,\n\t0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xdd,\n\t0x01, 0x12, 0x19, 0x0a, 0x14, 0x53, 0x74, 0x6f, 0x72, 0x79, 0x62, 0x6c, 0x6f, 0x6b, 0x41, 0x63,\n\t0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xde, 0x01, 0x12, 0x0d, 0x0a, 0x08,\n\t0x47, 0x72, 0x61, 0x70, 0x68, 0x43, 0x4d, 0x53, 0x10, 0xdf, 0x01, 0x12, 0x10, 0x0a, 0x0b, 0x43,\n\t0x68, 0x65, 0x63, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x10, 0xe0, 0x01, 0x12, 0x0f, 0x0a,\n\t0x0a, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x6b, 0x69, 0x74, 0x10, 0xe1, 0x01, 0x12, 0x11,\n\t0x0a, 0x0c, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x47, 0x75, 0x72, 0x75, 0x10, 0xe2,\n\t0x01, 0x12, 0x0c, 0x0a, 0x07, 0x4b, 0x61, 0x6c, 0x65, 0x79, 0x72, 0x61, 0x10, 0xe3, 0x01, 0x12,\n\t0x0f, 0x0a, 0x0a, 0x4d, 0x61, 0x69, 0x6c, 0x65, 0x72, 0x6c, 0x69, 0x74, 0x65, 0x10, 0xe4, 0x01,\n\t0x12, 0x0d, 0x0a, 0x08, 0x51, 0x75, 0x61, 0x6c, 0x61, 0x72, 0x6f, 0x6f, 0x10, 0xe5, 0x01, 0x12,\n\t0x19, 0x0a, 0x14, 0x53, 0x61, 0x74, 0x69, 0x73, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x50, 0x72, 0x6f,\n\t0x6a, 0x65, 0x63, 0x74, 0x6b, 0x65, 0x79, 0x10, 0xe6, 0x01, 0x12, 0x17, 0x0a, 0x12, 0x53, 0x61,\n\t0x74, 0x69, 0x73, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x57, 0x72, 0x69, 0x74, 0x65, 0x6b, 0x65, 0x79,\n\t0x10, 0xe7, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x61, 0x74,\n\t0x10, 0xe8, 0x01, 0x12, 0x13, 0x0a, 0x0e, 0x53, 0x75, 0x72, 0x76, 0x65, 0x79, 0x41, 0x6e, 0x79,\n\t0x70, 0x6c, 0x61, 0x63, 0x65, 0x10, 0xe9, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x75, 0x72, 0x76,\n\t0x65, 0x79, 0x42, 0x6f, 0x74, 0x10, 0xea, 0x01, 0x12, 0x0e, 0x0a, 0x09, 0x57, 0x65, 0x62, 0x65,\n\t0x6e, 0x67, 0x61, 0x67, 0x65, 0x10, 0xeb, 0x01, 0x12, 0x12, 0x0a, 0x0d, 0x5a, 0x6f, 0x6e, 0x6b,\n\t0x61, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x10, 0xec, 0x01, 0x12, 0x0e, 0x0a, 0x09,\n\t0x44, 0x65, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, 0x10, 0xed, 0x01, 0x12, 0x0c, 0x0a, 0x07,\n\t0x46, 0x65, 0x65, 0x64, 0x69, 0x65, 0x72, 0x10, 0xee, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x41, 0x62,\n\t0x79, 0x73, 0x73, 0x61, 0x6c, 0x65, 0x10, 0xef, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x4d, 0x61, 0x67,\n\t0x6e, 0x65, 0x74, 0x69, 0x63, 0x10, 0xf0, 0x01, 0x12, 0x10, 0x0a, 0x07, 0x4e, 0x79, 0x74, 0x69,\n\t0x6d, 0x65, 0x73, 0x10, 0xf1, 0x01, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x50, 0x6f,\n\t0x6c, 0x79, 0x67, 0x6f, 0x6e, 0x10, 0xf2, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x50, 0x6f, 0x77, 0x72,\n\t0x62, 0x6f, 0x74, 0x10, 0xf3, 0x01, 0x12, 0x13, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65,\n\t0x63, 0x74, 0x49, 0x4f, 0x10, 0xf4, 0x01, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x53,\n\t0x6b, 0x72, 0x61, 0x70, 0x70, 0x69, 0x6f, 0x10, 0xf5, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x4d, 0x6f,\n\t0x6e, 0x64, 0x61, 0x79, 0x10, 0xf6, 0x01, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x6d, 0x61, 0x72, 0x74,\n\t0x73, 0x68, 0x65, 0x65, 0x74, 0x73, 0x10, 0xf7, 0x01, 0x12, 0x0a, 0x0a, 0x05, 0x57, 0x72, 0x69,\n\t0x6b, 0x65, 0x10, 0xf8, 0x01, 0x12, 0x0a, 0x0a, 0x05, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x10, 0xf9,\n\t0x01, 0x12, 0x0d, 0x0a, 0x08, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x6b, 0x69, 0x74, 0x10, 0xfa, 0x01,\n\t0x12, 0x13, 0x0a, 0x0a, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x6f, 0x6d, 0x61, 0x74, 0x10, 0xfb,\n\t0x01, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x61, 0x6c, 0x65, 0x73, 0x62, 0x6c,\n\t0x69, 0x6e, 0x6b, 0x10, 0xfc, 0x01, 0x12, 0x0a, 0x0a, 0x05, 0x42, 0x6f, 0x72, 0x65, 0x64, 0x10,\n\t0xfd, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x43, 0x61, 0x6d, 0x70, 0x61, 0x79, 0x6e, 0x10, 0xfe, 0x01,\n\t0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6c, 0x69, 0x6e, 0x63, 0x68, 0x70, 0x61, 0x64, 0x10, 0xff, 0x01,\n\t0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x48, 0x75, 0x62, 0x10, 0x80,\n\t0x02, 0x12, 0x0d, 0x0a, 0x08, 0x44, 0x65, 0x62, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x10, 0x81, 0x02,\n\t0x12, 0x0d, 0x0a, 0x08, 0x44, 0x79, 0x73, 0x70, 0x61, 0x74, 0x63, 0x68, 0x10, 0x82, 0x02, 0x12,\n\t0x10, 0x0a, 0x0b, 0x47, 0x75, 0x61, 0x72, 0x64, 0x69, 0x61, 0x6e, 0x61, 0x70, 0x69, 0x10, 0x83,\n\t0x02, 0x12, 0x0c, 0x0a, 0x07, 0x48, 0x61, 0x72, 0x76, 0x65, 0x73, 0x74, 0x10, 0x84, 0x02, 0x12,\n\t0x0c, 0x0a, 0x07, 0x4d, 0x6f, 0x6f, 0x73, 0x65, 0x6e, 0x64, 0x10, 0x85, 0x02, 0x12, 0x10, 0x0a,\n\t0x0b, 0x4f, 0x70, 0x65, 0x6e, 0x57, 0x65, 0x61, 0x74, 0x68, 0x65, 0x72, 0x10, 0x86, 0x02, 0x12,\n\t0x0d, 0x0a, 0x08, 0x53, 0x69, 0x74, 0x65, 0x6c, 0x65, 0x61, 0x66, 0x10, 0x87, 0x02, 0x12, 0x10,\n\t0x0a, 0x0b, 0x53, 0x71, 0x75, 0x61, 0x72, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x10, 0x88, 0x02,\n\t0x12, 0x0c, 0x0a, 0x07, 0x46, 0x6c, 0x6f, 0x77, 0x46, 0x6c, 0x75, 0x10, 0x89, 0x02, 0x12, 0x0b,\n\t0x0a, 0x06, 0x4e, 0x69, 0x6d, 0x62, 0x6c, 0x65, 0x10, 0x8a, 0x02, 0x12, 0x14, 0x0a, 0x0f, 0x4c,\n\t0x65, 0x73, 0x73, 0x41, 0x6e, 0x6e, 0x6f, 0x79, 0x69, 0x6e, 0x67, 0x43, 0x52, 0x4d, 0x10, 0x8b,\n\t0x02, 0x12, 0x0c, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x68, 0x75, 0x6e, 0x74, 0x10, 0x8c, 0x02, 0x12,\n\t0x0c, 0x0a, 0x07, 0x41, 0x70, 0x70, 0x74, 0x69, 0x76, 0x6f, 0x10, 0x8d, 0x02, 0x12, 0x0f, 0x0a,\n\t0x0a, 0x43, 0x61, 0x70, 0x73, 0x75, 0x6c, 0x65, 0x43, 0x52, 0x4d, 0x10, 0x8e, 0x02, 0x12, 0x0e,\n\t0x0a, 0x09, 0x49, 0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x6c, 0x79, 0x10, 0x8f, 0x02, 0x12, 0x0a,\n\t0x0a, 0x05, 0x4b, 0x79, 0x6c, 0x61, 0x73, 0x10, 0x90, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x4f, 0x6e,\n\t0x65, 0x70, 0x61, 0x67, 0x65, 0x43, 0x52, 0x4d, 0x10, 0x91, 0x02, 0x12, 0x09, 0x0a, 0x04, 0x55,\n\t0x73, 0x65, 0x72, 0x10, 0x92, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x73, 0x70, 0x65,\n\t0x63, 0x74, 0x43, 0x52, 0x4d, 0x10, 0x93, 0x02, 0x12, 0x18, 0x0a, 0x13, 0x52, 0x65, 0x61, 0x6c,\n\t0x6c, 0x79, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x10,\n\t0x94, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x41, 0x69, 0x72, 0x73, 0x68, 0x69, 0x70, 0x10, 0x95, 0x02,\n\t0x12, 0x0a, 0x0a, 0x05, 0x41, 0x72, 0x74, 0x73, 0x79, 0x10, 0x96, 0x02, 0x12, 0x0b, 0x0a, 0x06,\n\t0x59, 0x61, 0x6e, 0x64, 0x65, 0x78, 0x10, 0x97, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x6c, 0x6f,\n\t0x63, 0x6b, 0x69, 0x66, 0x79, 0x10, 0x98, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x44, 0x6e, 0x73, 0x63,\n\t0x68, 0x65, 0x63, 0x6b, 0x10, 0x99, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x45, 0x61, 0x73, 0x79, 0x49,\n\t0x6e, 0x73, 0x69, 0x67, 0x68, 0x74, 0x10, 0x9a, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x45, 0x74, 0x68,\n\t0x70, 0x6c, 0x6f, 0x72, 0x65, 0x72, 0x10, 0x9b, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x45, 0x76, 0x65,\n\t0x72, 0x68, 0x6f, 0x75, 0x72, 0x10, 0x9c, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x46, 0x75, 0x6c, 0x63,\n\t0x72, 0x75, 0x6d, 0x10, 0x9d, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x47, 0x65, 0x6f, 0x49, 0x70, 0x69,\n\t0x66, 0x69, 0x10, 0x9e, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x4a, 0x6f, 0x74, 0x66, 0x6f, 0x72, 0x6d,\n\t0x10, 0x9f, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x52, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x72, 0x10, 0xa0,\n\t0x02, 0x12, 0x10, 0x0a, 0x0b, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x61, 0x70, 0x69,\n\t0x10, 0xa1, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x54, 0x6f, 0x67, 0x67, 0x6c, 0x54, 0x72, 0x61, 0x63,\n\t0x6b, 0x10, 0xa2, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x56, 0x70, 0x6e, 0x61, 0x70, 0x69, 0x10, 0xa3,\n\t0x02, 0x12, 0x0e, 0x0a, 0x09, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xa4,\n\t0x02, 0x12, 0x0b, 0x0a, 0x06, 0x41, 0x70, 0x6f, 0x6c, 0x6c, 0x6f, 0x10, 0xa5, 0x02, 0x12, 0x0d,\n\t0x0a, 0x08, 0x45, 0x76, 0x65, 0x72, 0x73, 0x69, 0x67, 0x6e, 0x10, 0xa6, 0x02, 0x12, 0x09, 0x0a,\n\t0x04, 0x4a, 0x75, 0x72, 0x6f, 0x10, 0xa7, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x4b, 0x61, 0x72, 0x6d,\n\t0x61, 0x43, 0x52, 0x4d, 0x10, 0xa8, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x4d, 0x65, 0x74, 0x72, 0x69,\n\t0x6c, 0x6f, 0x10, 0xa9, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x61, 0x6e, 0x64, 0x61, 0x64, 0x6f,\n\t0x63, 0x10, 0xaa, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x52, 0x65, 0x76, 0x61, 0x6d, 0x70, 0x43, 0x52,\n\t0x4d, 0x10, 0xab, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x61, 0x6c, 0x65, 0x73, 0x63, 0x6f, 0x6f,\n\t0x6b, 0x69, 0x65, 0x10, 0xac, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x41, 0x6c, 0x63, 0x6f, 0x6e, 0x6f,\n\t0x73, 0x74, 0x10, 0xad, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x42, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x72,\n\t0x10, 0xae, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x41, 0x63, 0x63, 0x75, 0x77, 0x65, 0x61, 0x74, 0x68,\n\t0x65, 0x72, 0x10, 0xaf, 0x02, 0x12, 0x13, 0x0a, 0x0a, 0x4f, 0x70, 0x65, 0x6e, 0x67, 0x72, 0x61,\n\t0x70, 0x68, 0x72, 0x10, 0xb0, 0x02, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x09, 0x0a, 0x04, 0x52, 0x61,\n\t0x77, 0x67, 0x10, 0xb1, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x52, 0x69, 0x6f, 0x74, 0x67, 0x61, 0x6d,\n\t0x65, 0x73, 0x10, 0xb2, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x61,\n\t0x72, 0x79, 0x10, 0xb3, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x72, 0x6d, 0x67, 0x6c,\n\t0x61, 0x73, 0x73, 0x10, 0xb4, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x6f, 0x6d, 0x74, 0x6f, 0x6d,\n\t0x10, 0xb5, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x77, 0x69, 0x74, 0x63, 0x68, 0x10, 0xb6, 0x02,\n\t0x12, 0x0b, 0x0a, 0x06, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x6f, 0x10, 0xb7, 0x02, 0x12, 0x0e, 0x0a,\n\t0x09, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x77, 0x61, 0x79, 0x73, 0x10, 0xb8, 0x02, 0x12, 0x0f, 0x0a,\n\t0x0a, 0x56, 0x65, 0x65, 0x76, 0x61, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x10, 0xb9, 0x02, 0x12, 0x10,\n\t0x0a, 0x0b, 0x4b, 0x69, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x10, 0xba, 0x02,\n\t0x12, 0x17, 0x0a, 0x12, 0x53, 0x68, 0x6f, 0x70, 0x65, 0x65, 0x4f, 0x70, 0x65, 0x6e, 0x50, 0x6c,\n\t0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x10, 0xbb, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x54, 0x65, 0x61,\n\t0x6d, 0x56, 0x69, 0x65, 0x77, 0x65, 0x72, 0x10, 0xbc, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x42, 0x75,\n\t0x6c, 0x62, 0x75, 0x6c, 0x10, 0xbd, 0x02, 0x12, 0x16, 0x0a, 0x11, 0x43, 0x65, 0x6e, 0x74, 0x72,\n\t0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x52, 0x4d, 0x10, 0xbe, 0x02, 0x12,\n\t0x0d, 0x0a, 0x08, 0x54, 0x65, 0x61, 0x6d, 0x67, 0x61, 0x74, 0x65, 0x10, 0xbf, 0x02, 0x12, 0x0c,\n\t0x0a, 0x07, 0x41, 0x78, 0x6f, 0x6e, 0x61, 0x75, 0x74, 0x10, 0xc0, 0x02, 0x12, 0x0b, 0x0a, 0x06,\n\t0x54, 0x79, 0x6e, 0x74, 0x65, 0x63, 0x10, 0xc1, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x41, 0x70, 0x70,\n\t0x63, 0x75, 0x65, 0x73, 0x10, 0xc2, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x6f, 0x6b,\n\t0x6c, 0x6f, 0x73, 0x65, 0x10, 0xc3, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6c, 0x6f, 0x75, 0x64,\n\t0x70, 0x6c, 0x61, 0x6e, 0x10, 0xc4, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x44, 0x6f, 0x74, 0x64, 0x69,\n\t0x67, 0x69, 0x74, 0x61, 0x6c, 0x10, 0xc5, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x45,\n\t0x6d, 0x61, 0x69, 0x6c, 0x10, 0xc6, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x45, 0x6d,\n\t0x61, 0x69, 0x6c, 0x73, 0x10, 0xc7, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x4b, 0x6f, 0x6e, 0x74, 0x65,\n\t0x6e, 0x74, 0x10, 0xc8, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x4c, 0x65, 0x61, 0x64, 0x66, 0x65, 0x65,\n\t0x64, 0x65, 0x72, 0x10, 0xc9, 0x02, 0x12, 0x0a, 0x0a, 0x05, 0x52, 0x61, 0x76, 0x65, 0x6e, 0x10,\n\t0xca, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x52, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x61, 0x63,\n\t0x68, 0x10, 0xcb, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x55, 0x70, 0x6c, 0x65, 0x61, 0x64, 0x10, 0xcc,\n\t0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x42, 0x72, 0x61, 0x6e, 0x64, 0x66, 0x65, 0x74, 0x63, 0x68, 0x10,\n\t0xcd, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x62, 0x69, 0x74, 0x10, 0xce,\n\t0x02, 0x12, 0x0c, 0x0a, 0x07, 0x43, 0x72, 0x6f, 0x77, 0x64, 0x69, 0x6e, 0x10, 0xcf, 0x02, 0x12,\n\t0x0d, 0x0a, 0x08, 0x4d, 0x61, 0x70, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0xd0, 0x02, 0x12, 0x0f,\n\t0x0a, 0x0a, 0x4e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x61, 0x62, 0x6c, 0x65, 0x10, 0xd1, 0x02, 0x12,\n\t0x0b, 0x0a, 0x06, 0x4f, 0x6e, 0x62, 0x75, 0x6b, 0x61, 0x10, 0xd2, 0x02, 0x12, 0x0c, 0x0a, 0x07,\n\t0x54, 0x6f, 0x64, 0x6f, 0x69, 0x73, 0x74, 0x10, 0xd3, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x74,\n\t0x6f, 0x72, 0x79, 0x63, 0x68, 0x69, 0x65, 0x66, 0x10, 0xd4, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x4c,\n\t0x69, 0x6e, 0x6b, 0x65, 0x64, 0x49, 0x6e, 0x10, 0xd5, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x59, 0x6f,\n\t0x75, 0x53, 0x69, 0x67, 0x6e, 0x10, 0xd6, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x44, 0x6f, 0x63, 0x6b,\n\t0x65, 0x72, 0x10, 0xd7, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x54, 0x65, 0x6c, 0x65, 0x73, 0x69, 0x67,\n\t0x6e, 0x10, 0xd8, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x70, 0x6f, 0x6f, 0x6e, 0x61, 0x63, 0x75,\n\t0x6c, 0x61, 0x72, 0x10, 0xd9, 0x02, 0x12, 0x11, 0x0a, 0x0c, 0x41, 0x65, 0x72, 0x69, 0x73, 0x77,\n\t0x65, 0x61, 0x74, 0x68, 0x65, 0x72, 0x10, 0xda, 0x02, 0x12, 0x11, 0x0a, 0x0c, 0x41, 0x6c, 0x70,\n\t0x68, 0x61, 0x76, 0x61, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x10, 0xdb, 0x02, 0x12, 0x0a, 0x0a, 0x05,\n\t0x49, 0x6d, 0x67, 0x75, 0x72, 0x10, 0xdc, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x49, 0x6d, 0x61, 0x67,\n\t0x67, 0x61, 0x10, 0xdd, 0x02, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x4d, 0x53, 0x41, 0x70, 0x69, 0x10,\n\t0xde, 0x02, 0x12, 0x11, 0x0a, 0x0c, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x73, 0x69,\n\t0x6f, 0x6e, 0x10, 0xdf, 0x02, 0x12, 0x12, 0x0a, 0x09, 0x42, 0x6c, 0x61, 0x62, 0x6c, 0x61, 0x62,\n\t0x75, 0x73, 0x10, 0xe0, 0x02, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x57, 0x6f, 0x72,\n\t0x64, 0x73, 0x41, 0x70, 0x69, 0x10, 0xe1, 0x02, 0x12, 0x12, 0x0a, 0x0d, 0x43, 0x75, 0x72, 0x72,\n\t0x65, 0x6e, 0x63, 0x79, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x10, 0xe2, 0x02, 0x12, 0x0d, 0x0a, 0x08,\n\t0x48, 0x74, 0x6d, 0x6c, 0x32, 0x50, 0x64, 0x66, 0x10, 0xe3, 0x02, 0x12, 0x12, 0x0a, 0x0d, 0x49,\n\t0x50, 0x47, 0x65, 0x6f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0xe4, 0x02, 0x12,\n\t0x0b, 0x0a, 0x06, 0x4f, 0x77, 0x6c, 0x62, 0x6f, 0x74, 0x10, 0xe5, 0x02, 0x12, 0x11, 0x0a, 0x0c,\n\t0x43, 0x6c, 0x6f, 0x75, 0x64, 0x6d, 0x65, 0x72, 0x73, 0x69, 0x76, 0x65, 0x10, 0xe6, 0x02, 0x12,\n\t0x0d, 0x0a, 0x08, 0x44, 0x79, 0x6e, 0x61, 0x6c, 0x69, 0x73, 0x74, 0x10, 0xe7, 0x02, 0x12, 0x14,\n\t0x0a, 0x0f, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x41, 0x50,\n\t0x49, 0x10, 0xe8, 0x02, 0x12, 0x0f, 0x0a, 0x0a, 0x48, 0x6f, 0x6c, 0x69, 0x64, 0x61, 0x79, 0x41,\n\t0x50, 0x49, 0x10, 0xe9, 0x02, 0x12, 0x0a, 0x0a, 0x05, 0x49, 0x70, 0x61, 0x70, 0x69, 0x10, 0xea,\n\t0x02, 0x12, 0x10, 0x0a, 0x0b, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x73, 0x74, 0x61, 0x63, 0x6b,\n\t0x10, 0xeb, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x4e, 0x75, 0x74, 0x72, 0x69, 0x74, 0x69, 0x6f, 0x6e,\n\t0x69, 0x78, 0x10, 0xec, 0x02, 0x12, 0x0a, 0x0a, 0x05, 0x53, 0x77, 0x65, 0x6c, 0x6c, 0x10, 0xed,\n\t0x02, 0x12, 0x19, 0x0a, 0x14, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x65, 0x72, 0x73,\n\t0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xee, 0x02, 0x12, 0x0e, 0x0a, 0x05,\n\t0x4e, 0x69, 0x74, 0x72, 0x6f, 0x10, 0xef, 0x02, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x08, 0x0a, 0x03,\n\t0x52, 0x65, 0x76, 0x10, 0xf0, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x52, 0x75, 0x6e, 0x52, 0x75, 0x6e,\n\t0x49, 0x74, 0x10, 0xf1, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x54, 0x79, 0x70, 0x65, 0x66, 0x6f, 0x72,\n\t0x6d, 0x10, 0xf2, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x4d, 0x69, 0x78, 0x70, 0x61, 0x6e, 0x65, 0x6c,\n\t0x10, 0xf3, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x72, 0x61, 0x64, 0x69, 0x65, 0x72, 0x10, 0xf4,\n\t0x02, 0x12, 0x0d, 0x0a, 0x08, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, 0x10, 0xf5, 0x02,\n\t0x12, 0x0d, 0x0a, 0x08, 0x56, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x79, 0x10, 0xf6, 0x02, 0x12,\n\t0x0b, 0x0a, 0x06, 0x41, 0x6c, 0x65, 0x67, 0x72, 0x61, 0x10, 0xf7, 0x02, 0x12, 0x09, 0x0a, 0x04,\n\t0x41, 0x75, 0x64, 0x64, 0x10, 0xf8, 0x02, 0x12, 0x10, 0x0a, 0x0b, 0x42, 0x61, 0x72, 0x65, 0x6d,\n\t0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x10, 0xf9, 0x02, 0x12, 0x0c, 0x0a, 0x07, 0x43, 0x6f, 0x69,\n\t0x6e, 0x6c, 0x69, 0x62, 0x10, 0xfa, 0x02, 0x12, 0x15, 0x0a, 0x10, 0x45, 0x78, 0x63, 0x68, 0x61,\n\t0x6e, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x73, 0x41, 0x50, 0x49, 0x10, 0xfb, 0x02, 0x12, 0x12,\n\t0x0a, 0x0d, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x63, 0x6f, 0x6f, 0x70, 0x10,\n\t0xfc, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x46, 0x58, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x74, 0x10, 0xfd,\n\t0x02, 0x12, 0x12, 0x0a, 0x0d, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x43, 0x6c, 0x6f,\n\t0x75, 0x64, 0x10, 0xfe, 0x02, 0x12, 0x0e, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x41,\n\t0x50, 0x49, 0x10, 0xff, 0x02, 0x12, 0x0d, 0x0a, 0x08, 0x41, 0x62, 0x73, 0x74, 0x72, 0x61, 0x63,\n\t0x74, 0x10, 0x80, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x42, 0x69, 0x6c, 0x6c, 0x6f, 0x6d, 0x61, 0x74,\n\t0x10, 0x81, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x44, 0x6f, 0x76, 0x69, 0x63, 0x6f, 0x10, 0x82, 0x03,\n\t0x12, 0x0b, 0x0a, 0x06, 0x42, 0x69, 0x74, 0x62, 0x61, 0x72, 0x10, 0x83, 0x03, 0x12, 0x0c, 0x0a,\n\t0x07, 0x42, 0x75, 0x67, 0x73, 0x6e, 0x61, 0x67, 0x10, 0x84, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x41,\n\t0x73, 0x73, 0x65, 0x6d, 0x62, 0x6c, 0x79, 0x41, 0x49, 0x10, 0x85, 0x03, 0x12, 0x0f, 0x0a, 0x0a,\n\t0x41, 0x64, 0x61, 0x66, 0x72, 0x75, 0x69, 0x74, 0x49, 0x4f, 0x10, 0x86, 0x03, 0x12, 0x0a, 0x0a,\n\t0x05, 0x41, 0x70, 0x69, 0x66, 0x79, 0x10, 0x87, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6f, 0x69,\n\t0x6e, 0x47, 0x65, 0x63, 0x6b, 0x6f, 0x10, 0x88, 0x03, 0x12, 0x12, 0x0a, 0x0d, 0x43, 0x72, 0x79,\n\t0x70, 0x74, 0x6f, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x65, 0x10, 0x89, 0x03, 0x12, 0x0e, 0x0a,\n\t0x09, 0x46, 0x75, 0x6c, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x10, 0x8a, 0x03, 0x12, 0x0e, 0x0a,\n\t0x09, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x10, 0x8b, 0x03, 0x12, 0x0d, 0x0a,\n\t0x08, 0x4c, 0x6f, 0x79, 0x76, 0x65, 0x72, 0x73, 0x65, 0x10, 0x8c, 0x03, 0x12, 0x0c, 0x0a, 0x07,\n\t0x4e, 0x65, 0x74, 0x43, 0x6f, 0x72, 0x65, 0x10, 0x8d, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x61,\n\t0x75, 0x63, 0x65, 0x4c, 0x61, 0x62, 0x73, 0x10, 0x8e, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x41, 0x6c,\n\t0x69, 0x65, 0x6e, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x10, 0x8f, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x41,\n\t0x70, 0x69, 0x66, 0x6c, 0x61, 0x73, 0x68, 0x10, 0x91, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6f,\n\t0x69, 0x6e, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x10, 0x92, 0x03, 0x12, 0x10, 0x0a, 0x0b, 0x43, 0x75,\n\t0x72, 0x72, 0x65, 0x6e, 0x74, 0x73, 0x41, 0x50, 0x49, 0x10, 0x93, 0x03, 0x12, 0x0c, 0x0a, 0x07,\n\t0x44, 0x61, 0x74, 0x61, 0x47, 0x6f, 0x76, 0x10, 0x94, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x45, 0x6e,\n\t0x69, 0x67, 0x6d, 0x61, 0x10, 0x95, 0x03, 0x12, 0x1a, 0x0a, 0x15, 0x46, 0x69, 0x6e, 0x61, 0x6e,\n\t0x63, 0x69, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x65, 0x70,\n\t0x10, 0x96, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x47, 0x65, 0x6f, 0x63, 0x6f, 0x64, 0x69, 0x6f, 0x10,\n\t0x97, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x48, 0x65, 0x72, 0x65, 0x41, 0x50, 0x49, 0x10, 0x98, 0x03,\n\t0x12, 0x13, 0x0a, 0x0a, 0x4d, 0x61, 0x63, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x10, 0x99,\n\t0x03, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0c, 0x0a, 0x07, 0x4f, 0x4f, 0x50, 0x53, 0x70, 0x61, 0x6d,\n\t0x10, 0x9a, 0x03, 0x12, 0x10, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73,\n\t0x49, 0x4f, 0x10, 0x9b, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x63, 0x72, 0x61, 0x70, 0x65, 0x72,\n\t0x41, 0x50, 0x49, 0x10, 0x9c, 0x03, 0x12, 0x13, 0x0a, 0x0e, 0x53, 0x65, 0x63, 0x75, 0x72, 0x69,\n\t0x74, 0x79, 0x54, 0x72, 0x61, 0x69, 0x6c, 0x73, 0x10, 0x9d, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x54,\n\t0x6f, 0x6d, 0x6f, 0x72, 0x72, 0x6f, 0x77, 0x49, 0x4f, 0x10, 0x9e, 0x03, 0x12, 0x13, 0x0a, 0x0e,\n\t0x57, 0x6f, 0x72, 0x6c, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x10, 0x9f,\n\t0x03, 0x12, 0x11, 0x0a, 0x0c, 0x46, 0x61, 0x63, 0x65, 0x50, 0x6c, 0x75, 0x73, 0x50, 0x6c, 0x75,\n\t0x73, 0x10, 0xa0, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x56, 0x6f, 0x69, 0x63, 0x65, 0x67, 0x61, 0x69,\n\t0x6e, 0x10, 0xa1, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x44, 0x65, 0x65, 0x70, 0x67, 0x72, 0x61, 0x6d,\n\t0x10, 0xa2, 0x03, 0x12, 0x13, 0x0a, 0x0e, 0x56, 0x69, 0x73, 0x75, 0x61, 0x6c, 0x43, 0x72, 0x6f,\n\t0x73, 0x73, 0x69, 0x6e, 0x67, 0x10, 0xa3, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x46, 0x69, 0x6e, 0x6e,\n\t0x68, 0x75, 0x62, 0x10, 0xa4, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x69, 0x69, 0x6e, 0x67, 0x6f,\n\t0x10, 0xa5, 0x03, 0x12, 0x10, 0x0a, 0x0b, 0x52, 0x69, 0x6e, 0x67, 0x43, 0x65, 0x6e, 0x74, 0x72,\n\t0x61, 0x6c, 0x10, 0xa6, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x69, 0x6e, 0x61, 0x67, 0x65, 0x10,\n\t0xa7, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x45, 0x64, 0x61, 0x6d, 0x61, 0x6d, 0x10, 0xa8, 0x03, 0x12,\n\t0x10, 0x0a, 0x0b, 0x48, 0x79, 0x70, 0x65, 0x41, 0x75, 0x64, 0x69, 0x74, 0x6f, 0x72, 0x10, 0xa9,\n\t0x03, 0x12, 0x0a, 0x0a, 0x05, 0x47, 0x65, 0x6e, 0x67, 0x6f, 0x10, 0xaa, 0x03, 0x12, 0x0a, 0x0a,\n\t0x05, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x10, 0xab, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x46, 0x6c, 0x65,\n\t0x65, 0x74, 0x62, 0x61, 0x73, 0x65, 0x10, 0xac, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x42, 0x75, 0x62,\n\t0x62, 0x6c, 0x65, 0x10, 0xad, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72,\n\t0x62, 0x65, 0x61, 0x72, 0x10, 0xae, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x41, 0x64, 0x7a, 0x75, 0x6e,\n\t0x61, 0x10, 0xaf, 0x03, 0x12, 0x13, 0x0a, 0x0e, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x41,\n\t0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x10, 0xb0, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x6f, 0x6d,\n\t0x6d, 0x65, 0x72, 0x63, 0x65, 0x4a, 0x53, 0x10, 0xb1, 0x03, 0x12, 0x13, 0x0a, 0x0e, 0x44, 0x65,\n\t0x74, 0x65, 0x63, 0x74, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x10, 0xb2, 0x03, 0x12,\n\t0x11, 0x0a, 0x08, 0x46, 0x61, 0x6b, 0x65, 0x4a, 0x53, 0x4f, 0x4e, 0x10, 0xb3, 0x03, 0x1a, 0x02,\n\t0x08, 0x01, 0x12, 0x10, 0x0a, 0x0b, 0x47, 0x72, 0x61, 0x70, 0x68, 0x68, 0x6f, 0x70, 0x70, 0x65,\n\t0x72, 0x10, 0xb4, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x4c, 0x65, 0x78, 0x69, 0x67, 0x72, 0x61, 0x6d,\n\t0x10, 0xb5, 0x03, 0x12, 0x10, 0x0a, 0x0b, 0x4c, 0x69, 0x6e, 0x6b, 0x50, 0x72, 0x65, 0x76, 0x69,\n\t0x65, 0x77, 0x10, 0xb6, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x4e, 0x75, 0x6d, 0x76, 0x65, 0x72, 0x69,\n\t0x66, 0x79, 0x10, 0xb7, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x72,\n\t0x61, 0x77, 0x6c, 0x10, 0xb8, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x5a, 0x69, 0x70, 0x43, 0x6f, 0x64,\n\t0x65, 0x41, 0x50, 0x49, 0x10, 0xb9, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6f, 0x6d, 0x65, 0x74,\n\t0x63, 0x68, 0x61, 0x74, 0x10, 0xba, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x4b, 0x65, 0x79, 0x67, 0x65,\n\t0x6e, 0x10, 0xbb, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x4d, 0x69, 0x78, 0x63, 0x6c, 0x6f, 0x75, 0x64,\n\t0x10, 0xbc, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x61, 0x74, 0x75, 0x6d, 0x49, 0x4f, 0x10, 0xbd,\n\t0x03, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x10, 0xbe, 0x03, 0x12,\n\t0x0f, 0x0a, 0x06, 0x4c, 0x61, 0x73, 0x74, 0x66, 0x6d, 0x10, 0xbf, 0x03, 0x1a, 0x02, 0x08, 0x01,\n\t0x12, 0x0d, 0x0a, 0x08, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x68, 0x6f, 0x74, 0x10, 0xc0, 0x03, 0x12,\n\t0x0c, 0x0a, 0x07, 0x4a, 0x53, 0x4f, 0x4e, 0x62, 0x69, 0x6e, 0x10, 0xc1, 0x03, 0x12, 0x0f, 0x0a,\n\t0x0a, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x51, 0x10, 0xc2, 0x03, 0x12, 0x12,\n\t0x0a, 0x0d, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x73, 0x68, 0x6f, 0x74, 0x41, 0x50, 0x49, 0x10,\n\t0xc3, 0x03, 0x12, 0x11, 0x0a, 0x0c, 0x57, 0x65, 0x61, 0x74, 0x68, 0x65, 0x72, 0x53, 0x74, 0x61,\n\t0x63, 0x6b, 0x10, 0xc4, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x41, 0x6d, 0x61, 0x64, 0x65, 0x75, 0x73,\n\t0x10, 0xc5, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x46, 0x6f, 0x75, 0x72, 0x53, 0x71, 0x75, 0x61, 0x72,\n\t0x65, 0x10, 0xc6, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x6c, 0x69, 0x63, 0x6b, 0x72, 0x10, 0xc7,\n\t0x03, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6c, 0x69, 0x63, 0x6b, 0x48, 0x65, 0x6c, 0x70, 0x10, 0xc8,\n\t0x03, 0x12, 0x0a, 0x0a, 0x05, 0x41, 0x6d, 0x62, 0x65, 0x65, 0x10, 0xc9, 0x03, 0x12, 0x0d, 0x0a,\n\t0x08, 0x41, 0x70, 0x69, 0x32, 0x43, 0x61, 0x72, 0x74, 0x10, 0xca, 0x03, 0x12, 0x0f, 0x0a, 0x0a,\n\t0x48, 0x79, 0x70, 0x65, 0x72, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x10, 0xcb, 0x03, 0x12, 0x0e, 0x0a,\n\t0x09, 0x4b, 0x61, 0x6b, 0x61, 0x6f, 0x54, 0x61, 0x6c, 0x6b, 0x10, 0xcc, 0x03, 0x12, 0x0c, 0x0a,\n\t0x07, 0x52, 0x69, 0x74, 0x65, 0x4b, 0x69, 0x74, 0x10, 0xcd, 0x03, 0x12, 0x11, 0x0a, 0x0c, 0x53,\n\t0x68, 0x75, 0x74, 0x74, 0x65, 0x72, 0x73, 0x74, 0x6f, 0x63, 0x6b, 0x10, 0xce, 0x03, 0x12, 0x12,\n\t0x0a, 0x09, 0x54, 0x65, 0x78, 0x74, 0x32, 0x44, 0x61, 0x74, 0x61, 0x10, 0xcf, 0x03, 0x1a, 0x02,\n\t0x08, 0x01, 0x12, 0x13, 0x0a, 0x0e, 0x59, 0x6f, 0x75, 0x4e, 0x65, 0x65, 0x64, 0x41, 0x42, 0x75,\n\t0x64, 0x67, 0x65, 0x74, 0x10, 0xd0, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x43, 0x72, 0x69, 0x63, 0x6b,\n\t0x65, 0x74, 0x10, 0xd1, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x74, 0x61,\n\t0x63, 0x6b, 0x10, 0xd2, 0x03, 0x12, 0x0a, 0x0a, 0x05, 0x47, 0x79, 0x61, 0x7a, 0x6f, 0x10, 0xd3,\n\t0x03, 0x12, 0x0e, 0x0a, 0x09, 0x4d, 0x61, 0x76, 0x65, 0x6e, 0x6c, 0x69, 0x6e, 0x6b, 0x10, 0xd4,\n\t0x03, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x68, 0x65, 0x65, 0x74, 0x79, 0x10, 0xd5, 0x03, 0x12, 0x0f,\n\t0x0a, 0x0a, 0x53, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x6d, 0x6f, 0x6e, 0x6b, 0x10, 0xd6, 0x03, 0x12,\n\t0x0e, 0x0a, 0x09, 0x53, 0x74, 0x6f, 0x63, 0x6b, 0x64, 0x61, 0x74, 0x61, 0x10, 0xd7, 0x03, 0x12,\n\t0x0d, 0x0a, 0x08, 0x55, 0x6e, 0x73, 0x70, 0x6c, 0x61, 0x73, 0x68, 0x10, 0xd8, 0x03, 0x12, 0x0e,\n\t0x0a, 0x09, 0x41, 0x6c, 0x6c, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x10, 0xd9, 0x03, 0x12, 0x11,\n\t0x0a, 0x0c, 0x43, 0x61, 0x6c, 0x6f, 0x72, 0x69, 0x65, 0x4e, 0x69, 0x6e, 0x6a, 0x61, 0x10, 0xda,\n\t0x03, 0x12, 0x0e, 0x0a, 0x09, 0x57, 0x61, 0x6c, 0x6b, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x10, 0xdb,\n\t0x03, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x74, 0x72, 0x61, 0x76, 0x61, 0x10, 0xdc, 0x03, 0x12, 0x0b,\n\t0x0a, 0x06, 0x43, 0x69, 0x63, 0x65, 0x72, 0x6f, 0x10, 0xdd, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x49,\n\t0x50, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x10, 0xde, 0x03, 0x12, 0x11, 0x0a, 0x0c, 0x50,\n\t0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x44, 0x6f, 0x74, 0x73, 0x10, 0xdf, 0x03, 0x12, 0x0c,\n\t0x0a, 0x07, 0x52, 0x6f, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x10, 0xe0, 0x03, 0x12, 0x0c, 0x0a, 0x07,\n\t0x4d, 0x61, 0x69, 0x6c, 0x73, 0x61, 0x63, 0x10, 0xe1, 0x03, 0x12, 0x0a, 0x0a, 0x05, 0x57, 0x68,\n\t0x6f, 0x78, 0x79, 0x10, 0xe2, 0x03, 0x12, 0x11, 0x0a, 0x0c, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x57,\n\t0x65, 0x61, 0x74, 0x68, 0x65, 0x72, 0x10, 0xe3, 0x03, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x70, 0x69,\n\t0x46, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x10, 0xe4, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x41, 0x79, 0x6c,\n\t0x69, 0x65, 0x6e, 0x10, 0xe5, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x47, 0x65, 0x6f, 0x63, 0x6f, 0x64,\n\t0x65, 0x10, 0xe6, 0x03, 0x12, 0x0f, 0x0a, 0x0a, 0x49, 0x63, 0x6f, 0x6e, 0x46, 0x69, 0x6e, 0x64,\n\t0x65, 0x72, 0x10, 0xe7, 0x03, 0x12, 0x0e, 0x0a, 0x05, 0x49, 0x70, 0x69, 0x66, 0x79, 0x10, 0xe8,\n\t0x03, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x12, 0x0a, 0x0d, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67,\n\t0x65, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x10, 0xe9, 0x03, 0x12, 0x08, 0x0a, 0x03, 0x4c, 0x6f, 0x62,\n\t0x10, 0xea, 0x03, 0x12, 0x12, 0x0a, 0x09, 0x4f, 0x6e, 0x57, 0x61, 0x74, 0x65, 0x72, 0x49, 0x4f,\n\t0x10, 0xeb, 0x03, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x61, 0x73, 0x74, 0x65,\n\t0x62, 0x69, 0x6e, 0x10, 0xec, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x64, 0x66, 0x4c, 0x61, 0x79,\n\t0x65, 0x72, 0x10, 0xed, 0x03, 0x12, 0x0c, 0x0a, 0x07, 0x50, 0x69, 0x78, 0x61, 0x62, 0x61, 0x79,\n\t0x10, 0xee, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x52, 0x65, 0x61, 0x64, 0x4d, 0x65, 0x10, 0xef, 0x03,\n\t0x12, 0x0d, 0x0a, 0x08, 0x56, 0x61, 0x74, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x10, 0xf0, 0x03, 0x12,\n\t0x0f, 0x0a, 0x0a, 0x56, 0x69, 0x72, 0x75, 0x73, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x10, 0xf1, 0x03,\n\t0x12, 0x0e, 0x0a, 0x09, 0x41, 0x69, 0x72, 0x56, 0x69, 0x73, 0x75, 0x61, 0x6c, 0x10, 0xf2, 0x03,\n\t0x12, 0x13, 0x0a, 0x0e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x66, 0x72, 0x65, 0x61,\n\t0x6b, 0x73, 0x10, 0xf3, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x44, 0x75, 0x66, 0x66, 0x65, 0x6c, 0x10,\n\t0xf4, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x6c, 0x61, 0x74, 0x49, 0x4f, 0x10, 0xf5, 0x03, 0x12,\n\t0x08, 0x0a, 0x03, 0x4d, 0x33, 0x6f, 0x10, 0xf6, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x4d, 0x65, 0x73,\n\t0x69, 0x62, 0x6f, 0x10, 0xf7, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x4f, 0x70, 0x65, 0x6e, 0x75, 0x76,\n\t0x10, 0xf8, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x6e, 0x69, 0x70, 0x63, 0x61, 0x72, 0x74, 0x10,\n\t0xf9, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x42, 0x65, 0x73, 0x74, 0x74, 0x69, 0x6d, 0x65, 0x10, 0xfa,\n\t0x03, 0x12, 0x10, 0x0a, 0x0b, 0x48, 0x61, 0x70, 0x70, 0x79, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65,\n\t0x10, 0xfb, 0x03, 0x12, 0x0d, 0x0a, 0x08, 0x48, 0x75, 0x6d, 0x61, 0x6e, 0x69, 0x74, 0x79, 0x10,\n\t0xfc, 0x03, 0x12, 0x0b, 0x0a, 0x06, 0x49, 0x6d, 0x70, 0x61, 0x6c, 0x61, 0x10, 0xfd, 0x03, 0x12,\n\t0x10, 0x0a, 0x0b, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x10, 0xfe,\n\t0x03, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x6f, 0x50, 0x69, 0x6c, 0x6f, 0x74, 0x10, 0xff,\n\t0x03, 0x12, 0x0b, 0x0a, 0x06, 0x42, 0x69, 0x74, 0x6d, 0x65, 0x78, 0x10, 0x80, 0x04, 0x12, 0x0d,\n\t0x0a, 0x08, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x44, 0x6f, 0x63, 0x10, 0x81, 0x04, 0x12, 0x0c, 0x0a,\n\t0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x72, 0x69, 0x10, 0x82, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x50,\n\t0x64, 0x66, 0x53, 0x68, 0x69, 0x66, 0x74, 0x10, 0x83, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x6f,\n\t0x6c, 0x6f, 0x6e, 0x69, 0x65, 0x78, 0x10, 0x84, 0x04, 0x12, 0x19, 0x0a, 0x14, 0x52, 0x65, 0x73,\n\t0x74, 0x70, 0x61, 0x63, 0x6b, 0x48, 0x74, 0x6d, 0x6c, 0x54, 0x6f, 0x50, 0x64, 0x66, 0x41, 0x50,\n\t0x49, 0x10, 0x85, 0x04, 0x12, 0x1a, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x74, 0x70, 0x61, 0x63, 0x6b,\n\t0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x73, 0x68, 0x6f, 0x74, 0x41, 0x50, 0x49, 0x10, 0x86, 0x04,\n\t0x12, 0x16, 0x0a, 0x11, 0x53, 0x68, 0x75, 0x74, 0x74, 0x65, 0x72, 0x73, 0x74, 0x6f, 0x63, 0x6b,\n\t0x4f, 0x41, 0x75, 0x74, 0x68, 0x10, 0x87, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x6b, 0x79, 0x42,\n\t0x69, 0x6f, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x10, 0x88, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x62,\n\t0x75, 0x73, 0x65, 0x49, 0x50, 0x44, 0x42, 0x10, 0x89, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x41, 0x6c,\n\t0x65, 0x74, 0x68, 0x65, 0x69, 0x61, 0x41, 0x70, 0x69, 0x10, 0x8a, 0x04, 0x12, 0x0c, 0x0a, 0x07,\n\t0x42, 0x6c, 0x69, 0x74, 0x41, 0x70, 0x70, 0x10, 0x8b, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x43, 0x65,\n\t0x6e, 0x73, 0x79, 0x73, 0x10, 0x8c, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x6c, 0x6f, 0x76, 0x65,\n\t0x72, 0x6c, 0x79, 0x10, 0x8d, 0x04, 0x12, 0x11, 0x0a, 0x0c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72,\n\t0x79, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x10, 0x8e, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x69, 0x6c,\n\t0x65, 0x49, 0x4f, 0x10, 0x8f, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x46, 0x6c, 0x69, 0x67, 0x68, 0x74,\n\t0x41, 0x70, 0x69, 0x10, 0x90, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x47, 0x65, 0x6f, 0x61, 0x70, 0x69,\n\t0x66, 0x79, 0x10, 0x91, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x49, 0x50, 0x69, 0x6e, 0x66, 0x6f, 0x44,\n\t0x42, 0x10, 0x92, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x53, 0x74, 0x61,\n\t0x63, 0x6b, 0x10, 0x93, 0x04, 0x12, 0x13, 0x0a, 0x0e, 0x4e, 0x61, 0x73, 0x64, 0x61, 0x71, 0x44,\n\t0x61, 0x74, 0x61, 0x4c, 0x69, 0x6e, 0x6b, 0x10, 0x94, 0x04, 0x12, 0x11, 0x0a, 0x0c, 0x4f, 0x70,\n\t0x65, 0x6e, 0x43, 0x61, 0x67, 0x65, 0x44, 0x61, 0x74, 0x61, 0x10, 0x95, 0x04, 0x12, 0x0d, 0x0a,\n\t0x08, 0x50, 0x61, 0x79, 0x6d, 0x6f, 0x6e, 0x67, 0x6f, 0x10, 0x96, 0x04, 0x12, 0x12, 0x0a, 0x0d,\n\t0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x10, 0x97, 0x04,\n\t0x12, 0x0e, 0x0a, 0x09, 0x52, 0x65, 0x62, 0x72, 0x61, 0x6e, 0x64, 0x6c, 0x79, 0x10, 0x98, 0x04,\n\t0x12, 0x14, 0x0a, 0x0f, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x73, 0x68, 0x6f, 0x74, 0x4c, 0x61,\n\t0x79, 0x65, 0x72, 0x10, 0x99, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x74, 0x79, 0x74, 0x63, 0x68,\n\t0x10, 0x9a, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x55, 0x6e, 0x70, 0x6c, 0x75, 0x67, 0x67, 0x10, 0x9b,\n\t0x04, 0x12, 0x10, 0x0a, 0x0b, 0x55, 0x50, 0x43, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65,\n\t0x10, 0x9c, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x63, 0x6b,\n\t0x10, 0x9d, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x47, 0x65, 0x6f, 0x63, 0x6f, 0x64, 0x69, 0x66, 0x79,\n\t0x10, 0x9e, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x4e, 0x65, 0x77, 0x73, 0x63, 0x61, 0x74, 0x63, 0x68,\n\t0x65, 0x72, 0x10, 0x9f, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x4e, 0x69, 0x63, 0x65, 0x72, 0x65, 0x70,\n\t0x6c, 0x79, 0x10, 0xa0, 0x04, 0x12, 0x11, 0x0a, 0x0c, 0x50, 0x61, 0x72, 0x74, 0x6e, 0x65, 0x72,\n\t0x73, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xa1, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x52, 0x6f, 0x75, 0x74,\n\t0x65, 0x34, 0x6d, 0x65, 0x10, 0xa2, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x63, 0x72, 0x61, 0x70,\n\t0x65, 0x6f, 0x77, 0x6c, 0x10, 0xa3, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x63, 0x72, 0x61, 0x70,\n\t0x69, 0x6e, 0x67, 0x44, 0x6f, 0x67, 0x10, 0xa4, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x74, 0x72,\n\t0x65, 0x61, 0x6b, 0x10, 0xa5, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x56, 0x65, 0x72, 0x69, 0x70, 0x68,\n\t0x6f, 0x6e, 0x65, 0x10, 0xa6, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x57, 0x65, 0x62, 0x73, 0x63, 0x72,\n\t0x61, 0x70, 0x69, 0x6e, 0x67, 0x10, 0xa7, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x5a, 0x65, 0x6e, 0x73,\n\t0x63, 0x72, 0x61, 0x70, 0x65, 0x10, 0xa8, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x5a, 0x65, 0x6e, 0x73,\n\t0x65, 0x72, 0x70, 0x10, 0xa9, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x43, 0x6f, 0x69, 0x6e, 0x41, 0x70,\n\t0x69, 0x10, 0xaa, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x47, 0x69, 0x74, 0x74, 0x65, 0x72, 0x10, 0xab,\n\t0x04, 0x12, 0x09, 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x10, 0xac, 0x04, 0x12, 0x0d, 0x0a, 0x08,\n\t0x49, 0x65, 0x78, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x10, 0xad, 0x04, 0x12, 0x11, 0x0a, 0x08, 0x52,\n\t0x65, 0x73, 0x74, 0x70, 0x61, 0x63, 0x6b, 0x10, 0xae, 0x04, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0f,\n\t0x0a, 0x0a, 0x53, 0x63, 0x72, 0x61, 0x70, 0x65, 0x72, 0x42, 0x6f, 0x78, 0x10, 0xaf, 0x04, 0x12,\n\t0x10, 0x0a, 0x0b, 0x53, 0x63, 0x72, 0x61, 0x70, 0x69, 0x6e, 0x67, 0x41, 0x6e, 0x74, 0x10, 0xb0,\n\t0x04, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x65, 0x72, 0x70, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xb1,\n\t0x04, 0x12, 0x12, 0x0a, 0x0d, 0x53, 0x6d, 0x61, 0x72, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x65,\n\t0x74, 0x73, 0x10, 0xb2, 0x04, 0x12, 0x11, 0x0a, 0x0c, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x4d,\n\t0x61, 0x73, 0x74, 0x65, 0x72, 0x10, 0xb3, 0x04, 0x12, 0x12, 0x0a, 0x0d, 0x41, 0x76, 0x69, 0x61,\n\t0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xb4, 0x04, 0x12, 0x0d, 0x0a, 0x08,\n\t0x42, 0x6f, 0x6d, 0x62, 0x42, 0x6f, 0x6d, 0x62, 0x10, 0xb5, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x43,\n\t0x6f, 0x6d, 0x6d, 0x6f, 0x64, 0x69, 0x74, 0x69, 0x65, 0x73, 0x10, 0xb6, 0x04, 0x12, 0x0a, 0x0a,\n\t0x05, 0x44, 0x66, 0x75, 0x73, 0x65, 0x10, 0xb7, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x45, 0x64, 0x65,\n\t0x6e, 0x41, 0x49, 0x10, 0xb8, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x47, 0x6c, 0x61, 0x73, 0x73, 0x6e,\n\t0x6f, 0x64, 0x65, 0x10, 0xb9, 0x04, 0x12, 0x09, 0x0a, 0x04, 0x47, 0x75, 0x72, 0x75, 0x10, 0xba,\n\t0x04, 0x12, 0x09, 0x0a, 0x04, 0x48, 0x69, 0x76, 0x65, 0x10, 0xbb, 0x04, 0x12, 0x0c, 0x0a, 0x07,\n\t0x48, 0x69, 0x76, 0x65, 0x61, 0x67, 0x65, 0x10, 0xbc, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x4b, 0x69,\n\t0x63, 0x6b, 0x62, 0x6f, 0x78, 0x10, 0xbd, 0x04, 0x12, 0x11, 0x0a, 0x08, 0x50, 0x61, 0x73, 0x73,\n\t0x62, 0x61, 0x73, 0x65, 0x10, 0xbe, 0x04, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x50,\n\t0x6f, 0x73, 0x74, 0x61, 0x67, 0x65, 0x41, 0x70, 0x70, 0x10, 0xbf, 0x04, 0x12, 0x0e, 0x0a, 0x09,\n\t0x50, 0x75, 0x72, 0x65, 0x53, 0x74, 0x61, 0x6b, 0x65, 0x10, 0xc0, 0x04, 0x12, 0x0b, 0x0a, 0x06,\n\t0x51, 0x75, 0x62, 0x6f, 0x6c, 0x65, 0x10, 0xc1, 0x04, 0x12, 0x14, 0x0a, 0x0f, 0x43, 0x61, 0x72,\n\t0x62, 0x6f, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x10, 0xc2, 0x04, 0x12,\n\t0x0d, 0x0a, 0x08, 0x49, 0x6e, 0x74, 0x72, 0x69, 0x6e, 0x69, 0x6f, 0x10, 0xc3, 0x04, 0x12, 0x15,\n\t0x0a, 0x0c, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x10, 0xc4,\n\t0x04, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x63, 0x72, 0x61, 0x70, 0x65, 0x53,\n\t0x74, 0x61, 0x63, 0x6b, 0x10, 0xc5, 0x04, 0x12, 0x19, 0x0a, 0x14, 0x54, 0x65, 0x63, 0x68, 0x6e,\n\t0x69, 0x63, 0x61, 0x6c, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x41, 0x70, 0x69, 0x10,\n\t0xc6, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x55, 0x72, 0x6c, 0x73, 0x63, 0x61, 0x6e, 0x10, 0xc7, 0x04,\n\t0x12, 0x0e, 0x0a, 0x09, 0x42, 0x61, 0x73, 0x65, 0x41, 0x70, 0x69, 0x49, 0x4f, 0x10, 0xc8, 0x04,\n\t0x12, 0x0c, 0x0a, 0x07, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x4f, 0x10, 0xc9, 0x04, 0x12, 0x08,\n\t0x0a, 0x03, 0x54, 0x4c, 0x79, 0x10, 0xca, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x68, 0x6f, 0x72,\n\t0x74, 0x63, 0x75, 0x74, 0x10, 0xcb, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x66, 0x6f,\n\t0x6c, 0x6c, 0x6f, 0x77, 0x10, 0xcc, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x54, 0x68, 0x69, 0x6e, 0x6b,\n\t0x69, 0x66, 0x69, 0x63, 0x10, 0xcd, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x65, 0x65, 0x64, 0x6c,\n\t0x79, 0x10, 0xce, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x74, 0x69, 0x74, 0x63, 0x68, 0x64, 0x61,\n\t0x74, 0x61, 0x10, 0xcf, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x46, 0x65, 0x74, 0x63, 0x68, 0x72, 0x73,\n\t0x73, 0x10, 0xd0, 0x04, 0x12, 0x11, 0x0a, 0x0c, 0x53, 0x69, 0x67, 0x6e, 0x75, 0x70, 0x67, 0x65,\n\t0x6e, 0x69, 0x75, 0x73, 0x10, 0xd1, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x69, 0x67, 0x6e, 0x61,\n\t0x74, 0x75, 0x72, 0x69, 0x74, 0x10, 0xd2, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x4f, 0x70, 0x74, 0x69,\n\t0x6d, 0x69, 0x7a, 0x65, 0x6c, 0x79, 0x10, 0xd3, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x4f, 0x63, 0x72,\n\t0x53, 0x70, 0x61, 0x63, 0x65, 0x10, 0xd4, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x57, 0x65, 0x61, 0x74,\n\t0x68, 0x65, 0x72, 0x42, 0x69, 0x74, 0x10, 0xd5, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x42, 0x75, 0x64,\n\t0x64, 0x79, 0x4e, 0x53, 0x10, 0xd6, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x5a, 0x69, 0x70, 0x41, 0x50,\n\t0x49, 0x10, 0xd7, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x5a, 0x69, 0x70, 0x42, 0x6f, 0x6f, 0x6b, 0x73,\n\t0x10, 0xd8, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x4f, 0x6e, 0x65, 0x64, 0x65, 0x73, 0x6b, 0x10, 0xd9,\n\t0x04, 0x12, 0x0c, 0x0a, 0x07, 0x42, 0x75, 0x67, 0x68, 0x65, 0x72, 0x64, 0x10, 0xda, 0x04, 0x12,\n\t0x0f, 0x0a, 0x0a, 0x42, 0x6c, 0x61, 0x7a, 0x65, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x10, 0xdb, 0x04,\n\t0x12, 0x0d, 0x0a, 0x08, 0x41, 0x75, 0x74, 0x6f, 0x64, 0x65, 0x73, 0x6b, 0x10, 0xdc, 0x04, 0x12,\n\t0x08, 0x0a, 0x03, 0x54, 0x72, 0x75, 0x10, 0xdd, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x55, 0x6e, 0x69,\n\t0x66, 0x79, 0x49, 0x44, 0x10, 0xde, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x72, 0x69, 0x6d, 0x62,\n\t0x6c, 0x65, 0x10, 0xdf, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x6d, 0x6f, 0x6f, 0x63, 0x68, 0x10,\n\t0xe0, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x65, 0x6d, 0x61, 0x70, 0x68, 0x6f, 0x72, 0x65, 0x10,\n\t0xe1, 0x04, 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x65, 0x6c, 0x6e, 0x79, 0x78, 0x10, 0xe2, 0x04, 0x12,\n\t0x0f, 0x0a, 0x0a, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x77, 0x69, 0x72, 0x65, 0x10, 0xe3, 0x04,\n\t0x12, 0x0e, 0x0a, 0x09, 0x54, 0x65, 0x78, 0x74, 0x6d, 0x61, 0x67, 0x69, 0x63, 0x10, 0xe4, 0x04,\n\t0x12, 0x0e, 0x0a, 0x09, 0x53, 0x65, 0x72, 0x70, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x10, 0xe5, 0x04,\n\t0x12, 0x0b, 0x0a, 0x06, 0x50, 0x6c, 0x61, 0x6e, 0x79, 0x6f, 0x10, 0xe6, 0x04, 0x12, 0x0f, 0x0a,\n\t0x0a, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x79, 0x62, 0x6f, 0x6f, 0x6b, 0x10, 0xe7, 0x04, 0x12, 0x09,\n\t0x0a, 0x04, 0x56, 0x79, 0x74, 0x65, 0x10, 0xe8, 0x04, 0x12, 0x0a, 0x0a, 0x05, 0x4e, 0x79, 0x6c,\n\t0x61, 0x73, 0x10, 0xe9, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x71, 0x75, 0x61, 0x72, 0x65, 0x75,\n\t0x70, 0x10, 0xea, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x44, 0x61, 0x6e, 0x64, 0x65, 0x6c, 0x69, 0x6f,\n\t0x6e, 0x10, 0xeb, 0x04, 0x12, 0x11, 0x0a, 0x08, 0x44, 0x61, 0x74, 0x61, 0x46, 0x69, 0x72, 0x65,\n\t0x10, 0xec, 0x04, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x44, 0x65, 0x65, 0x70, 0x41,\n\t0x49, 0x10, 0xed, 0x04, 0x12, 0x11, 0x0a, 0x0c, 0x4d, 0x65, 0x61, 0x6e, 0x69, 0x6e, 0x67, 0x43,\n\t0x6c, 0x6f, 0x75, 0x64, 0x10, 0xee, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x4e, 0x65, 0x75, 0x74, 0x72,\n\t0x69, 0x6e, 0x6f, 0x41, 0x70, 0x69, 0x10, 0xef, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x74, 0x6f,\n\t0x72, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x10, 0xf0, 0x04, 0x12, 0x0c, 0x0a, 0x07, 0x53, 0x68, 0x69,\n\t0x70, 0x64, 0x61, 0x79, 0x10, 0xf1, 0x04, 0x12, 0x12, 0x0a, 0x09, 0x53, 0x65, 0x6e, 0x74, 0x69,\n\t0x6d, 0x65, 0x6e, 0x74, 0x10, 0xf2, 0x04, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x18, 0x0a, 0x13, 0x53,\n\t0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x68, 0x61, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x69,\n\t0x6e, 0x67, 0x10, 0xf3, 0x04, 0x12, 0x10, 0x0a, 0x0b, 0x54, 0x65, 0x61, 0x6d, 0x77, 0x6f, 0x72,\n\t0x6b, 0x43, 0x52, 0x4d, 0x10, 0xf4, 0x04, 0x12, 0x11, 0x0a, 0x0c, 0x54, 0x65, 0x61, 0x6d, 0x77,\n\t0x6f, 0x72, 0x6b, 0x44, 0x65, 0x73, 0x6b, 0x10, 0xf5, 0x04, 0x12, 0x13, 0x0a, 0x0e, 0x54, 0x65,\n\t0x61, 0x6d, 0x77, 0x6f, 0x72, 0x6b, 0x53, 0x70, 0x61, 0x63, 0x65, 0x73, 0x10, 0xf6, 0x04, 0x12,\n\t0x0f, 0x0a, 0x0a, 0x54, 0x68, 0x65, 0x4f, 0x64, 0x64, 0x73, 0x41, 0x70, 0x69, 0x10, 0xf7, 0x04,\n\t0x12, 0x0b, 0x0a, 0x06, 0x41, 0x70, 0x61, 0x63, 0x74, 0x61, 0x10, 0xf8, 0x04, 0x12, 0x0f, 0x0a,\n\t0x0a, 0x47, 0x65, 0x74, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x10, 0xf9, 0x04, 0x12, 0x0e,\n\t0x0a, 0x05, 0x48, 0x61, 0x70, 0x70, 0x69, 0x10, 0xfa, 0x04, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0a,\n\t0x0a, 0x05, 0x4f, 0x61, 0x6e, 0x64, 0x61, 0x10, 0xfb, 0x04, 0x12, 0x0e, 0x0a, 0x09, 0x46, 0x61,\n\t0x73, 0x74, 0x46, 0x6f, 0x72, 0x65, 0x78, 0x10, 0xfc, 0x04, 0x12, 0x0d, 0x0a, 0x08, 0x41, 0x50,\n\t0x49, 0x4d, 0x61, 0x74, 0x69, 0x63, 0x10, 0xfd, 0x04, 0x12, 0x0f, 0x0a, 0x0a, 0x56, 0x65, 0x72,\n\t0x73, 0x69, 0x6f, 0x6e, 0x45, 0x79, 0x65, 0x10, 0xfe, 0x04, 0x12, 0x15, 0x0a, 0x10, 0x45, 0x61,\n\t0x67, 0x6c, 0x65, 0x45, 0x79, 0x65, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x10, 0xff,\n\t0x04, 0x12, 0x11, 0x0a, 0x0c, 0x54, 0x68, 0x6f, 0x75, 0x73, 0x61, 0x6e, 0x64, 0x45, 0x79, 0x65,\n\t0x73, 0x10, 0x80, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x50, 0x44,\n\t0x46, 0x10, 0x81, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x46, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x73, 0x74,\n\t0x61, 0x74, 0x73, 0x10, 0x82, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x43, 0x68, 0x65, 0x63, 0x49, 0x4f,\n\t0x10, 0x83, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x10,\n\t0x84, 0x05, 0x12, 0x13, 0x0a, 0x0a, 0x41, 0x70, 0x69, 0x53, 0x63, 0x69, 0x65, 0x6e, 0x63, 0x65,\n\t0x10, 0x85, 0x05, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x41, 0x70, 0x70, 0x53, 0x79,\n\t0x6e, 0x65, 0x72, 0x67, 0x79, 0x10, 0x86, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x43, 0x61, 0x66, 0x6c,\n\t0x6f, 0x75, 0x10, 0x87, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x43, 0x61, 0x73, 0x70, 0x69, 0x6f, 0x10,\n\t0x88, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x6c, 0x79, 0x48, 0x51, 0x10,\n\t0x89, 0x05, 0x12, 0x12, 0x0a, 0x0d, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x45, 0x6c, 0x65, 0x6d, 0x65,\n\t0x6e, 0x74, 0x73, 0x10, 0x8a, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x44, 0x72, 0x6f, 0x6e, 0x61, 0x48,\n\t0x51, 0x10, 0x8b, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x78, 0x10,\n\t0x8c, 0x05, 0x12, 0x09, 0x0a, 0x04, 0x46, 0x6d, 0x66, 0x77, 0x10, 0x8d, 0x05, 0x12, 0x0c, 0x0a,\n\t0x07, 0x47, 0x6f, 0x6f, 0x64, 0x44, 0x61, 0x79, 0x10, 0x8e, 0x05, 0x12, 0x09, 0x0a, 0x04, 0x4c,\n\t0x75, 0x6e, 0x6f, 0x10, 0x8f, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x4d, 0x65, 0x69, 0x73, 0x74, 0x65,\n\t0x72, 0x74, 0x61, 0x73, 0x6b, 0x10, 0x90, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x4d, 0x69, 0x6e, 0x64,\n\t0x6d, 0x65, 0x69, 0x73, 0x74, 0x65, 0x72, 0x10, 0x91, 0x05, 0x12, 0x13, 0x0a, 0x0e, 0x50, 0x65,\n\t0x6f, 0x70, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x4c, 0x61, 0x62, 0x73, 0x10, 0x92, 0x05, 0x12,\n\t0x14, 0x0a, 0x0b, 0x53, 0x63, 0x72, 0x61, 0x70, 0x65, 0x72, 0x53, 0x69, 0x74, 0x65, 0x10, 0x93,\n\t0x05, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x53, 0x63, 0x72, 0x61, 0x70, 0x66, 0x6c,\n\t0x79, 0x10, 0x94, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x79, 0x4e, 0x6f,\n\t0x74, 0x65, 0x64, 0x10, 0x95, 0x05, 0x12, 0x12, 0x0a, 0x0d, 0x54, 0x72, 0x61, 0x76, 0x65, 0x6c,\n\t0x50, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x73, 0x10, 0x96, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x57, 0x65,\n\t0x62, 0x53, 0x63, 0x72, 0x61, 0x70, 0x65, 0x72, 0x10, 0x97, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x43,\n\t0x6f, 0x6e, 0x76, 0x69, 0x65, 0x72, 0x10, 0x98, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x43, 0x6f, 0x75,\n\t0x72, 0x69, 0x65, 0x72, 0x10, 0x99, 0x05, 0x12, 0x0a, 0x0a, 0x05, 0x44, 0x69, 0x74, 0x74, 0x6f,\n\t0x10, 0x9a, 0x05, 0x12, 0x0a, 0x0a, 0x05, 0x46, 0x69, 0x6e, 0x64, 0x6c, 0x10, 0x9b, 0x05, 0x12,\n\t0x0d, 0x0a, 0x08, 0x4c, 0x65, 0x6e, 0x64, 0x66, 0x6c, 0x6f, 0x77, 0x10, 0x9c, 0x05, 0x12, 0x0f,\n\t0x0a, 0x0a, 0x4d, 0x6f, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x9d, 0x05, 0x12,\n\t0x11, 0x0a, 0x0c, 0x4f, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x74, 0x61, 0x73, 0x6f, 0x66, 0x74, 0x10,\n\t0x9e, 0x05, 0x12, 0x0a, 0x0a, 0x05, 0x50, 0x6f, 0x64, 0x69, 0x6f, 0x10, 0x9f, 0x05, 0x12, 0x10,\n\t0x0a, 0x07, 0x52, 0x6f, 0x63, 0x6b, 0x73, 0x65, 0x74, 0x10, 0xa0, 0x05, 0x1a, 0x02, 0x08, 0x01,\n\t0x12, 0x0a, 0x0a, 0x05, 0x52, 0x6f, 0x77, 0x6e, 0x64, 0x10, 0xa1, 0x05, 0x12, 0x0e, 0x0a, 0x09,\n\t0x53, 0x68, 0x6f, 0x74, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xa2, 0x05, 0x12, 0x0d, 0x0a, 0x08,\n\t0x53, 0x77, 0x69, 0x66, 0x74, 0x79, 0x70, 0x65, 0x10, 0xa3, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x54,\n\t0x77, 0x69, 0x74, 0x74, 0x65, 0x72, 0x10, 0xa4, 0x05, 0x12, 0x0a, 0x0a, 0x05, 0x48, 0x6f, 0x6e,\n\t0x65, 0x79, 0x10, 0xa5, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x46, 0x72, 0x65, 0x73, 0x68, 0x64, 0x65,\n\t0x73, 0x6b, 0x10, 0xa6, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x55, 0x70, 0x77, 0x61, 0x76, 0x65, 0x10,\n\t0xa7, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x46, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x10, 0xa8,\n\t0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x46, 0x72, 0x65, 0x73, 0x68, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x10,\n\t0xa9, 0x05, 0x12, 0x09, 0x0a, 0x04, 0x4d, 0x69, 0x74, 0x65, 0x10, 0xaa, 0x05, 0x12, 0x0b, 0x0a,\n\t0x06, 0x44, 0x65, 0x70, 0x75, 0x74, 0x79, 0x10, 0xab, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x42, 0x65,\n\t0x65, 0x62, 0x6f, 0x6c, 0x65, 0x10, 0xac, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x61, 0x73, 0x68,\n\t0x62, 0x6f, 0x61, 0x72, 0x64, 0x10, 0xad, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x4b, 0x61, 0x6e, 0x62,\n\t0x61, 0x6e, 0x10, 0xae, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x6e, 0x61,\n\t0x70, 0x73, 0x10, 0xaf, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x4d, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72,\n\t0x76, 0x61, 0x6c, 0x73, 0x10, 0xb0, 0x05, 0x12, 0x11, 0x0a, 0x0c, 0x49, 0x6e, 0x76, 0x6f, 0x69,\n\t0x63, 0x65, 0x4f, 0x63, 0x65, 0x61, 0x6e, 0x10, 0xb1, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x68,\n\t0x65, 0x72, 0x70, 0x61, 0x64, 0x65, 0x73, 0x6b, 0x10, 0xb2, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x4d,\n\t0x72, 0x74, 0x69, 0x63, 0x6b, 0x74, 0x6f, 0x63, 0x6b, 0x10, 0xb3, 0x05, 0x12, 0x0d, 0x0a, 0x08,\n\t0x43, 0x68, 0x61, 0x74, 0x66, 0x75, 0x6c, 0x65, 0x10, 0xb4, 0x05, 0x12, 0x11, 0x0a, 0x0c, 0x41,\n\t0x65, 0x72, 0x6f, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x10, 0xb5, 0x05, 0x12, 0x11,\n\t0x0a, 0x0c, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x6f, 0x63, 0x74, 0x6f, 0x70, 0x75, 0x73, 0x10, 0xb6,\n\t0x05, 0x12, 0x11, 0x0a, 0x08, 0x46, 0x75, 0x73, 0x65, 0x62, 0x69, 0x6c, 0x6c, 0x10, 0xb7, 0x05,\n\t0x1a, 0x02, 0x08, 0x01, 0x12, 0x0f, 0x0a, 0x0a, 0x47, 0x65, 0x63, 0x6b, 0x6f, 0x62, 0x6f, 0x61,\n\t0x72, 0x64, 0x10, 0xb8, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x47, 0x6f, 0x73, 0x71, 0x75, 0x61, 0x72,\n\t0x65, 0x64, 0x10, 0xb9, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x4d, 0x6f, 0x6f, 0x6e, 0x63, 0x6c, 0x65,\n\t0x72, 0x6b, 0x10, 0xba, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x50, 0x61, 0x79, 0x6d, 0x6f, 0x61, 0x70,\n\t0x70, 0x10, 0xbb, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x4d, 0x69, 0x78, 0x6d, 0x61, 0x78, 0x10, 0xbc,\n\t0x05, 0x12, 0x0e, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x73, 0x74, 0x10, 0xbd,\n\t0x05, 0x12, 0x10, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x61, 0x69, 0x72, 0x73, 0x68, 0x6f, 0x70, 0x72,\n\t0x10, 0xbe, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x47, 0x6f, 0x73, 0x68, 0x69, 0x70, 0x70, 0x6f, 0x10,\n\t0xbf, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x69, 0x67, 0x6f, 0x70, 0x74, 0x10, 0xc0, 0x05, 0x12,\n\t0x0d, 0x0a, 0x08, 0x53, 0x75, 0x67, 0x65, 0x73, 0x74, 0x65, 0x72, 0x10, 0xc1, 0x05, 0x12, 0x0c,\n\t0x0a, 0x07, 0x56, 0x69, 0x65, 0x77, 0x6e, 0x65, 0x6f, 0x10, 0xc2, 0x05, 0x12, 0x0e, 0x0a, 0x09,\n\t0x42, 0x6f, 0x6f, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x65, 0x10, 0xc3, 0x05, 0x12, 0x10, 0x0a, 0x0b,\n\t0x43, 0x61, 0x70, 0x74, 0x61, 0x69, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x10, 0xc4, 0x05, 0x12, 0x0e,\n\t0x0a, 0x09, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x76, 0x69, 0x73, 0x74, 0x10, 0xc5, 0x05, 0x12, 0x0c,\n\t0x0a, 0x07, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x67, 0x6f, 0x10, 0xc6, 0x05, 0x12, 0x0a, 0x0a, 0x05,\n\t0x43, 0x6c, 0x6f, 0x7a, 0x65, 0x10, 0xc7, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x6f, 0x72, 0x6d,\n\t0x49, 0x4f, 0x10, 0xc8, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x46, 0x6f, 0x72, 0x6d, 0x42, 0x75, 0x63,\n\t0x6b, 0x65, 0x74, 0x10, 0xc9, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x47, 0x6f, 0x43, 0x61, 0x6e, 0x76,\n\t0x61, 0x73, 0x10, 0xca, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x4d, 0x61, 0x64, 0x4b, 0x75, 0x64, 0x75,\n\t0x10, 0xcb, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x4e, 0x6f, 0x7a, 0x62, 0x65, 0x54, 0x65, 0x61, 0x6d,\n\t0x73, 0x10, 0xcc, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x50, 0x61, 0x70, 0x79, 0x72, 0x73, 0x10, 0xcd,\n\t0x05, 0x12, 0x12, 0x0a, 0x0d, 0x53, 0x75, 0x70, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x65, 0x73, 0x41,\n\t0x50, 0x49, 0x10, 0xce, 0x05, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x61, 0x6c, 0x6c, 0x79, 0x66, 0x79,\n\t0x10, 0xcf, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x5a, 0x65, 0x6e, 0x6b, 0x69, 0x74, 0x41, 0x50, 0x49,\n\t0x10, 0xd0, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x49, 0x6d, 0x61, 0x67,\n\t0x65, 0x10, 0xd1, 0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x43, 0x61,\n\t0x72, 0x65, 0x10, 0xd2, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x42, 0x6f, 0x72, 0x67, 0x62, 0x61, 0x73,\n\t0x65, 0x10, 0xd3, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x50, 0x69, 0x70, 0x65, 0x64, 0x72, 0x65, 0x61,\n\t0x6d, 0x10, 0xd4, 0x05, 0x12, 0x09, 0x0a, 0x04, 0x53, 0x69, 0x72, 0x76, 0x10, 0xd5, 0x05, 0x12,\n\t0x0c, 0x0a, 0x07, 0x44, 0x69, 0x66, 0x66, 0x62, 0x6f, 0x74, 0x10, 0xd6, 0x05, 0x12, 0x10, 0x0a,\n\t0x0b, 0x45, 0x69, 0x67, 0x68, 0x74, 0x78, 0x45, 0x69, 0x67, 0x68, 0x74, 0x10, 0xd7, 0x05, 0x12,\n\t0x0c, 0x0a, 0x07, 0x53, 0x65, 0x6e, 0x64, 0x6f, 0x73, 0x6f, 0x10, 0xd8, 0x05, 0x12, 0x11, 0x0a,\n\t0x0c, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x66, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0xd9, 0x05,\n\t0x12, 0x0e, 0x0a, 0x09, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x10, 0xda, 0x05,\n\t0x12, 0x0f, 0x0a, 0x0a, 0x50, 0x61, 0x6e, 0x64, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x10, 0xdb,\n\t0x05, 0x12, 0x0a, 0x0a, 0x05, 0x50, 0x61, 0x79, 0x6d, 0x6f, 0x10, 0xdc, 0x05, 0x12, 0x1d, 0x0a,\n\t0x18, 0x41, 0x76, 0x61, 0x7a, 0x61, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63,\n\t0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xdd, 0x05, 0x12, 0x14, 0x0a, 0x0f,\n\t0x50, 0x6c, 0x61, 0x6e, 0x76, 0x69, 0x65, 0x77, 0x4c, 0x65, 0x61, 0x6e, 0x4b, 0x69, 0x74, 0x10,\n\t0xde, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x4c, 0x69, 0x76, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x6d, 0x10,\n\t0xdf, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x4b, 0x75, 0x43, 0x6f, 0x69, 0x6e, 0x10, 0xe0, 0x05, 0x12,\n\t0x0c, 0x0a, 0x07, 0x4d, 0x65, 0x74, 0x61, 0x41, 0x50, 0x49, 0x10, 0xe1, 0x05, 0x12, 0x0d, 0x0a,\n\t0x08, 0x4e, 0x69, 0x63, 0x65, 0x48, 0x61, 0x73, 0x68, 0x10, 0xe2, 0x05, 0x12, 0x0a, 0x0a, 0x05,\n\t0x43, 0x65, 0x78, 0x49, 0x4f, 0x10, 0xe3, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x4b, 0x6c, 0x69, 0x70,\n\t0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x10, 0xe4, 0x05, 0x12, 0x0e, 0x0a, 0x09, 0x44, 0x79, 0x6e, 0x61,\n\t0x74, 0x72, 0x61, 0x63, 0x65, 0x10, 0xe5, 0x05, 0x12, 0x11, 0x0a, 0x0c, 0x4d, 0x6f, 0x6c, 0x6c,\n\t0x69, 0x65, 0x41, 0x50, 0x49, 0x4b, 0x65, 0x79, 0x10, 0xe6, 0x05, 0x12, 0x16, 0x0a, 0x11, 0x4d,\n\t0x6f, 0x6c, 0x6c, 0x69, 0x65, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e,\n\t0x10, 0xe7, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x42, 0x61, 0x73, 0x69, 0x73, 0x54, 0x68, 0x65, 0x6f,\n\t0x72, 0x79, 0x10, 0xe8, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x4e, 0x6f, 0x72, 0x64, 0x69, 0x67, 0x65,\n\t0x6e, 0x10, 0xe9, 0x05, 0x12, 0x1c, 0x0a, 0x17, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x6d, 0x69, 0x74,\n\t0x68, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x10,\n\t0xea, 0x05, 0x12, 0x13, 0x0a, 0x0e, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x6d, 0x69, 0x74, 0x68, 0x54,\n\t0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xeb, 0x05, 0x12, 0x08, 0x0a, 0x03, 0x4d, 0x75, 0x78, 0x10, 0xec,\n\t0x05, 0x12, 0x0b, 0x0a, 0x06, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x10, 0xed, 0x05, 0x12, 0x0d,\n\t0x0a, 0x08, 0x53, 0x65, 0x6e, 0x64, 0x62, 0x69, 0x72, 0x64, 0x10, 0xee, 0x05, 0x12, 0x1c, 0x0a,\n\t0x17, 0x53, 0x65, 0x6e, 0x64, 0x62, 0x69, 0x72, 0x64, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a,\n\t0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x50, 0x49, 0x10, 0xef, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x4d,\n\t0x69, 0x64, 0x69, 0x73, 0x65, 0x10, 0xf0, 0x05, 0x12, 0x0d, 0x0a, 0x08, 0x4d, 0x6f, 0x63, 0x6b,\n\t0x61, 0x72, 0x6f, 0x6f, 0x10, 0xf1, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x49, 0x6d, 0x61, 0x67, 0x65,\n\t0x34, 0x10, 0xf2, 0x05, 0x12, 0x0b, 0x0a, 0x06, 0x50, 0x69, 0x6e, 0x61, 0x74, 0x61, 0x10, 0xf3,\n\t0x05, 0x12, 0x11, 0x0a, 0x0c, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x63,\n\t0x6b, 0x10, 0xf4, 0x05, 0x12, 0x1c, 0x0a, 0x13, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x42, 0x72, 0x6f,\n\t0x77, 0x73, 0x65, 0x72, 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x10, 0xf5, 0x05, 0x1a, 0x02,\n\t0x08, 0x01, 0x12, 0x0d, 0x0a, 0x08, 0x4c, 0x6f, 0x61, 0x64, 0x6d, 0x69, 0x6c, 0x6c, 0x10, 0xf6,\n\t0x05, 0x12, 0x0f, 0x0a, 0x0a, 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x42, 0x6f, 0x74, 0x10,\n\t0xf7, 0x05, 0x12, 0x10, 0x0a, 0x0b, 0x4b, 0x6e, 0x61, 0x70, 0x73, 0x61, 0x63, 0x6b, 0x50, 0x72,\n\t0x6f, 0x10, 0xf8, 0x05, 0x12, 0x09, 0x0a, 0x04, 0x51, 0x61, 0x73, 0x65, 0x10, 0xf9, 0x05, 0x12,\n\t0x0e, 0x0a, 0x09, 0x44, 0x61, 0x72, 0x65, 0x62, 0x6f, 0x6f, 0x73, 0x74, 0x10, 0xfa, 0x05, 0x12,\n\t0x0d, 0x0a, 0x08, 0x47, 0x54, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x78, 0x10, 0xfb, 0x05, 0x12, 0x0d,\n\t0x0a, 0x08, 0x48, 0x6f, 0x6c, 0x69, 0x73, 0x74, 0x69, 0x63, 0x10, 0xfc, 0x05, 0x12, 0x0c, 0x0a,\n\t0x07, 0x50, 0x61, 0x72, 0x73, 0x65, 0x72, 0x73, 0x10, 0xfd, 0x05, 0x12, 0x12, 0x0a, 0x0d, 0x53,\n\t0x63, 0x72, 0x75, 0x74, 0x69, 0x6e, 0x69, 0x7a, 0x65, 0x72, 0x43, 0x69, 0x10, 0xfe, 0x05, 0x12,\n\t0x0f, 0x0a, 0x0a, 0x53, 0x6f, 0x6e, 0x61, 0x72, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x10, 0xff, 0x05,\n\t0x12, 0x10, 0x0a, 0x0b, 0x41, 0x50, 0x49, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x10,\n\t0x80, 0x06, 0x12, 0x14, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,\n\t0x54, 0x6f, 0x6f, 0x6c, 0x73, 0x10, 0x81, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x72, 0x61, 0x66,\n\t0x74, 0x4d, 0x79, 0x50, 0x44, 0x46, 0x10, 0x82, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x45, 0x78, 0x70,\n\t0x6f, 0x72, 0x74, 0x53, 0x44, 0x4b, 0x10, 0x83, 0x06, 0x12, 0x15, 0x0a, 0x0c, 0x47, 0x6c, 0x69,\n\t0x74, 0x74, 0x65, 0x72, 0x6c, 0x79, 0x41, 0x50, 0x49, 0x10, 0x84, 0x06, 0x1a, 0x02, 0x08, 0x01,\n\t0x12, 0x0d, 0x0a, 0x08, 0x48, 0x79, 0x62, 0x69, 0x73, 0x63, 0x75, 0x73, 0x10, 0x85, 0x06, 0x12,\n\t0x09, 0x0a, 0x04, 0x4d, 0x69, 0x72, 0x6f, 0x10, 0x86, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x74,\n\t0x61, 0x74, 0x75, 0x73, 0x70, 0x61, 0x67, 0x65, 0x10, 0x87, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x53,\n\t0x74, 0x61, 0x74, 0x75, 0x73, 0x70, 0x61, 0x6c, 0x10, 0x88, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x54,\n\t0x65, 0x6c, 0x65, 0x74, 0x79, 0x70, 0x65, 0x10, 0x89, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x54, 0x69,\n\t0x6d, 0x65, 0x43, 0x61, 0x6d, 0x70, 0x10, 0x8a, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x55, 0x73, 0x65,\n\t0x72, 0x66, 0x6c, 0x6f, 0x77, 0x10, 0x8b, 0x06, 0x12, 0x0b, 0x0a, 0x06, 0x57, 0x69, 0x73, 0x74,\n\t0x69, 0x61, 0x10, 0x8c, 0x06, 0x12, 0x13, 0x0a, 0x0a, 0x53, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x61,\n\t0x64, 0x61, 0x72, 0x10, 0x8d, 0x06, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x10, 0x0a, 0x0b, 0x55, 0x70,\n\t0x74, 0x69, 0x6d, 0x65, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x10, 0x8e, 0x06, 0x12, 0x0e, 0x0a, 0x09,\n\t0x43, 0x6f, 0x64, 0x65, 0x71, 0x75, 0x69, 0x72, 0x79, 0x10, 0x8f, 0x06, 0x12, 0x11, 0x0a, 0x0c,\n\t0x45, 0x78, 0x74, 0x72, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x41, 0x50, 0x49, 0x10, 0x90, 0x06, 0x12,\n\t0x0d, 0x0a, 0x08, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x10, 0x91, 0x06, 0x12, 0x0e,\n\t0x0a, 0x09, 0x4d, 0x61, 0x67, 0x69, 0x63, 0x42, 0x65, 0x6c, 0x6c, 0x10, 0x92, 0x06, 0x12, 0x0f,\n\t0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x72, 0x6d, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x10, 0x93, 0x06, 0x12,\n\t0x0d, 0x0a, 0x08, 0x41, 0x70, 0x69, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x10, 0x94, 0x06, 0x12, 0x0b,\n\t0x0a, 0x06, 0x44, 0x69, 0x73, 0x71, 0x75, 0x73, 0x10, 0x95, 0x06, 0x12, 0x0b, 0x0a, 0x06, 0x57,\n\t0x6f, 0x6f, 0x70, 0x72, 0x61, 0x10, 0x96, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x50, 0x61, 0x70, 0x65,\n\t0x72, 0x66, 0x6f, 0x72, 0x6d, 0x10, 0x97, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x47, 0x75, 0x6d, 0x72,\n\t0x6f, 0x61, 0x64, 0x10, 0x98, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x50, 0x61, 0x79, 0x64, 0x69, 0x72,\n\t0x74, 0x61, 0x70, 0x70, 0x10, 0x99, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x44, 0x65, 0x74, 0x65, 0x63,\n\t0x74, 0x69, 0x66, 0x79, 0x10, 0x9a, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x75,\n\t0x73, 0x63, 0x61, 0x6b, 0x65, 0x10, 0x9b, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x4a, 0x75, 0x6d, 0x70,\n\t0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x10, 0x9c, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x4c, 0x75, 0x6e,\n\t0x63, 0x68, 0x4d, 0x6f, 0x6e, 0x65, 0x79, 0x10, 0x9d, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x52, 0x6f,\n\t0x73, 0x65, 0x74, 0x74, 0x65, 0x10, 0x9e, 0x06, 0x12, 0x09, 0x0a, 0x04, 0x59, 0x65, 0x6c, 0x70,\n\t0x10, 0x9f, 0x06, 0x12, 0x0a, 0x0a, 0x05, 0x41, 0x74, 0x65, 0x72, 0x61, 0x10, 0xa0, 0x06, 0x12,\n\t0x12, 0x0a, 0x0d, 0x45, 0x63, 0x6f, 0x53, 0x74, 0x72, 0x75, 0x78, 0x75, 0x72, 0x65, 0x49, 0x54,\n\t0x10, 0xa1, 0x06, 0x12, 0x08, 0x0a, 0x03, 0x41, 0x68, 0x61, 0x10, 0xa2, 0x06, 0x12, 0x0d, 0x0a,\n\t0x08, 0x50, 0x61, 0x72, 0x73, 0x65, 0x68, 0x75, 0x62, 0x10, 0xa3, 0x06, 0x12, 0x11, 0x0a, 0x0c,\n\t0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x10, 0xa4, 0x06, 0x12,\n\t0x0f, 0x0a, 0x0a, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x73, 0x6d, 0x69, 0x74, 0x68, 0x10, 0xa5, 0x06,\n\t0x12, 0x11, 0x0a, 0x08, 0x46, 0x6c, 0x6f, 0x77, 0x64, 0x61, 0x73, 0x68, 0x10, 0xa6, 0x06, 0x1a,\n\t0x02, 0x08, 0x01, 0x12, 0x11, 0x0a, 0x08, 0x46, 0x6c, 0x6f, 0x77, 0x64, 0x6f, 0x63, 0x6b, 0x10,\n\t0xa7, 0x06, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0b, 0x0a, 0x06, 0x46, 0x69, 0x62, 0x65, 0x72, 0x79,\n\t0x10, 0xa8, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x54, 0x79, 0x70, 0x65, 0x74, 0x61, 0x6c, 0x6b, 0x10,\n\t0xa9, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x56, 0x6f, 0x6f, 0x64, 0x6f, 0x6f, 0x53, 0x4d, 0x53, 0x10,\n\t0xaa, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x5a, 0x75, 0x6c, 0x69, 0x70, 0x43, 0x68, 0x61, 0x74, 0x10,\n\t0xab, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x46, 0x6f, 0x72, 0x6d, 0x63, 0x72, 0x61, 0x66, 0x74, 0x10,\n\t0xac, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x49, 0x65, 0x78, 0x61, 0x70, 0x69, 0x73, 0x10, 0xad, 0x06,\n\t0x12, 0x0e, 0x0a, 0x09, 0x52, 0x65, 0x61, 0x63, 0x68, 0x6d, 0x61, 0x69, 0x6c, 0x10, 0xae, 0x06,\n\t0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x68, 0x61, 0x72, 0x74, 0x6d, 0x6f, 0x67, 0x75, 0x6c, 0x10, 0xaf,\n\t0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x41, 0x70, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x64, 0x10,\n\t0xb0, 0x06, 0x12, 0x08, 0x0a, 0x03, 0x57, 0x69, 0x74, 0x10, 0xb1, 0x06, 0x12, 0x15, 0x0a, 0x10,\n\t0x52, 0x65, 0x63, 0x68, 0x61, 0x72, 0x67, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73,\n\t0x10, 0xb2, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x44, 0x69, 0x67, 0x67, 0x65, 0x72, 0x6e, 0x61, 0x75,\n\t0x74, 0x10, 0xb3, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x4d, 0x6f, 0x6e, 0x6b, 0x65, 0x79, 0x4c, 0x65,\n\t0x61, 0x72, 0x6e, 0x10, 0xb4, 0x06, 0x12, 0x0a, 0x0a, 0x05, 0x44, 0x75, 0x70, 0x6c, 0x79, 0x10,\n\t0xb5, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x50, 0x6f, 0x73, 0x74, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x10,\n\t0xb6, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x32, 0x10, 0xb7,\n\t0x06, 0x12, 0x0c, 0x0a, 0x07, 0x5a, 0x65, 0x6e, 0x52, 0x6f, 0x77, 0x73, 0x10, 0xb8, 0x06, 0x12,\n\t0x10, 0x0a, 0x0b, 0x5a, 0x69, 0x70, 0x63, 0x6f, 0x64, 0x65, 0x62, 0x61, 0x73, 0x65, 0x10, 0xb9,\n\t0x06, 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x65, 0x66, 0x74, 0x65, 0x72, 0x10, 0xba, 0x06, 0x12, 0x0a,\n\t0x0a, 0x05, 0x54, 0x77, 0x69, 0x73, 0x74, 0x10, 0xbb, 0x06, 0x12, 0x16, 0x0a, 0x11, 0x42, 0x72,\n\t0x61, 0x69, 0x6e, 0x74, 0x72, 0x65, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x10,\n\t0xbc, 0x06, 0x12, 0x11, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x43, 0x6f, 0x6e, 0x76, 0x65,\n\t0x72, 0x74, 0x10, 0xbd, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x47, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61,\n\t0x10, 0xbe, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x41, 0x70,\n\t0x69, 0x10, 0xbf, 0x06, 0x12, 0x11, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72,\n\t0x77, 0x69, 0x73, 0x65, 0x10, 0xc0, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x42, 0x75, 0x6c, 0x6b, 0x73,\n\t0x6d, 0x73, 0x10, 0xc1, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x44, 0x61, 0x74, 0x61, 0x62, 0x6f, 0x78,\n\t0x10, 0xc2, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x4f, 0x6e, 0x65, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c,\n\t0x10, 0xc3, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x52, 0x65, 0x6e, 0x74, 0x6d, 0x61, 0x6e, 0x10, 0xc4,\n\t0x06, 0x12, 0x0c, 0x0a, 0x07, 0x50, 0x61, 0x72, 0x73, 0x65, 0x75, 0x72, 0x10, 0xc5, 0x06, 0x12,\n\t0x0e, 0x0a, 0x09, 0x44, 0x6f, 0x63, 0x70, 0x61, 0x72, 0x73, 0x65, 0x72, 0x10, 0xc6, 0x06, 0x12,\n\t0x0d, 0x0a, 0x08, 0x46, 0x6f, 0x72, 0x6d, 0x73, 0x69, 0x74, 0x65, 0x10, 0xc7, 0x06, 0x12, 0x11,\n\t0x0a, 0x0c, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x74, 0x61, 0x69, 0x6c, 0x6f, 0x72, 0x10, 0xc8,\n\t0x06, 0x12, 0x0c, 0x0a, 0x07, 0x4c, 0x65, 0x6d, 0x6c, 0x69, 0x73, 0x74, 0x10, 0xc9, 0x06, 0x12,\n\t0x0c, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x70, 0x61, 0x64, 0x10, 0xca, 0x06, 0x12, 0x0e, 0x0a,\n\t0x09, 0x46, 0x6f, 0x72, 0x6d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x10, 0xcb, 0x06, 0x12, 0x10, 0x0a,\n\t0x0b, 0x43, 0x6f, 0x64, 0x65, 0x63, 0x6c, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x10, 0xcc, 0x06, 0x12,\n\t0x0e, 0x0a, 0x09, 0x43, 0x6f, 0x64, 0x65, 0x6d, 0x61, 0x67, 0x69, 0x63, 0x10, 0xcd, 0x06, 0x12,\n\t0x0a, 0x0a, 0x05, 0x56, 0x62, 0x6f, 0x75, 0x74, 0x10, 0xce, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x4e,\n\t0x69, 0x67, 0x68, 0x74, 0x66, 0x61, 0x6c, 0x6c, 0x10, 0xcf, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x46,\n\t0x6c, 0x69, 0x67, 0x68, 0x74, 0x4c, 0x61, 0x62, 0x73, 0x10, 0xd0, 0x06, 0x12, 0x11, 0x0a, 0x0c,\n\t0x53, 0x70, 0x65, 0x65, 0x63, 0x68, 0x54, 0x65, 0x78, 0x74, 0x41, 0x49, 0x10, 0xd1, 0x06, 0x12,\n\t0x0d, 0x0a, 0x08, 0x50, 0x6f, 0x6c, 0x6c, 0x73, 0x41, 0x50, 0x49, 0x10, 0xd2, 0x06, 0x12, 0x0b,\n\t0x0a, 0x06, 0x53, 0x69, 0x6d, 0x46, 0x69, 0x6e, 0x10, 0xd3, 0x06, 0x12, 0x0a, 0x0a, 0x05, 0x53,\n\t0x63, 0x61, 0x6c, 0x72, 0x10, 0xd4, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x4b, 0x61, 0x6e, 0x62, 0x61,\n\t0x6e, 0x74, 0x6f, 0x6f, 0x6c, 0x10, 0xd5, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x42, 0x72, 0x69, 0x67,\n\t0x68, 0x74, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x10, 0xd6, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x48, 0x6f,\n\t0x74, 0x77, 0x69, 0x72, 0x65, 0x10, 0xd7, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x49, 0x6e, 0x73, 0x74,\n\t0x61, 0x62, 0x6f, 0x74, 0x10, 0xd8, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x54, 0x69, 0x6d, 0x65, 0x6b,\n\t0x69, 0x74, 0x10, 0xd9, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x65,\n\t0x6c, 0x6c, 0x65, 0x72, 0x10, 0xda, 0x06, 0x12, 0x11, 0x0a, 0x0c, 0x4d, 0x6f, 0x6a, 0x6f, 0x68,\n\t0x65, 0x6c, 0x70, 0x64, 0x65, 0x73, 0x6b, 0x10, 0xdb, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x43, 0x72,\n\t0x65, 0x61, 0x74, 0x65, 0x73, 0x65, 0x6e, 0x64, 0x10, 0xdc, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x47,\n\t0x65, 0x74, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x10, 0xdd, 0x06, 0x12, 0x0c, 0x0a,\n\t0x07, 0x44, 0x79, 0x6e, 0x61, 0x64, 0x6f, 0x74, 0x10, 0xde, 0x06, 0x12, 0x0a, 0x0a, 0x05, 0x44,\n\t0x65, 0x6d, 0x69, 0x6f, 0x10, 0xdf, 0x06, 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x6f, 0x6b, 0x65, 0x65,\n\t0x74, 0x10, 0xe0, 0x06, 0x12, 0x11, 0x0a, 0x0c, 0x4d, 0x79, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69,\n\t0x6d, 0x65, 0x6e, 0x74, 0x10, 0xe1, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6f, 0x70, 0x79, 0x73,\n\t0x63, 0x61, 0x70, 0x65, 0x10, 0xe2, 0x06, 0x12, 0x0d, 0x0a, 0x08, 0x42, 0x65, 0x73, 0x6e, 0x61,\n\t0x70, 0x70, 0x79, 0x10, 0xe3, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x53, 0x61, 0x6c, 0x65, 0x73, 0x6d,\n\t0x61, 0x74, 0x65, 0x10, 0xe4, 0x06, 0x12, 0x13, 0x0a, 0x0a, 0x48, 0x65, 0x61, 0x74, 0x6d, 0x61,\n\t0x70, 0x61, 0x70, 0x69, 0x10, 0xe5, 0x06, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x11, 0x0a, 0x0c, 0x57,\n\t0x65, 0x62, 0x73, 0x69, 0x74, 0x65, 0x70, 0x75, 0x6c, 0x73, 0x65, 0x10, 0xe6, 0x06, 0x12, 0x0e,\n\t0x0a, 0x09, 0x55, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x66, 0x79, 0x10, 0xe7, 0x06, 0x12, 0x0c,\n\t0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x10, 0xe8, 0x06, 0x12, 0x0d, 0x0a, 0x08,\n\t0x50, 0x44, 0x46, 0x6d, 0x79, 0x55, 0x52, 0x4c, 0x10, 0xe9, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x41,\n\t0x70, 0x69, 0x32, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x10, 0xea, 0x06, 0x12, 0x0d, 0x0a,\n\t0x08, 0x4f, 0x70, 0x73, 0x67, 0x65, 0x6e, 0x69, 0x65, 0x10, 0xeb, 0x06, 0x12, 0x0b, 0x0a, 0x06,\n\t0x47, 0x65, 0x6d, 0x69, 0x6e, 0x69, 0x10, 0xec, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x48, 0x6f, 0x6e,\n\t0x65, 0x79, 0x63, 0x6f, 0x6d, 0x62, 0x10, 0xed, 0x06, 0x12, 0x14, 0x0a, 0x0f, 0x4b, 0x61, 0x6c,\n\t0x74, 0x75, 0x72, 0x61, 0x41, 0x70, 0x70, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xee, 0x06, 0x12,\n\t0x13, 0x0a, 0x0e, 0x4b, 0x61, 0x6c, 0x74, 0x75, 0x72, 0x61, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,\n\t0x6e, 0x10, 0xef, 0x06, 0x12, 0x0a, 0x0a, 0x05, 0x42, 0x69, 0x74, 0x47, 0x6f, 0x10, 0xf0, 0x06,\n\t0x12, 0x0d, 0x0a, 0x08, 0x4f, 0x70, 0x74, 0x69, 0x64, 0x61, 0x73, 0x68, 0x10, 0xf1, 0x06, 0x12,\n\t0x0a, 0x0a, 0x05, 0x49, 0x6d, 0x67, 0x69, 0x78, 0x10, 0xf2, 0x06, 0x12, 0x10, 0x0a, 0x0b, 0x49,\n\t0x6d, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x54, 0x65, 0x78, 0x74, 0x10, 0xf3, 0x06, 0x12, 0x10, 0x0a,\n\t0x0b, 0x50, 0x61, 0x67, 0x65, 0x32, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x10, 0xf4, 0x06, 0x12,\n\t0x0e, 0x0a, 0x09, 0x51, 0x75, 0x69, 0x63, 0x6b, 0x62, 0x61, 0x73, 0x65, 0x10, 0xf5, 0x06, 0x12,\n\t0x0d, 0x0a, 0x08, 0x52, 0x65, 0x64, 0x62, 0x6f, 0x6f, 0x74, 0x68, 0x10, 0xf6, 0x06, 0x12, 0x0b,\n\t0x0a, 0x06, 0x4e, 0x75, 0x62, 0x65, 0x6c, 0x61, 0x10, 0xf7, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x49,\n\t0x6e, 0x66, 0x6f, 0x62, 0x69, 0x70, 0x10, 0xf8, 0x06, 0x12, 0x0a, 0x0a, 0x05, 0x55, 0x70, 0x72,\n\t0x6f, 0x63, 0x10, 0xf9, 0x06, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74,\n\t0x62, 0x65, 0x65, 0x10, 0xfa, 0x06, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x66, 0x74, 0x65, 0x72, 0x73,\n\t0x68, 0x69, 0x70, 0x10, 0xfb, 0x06, 0x12, 0x0c, 0x0a, 0x07, 0x45, 0x64, 0x75, 0x73, 0x69, 0x67,\n\t0x6e, 0x10, 0xfc, 0x06, 0x12, 0x0b, 0x0a, 0x06, 0x54, 0x65, 0x61, 0x6d, 0x75, 0x70, 0x10, 0xfd,\n\t0x06, 0x12, 0x0c, 0x0a, 0x07, 0x57, 0x6f, 0x72, 0x6b, 0x64, 0x61, 0x79, 0x10, 0xfe, 0x06, 0x12,\n\t0x0c, 0x0a, 0x07, 0x4d, 0x6f, 0x6e, 0x67, 0x6f, 0x44, 0x42, 0x10, 0xff, 0x06, 0x12, 0x08, 0x0a,\n\t0x03, 0x4e, 0x47, 0x43, 0x10, 0x80, 0x07, 0x12, 0x13, 0x0a, 0x0e, 0x44, 0x69, 0x67, 0x69, 0x74,\n\t0x61, 0x6c, 0x4f, 0x63, 0x65, 0x61, 0x6e, 0x56, 0x32, 0x10, 0x81, 0x07, 0x12, 0x0e, 0x0a, 0x09,\n\t0x53, 0x51, 0x4c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x10, 0x82, 0x07, 0x12, 0x08, 0x0a, 0x03,\n\t0x46, 0x54, 0x50, 0x10, 0x83, 0x07, 0x12, 0x0a, 0x0a, 0x05, 0x52, 0x65, 0x64, 0x69, 0x73, 0x10,\n\t0x84, 0x07, 0x12, 0x09, 0x0a, 0x04, 0x4c, 0x44, 0x41, 0x50, 0x10, 0x85, 0x07, 0x12, 0x0c, 0x0a,\n\t0x07, 0x53, 0x68, 0x6f, 0x70, 0x69, 0x66, 0x79, 0x10, 0x86, 0x07, 0x12, 0x0d, 0x0a, 0x08, 0x52,\n\t0x61, 0x62, 0x62, 0x69, 0x74, 0x4d, 0x51, 0x10, 0x87, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x43, 0x75,\n\t0x73, 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x88, 0x07, 0x12, 0x0e, 0x0a, 0x09,\n\t0x45, 0x74, 0x68, 0x65, 0x72, 0x73, 0x63, 0x61, 0x6e, 0x10, 0x89, 0x07, 0x12, 0x0b, 0x0a, 0x06,\n\t0x49, 0x6e, 0x66, 0x75, 0x72, 0x61, 0x10, 0x8a, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x41, 0x6c, 0x63,\n\t0x68, 0x65, 0x6d, 0x79, 0x10, 0x8b, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x63, 0x6b,\n\t0x4e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x10, 0x8c, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x4d, 0x6f, 0x72,\n\t0x61, 0x6c, 0x69, 0x73, 0x10, 0x8d, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x42, 0x73, 0x63, 0x53, 0x63,\n\t0x61, 0x6e, 0x10, 0x8e, 0x07, 0x12, 0x16, 0x0a, 0x0d, 0x43, 0x6f, 0x69, 0x6e, 0x4d, 0x61, 0x72,\n\t0x6b, 0x65, 0x74, 0x43, 0x61, 0x70, 0x10, 0x8f, 0x07, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x0a, 0x0a,\n\t0x05, 0x50, 0x65, 0x72, 0x63, 0x79, 0x10, 0x90, 0x07, 0x12, 0x11, 0x0a, 0x0c, 0x54, 0x69, 0x6e,\n\t0x65, 0x73, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x10, 0x91, 0x07, 0x12, 0x0b, 0x0a, 0x06,\n\t0x50, 0x75, 0x6c, 0x75, 0x6d, 0x69, 0x10, 0x92, 0x07, 0x12, 0x12, 0x0a, 0x0d, 0x53, 0x75, 0x70,\n\t0x61, 0x62, 0x61, 0x73, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x93, 0x07, 0x12, 0x10, 0x0a,\n\t0x0b, 0x4e, 0x75, 0x47, 0x65, 0x74, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0x94, 0x07, 0x12,\n\t0x0a, 0x0a, 0x05, 0x41, 0x69, 0x76, 0x65, 0x6e, 0x10, 0x95, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x50,\n\t0x72, 0x65, 0x66, 0x65, 0x63, 0x74, 0x10, 0x96, 0x07, 0x12, 0x0d, 0x0a, 0x08, 0x44, 0x6f, 0x63,\n\t0x75, 0x73, 0x69, 0x67, 0x6e, 0x10, 0x97, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x43, 0x6f, 0x75, 0x63,\n\t0x68, 0x62, 0x61, 0x73, 0x65, 0x10, 0x98, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x44, 0x6f, 0x63, 0x6b,\n\t0x65, 0x72, 0x68, 0x75, 0x62, 0x10, 0x99, 0x07, 0x12, 0x19, 0x0a, 0x14, 0x54, 0x72, 0x75, 0x66,\n\t0x66, 0x6c, 0x65, 0x68, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65,\n\t0x10, 0x9a, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x45, 0x6e, 0x76, 0x6f, 0x79, 0x41, 0x70, 0x69, 0x4b,\n\t0x65, 0x79, 0x10, 0x9b, 0x07, 0x12, 0x11, 0x0a, 0x0c, 0x47, 0x69, 0x74, 0x48, 0x75, 0x62, 0x4f,\n\t0x61, 0x75, 0x74, 0x68, 0x32, 0x10, 0x9c, 0x07, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x61, 0x6c, 0x65,\n\t0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x10, 0x9d, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x48, 0x75, 0x67,\n\t0x67, 0x69, 0x6e, 0x67, 0x46, 0x61, 0x63, 0x65, 0x10, 0x9e, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x53,\n\t0x6e, 0x6f, 0x77, 0x66, 0x6c, 0x61, 0x6b, 0x65, 0x10, 0x9f, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x53,\n\t0x6f, 0x75, 0x72, 0x63, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x10, 0xa0, 0x07, 0x12, 0x0e, 0x0a,\n\t0x09, 0x54, 0x61, 0x69, 0x6c, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x10, 0xa1, 0x07, 0x12, 0x10, 0x0a,\n\t0x0b, 0x57, 0x65, 0x62, 0x33, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x10, 0xa2, 0x07, 0x12,\n\t0x11, 0x0a, 0x0c, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x10,\n\t0xa3, 0x07, 0x12, 0x12, 0x0a, 0x0d, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x74, 0x53, 0x63, 0x61, 0x6c,\n\t0x65, 0x44, 0x62, 0x10, 0xa4, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x6e, 0x74, 0x68, 0x72, 0x6f,\n\t0x70, 0x69, 0x63, 0x10, 0xa5, 0x07, 0x12, 0x09, 0x0a, 0x04, 0x52, 0x61, 0x6d, 0x70, 0x10, 0xa6,\n\t0x07, 0x12, 0x0c, 0x0a, 0x07, 0x4b, 0x6c, 0x61, 0x76, 0x69, 0x79, 0x6f, 0x10, 0xa7, 0x07, 0x12,\n\t0x14, 0x0a, 0x0f, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x43, 0x6f,\n\t0x64, 0x79, 0x10, 0xa8, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x56, 0x6f, 0x69, 0x63, 0x65, 0x66, 0x6c,\n\t0x6f, 0x77, 0x10, 0xa9, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x50, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79,\n\t0x10, 0xaa, 0x07, 0x12, 0x0b, 0x0a, 0x06, 0x49, 0x50, 0x49, 0x6e, 0x66, 0x6f, 0x10, 0xab, 0x07,\n\t0x12, 0x10, 0x0a, 0x0b, 0x49, 0x70, 0x32, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x10,\n\t0xac, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6d, 0x6f, 0x6a, 0x6f, 0x10,\n\t0xad, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x10,\n\t0xae, 0x07, 0x12, 0x13, 0x0a, 0x0e, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x54,\n\t0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xaf, 0x07, 0x12, 0x0b, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x67, 0x6c,\n\t0x79, 0x10, 0xb0, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x4f, 0x70, 0x65, 0x6e, 0x56, 0x70, 0x6e, 0x10,\n\t0xb1, 0x07, 0x12, 0x1e, 0x0a, 0x19, 0x56, 0x61, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x43, 0x6c, 0x6f,\n\t0x75, 0x64, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10,\n\t0xb2, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x42, 0x65, 0x74, 0x74, 0x65, 0x72, 0x53, 0x74, 0x61, 0x63,\n\t0x6b, 0x10, 0xb3, 0x07, 0x12, 0x0d, 0x0a, 0x08, 0x5a, 0x65, 0x72, 0x6f, 0x54, 0x69, 0x65, 0x72,\n\t0x10, 0xb4, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x4f, 0x70, 0x74, 0x69, 0x63, 0x73,\n\t0x10, 0xb5, 0x07, 0x12, 0x0d, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x10,\n\t0xb6, 0x07, 0x12, 0x15, 0x0a, 0x0c, 0x43, 0x6f, 0x69, 0x6e, 0x62, 0x61, 0x73, 0x65, 0x57, 0x61,\n\t0x61, 0x53, 0x10, 0xb7, 0x07, 0x1a, 0x02, 0x08, 0x01, 0x12, 0x11, 0x0a, 0x0c, 0x4c, 0x65, 0x6d,\n\t0x6f, 0x6e, 0x53, 0x71, 0x75, 0x65, 0x65, 0x7a, 0x79, 0x10, 0xb8, 0x07, 0x12, 0x0d, 0x0a, 0x08,\n\t0x42, 0x75, 0x64, 0x69, 0x62, 0x61, 0x73, 0x65, 0x10, 0xb9, 0x07, 0x12, 0x0f, 0x0a, 0x0a, 0x44,\n\t0x65, 0x6e, 0x6f, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x10, 0xba, 0x07, 0x12, 0x0b, 0x0a, 0x06,\n\t0x53, 0x74, 0x72, 0x69, 0x70, 0x6f, 0x10, 0xbb, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x52, 0x65, 0x70,\n\t0x6c, 0x79, 0x49, 0x4f, 0x10, 0xbc, 0x07, 0x12, 0x0f, 0x0a, 0x0a, 0x41, 0x7a, 0x75, 0x72, 0x65,\n\t0x42, 0x61, 0x74, 0x63, 0x68, 0x10, 0xbd, 0x07, 0x12, 0x1b, 0x0a, 0x16, 0x41, 0x7a, 0x75, 0x72,\n\t0x65, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,\n\t0x72, 0x79, 0x10, 0xbe, 0x07, 0x12, 0x12, 0x0a, 0x0d, 0x41, 0x57, 0x53, 0x53, 0x65, 0x73, 0x73,\n\t0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x10, 0xbf, 0x07, 0x12, 0x09, 0x0a, 0x04, 0x43, 0x6f, 0x64,\n\t0x61, 0x10, 0xc0, 0x07, 0x12, 0x0b, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x7a, 0x49, 0x4f, 0x10, 0xc1,\n\t0x07, 0x12, 0x0f, 0x0a, 0x0a, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x62, 0x72, 0x69, 0x74, 0x65, 0x10,\n\t0xc2, 0x07, 0x12, 0x1a, 0x0a, 0x15, 0x47, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x53, 0x65, 0x72,\n\t0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x10, 0xc3, 0x07, 0x12, 0x13,\n\t0x0a, 0x0e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, 0x69, 0x6e, 0x61, 0x6e, 0x63, 0x65,\n\t0x10, 0xc4, 0x07, 0x12, 0x0d, 0x0a, 0x08, 0x4f, 0x76, 0x65, 0x72, 0x6c, 0x6f, 0x6f, 0x70, 0x10,\n\t0xc5, 0x07, 0x12, 0x0a, 0x0a, 0x05, 0x4e, 0x67, 0x72, 0x6f, 0x6b, 0x10, 0xc6, 0x07, 0x12, 0x0e,\n\t0x0a, 0x09, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x10, 0xc7, 0x07, 0x12, 0x0d,\n\t0x0a, 0x08, 0x50, 0x6f, 0x73, 0x74, 0x67, 0x72, 0x65, 0x73, 0x10, 0xc8, 0x07, 0x12, 0x2a, 0x0a,\n\t0x25, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x44, 0x69, 0x72, 0x65,\n\t0x63, 0x74, 0x6f, 0x72, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,\n\t0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x10, 0xc9, 0x07, 0x12, 0x20, 0x0a, 0x1b, 0x41, 0x7a, 0x75,\n\t0x72, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x64, 0x69, 0x73, 0x41,\n\t0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x10, 0xca, 0x07, 0x12, 0x21, 0x0a, 0x1c, 0x41,\n\t0x7a, 0x75, 0x72, 0x65, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x44, 0x42, 0x4b, 0x65, 0x79, 0x49,\n\t0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x10, 0xcb, 0x07, 0x12, 0x23,\n\t0x0a, 0x1e, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x44, 0x65, 0x76, 0x6f, 0x70, 0x73, 0x50, 0x65, 0x72,\n\t0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e,\n\t0x10, 0xcc, 0x07, 0x12, 0x15, 0x0a, 0x10, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x46, 0x75, 0x6e, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x10, 0xcd, 0x07, 0x12, 0x2c, 0x0a, 0x27, 0x41, 0x7a,\n\t0x75, 0x72, 0x65, 0x4d, 0x4c, 0x57, 0x65, 0x62, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43,\n\t0x6c, 0x61, 0x73, 0x73, 0x69, 0x63, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x61, 0x62,\n\t0x6c, 0x65, 0x4b, 0x65, 0x79, 0x10, 0xce, 0x07, 0x12, 0x12, 0x0a, 0x0d, 0x41, 0x7a, 0x75, 0x72,\n\t0x65, 0x53, 0x61, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xcf, 0x07, 0x12, 0x18, 0x0a, 0x13,\n\t0x41, 0x7a, 0x75, 0x72, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x41, 0x64, 0x6d, 0x69, 0x6e,\n\t0x4b, 0x65, 0x79, 0x10, 0xd0, 0x07, 0x12, 0x18, 0x0a, 0x13, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x53,\n\t0x65, 0x61, 0x72, 0x63, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4b, 0x65, 0x79, 0x10, 0xd1, 0x07,\n\t0x12, 0x1f, 0x0a, 0x1a, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,\n\t0x65, 0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x10, 0xd2,\n\t0x07, 0x12, 0x0d, 0x0a, 0x08, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x53, 0x51, 0x4c, 0x10, 0xd3, 0x07,\n\t0x12, 0x0a, 0x0a, 0x05, 0x46, 0x6c, 0x79, 0x49, 0x4f, 0x10, 0xd4, 0x07, 0x12, 0x0e, 0x0a, 0x09,\n\t0x42, 0x75, 0x69, 0x6c, 0x74, 0x57, 0x69, 0x74, 0x68, 0x10, 0xd5, 0x07, 0x12, 0x0f, 0x0a, 0x0a,\n\t0x4a, 0x75, 0x70, 0x69, 0x74, 0x65, 0x72, 0x4f, 0x6e, 0x65, 0x10, 0xd6, 0x07, 0x12, 0x25, 0x0a,\n\t0x20, 0x47, 0x43, 0x50, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44,\n\t0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,\n\t0x73, 0x10, 0xd7, 0x07, 0x12, 0x08, 0x0a, 0x03, 0x57, 0x69, 0x7a, 0x10, 0xd8, 0x07, 0x12, 0x0c,\n\t0x0a, 0x07, 0x50, 0x61, 0x67, 0x61, 0x72, 0x6d, 0x65, 0x10, 0xd9, 0x07, 0x12, 0x0c, 0x0a, 0x07,\n\t0x4f, 0x6e, 0x66, 0x6c, 0x65, 0x65, 0x74, 0x10, 0xda, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x49, 0x6e,\n\t0x74, 0x72, 0x61, 0x34, 0x32, 0x10, 0xdb, 0x07, 0x12, 0x09, 0x0a, 0x04, 0x47, 0x72, 0x6f, 0x71,\n\t0x10, 0xdc, 0x07, 0x12, 0x17, 0x0a, 0x12, 0x54, 0x77, 0x69, 0x74, 0x74, 0x65, 0x72, 0x43, 0x6f,\n\t0x6e, 0x73, 0x75, 0x6d, 0x65, 0x72, 0x6b, 0x65, 0x79, 0x10, 0xdd, 0x07, 0x12, 0x0b, 0x0a, 0x06,\n\t0x45, 0x72, 0x61, 0x73, 0x65, 0x72, 0x10, 0xde, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x4c, 0x61, 0x72,\n\t0x6b, 0x53, 0x75, 0x69, 0x74, 0x65, 0x10, 0xdf, 0x07, 0x12, 0x14, 0x0a, 0x0f, 0x4c, 0x61, 0x72,\n\t0x6b, 0x53, 0x75, 0x69, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0xe0, 0x07, 0x12,\n\t0x0e, 0x0a, 0x09, 0x45, 0x6e, 0x64, 0x6f, 0x72, 0x4c, 0x61, 0x62, 0x73, 0x10, 0xe1, 0x07, 0x12,\n\t0x0f, 0x0a, 0x0a, 0x45, 0x6c, 0x65, 0x76, 0x65, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x10, 0xe2, 0x07,\n\t0x12, 0x0d, 0x0a, 0x08, 0x4e, 0x65, 0x74, 0x73, 0x75, 0x69, 0x74, 0x65, 0x10, 0xe3, 0x07, 0x12,\n\t0x14, 0x0a, 0x0f, 0x52, 0x6f, 0x62, 0x69, 0x6e, 0x68, 0x6f, 0x6f, 0x64, 0x43, 0x72, 0x79, 0x70,\n\t0x74, 0x6f, 0x10, 0xe4, 0x07, 0x12, 0x0a, 0x0a, 0x05, 0x4e, 0x56, 0x41, 0x50, 0x49, 0x10, 0xe5,\n\t0x07, 0x12, 0x09, 0x0a, 0x04, 0x50, 0x79, 0x50, 0x49, 0x10, 0xe6, 0x07, 0x12, 0x0f, 0x0a, 0x0a,\n\t0x52, 0x61, 0x69, 0x6c, 0x77, 0x61, 0x79, 0x41, 0x70, 0x70, 0x10, 0xe7, 0x07, 0x12, 0x0b, 0x0a,\n\t0x06, 0x4d, 0x65, 0x72, 0x61, 0x6b, 0x69, 0x10, 0xe8, 0x07, 0x12, 0x15, 0x0a, 0x10, 0x53, 0x61,\n\t0x6c, 0x61, 0x64, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0xe9,\n\t0x07, 0x12, 0x08, 0x0a, 0x03, 0x42, 0x6f, 0x78, 0x10, 0xea, 0x07, 0x12, 0x0d, 0x0a, 0x08, 0x42,\n\t0x6f, 0x78, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x10, 0xeb, 0x07, 0x12, 0x0f, 0x0a, 0x0a, 0x41, 0x70,\n\t0x69, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x10, 0xec, 0x07, 0x12, 0x15, 0x0a, 0x10, 0x57,\n\t0x65, 0x69, 0x67, 0x68, 0x74, 0x73, 0x41, 0x6e, 0x64, 0x42, 0x69, 0x61, 0x73, 0x65, 0x73, 0x10,\n\t0xed, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x5a, 0x6f, 0x68, 0x6f, 0x43, 0x52, 0x4d, 0x10, 0xee, 0x07,\n\t0x12, 0x10, 0x0a, 0x0b, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x4f, 0x70, 0x65, 0x6e, 0x41, 0x49, 0x10,\n\t0xef, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x47, 0x6f, 0x44, 0x61, 0x64, 0x64, 0x79, 0x10, 0xf0, 0x07,\n\t0x12, 0x0d, 0x0a, 0x08, 0x46, 0x6c, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x10, 0xf1, 0x07, 0x12,\n\t0x16, 0x0a, 0x11, 0x54, 0x77, 0x69, 0x74, 0x63, 0x68, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54,\n\t0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xf2, 0x07, 0x12, 0x11, 0x0a, 0x0c, 0x54, 0x77, 0x69, 0x6c, 0x69,\n\t0x6f, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0xf3, 0x07, 0x12, 0x0b, 0x0a, 0x06, 0x53, 0x61,\n\t0x6e, 0x69, 0x74, 0x79, 0x10, 0xf4, 0x07, 0x12, 0x16, 0x0a, 0x11, 0x41, 0x7a, 0x75, 0x72, 0x65,\n\t0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xf5, 0x07, 0x12,\n\t0x12, 0x0a, 0x0d, 0x41, 0x69, 0x72, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x41, 0x75, 0x74, 0x68,\n\t0x10, 0xf6, 0x07, 0x12, 0x20, 0x0a, 0x1b, 0x41, 0x69, 0x72, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x50,\n\t0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b,\n\t0x65, 0x6e, 0x10, 0xf7, 0x07, 0x12, 0x21, 0x0a, 0x1c, 0x53, 0x74, 0x6f, 0x72, 0x79, 0x62, 0x6c,\n\t0x6f, 0x6b, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73,\n\t0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xf8, 0x07, 0x12, 0x13, 0x0a, 0x0e, 0x53, 0x65, 0x6e, 0x74,\n\t0x72, 0x79, 0x4f, 0x72, 0x67, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0xf9, 0x07, 0x12, 0x24, 0x0a,\n\t0x1f, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x41, 0x70, 0x69, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,\n\t0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4b, 0x65, 0x79,\n\t0x10, 0xfa, 0x07, 0x12, 0x26, 0x0a, 0x21, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x41, 0x50, 0x49, 0x4d,\n\t0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,\n\t0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x10, 0xfb, 0x07, 0x12, 0x0c, 0x0a, 0x07, 0x48,\n\t0x61, 0x72, 0x6e, 0x65, 0x73, 0x73, 0x10, 0xfc, 0x07, 0x12, 0x0d, 0x0a, 0x08, 0x4c, 0x61, 0x6e,\n\t0x67, 0x66, 0x75, 0x73, 0x65, 0x10, 0xfd, 0x07, 0x12, 0x18, 0x0a, 0x13, 0x42, 0x69, 0x6e, 0x67,\n\t0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x10,\n\t0xfe, 0x07, 0x12, 0x08, 0x0a, 0x03, 0x58, 0x41, 0x49, 0x10, 0xff, 0x07, 0x12, 0x1d, 0x0a, 0x18,\n\t0x41, 0x7a, 0x75, 0x72, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67,\n\t0x65, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x10, 0x80, 0x08, 0x12, 0x23, 0x0a, 0x1e, 0x41,\n\t0x7a, 0x75, 0x72, 0x65, 0x41, 0x70, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x43, 0x6f, 0x6e,\n\t0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x10, 0x81, 0x08,\n\t0x12, 0x0d, 0x0a, 0x08, 0x44, 0x65, 0x65, 0x70, 0x53, 0x65, 0x65, 0x6b, 0x10, 0x82, 0x08, 0x12,\n\t0x18, 0x0a, 0x13, 0x53, 0x74, 0x72, 0x69, 0x70, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74,\n\t0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x10, 0x83, 0x08, 0x12, 0x0e, 0x0a, 0x09, 0x4c, 0x61, 0x6e,\n\t0x67, 0x53, 0x6d, 0x69, 0x74, 0x68, 0x10, 0x84, 0x08, 0x12, 0x19, 0x0a, 0x14, 0x42, 0x69, 0x74,\n\t0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x41, 0x70, 0x70, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,\n\t0x64, 0x10, 0x85, 0x08, 0x12, 0x0b, 0x0a, 0x06, 0x48, 0x61, 0x73, 0x75, 0x72, 0x61, 0x10, 0x86,\n\t0x08, 0x12, 0x1b, 0x0a, 0x16, 0x53, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x52,\n\t0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x87, 0x08, 0x12, 0x13,\n\t0x0a, 0x0e, 0x41, 0x6e, 0x79, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x4f, 0x41, 0x75, 0x74, 0x68, 0x32,\n\t0x10, 0x88, 0x08, 0x12, 0x0d, 0x0a, 0x08, 0x57, 0x65, 0x62, 0x65, 0x78, 0x42, 0x6f, 0x74, 0x10,\n\t0x89, 0x08, 0x12, 0x1f, 0x0a, 0x1a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x50, 0x65, 0x72,\n\t0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e,\n\t0x10, 0x8a, 0x08, 0x12, 0x0b, 0x0a, 0x06, 0x52, 0x6f, 0x6f, 0x74, 0x6c, 0x79, 0x10, 0x8b, 0x08,\n\t0x12, 0x17, 0x0a, 0x12, 0x48, 0x61, 0x73, 0x68, 0x69, 0x43, 0x6f, 0x72, 0x70, 0x56, 0x61, 0x75,\n\t0x6c, 0x74, 0x41, 0x75, 0x74, 0x68, 0x10, 0x8c, 0x08, 0x12, 0x16, 0x0a, 0x11, 0x50, 0x68, 0x72,\n\t0x61, 0x73, 0x65, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x8d,\n\t0x08, 0x12, 0x0e, 0x0a, 0x09, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x72, 0x6f, 0x6f, 0x6d, 0x10, 0x8e,\n\t0x08, 0x12, 0x08, 0x0a, 0x03, 0x4a, 0x57, 0x54, 0x10, 0x8f, 0x08, 0x12, 0x10, 0x0a, 0x0b, 0x4f,\n\t0x70, 0x65, 0x6e, 0x41, 0x49, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x10, 0x90, 0x08, 0x12, 0x17, 0x0a,\n\t0x12, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x47, 0x65, 0x6d, 0x69, 0x6e, 0x69, 0x41, 0x50, 0x49,\n\t0x4b, 0x65, 0x79, 0x10, 0x91, 0x08, 0x12, 0x1e, 0x0a, 0x19, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61,\n\t0x63, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x6f,\n\t0x6b, 0x65, 0x6e, 0x10, 0x92, 0x08, 0x12, 0x12, 0x0a, 0x0d, 0x44, 0x61, 0x74, 0x61, 0x64, 0x6f,\n\t0x67, 0x41, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x10, 0x93, 0x08, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69,\n\t0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65,\n\t0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65,\n\t0x68, 0x6f, 0x67, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62, 0x2f, 0x64, 0x65,\n\t0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x33,\n}\n\nvar (\n\tfile_detectors_proto_rawDescOnce sync.Once\n\tfile_detectors_proto_rawDescData = file_detectors_proto_rawDesc\n)\n\nfunc file_detectors_proto_rawDescGZIP() []byte {\n\tfile_detectors_proto_rawDescOnce.Do(func() {\n\t\tfile_detectors_proto_rawDescData = protoimpl.X.CompressGZIP(file_detectors_proto_rawDescData)\n\t})\n\treturn file_detectors_proto_rawDescData\n}\n\nvar file_detectors_proto_enumTypes = make([]protoimpl.EnumInfo, 2)\nvar file_detectors_proto_msgTypes = make([]protoimpl.MessageInfo, 6)\nvar file_detectors_proto_goTypes = []interface{}{\n\t(DecoderType)(0),          // 0: detectors.DecoderType\n\t(DetectorType)(0),         // 1: detectors.DetectorType\n\t(*Result)(nil),            // 2: detectors.Result\n\t(*FalsePositiveInfo)(nil), // 3: detectors.FalsePositiveInfo\n\t(*StructuredData)(nil),    // 4: detectors.StructuredData\n\t(*TlsPrivateKey)(nil),     // 5: detectors.TlsPrivateKey\n\t(*GitHubSSHKey)(nil),      // 6: detectors.GitHubSSHKey\n\tnil,                       // 7: detectors.Result.ExtraDataEntry\n}\nvar file_detectors_proto_depIdxs = []int32{\n\t7, // 0: detectors.Result.extra_data:type_name -> detectors.Result.ExtraDataEntry\n\t4, // 1: detectors.Result.structured_data:type_name -> detectors.StructuredData\n\t0, // 2: detectors.Result.decoder_type:type_name -> detectors.DecoderType\n\t3, // 3: detectors.Result.false_positive_info:type_name -> detectors.FalsePositiveInfo\n\t5, // 4: detectors.StructuredData.tls_private_key:type_name -> detectors.TlsPrivateKey\n\t6, // 5: detectors.StructuredData.github_ssh_key:type_name -> detectors.GitHubSSHKey\n\t6, // [6:6] is the sub-list for method output_type\n\t6, // [6:6] is the sub-list for method input_type\n\t6, // [6:6] is the sub-list for extension type_name\n\t6, // [6:6] is the sub-list for extension extendee\n\t0, // [0:6] is the sub-list for field type_name\n}\n\nfunc init() { file_detectors_proto_init() }\nfunc file_detectors_proto_init() {\n\tif File_detectors_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_detectors_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Result); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_detectors_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*FalsePositiveInfo); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_detectors_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*StructuredData); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_detectors_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*TlsPrivateKey); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_detectors_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GitHubSSHKey); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_detectors_proto_rawDesc,\n\t\t\tNumEnums:      2,\n\t\t\tNumMessages:   6,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_detectors_proto_goTypes,\n\t\tDependencyIndexes: file_detectors_proto_depIdxs,\n\t\tEnumInfos:         file_detectors_proto_enumTypes,\n\t\tMessageInfos:      file_detectors_proto_msgTypes,\n\t}.Build()\n\tFile_detectors_proto = out.File\n\tfile_detectors_proto_rawDesc = nil\n\tfile_detectors_proto_goTypes = nil\n\tfile_detectors_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pkg/pb/detectorspb/detectors.pb.validate.go",
    "content": "// Code generated by protoc-gen-validate. DO NOT EDIT.\n// source: detectors.proto\n\npackage detectorspb\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/mail\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode/utf8\"\n\n\t\"google.golang.org/protobuf/types/known/anypb\"\n)\n\n// ensure the imports are used\nvar (\n\t_ = bytes.MinRead\n\t_ = errors.New(\"\")\n\t_ = fmt.Print\n\t_ = utf8.UTFMax\n\t_ = (*regexp.Regexp)(nil)\n\t_ = (*strings.Reader)(nil)\n\t_ = net.IPv4len\n\t_ = time.Duration(0)\n\t_ = (*url.URL)(nil)\n\t_ = (*mail.Address)(nil)\n\t_ = anypb.Any{}\n\t_ = sort.Sort\n)\n\n// Validate checks the field values on Result with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Result) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Result with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in ResultMultiError, or nil if none found.\nfunc (m *Result) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Result) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for SourceId\n\n\t// no validation rules for Redacted\n\n\t// no validation rules for Verified\n\n\t// no validation rules for Hash\n\n\t// no validation rules for ExtraData\n\n\tif all {\n\t\tswitch v := interface{}(m.GetStructuredData()).(type) {\n\t\tcase interface{ ValidateAll() error }:\n\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\terrors = append(errors, ResultValidationError{\n\t\t\t\t\tfield:  \"StructuredData\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t})\n\t\t\t}\n\t\tcase interface{ Validate() error }:\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\terrors = append(errors, ResultValidationError{\n\t\t\t\t\tfield:  \"StructuredData\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t} else if v, ok := interface{}(m.GetStructuredData()).(interface{ Validate() error }); ok {\n\t\tif err := v.Validate(); err != nil {\n\t\t\treturn ResultValidationError{\n\t\t\t\tfield:  \"StructuredData\",\n\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\tcause:  err,\n\t\t\t}\n\t\t}\n\t}\n\n\t// no validation rules for HashV2\n\n\t// no validation rules for DecoderType\n\n\t// no validation rules for VerificationErrorMessage\n\n\tif all {\n\t\tswitch v := interface{}(m.GetFalsePositiveInfo()).(type) {\n\t\tcase interface{ ValidateAll() error }:\n\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\terrors = append(errors, ResultValidationError{\n\t\t\t\t\tfield:  \"FalsePositiveInfo\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t})\n\t\t\t}\n\t\tcase interface{ Validate() error }:\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\terrors = append(errors, ResultValidationError{\n\t\t\t\t\tfield:  \"FalsePositiveInfo\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t} else if v, ok := interface{}(m.GetFalsePositiveInfo()).(interface{ Validate() error }); ok {\n\t\tif err := v.Validate(); err != nil {\n\t\t\treturn ResultValidationError{\n\t\t\t\tfield:  \"FalsePositiveInfo\",\n\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\tcause:  err,\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn ResultMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// ResultMultiError is an error wrapping multiple validation errors returned by\n// Result.ValidateAll() if the designated constraints aren't met.\ntype ResultMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m ResultMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m ResultMultiError) AllErrors() []error { return m }\n\n// ResultValidationError is the validation error returned by Result.Validate if\n// the designated constraints aren't met.\ntype ResultValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e ResultValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e ResultValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e ResultValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e ResultValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e ResultValidationError) ErrorName() string { return \"ResultValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e ResultValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sResult.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = ResultValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = ResultValidationError{}\n\n// Validate checks the field values on FalsePositiveInfo with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// first error encountered is returned, or nil if there are no violations.\nfunc (m *FalsePositiveInfo) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on FalsePositiveInfo with the rules\n// defined in the proto definition for this message. If any rules are\n// violated, the result is a list of violation errors wrapped in\n// FalsePositiveInfoMultiError, or nil if none found.\nfunc (m *FalsePositiveInfo) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *FalsePositiveInfo) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for WordMatch\n\n\t// no validation rules for LowEntropy\n\n\tif len(errors) > 0 {\n\t\treturn FalsePositiveInfoMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// FalsePositiveInfoMultiError is an error wrapping multiple validation errors\n// returned by FalsePositiveInfo.ValidateAll() if the designated constraints\n// aren't met.\ntype FalsePositiveInfoMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m FalsePositiveInfoMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m FalsePositiveInfoMultiError) AllErrors() []error { return m }\n\n// FalsePositiveInfoValidationError is the validation error returned by\n// FalsePositiveInfo.Validate if the designated constraints aren't met.\ntype FalsePositiveInfoValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e FalsePositiveInfoValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e FalsePositiveInfoValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e FalsePositiveInfoValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e FalsePositiveInfoValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e FalsePositiveInfoValidationError) ErrorName() string {\n\treturn \"FalsePositiveInfoValidationError\"\n}\n\n// Error satisfies the builtin error interface\nfunc (e FalsePositiveInfoValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sFalsePositiveInfo.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = FalsePositiveInfoValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = FalsePositiveInfoValidationError{}\n\n// Validate checks the field values on StructuredData with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *StructuredData) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on StructuredData with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in StructuredDataMultiError,\n// or nil if none found.\nfunc (m *StructuredData) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *StructuredData) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tfor idx, item := range m.GetTlsPrivateKey() {\n\t\t_, _ = idx, item\n\n\t\tif all {\n\t\t\tswitch v := interface{}(item).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, StructuredDataValidationError{\n\t\t\t\t\t\tfield:  fmt.Sprintf(\"TlsPrivateKey[%v]\", idx),\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, StructuredDataValidationError{\n\t\t\t\t\t\tfield:  fmt.Sprintf(\"TlsPrivateKey[%v]\", idx),\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(item).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn StructuredDataValidationError{\n\t\t\t\t\tfield:  fmt.Sprintf(\"TlsPrivateKey[%v]\", idx),\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\n\tfor idx, item := range m.GetGithubSshKey() {\n\t\t_, _ = idx, item\n\n\t\tif all {\n\t\t\tswitch v := interface{}(item).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, StructuredDataValidationError{\n\t\t\t\t\t\tfield:  fmt.Sprintf(\"GithubSshKey[%v]\", idx),\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, StructuredDataValidationError{\n\t\t\t\t\t\tfield:  fmt.Sprintf(\"GithubSshKey[%v]\", idx),\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(item).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn StructuredDataValidationError{\n\t\t\t\t\tfield:  fmt.Sprintf(\"GithubSshKey[%v]\", idx),\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn StructuredDataMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// StructuredDataMultiError is an error wrapping multiple validation errors\n// returned by StructuredData.ValidateAll() if the designated constraints\n// aren't met.\ntype StructuredDataMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m StructuredDataMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m StructuredDataMultiError) AllErrors() []error { return m }\n\n// StructuredDataValidationError is the validation error returned by\n// StructuredData.Validate if the designated constraints aren't met.\ntype StructuredDataValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e StructuredDataValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e StructuredDataValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e StructuredDataValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e StructuredDataValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e StructuredDataValidationError) ErrorName() string { return \"StructuredDataValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e StructuredDataValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sStructuredData.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = StructuredDataValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = StructuredDataValidationError{}\n\n// Validate checks the field values on TlsPrivateKey with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *TlsPrivateKey) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on TlsPrivateKey with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in TlsPrivateKeyMultiError, or\n// nil if none found.\nfunc (m *TlsPrivateKey) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *TlsPrivateKey) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for CertificateFingerprint\n\n\t// no validation rules for VerificationUrl\n\n\t// no validation rules for ExpirationTimestamp\n\n\tif len(errors) > 0 {\n\t\treturn TlsPrivateKeyMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// TlsPrivateKeyMultiError is an error wrapping multiple validation errors\n// returned by TlsPrivateKey.ValidateAll() if the designated constraints\n// aren't met.\ntype TlsPrivateKeyMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m TlsPrivateKeyMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m TlsPrivateKeyMultiError) AllErrors() []error { return m }\n\n// TlsPrivateKeyValidationError is the validation error returned by\n// TlsPrivateKey.Validate if the designated constraints aren't met.\ntype TlsPrivateKeyValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e TlsPrivateKeyValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e TlsPrivateKeyValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e TlsPrivateKeyValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e TlsPrivateKeyValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e TlsPrivateKeyValidationError) ErrorName() string { return \"TlsPrivateKeyValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e TlsPrivateKeyValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sTlsPrivateKey.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = TlsPrivateKeyValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = TlsPrivateKeyValidationError{}\n\n// Validate checks the field values on GitHubSSHKey with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *GitHubSSHKey) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on GitHubSSHKey with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in GitHubSSHKeyMultiError, or\n// nil if none found.\nfunc (m *GitHubSSHKey) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *GitHubSSHKey) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for User\n\n\t// no validation rules for PublicKeyFingerprint\n\n\tif len(errors) > 0 {\n\t\treturn GitHubSSHKeyMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// GitHubSSHKeyMultiError is an error wrapping multiple validation errors\n// returned by GitHubSSHKey.ValidateAll() if the designated constraints aren't met.\ntype GitHubSSHKeyMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m GitHubSSHKeyMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m GitHubSSHKeyMultiError) AllErrors() []error { return m }\n\n// GitHubSSHKeyValidationError is the validation error returned by\n// GitHubSSHKey.Validate if the designated constraints aren't met.\ntype GitHubSSHKeyValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e GitHubSSHKeyValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e GitHubSSHKeyValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e GitHubSSHKeyValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e GitHubSSHKeyValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e GitHubSSHKeyValidationError) ErrorName() string { return \"GitHubSSHKeyValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e GitHubSSHKeyValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sGitHubSSHKey.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = GitHubSSHKeyValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = GitHubSSHKeyValidationError{}\n"
  },
  {
    "path": "pkg/pb/source_metadatapb/source_metadata.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.33.0\n// \tprotoc        v4.25.3\n// source: source_metadata.proto\n\npackage source_metadatapb\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\ttimestamppb \"google.golang.org/protobuf/types/known/timestamppb\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype Visibility int32\n\nconst (\n\tVisibility_public  Visibility = 0\n\tVisibility_private Visibility = 1\n\tVisibility_shared  Visibility = 2\n\tVisibility_unknown Visibility = 3\n)\n\n// Enum value maps for Visibility.\nvar (\n\tVisibility_name = map[int32]string{\n\t\t0: \"public\",\n\t\t1: \"private\",\n\t\t2: \"shared\",\n\t\t3: \"unknown\",\n\t}\n\tVisibility_value = map[string]int32{\n\t\t\"public\":  0,\n\t\t\"private\": 1,\n\t\t\"shared\":  2,\n\t\t\"unknown\": 3,\n\t}\n)\n\nfunc (x Visibility) Enum() *Visibility {\n\tp := new(Visibility)\n\t*p = x\n\treturn p\n}\n\nfunc (x Visibility) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Visibility) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_source_metadata_proto_enumTypes[0].Descriptor()\n}\n\nfunc (Visibility) Type() protoreflect.EnumType {\n\treturn &file_source_metadata_proto_enumTypes[0]\n}\n\nfunc (x Visibility) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use Visibility.Descriptor instead.\nfunc (Visibility) EnumDescriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{0}\n}\n\ntype PostmanLocationType int32\n\nconst (\n\tPostmanLocationType_UNKNOWN_POSTMAN          PostmanLocationType = 0\n\tPostmanLocationType_REQUEST_QUERY_PARAMETER  PostmanLocationType = 1\n\tPostmanLocationType_REQUEST_AUTHORIZATION    PostmanLocationType = 2\n\tPostmanLocationType_REQUEST_HEADER           PostmanLocationType = 3\n\tPostmanLocationType_REQUEST_BODY_FORM_DATA   PostmanLocationType = 4\n\tPostmanLocationType_REQUEST_BODY_RAW         PostmanLocationType = 5\n\tPostmanLocationType_REQUEST_BODY_URL_ENCODED PostmanLocationType = 6\n\tPostmanLocationType_REQUEST_BODY_GRAPHQL     PostmanLocationType = 7\n\tPostmanLocationType_REQUEST_SCRIPT           PostmanLocationType = 8\n\tPostmanLocationType_REQUEST_URL              PostmanLocationType = 9\n\tPostmanLocationType_ENVIRONMENT_VARIABLE     PostmanLocationType = 10\n\tPostmanLocationType_FOLDER_AUTHORIZATION     PostmanLocationType = 11\n\tPostmanLocationType_FOLDER_SCRIPT            PostmanLocationType = 12\n\tPostmanLocationType_COLLECTION_SCRIPT        PostmanLocationType = 13\n\tPostmanLocationType_COLLECTION_VARIABLE      PostmanLocationType = 14\n\tPostmanLocationType_COLLECTION_AUTHORIZATION PostmanLocationType = 15\n\tPostmanLocationType_RESPONSE_BODY            PostmanLocationType = 16\n\tPostmanLocationType_RESPONSE_HEADER          PostmanLocationType = 17\n)\n\n// Enum value maps for PostmanLocationType.\nvar (\n\tPostmanLocationType_name = map[int32]string{\n\t\t0:  \"UNKNOWN_POSTMAN\",\n\t\t1:  \"REQUEST_QUERY_PARAMETER\",\n\t\t2:  \"REQUEST_AUTHORIZATION\",\n\t\t3:  \"REQUEST_HEADER\",\n\t\t4:  \"REQUEST_BODY_FORM_DATA\",\n\t\t5:  \"REQUEST_BODY_RAW\",\n\t\t6:  \"REQUEST_BODY_URL_ENCODED\",\n\t\t7:  \"REQUEST_BODY_GRAPHQL\",\n\t\t8:  \"REQUEST_SCRIPT\",\n\t\t9:  \"REQUEST_URL\",\n\t\t10: \"ENVIRONMENT_VARIABLE\",\n\t\t11: \"FOLDER_AUTHORIZATION\",\n\t\t12: \"FOLDER_SCRIPT\",\n\t\t13: \"COLLECTION_SCRIPT\",\n\t\t14: \"COLLECTION_VARIABLE\",\n\t\t15: \"COLLECTION_AUTHORIZATION\",\n\t\t16: \"RESPONSE_BODY\",\n\t\t17: \"RESPONSE_HEADER\",\n\t}\n\tPostmanLocationType_value = map[string]int32{\n\t\t\"UNKNOWN_POSTMAN\":          0,\n\t\t\"REQUEST_QUERY_PARAMETER\":  1,\n\t\t\"REQUEST_AUTHORIZATION\":    2,\n\t\t\"REQUEST_HEADER\":           3,\n\t\t\"REQUEST_BODY_FORM_DATA\":   4,\n\t\t\"REQUEST_BODY_RAW\":         5,\n\t\t\"REQUEST_BODY_URL_ENCODED\": 6,\n\t\t\"REQUEST_BODY_GRAPHQL\":     7,\n\t\t\"REQUEST_SCRIPT\":           8,\n\t\t\"REQUEST_URL\":              9,\n\t\t\"ENVIRONMENT_VARIABLE\":     10,\n\t\t\"FOLDER_AUTHORIZATION\":     11,\n\t\t\"FOLDER_SCRIPT\":            12,\n\t\t\"COLLECTION_SCRIPT\":        13,\n\t\t\"COLLECTION_VARIABLE\":      14,\n\t\t\"COLLECTION_AUTHORIZATION\": 15,\n\t\t\"RESPONSE_BODY\":            16,\n\t\t\"RESPONSE_HEADER\":          17,\n\t}\n)\n\nfunc (x PostmanLocationType) Enum() *PostmanLocationType {\n\tp := new(PostmanLocationType)\n\t*p = x\n\treturn p\n}\n\nfunc (x PostmanLocationType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (PostmanLocationType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_source_metadata_proto_enumTypes[1].Descriptor()\n}\n\nfunc (PostmanLocationType) Type() protoreflect.EnumType {\n\treturn &file_source_metadata_proto_enumTypes[1]\n}\n\nfunc (x PostmanLocationType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use PostmanLocationType.Descriptor instead.\nfunc (PostmanLocationType) EnumDescriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{1}\n}\n\ntype Azure struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tContainer string `protobuf:\"bytes,1,opt,name=container,proto3\" json:\"container,omitempty\"`\n\tFile      string `protobuf:\"bytes,2,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tUploaded  string `protobuf:\"bytes,3,opt,name=uploaded,proto3\" json:\"uploaded,omitempty\"`\n\tLink      string `protobuf:\"bytes,4,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tEmail     string `protobuf:\"bytes,5,opt,name=email,proto3\" json:\"email,omitempty\"`\n}\n\nfunc (x *Azure) Reset() {\n\t*x = Azure{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Azure) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Azure) ProtoMessage() {}\n\nfunc (x *Azure) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Azure.ProtoReflect.Descriptor instead.\nfunc (*Azure) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Azure) GetContainer() string {\n\tif x != nil {\n\t\treturn x.Container\n\t}\n\treturn \"\"\n}\n\nfunc (x *Azure) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *Azure) GetUploaded() string {\n\tif x != nil {\n\t\treturn x.Uploaded\n\t}\n\treturn \"\"\n}\n\nfunc (x *Azure) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *Azure) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\ntype Bitbucket struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tFile       string `protobuf:\"bytes,1,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tRepository string `protobuf:\"bytes,2,opt,name=repository,proto3\" json:\"repository,omitempty\"`\n\tWorkspace  string `protobuf:\"bytes,3,opt,name=workspace,proto3\" json:\"workspace,omitempty\"`\n\tSnippetId  string `protobuf:\"bytes,4,opt,name=snippet_id,json=snippetId,proto3\" json:\"snippet_id,omitempty\"`\n\tTitle      string `protobuf:\"bytes,5,opt,name=title,proto3\" json:\"title,omitempty\"`\n\tCommit     string `protobuf:\"bytes,6,opt,name=commit,proto3\" json:\"commit,omitempty\"`\n\tEmail      string `protobuf:\"bytes,7,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tLink       string `protobuf:\"bytes,8,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tTimestamp  string `protobuf:\"bytes,9,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\tLine       int64  `protobuf:\"varint,10,opt,name=line,proto3\" json:\"line,omitempty\"`\n}\n\nfunc (x *Bitbucket) Reset() {\n\t*x = Bitbucket{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Bitbucket) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Bitbucket) ProtoMessage() {}\n\nfunc (x *Bitbucket) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Bitbucket.ProtoReflect.Descriptor instead.\nfunc (*Bitbucket) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Bitbucket) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *Bitbucket) GetRepository() string {\n\tif x != nil {\n\t\treturn x.Repository\n\t}\n\treturn \"\"\n}\n\nfunc (x *Bitbucket) GetWorkspace() string {\n\tif x != nil {\n\t\treturn x.Workspace\n\t}\n\treturn \"\"\n}\n\nfunc (x *Bitbucket) GetSnippetId() string {\n\tif x != nil {\n\t\treturn x.SnippetId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Bitbucket) GetTitle() string {\n\tif x != nil {\n\t\treturn x.Title\n\t}\n\treturn \"\"\n}\n\nfunc (x *Bitbucket) GetCommit() string {\n\tif x != nil {\n\t\treturn x.Commit\n\t}\n\treturn \"\"\n}\n\nfunc (x *Bitbucket) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *Bitbucket) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *Bitbucket) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\nfunc (x *Bitbucket) GetLine() int64 {\n\tif x != nil {\n\t\treturn x.Line\n\t}\n\treturn 0\n}\n\ntype Buildkite struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tOrg         string `protobuf:\"bytes,1,opt,name=org,proto3\" json:\"org,omitempty\"`\n\tPipeline    string `protobuf:\"bytes,2,opt,name=pipeline,proto3\" json:\"pipeline,omitempty\"`\n\tLink        string `protobuf:\"bytes,3,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tEmail       string `protobuf:\"bytes,4,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tBuildNumber int64  `protobuf:\"varint,5,opt,name=build_number,json=buildNumber,proto3\" json:\"build_number,omitempty\"`\n\tTimestamp   string `protobuf:\"bytes,6,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n}\n\nfunc (x *Buildkite) Reset() {\n\t*x = Buildkite{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[2]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Buildkite) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Buildkite) ProtoMessage() {}\n\nfunc (x *Buildkite) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[2]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Buildkite.ProtoReflect.Descriptor instead.\nfunc (*Buildkite) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *Buildkite) GetOrg() string {\n\tif x != nil {\n\t\treturn x.Org\n\t}\n\treturn \"\"\n}\n\nfunc (x *Buildkite) GetPipeline() string {\n\tif x != nil {\n\t\treturn x.Pipeline\n\t}\n\treturn \"\"\n}\n\nfunc (x *Buildkite) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *Buildkite) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *Buildkite) GetBuildNumber() int64 {\n\tif x != nil {\n\t\treturn x.BuildNumber\n\t}\n\treturn 0\n}\n\nfunc (x *Buildkite) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\ntype CircleCI struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tVcsType     string `protobuf:\"bytes,1,opt,name=vcs_type,json=vcsType,proto3\" json:\"vcs_type,omitempty\"`\n\tUsername    string `protobuf:\"bytes,2,opt,name=username,proto3\" json:\"username,omitempty\"`\n\tRepository  string `protobuf:\"bytes,3,opt,name=repository,proto3\" json:\"repository,omitempty\"`\n\tBuildNumber int64  `protobuf:\"varint,4,opt,name=build_number,json=buildNumber,proto3\" json:\"build_number,omitempty\"`\n\tBuildStep   string `protobuf:\"bytes,5,opt,name=build_step,json=buildStep,proto3\" json:\"build_step,omitempty\"`\n\tLink        string `protobuf:\"bytes,6,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tEmail       string `protobuf:\"bytes,7,opt,name=email,proto3\" json:\"email,omitempty\"`\n}\n\nfunc (x *CircleCI) Reset() {\n\t*x = CircleCI{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[3]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CircleCI) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CircleCI) ProtoMessage() {}\n\nfunc (x *CircleCI) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[3]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CircleCI.ProtoReflect.Descriptor instead.\nfunc (*CircleCI) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *CircleCI) GetVcsType() string {\n\tif x != nil {\n\t\treturn x.VcsType\n\t}\n\treturn \"\"\n}\n\nfunc (x *CircleCI) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *CircleCI) GetRepository() string {\n\tif x != nil {\n\t\treturn x.Repository\n\t}\n\treturn \"\"\n}\n\nfunc (x *CircleCI) GetBuildNumber() int64 {\n\tif x != nil {\n\t\treturn x.BuildNumber\n\t}\n\treturn 0\n}\n\nfunc (x *CircleCI) GetBuildStep() string {\n\tif x != nil {\n\t\treturn x.BuildStep\n\t}\n\treturn \"\"\n}\n\nfunc (x *CircleCI) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *CircleCI) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\ntype TravisCI struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tUsername    string `protobuf:\"bytes,2,opt,name=username,proto3\" json:\"username,omitempty\"`\n\tRepository  string `protobuf:\"bytes,3,opt,name=repository,proto3\" json:\"repository,omitempty\"`\n\tBuildNumber string `protobuf:\"bytes,4,opt,name=build_number,json=buildNumber,proto3\" json:\"build_number,omitempty\"`\n\tJobNumber   string `protobuf:\"bytes,5,opt,name=job_number,json=jobNumber,proto3\" json:\"job_number,omitempty\"`\n\tLink        string `protobuf:\"bytes,6,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tPublic      bool   `protobuf:\"varint,7,opt,name=public,proto3\" json:\"public,omitempty\"`\n}\n\nfunc (x *TravisCI) Reset() {\n\t*x = TravisCI{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[4]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *TravisCI) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TravisCI) ProtoMessage() {}\n\nfunc (x *TravisCI) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[4]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TravisCI.ProtoReflect.Descriptor instead.\nfunc (*TravisCI) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *TravisCI) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *TravisCI) GetRepository() string {\n\tif x != nil {\n\t\treturn x.Repository\n\t}\n\treturn \"\"\n}\n\nfunc (x *TravisCI) GetBuildNumber() string {\n\tif x != nil {\n\t\treturn x.BuildNumber\n\t}\n\treturn \"\"\n}\n\nfunc (x *TravisCI) GetJobNumber() string {\n\tif x != nil {\n\t\treturn x.JobNumber\n\t}\n\treturn \"\"\n}\n\nfunc (x *TravisCI) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *TravisCI) GetPublic() bool {\n\tif x != nil {\n\t\treturn x.Public\n\t}\n\treturn false\n}\n\ntype Confluence struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tPage      string `protobuf:\"bytes,1,opt,name=page,proto3\" json:\"page,omitempty\"`\n\tSpace     string `protobuf:\"bytes,2,opt,name=space,proto3\" json:\"space,omitempty\"`\n\tVersion   string `protobuf:\"bytes,3,opt,name=version,proto3\" json:\"version,omitempty\"`\n\tLink      string `protobuf:\"bytes,4,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tEmail     string `protobuf:\"bytes,5,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tTimestamp string `protobuf:\"bytes,6,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\tLocation  string `protobuf:\"bytes,7,opt,name=location,proto3\" json:\"location,omitempty\"`\n\tFile      string `protobuf:\"bytes,8,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tUser      string `protobuf:\"bytes,9,opt,name=user,proto3\" json:\"user,omitempty\"`\n\tCommentId string `protobuf:\"bytes,10,opt,name=comment_id,json=commentId,proto3\" json:\"comment_id,omitempty\"`\n}\n\nfunc (x *Confluence) Reset() {\n\t*x = Confluence{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[5]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Confluence) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Confluence) ProtoMessage() {}\n\nfunc (x *Confluence) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[5]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Confluence.ProtoReflect.Descriptor instead.\nfunc (*Confluence) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *Confluence) GetPage() string {\n\tif x != nil {\n\t\treturn x.Page\n\t}\n\treturn \"\"\n}\n\nfunc (x *Confluence) GetSpace() string {\n\tif x != nil {\n\t\treturn x.Space\n\t}\n\treturn \"\"\n}\n\nfunc (x *Confluence) GetVersion() string {\n\tif x != nil {\n\t\treturn x.Version\n\t}\n\treturn \"\"\n}\n\nfunc (x *Confluence) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *Confluence) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *Confluence) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\nfunc (x *Confluence) GetLocation() string {\n\tif x != nil {\n\t\treturn x.Location\n\t}\n\treturn \"\"\n}\n\nfunc (x *Confluence) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *Confluence) GetUser() string {\n\tif x != nil {\n\t\treturn x.User\n\t}\n\treturn \"\"\n}\n\nfunc (x *Confluence) GetCommentId() string {\n\tif x != nil {\n\t\treturn x.CommentId\n\t}\n\treturn \"\"\n}\n\ntype Docker struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tFile  string `protobuf:\"bytes,1,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tImage string `protobuf:\"bytes,2,opt,name=image,proto3\" json:\"image,omitempty\"`\n\tLayer string `protobuf:\"bytes,3,opt,name=layer,proto3\" json:\"layer,omitempty\"`\n\tTag   string `protobuf:\"bytes,4,opt,name=tag,proto3\" json:\"tag,omitempty\"`\n}\n\nfunc (x *Docker) Reset() {\n\t*x = Docker{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[6]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Docker) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Docker) ProtoMessage() {}\n\nfunc (x *Docker) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[6]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Docker.ProtoReflect.Descriptor instead.\nfunc (*Docker) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *Docker) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *Docker) GetImage() string {\n\tif x != nil {\n\t\treturn x.Image\n\t}\n\treturn \"\"\n}\n\nfunc (x *Docker) GetLayer() string {\n\tif x != nil {\n\t\treturn x.Layer\n\t}\n\treturn \"\"\n}\n\nfunc (x *Docker) GetTag() string {\n\tif x != nil {\n\t\treturn x.Tag\n\t}\n\treturn \"\"\n}\n\ntype ECR struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tFile     string `protobuf:\"bytes,1,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tLayer    string `protobuf:\"bytes,2,opt,name=layer,proto3\" json:\"layer,omitempty\"`\n\tImage    string `protobuf:\"bytes,3,opt,name=image,proto3\" json:\"image,omitempty\"`\n\tRegistry string `protobuf:\"bytes,4,opt,name=registry,proto3\" json:\"registry,omitempty\"`\n\tRegion   string `protobuf:\"bytes,5,opt,name=region,proto3\" json:\"region,omitempty\"`\n\tLink     string `protobuf:\"bytes,6,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tEmail    string `protobuf:\"bytes,7,opt,name=email,proto3\" json:\"email,omitempty\"`\n}\n\nfunc (x *ECR) Reset() {\n\t*x = ECR{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[7]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ECR) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ECR) ProtoMessage() {}\n\nfunc (x *ECR) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[7]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ECR.ProtoReflect.Descriptor instead.\nfunc (*ECR) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *ECR) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *ECR) GetLayer() string {\n\tif x != nil {\n\t\treturn x.Layer\n\t}\n\treturn \"\"\n}\n\nfunc (x *ECR) GetImage() string {\n\tif x != nil {\n\t\treturn x.Image\n\t}\n\treturn \"\"\n}\n\nfunc (x *ECR) GetRegistry() string {\n\tif x != nil {\n\t\treturn x.Registry\n\t}\n\treturn \"\"\n}\n\nfunc (x *ECR) GetRegion() string {\n\tif x != nil {\n\t\treturn x.Region\n\t}\n\treturn \"\"\n}\n\nfunc (x *ECR) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *ECR) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\ntype Filesystem struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tFile  string `protobuf:\"bytes,1,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tLink  string `protobuf:\"bytes,2,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tEmail string `protobuf:\"bytes,3,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tLine  int64  `protobuf:\"varint,4,opt,name=line,proto3\" json:\"line,omitempty\"`\n}\n\nfunc (x *Filesystem) Reset() {\n\t*x = Filesystem{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[8]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Filesystem) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Filesystem) ProtoMessage() {}\n\nfunc (x *Filesystem) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[8]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Filesystem.ProtoReflect.Descriptor instead.\nfunc (*Filesystem) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *Filesystem) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *Filesystem) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *Filesystem) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *Filesystem) GetLine() int64 {\n\tif x != nil {\n\t\treturn x.Line\n\t}\n\treturn 0\n}\n\ntype Git struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCommit              string `protobuf:\"bytes,1,opt,name=commit,proto3\" json:\"commit,omitempty\"`\n\tFile                string `protobuf:\"bytes,2,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tEmail               string `protobuf:\"bytes,3,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tRepository          string `protobuf:\"bytes,4,opt,name=repository,proto3\" json:\"repository,omitempty\"`\n\tTimestamp           string `protobuf:\"bytes,5,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\tLine                int64  `protobuf:\"varint,6,opt,name=line,proto3\" json:\"line,omitempty\"`\n\tRepositoryLocalPath string `protobuf:\"bytes,7,opt,name=repository_local_path,json=repositoryLocalPath,proto3\" json:\"repository_local_path,omitempty\"`\n}\n\nfunc (x *Git) Reset() {\n\t*x = Git{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[9]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Git) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Git) ProtoMessage() {}\n\nfunc (x *Git) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[9]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Git.ProtoReflect.Descriptor instead.\nfunc (*Git) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *Git) GetCommit() string {\n\tif x != nil {\n\t\treturn x.Commit\n\t}\n\treturn \"\"\n}\n\nfunc (x *Git) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *Git) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *Git) GetRepository() string {\n\tif x != nil {\n\t\treturn x.Repository\n\t}\n\treturn \"\"\n}\n\nfunc (x *Git) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\nfunc (x *Git) GetLine() int64 {\n\tif x != nil {\n\t\treturn x.Line\n\t}\n\treturn 0\n}\n\nfunc (x *Git) GetRepositoryLocalPath() string {\n\tif x != nil {\n\t\treturn x.RepositoryLocalPath\n\t}\n\treturn \"\"\n}\n\ntype Github struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tLink                string     `protobuf:\"bytes,1,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tUsername            string     `protobuf:\"bytes,2,opt,name=username,proto3\" json:\"username,omitempty\"`\n\tRepository          string     `protobuf:\"bytes,3,opt,name=repository,proto3\" json:\"repository,omitempty\"`\n\tCommit              string     `protobuf:\"bytes,4,opt,name=commit,proto3\" json:\"commit,omitempty\"`\n\tEmail               string     `protobuf:\"bytes,5,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tFile                string     `protobuf:\"bytes,6,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tTimestamp           string     `protobuf:\"bytes,7,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\tLine                int64      `protobuf:\"varint,8,opt,name=line,proto3\" json:\"line,omitempty\"`\n\tVisibility          Visibility `protobuf:\"varint,9,opt,name=visibility,proto3,enum=source_metadata.Visibility\" json:\"visibility,omitempty\"`\n\tRepositoryLocalPath string     `protobuf:\"bytes,10,opt,name=repository_local_path,json=repositoryLocalPath,proto3\" json:\"repository_local_path,omitempty\"`\n}\n\nfunc (x *Github) Reset() {\n\t*x = Github{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[10]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Github) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Github) ProtoMessage() {}\n\nfunc (x *Github) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[10]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Github.ProtoReflect.Descriptor instead.\nfunc (*Github) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (x *Github) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *Github) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *Github) GetRepository() string {\n\tif x != nil {\n\t\treturn x.Repository\n\t}\n\treturn \"\"\n}\n\nfunc (x *Github) GetCommit() string {\n\tif x != nil {\n\t\treturn x.Commit\n\t}\n\treturn \"\"\n}\n\nfunc (x *Github) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *Github) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *Github) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\nfunc (x *Github) GetLine() int64 {\n\tif x != nil {\n\t\treturn x.Line\n\t}\n\treturn 0\n}\n\nfunc (x *Github) GetVisibility() Visibility {\n\tif x != nil {\n\t\treturn x.Visibility\n\t}\n\treturn Visibility_public\n}\n\nfunc (x *Github) GetRepositoryLocalPath() string {\n\tif x != nil {\n\t\treturn x.RepositoryLocalPath\n\t}\n\treturn \"\"\n}\n\ntype Gitlab struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCommit              string `protobuf:\"bytes,1,opt,name=commit,proto3\" json:\"commit,omitempty\"`\n\tFile                string `protobuf:\"bytes,2,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tLink                string `protobuf:\"bytes,3,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tEmail               string `protobuf:\"bytes,4,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tRepository          string `protobuf:\"bytes,5,opt,name=repository,proto3\" json:\"repository,omitempty\"`\n\tTimestamp           string `protobuf:\"bytes,6,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\tLine                int64  `protobuf:\"varint,7,opt,name=line,proto3\" json:\"line,omitempty\"`\n\tProjectId           int64  `protobuf:\"varint,8,opt,name=project_id,json=projectId,proto3\" json:\"project_id,omitempty\"`\n\tProjectName         string `protobuf:\"bytes,9,opt,name=project_name,json=projectName,proto3\" json:\"project_name,omitempty\"`\n\tProjectOwner        string `protobuf:\"bytes,10,opt,name=project_owner,json=projectOwner,proto3\" json:\"project_owner,omitempty\"`\n\tRepositoryLocalPath string `protobuf:\"bytes,11,opt,name=repository_local_path,json=repositoryLocalPath,proto3\" json:\"repository_local_path,omitempty\"`\n}\n\nfunc (x *Gitlab) Reset() {\n\t*x = Gitlab{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[11]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Gitlab) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Gitlab) ProtoMessage() {}\n\nfunc (x *Gitlab) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[11]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Gitlab.ProtoReflect.Descriptor instead.\nfunc (*Gitlab) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (x *Gitlab) GetCommit() string {\n\tif x != nil {\n\t\treturn x.Commit\n\t}\n\treturn \"\"\n}\n\nfunc (x *Gitlab) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *Gitlab) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *Gitlab) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *Gitlab) GetRepository() string {\n\tif x != nil {\n\t\treturn x.Repository\n\t}\n\treturn \"\"\n}\n\nfunc (x *Gitlab) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\nfunc (x *Gitlab) GetLine() int64 {\n\tif x != nil {\n\t\treturn x.Line\n\t}\n\treturn 0\n}\n\nfunc (x *Gitlab) GetProjectId() int64 {\n\tif x != nil {\n\t\treturn x.ProjectId\n\t}\n\treturn 0\n}\n\nfunc (x *Gitlab) GetProjectName() string {\n\tif x != nil {\n\t\treturn x.ProjectName\n\t}\n\treturn \"\"\n}\n\nfunc (x *Gitlab) GetProjectOwner() string {\n\tif x != nil {\n\t\treturn x.ProjectOwner\n\t}\n\treturn \"\"\n}\n\nfunc (x *Gitlab) GetRepositoryLocalPath() string {\n\tif x != nil {\n\t\treturn x.RepositoryLocalPath\n\t}\n\treturn \"\"\n}\n\ntype GCS struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tBucket      string   `protobuf:\"bytes,1,opt,name=bucket,proto3\" json:\"bucket,omitempty\"`\n\tFilename    string   `protobuf:\"bytes,2,opt,name=filename,proto3\" json:\"filename,omitempty\"`\n\tLink        string   `protobuf:\"bytes,3,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tEmail       string   `protobuf:\"bytes,4,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tCreatedAt   string   `protobuf:\"bytes,5,opt,name=created_at,json=createdAt,proto3\" json:\"created_at,omitempty\"`\n\tUpdatedAt   string   `protobuf:\"bytes,6,opt,name=updated_at,json=updatedAt,proto3\" json:\"updated_at,omitempty\"`\n\tAcls        []string `protobuf:\"bytes,7,rep,name=acls,proto3\" json:\"acls,omitempty\"`\n\tContentType string   `protobuf:\"bytes,8,opt,name=content_type,json=contentType,proto3\" json:\"content_type,omitempty\"`\n}\n\nfunc (x *GCS) Reset() {\n\t*x = GCS{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[12]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GCS) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GCS) ProtoMessage() {}\n\nfunc (x *GCS) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[12]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GCS.ProtoReflect.Descriptor instead.\nfunc (*GCS) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{12}\n}\n\nfunc (x *GCS) GetBucket() string {\n\tif x != nil {\n\t\treturn x.Bucket\n\t}\n\treturn \"\"\n}\n\nfunc (x *GCS) GetFilename() string {\n\tif x != nil {\n\t\treturn x.Filename\n\t}\n\treturn \"\"\n}\n\nfunc (x *GCS) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *GCS) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *GCS) GetCreatedAt() string {\n\tif x != nil {\n\t\treturn x.CreatedAt\n\t}\n\treturn \"\"\n}\n\nfunc (x *GCS) GetUpdatedAt() string {\n\tif x != nil {\n\t\treturn x.UpdatedAt\n\t}\n\treturn \"\"\n}\n\nfunc (x *GCS) GetAcls() []string {\n\tif x != nil {\n\t\treturn x.Acls\n\t}\n\treturn nil\n}\n\nfunc (x *GCS) GetContentType() string {\n\tif x != nil {\n\t\treturn x.ContentType\n\t}\n\treturn \"\"\n}\n\ntype Huggingface struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tLink         string     `protobuf:\"bytes,1,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tUsername     string     `protobuf:\"bytes,2,opt,name=username,proto3\" json:\"username,omitempty\"`\n\tRepository   string     `protobuf:\"bytes,3,opt,name=repository,proto3\" json:\"repository,omitempty\"`\n\tCommit       string     `protobuf:\"bytes,4,opt,name=commit,proto3\" json:\"commit,omitempty\"`\n\tEmail        string     `protobuf:\"bytes,5,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tFile         string     `protobuf:\"bytes,6,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tTimestamp    string     `protobuf:\"bytes,7,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\tLine         int64      `protobuf:\"varint,8,opt,name=line,proto3\" json:\"line,omitempty\"`\n\tVisibility   Visibility `protobuf:\"varint,9,opt,name=visibility,proto3,enum=source_metadata.Visibility\" json:\"visibility,omitempty\"`\n\tResourceType string     `protobuf:\"bytes,10,opt,name=resource_type,json=resourceType,proto3\" json:\"resource_type,omitempty\"`\n}\n\nfunc (x *Huggingface) Reset() {\n\t*x = Huggingface{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[13]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Huggingface) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Huggingface) ProtoMessage() {}\n\nfunc (x *Huggingface) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[13]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Huggingface.ProtoReflect.Descriptor instead.\nfunc (*Huggingface) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{13}\n}\n\nfunc (x *Huggingface) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *Huggingface) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *Huggingface) GetRepository() string {\n\tif x != nil {\n\t\treturn x.Repository\n\t}\n\treturn \"\"\n}\n\nfunc (x *Huggingface) GetCommit() string {\n\tif x != nil {\n\t\treturn x.Commit\n\t}\n\treturn \"\"\n}\n\nfunc (x *Huggingface) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *Huggingface) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *Huggingface) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\nfunc (x *Huggingface) GetLine() int64 {\n\tif x != nil {\n\t\treturn x.Line\n\t}\n\treturn 0\n}\n\nfunc (x *Huggingface) GetVisibility() Visibility {\n\tif x != nil {\n\t\treturn x.Visibility\n\t}\n\treturn Visibility_public\n}\n\nfunc (x *Huggingface) GetResourceType() string {\n\tif x != nil {\n\t\treturn x.ResourceType\n\t}\n\treturn \"\"\n}\n\ntype Jira struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tIssue     string `protobuf:\"bytes,1,opt,name=issue,proto3\" json:\"issue,omitempty\"`\n\tAuthor    string `protobuf:\"bytes,2,opt,name=author,proto3\" json:\"author,omitempty\"`\n\tLink      string `protobuf:\"bytes,3,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tLocation  string `protobuf:\"bytes,4,opt,name=location,proto3\" json:\"location,omitempty\"`\n\tEmail     string `protobuf:\"bytes,5,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tTimestamp string `protobuf:\"bytes,6,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n}\n\nfunc (x *Jira) Reset() {\n\t*x = Jira{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[14]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Jira) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Jira) ProtoMessage() {}\n\nfunc (x *Jira) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[14]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Jira.ProtoReflect.Descriptor instead.\nfunc (*Jira) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{14}\n}\n\nfunc (x *Jira) GetIssue() string {\n\tif x != nil {\n\t\treturn x.Issue\n\t}\n\treturn \"\"\n}\n\nfunc (x *Jira) GetAuthor() string {\n\tif x != nil {\n\t\treturn x.Author\n\t}\n\treturn \"\"\n}\n\nfunc (x *Jira) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *Jira) GetLocation() string {\n\tif x != nil {\n\t\treturn x.Location\n\t}\n\treturn \"\"\n}\n\nfunc (x *Jira) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *Jira) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\ntype NPM struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tFile    string `protobuf:\"bytes,1,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tPackage string `protobuf:\"bytes,2,opt,name=package,proto3\" json:\"package,omitempty\"`\n\tRelease string `protobuf:\"bytes,3,opt,name=release,proto3\" json:\"release,omitempty\"`\n\tLink    string `protobuf:\"bytes,4,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tEmail   string `protobuf:\"bytes,5,opt,name=email,proto3\" json:\"email,omitempty\"`\n}\n\nfunc (x *NPM) Reset() {\n\t*x = NPM{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[15]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *NPM) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NPM) ProtoMessage() {}\n\nfunc (x *NPM) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[15]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NPM.ProtoReflect.Descriptor instead.\nfunc (*NPM) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{15}\n}\n\nfunc (x *NPM) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *NPM) GetPackage() string {\n\tif x != nil {\n\t\treturn x.Package\n\t}\n\treturn \"\"\n}\n\nfunc (x *NPM) GetRelease() string {\n\tif x != nil {\n\t\treturn x.Release\n\t}\n\treturn \"\"\n}\n\nfunc (x *NPM) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *NPM) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\ntype PyPi struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tFile    string `protobuf:\"bytes,1,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tPackage string `protobuf:\"bytes,2,opt,name=package,proto3\" json:\"package,omitempty\"`\n\tRelease string `protobuf:\"bytes,3,opt,name=release,proto3\" json:\"release,omitempty\"`\n\tLink    string `protobuf:\"bytes,4,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tEmail   string `protobuf:\"bytes,5,opt,name=email,proto3\" json:\"email,omitempty\"`\n}\n\nfunc (x *PyPi) Reset() {\n\t*x = PyPi{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[16]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *PyPi) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PyPi) ProtoMessage() {}\n\nfunc (x *PyPi) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[16]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PyPi.ProtoReflect.Descriptor instead.\nfunc (*PyPi) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{16}\n}\n\nfunc (x *PyPi) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *PyPi) GetPackage() string {\n\tif x != nil {\n\t\treturn x.Package\n\t}\n\treturn \"\"\n}\n\nfunc (x *PyPi) GetRelease() string {\n\tif x != nil {\n\t\treturn x.Release\n\t}\n\treturn \"\"\n}\n\nfunc (x *PyPi) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *PyPi) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\ntype S3 struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tBucket    string `protobuf:\"bytes,1,opt,name=bucket,proto3\" json:\"bucket,omitempty\"`\n\tFile      string `protobuf:\"bytes,2,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tLink      string `protobuf:\"bytes,3,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tEmail     string `protobuf:\"bytes,5,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tTimestamp string `protobuf:\"bytes,6,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n}\n\nfunc (x *S3) Reset() {\n\t*x = S3{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[17]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *S3) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*S3) ProtoMessage() {}\n\nfunc (x *S3) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[17]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use S3.ProtoReflect.Descriptor instead.\nfunc (*S3) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{17}\n}\n\nfunc (x *S3) GetBucket() string {\n\tif x != nil {\n\t\treturn x.Bucket\n\t}\n\treturn \"\"\n}\n\nfunc (x *S3) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *S3) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *S3) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *S3) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\ntype Slack struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tChannelId   string     `protobuf:\"bytes,1,opt,name=channel_id,json=channelId,proto3\" json:\"channel_id,omitempty\"`\n\tChannelName string     `protobuf:\"bytes,2,opt,name=channel_name,json=channelName,proto3\" json:\"channel_name,omitempty\"`\n\tTimestamp   string     `protobuf:\"bytes,3,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\tUserId      string     `protobuf:\"bytes,4,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tLink        string     `protobuf:\"bytes,5,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tFile        string     `protobuf:\"bytes,6,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tEmail       string     `protobuf:\"bytes,7,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tVisibility  Visibility `protobuf:\"varint,8,opt,name=visibility,proto3,enum=source_metadata.Visibility\" json:\"visibility,omitempty\"`\n\tLocation    string     `protobuf:\"bytes,9,opt,name=location,proto3\" json:\"location,omitempty\"`\n}\n\nfunc (x *Slack) Reset() {\n\t*x = Slack{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[18]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Slack) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Slack) ProtoMessage() {}\n\nfunc (x *Slack) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[18]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Slack.ProtoReflect.Descriptor instead.\nfunc (*Slack) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{18}\n}\n\nfunc (x *Slack) GetChannelId() string {\n\tif x != nil {\n\t\treturn x.ChannelId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Slack) GetChannelName() string {\n\tif x != nil {\n\t\treturn x.ChannelName\n\t}\n\treturn \"\"\n}\n\nfunc (x *Slack) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\nfunc (x *Slack) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Slack) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *Slack) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *Slack) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *Slack) GetVisibility() Visibility {\n\tif x != nil {\n\t\treturn x.Visibility\n\t}\n\treturn Visibility_public\n}\n\nfunc (x *Slack) GetLocation() string {\n\tif x != nil {\n\t\treturn x.Location\n\t}\n\treturn \"\"\n}\n\ntype Gerrit struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tCommit    string `protobuf:\"bytes,1,opt,name=commit,proto3\" json:\"commit,omitempty\"`\n\tFile      string `protobuf:\"bytes,2,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tEmail     string `protobuf:\"bytes,3,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tProject   string `protobuf:\"bytes,4,opt,name=project,proto3\" json:\"project,omitempty\"` // projects are what Gerrit calls repositories\n\tTimestamp string `protobuf:\"bytes,5,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\tLine      int64  `protobuf:\"varint,6,opt,name=line,proto3\" json:\"line,omitempty\"`\n}\n\nfunc (x *Gerrit) Reset() {\n\t*x = Gerrit{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[19]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Gerrit) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Gerrit) ProtoMessage() {}\n\nfunc (x *Gerrit) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[19]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Gerrit.ProtoReflect.Descriptor instead.\nfunc (*Gerrit) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{19}\n}\n\nfunc (x *Gerrit) GetCommit() string {\n\tif x != nil {\n\t\treturn x.Commit\n\t}\n\treturn \"\"\n}\n\nfunc (x *Gerrit) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *Gerrit) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *Gerrit) GetProject() string {\n\tif x != nil {\n\t\treturn x.Project\n\t}\n\treturn \"\"\n}\n\nfunc (x *Gerrit) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\nfunc (x *Gerrit) GetLine() int64 {\n\tif x != nil {\n\t\treturn x.Line\n\t}\n\treturn 0\n}\n\ntype Test struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tFile string `protobuf:\"bytes,1,opt,name=file,proto3\" json:\"file,omitempty\"`\n}\n\nfunc (x *Test) Reset() {\n\t*x = Test{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[20]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Test) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Test) ProtoMessage() {}\n\nfunc (x *Test) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[20]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Test.ProtoReflect.Descriptor instead.\nfunc (*Test) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{20}\n}\n\nfunc (x *Test) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\ntype Jenkins struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tProjectName string `protobuf:\"bytes,1,opt,name=project_name,json=projectName,proto3\" json:\"project_name,omitempty\"`\n\tBuildNumber int64  `protobuf:\"varint,2,opt,name=build_number,json=buildNumber,proto3\" json:\"build_number,omitempty\"`\n\tLink        string `protobuf:\"bytes,3,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tTimestamp   string `protobuf:\"bytes,4,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n}\n\nfunc (x *Jenkins) Reset() {\n\t*x = Jenkins{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[21]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Jenkins) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Jenkins) ProtoMessage() {}\n\nfunc (x *Jenkins) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[21]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Jenkins.ProtoReflect.Descriptor instead.\nfunc (*Jenkins) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{21}\n}\n\nfunc (x *Jenkins) GetProjectName() string {\n\tif x != nil {\n\t\treturn x.ProjectName\n\t}\n\treturn \"\"\n}\n\nfunc (x *Jenkins) GetBuildNumber() int64 {\n\tif x != nil {\n\t\treturn x.BuildNumber\n\t}\n\treturn 0\n}\n\nfunc (x *Jenkins) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *Jenkins) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\ntype Teams struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tChannelId   string `protobuf:\"bytes,1,opt,name=channel_id,json=channelId,proto3\" json:\"channel_id,omitempty\"`\n\tChannelName string `protobuf:\"bytes,2,opt,name=channel_name,json=channelName,proto3\" json:\"channel_name,omitempty\"`\n\tTimestamp   string `protobuf:\"bytes,3,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\tUserId      string `protobuf:\"bytes,4,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tLink        string `protobuf:\"bytes,5,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tFile        string `protobuf:\"bytes,6,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tEmail       string `protobuf:\"bytes,7,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tLocation    string `protobuf:\"bytes,8,opt,name=location,proto3\" json:\"location,omitempty\"`\n\tTeamName    string `protobuf:\"bytes,9,opt,name=team_name,json=teamName,proto3\" json:\"team_name,omitempty\"`\n\tTeamId      string `protobuf:\"bytes,10,opt,name=team_id,json=teamId,proto3\" json:\"team_id,omitempty\"`\n}\n\nfunc (x *Teams) Reset() {\n\t*x = Teams{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[22]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Teams) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Teams) ProtoMessage() {}\n\nfunc (x *Teams) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[22]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Teams.ProtoReflect.Descriptor instead.\nfunc (*Teams) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{22}\n}\n\nfunc (x *Teams) GetChannelId() string {\n\tif x != nil {\n\t\treturn x.ChannelId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Teams) GetChannelName() string {\n\tif x != nil {\n\t\treturn x.ChannelName\n\t}\n\treturn \"\"\n}\n\nfunc (x *Teams) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\nfunc (x *Teams) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Teams) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *Teams) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *Teams) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *Teams) GetLocation() string {\n\tif x != nil {\n\t\treturn x.Location\n\t}\n\treturn \"\"\n}\n\nfunc (x *Teams) GetTeamName() string {\n\tif x != nil {\n\t\treturn x.TeamName\n\t}\n\treturn \"\"\n}\n\nfunc (x *Teams) GetTeamId() string {\n\tif x != nil {\n\t\treturn x.TeamId\n\t}\n\treturn \"\"\n}\n\n// https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-FileInfo\ntype Artifactory struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tRepo      string `protobuf:\"bytes,1,opt,name=repo,proto3\" json:\"repo,omitempty\"`\n\tPath      string `protobuf:\"bytes,2,opt,name=path,proto3\" json:\"path,omitempty\"`\n\tLink      string `protobuf:\"bytes,3,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tTimestamp string `protobuf:\"bytes,4,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\tUsername  string `protobuf:\"bytes,5,opt,name=username,proto3\" json:\"username,omitempty\"`\n\tEmail     string `protobuf:\"bytes,6,opt,name=email,proto3\" json:\"email,omitempty\"`\n}\n\nfunc (x *Artifactory) Reset() {\n\t*x = Artifactory{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[23]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Artifactory) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Artifactory) ProtoMessage() {}\n\nfunc (x *Artifactory) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[23]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Artifactory.ProtoReflect.Descriptor instead.\nfunc (*Artifactory) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{23}\n}\n\nfunc (x *Artifactory) GetRepo() string {\n\tif x != nil {\n\t\treturn x.Repo\n\t}\n\treturn \"\"\n}\n\nfunc (x *Artifactory) GetPath() string {\n\tif x != nil {\n\t\treturn x.Path\n\t}\n\treturn \"\"\n}\n\nfunc (x *Artifactory) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *Artifactory) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\nfunc (x *Artifactory) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *Artifactory) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\ntype Syslog struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tHostname  string `protobuf:\"bytes,1,opt,name=hostname,proto3\" json:\"hostname,omitempty\"`\n\tAppname   string `protobuf:\"bytes,2,opt,name=appname,proto3\" json:\"appname,omitempty\"`\n\tProcid    string `protobuf:\"bytes,3,opt,name=procid,proto3\" json:\"procid,omitempty\"`\n\tTimestamp string `protobuf:\"bytes,4,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\tClient    string `protobuf:\"bytes,5,opt,name=client,proto3\" json:\"client,omitempty\"`\n\tFacility  string `protobuf:\"bytes,6,opt,name=facility,proto3\" json:\"facility,omitempty\"`\n}\n\nfunc (x *Syslog) Reset() {\n\t*x = Syslog{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[24]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Syslog) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Syslog) ProtoMessage() {}\n\nfunc (x *Syslog) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[24]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Syslog.ProtoReflect.Descriptor instead.\nfunc (*Syslog) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{24}\n}\n\nfunc (x *Syslog) GetHostname() string {\n\tif x != nil {\n\t\treturn x.Hostname\n\t}\n\treturn \"\"\n}\n\nfunc (x *Syslog) GetAppname() string {\n\tif x != nil {\n\t\treturn x.Appname\n\t}\n\treturn \"\"\n}\n\nfunc (x *Syslog) GetProcid() string {\n\tif x != nil {\n\t\treturn x.Procid\n\t}\n\treturn \"\"\n}\n\nfunc (x *Syslog) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\nfunc (x *Syslog) GetClient() string {\n\tif x != nil {\n\t\treturn x.Client\n\t}\n\treturn \"\"\n}\n\nfunc (x *Syslog) GetFacility() string {\n\tif x != nil {\n\t\treturn x.Facility\n\t}\n\treturn \"\"\n}\n\ntype Forager struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Metadata:\n\t//\n\t//\t*Forager_Github\n\t//\t*Forager_Npm\n\t//\t*Forager_Pypi\n\tMetadata isForager_Metadata `protobuf_oneof:\"metadata\"`\n}\n\nfunc (x *Forager) Reset() {\n\t*x = Forager{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[25]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Forager) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Forager) ProtoMessage() {}\n\nfunc (x *Forager) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[25]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Forager.ProtoReflect.Descriptor instead.\nfunc (*Forager) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{25}\n}\n\nfunc (m *Forager) GetMetadata() isForager_Metadata {\n\tif m != nil {\n\t\treturn m.Metadata\n\t}\n\treturn nil\n}\n\nfunc (x *Forager) GetGithub() *Github {\n\tif x, ok := x.GetMetadata().(*Forager_Github); ok {\n\t\treturn x.Github\n\t}\n\treturn nil\n}\n\nfunc (x *Forager) GetNpm() *NPM {\n\tif x, ok := x.GetMetadata().(*Forager_Npm); ok {\n\t\treturn x.Npm\n\t}\n\treturn nil\n}\n\nfunc (x *Forager) GetPypi() *PyPi {\n\tif x, ok := x.GetMetadata().(*Forager_Pypi); ok {\n\t\treturn x.Pypi\n\t}\n\treturn nil\n}\n\ntype isForager_Metadata interface {\n\tisForager_Metadata()\n}\n\ntype Forager_Github struct {\n\tGithub *Github `protobuf:\"bytes,1,opt,name=github,proto3,oneof\"`\n}\n\ntype Forager_Npm struct {\n\tNpm *NPM `protobuf:\"bytes,2,opt,name=npm,proto3,oneof\"`\n}\n\ntype Forager_Pypi struct {\n\tPypi *PyPi `protobuf:\"bytes,3,opt,name=pypi,proto3,oneof\"`\n}\n\nfunc (*Forager_Github) isForager_Metadata() {}\n\nfunc (*Forager_Npm) isForager_Metadata() {}\n\nfunc (*Forager_Pypi) isForager_Metadata() {}\n\ntype SharePoint struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tLink      string `protobuf:\"bytes,1,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tTimestamp string `protobuf:\"bytes,2,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\tAuthor    string `protobuf:\"bytes,3,opt,name=author,proto3\" json:\"author,omitempty\"`\n\tTitle     string `protobuf:\"bytes,4,opt,name=title,proto3\" json:\"title,omitempty\"`\n\tViews     int64  `protobuf:\"varint,5,opt,name=views,proto3\" json:\"views,omitempty\"`\n\tDocid     string `protobuf:\"bytes,6,opt,name=docid,proto3\" json:\"docid,omitempty\"`\n\tEmail     string `protobuf:\"bytes,7,opt,name=email,proto3\" json:\"email,omitempty\"`\n}\n\nfunc (x *SharePoint) Reset() {\n\t*x = SharePoint{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[26]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SharePoint) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SharePoint) ProtoMessage() {}\n\nfunc (x *SharePoint) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[26]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SharePoint.ProtoReflect.Descriptor instead.\nfunc (*SharePoint) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{26}\n}\n\nfunc (x *SharePoint) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *SharePoint) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\nfunc (x *SharePoint) GetAuthor() string {\n\tif x != nil {\n\t\treturn x.Author\n\t}\n\treturn \"\"\n}\n\nfunc (x *SharePoint) GetTitle() string {\n\tif x != nil {\n\t\treturn x.Title\n\t}\n\treturn \"\"\n}\n\nfunc (x *SharePoint) GetViews() int64 {\n\tif x != nil {\n\t\treturn x.Views\n\t}\n\treturn 0\n}\n\nfunc (x *SharePoint) GetDocid() string {\n\tif x != nil {\n\t\treturn x.Docid\n\t}\n\treturn \"\"\n}\n\nfunc (x *SharePoint) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\ntype GoogleDrive struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tFile           string `protobuf:\"bytes,1,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tLink           string `protobuf:\"bytes,2,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tEmail          string `protobuf:\"bytes,3,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tTimestamp      string `protobuf:\"bytes,4,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\tShared         bool   `protobuf:\"varint,5,opt,name=shared,proto3\" json:\"shared,omitempty\"`\n\tLastModifiedBy string `protobuf:\"bytes,6,opt,name=last_modified_by,json=lastModifiedBy,proto3\" json:\"last_modified_by,omitempty\"`\n\tPath           string `protobuf:\"bytes,7,opt,name=path,proto3\" json:\"path,omitempty\"`\n}\n\nfunc (x *GoogleDrive) Reset() {\n\t*x = GoogleDrive{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[27]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GoogleDrive) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GoogleDrive) ProtoMessage() {}\n\nfunc (x *GoogleDrive) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[27]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GoogleDrive.ProtoReflect.Descriptor instead.\nfunc (*GoogleDrive) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{27}\n}\n\nfunc (x *GoogleDrive) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *GoogleDrive) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *GoogleDrive) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *GoogleDrive) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\nfunc (x *GoogleDrive) GetShared() bool {\n\tif x != nil {\n\t\treturn x.Shared\n\t}\n\treturn false\n}\n\nfunc (x *GoogleDrive) GetLastModifiedBy() string {\n\tif x != nil {\n\t\treturn x.LastModifiedBy\n\t}\n\treturn \"\"\n}\n\nfunc (x *GoogleDrive) GetPath() string {\n\tif x != nil {\n\t\treturn x.Path\n\t}\n\treturn \"\"\n}\n\ntype AzureRepos struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tLink         string     `protobuf:\"bytes,1,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tUsername     string     `protobuf:\"bytes,2,opt,name=username,proto3\" json:\"username,omitempty\"`\n\tRepository   string     `protobuf:\"bytes,3,opt,name=repository,proto3\" json:\"repository,omitempty\"`\n\tCommit       string     `protobuf:\"bytes,4,opt,name=commit,proto3\" json:\"commit,omitempty\"`\n\tEmail        string     `protobuf:\"bytes,5,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tFile         string     `protobuf:\"bytes,6,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tTimestamp    string     `protobuf:\"bytes,7,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\tLine         int64      `protobuf:\"varint,8,opt,name=line,proto3\" json:\"line,omitempty\"`\n\tVisibility   Visibility `protobuf:\"varint,9,opt,name=visibility,proto3,enum=source_metadata.Visibility\" json:\"visibility,omitempty\"`\n\tProject      string     `protobuf:\"bytes,10,opt,name=project,proto3\" json:\"project,omitempty\"`\n\tOrganization string     `protobuf:\"bytes,11,opt,name=organization,proto3\" json:\"organization,omitempty\"`\n}\n\nfunc (x *AzureRepos) Reset() {\n\t*x = AzureRepos{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[28]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AzureRepos) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AzureRepos) ProtoMessage() {}\n\nfunc (x *AzureRepos) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[28]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AzureRepos.ProtoReflect.Descriptor instead.\nfunc (*AzureRepos) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{28}\n}\n\nfunc (x *AzureRepos) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *AzureRepos) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *AzureRepos) GetRepository() string {\n\tif x != nil {\n\t\treturn x.Repository\n\t}\n\treturn \"\"\n}\n\nfunc (x *AzureRepos) GetCommit() string {\n\tif x != nil {\n\t\treturn x.Commit\n\t}\n\treturn \"\"\n}\n\nfunc (x *AzureRepos) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *AzureRepos) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *AzureRepos) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\nfunc (x *AzureRepos) GetLine() int64 {\n\tif x != nil {\n\t\treturn x.Line\n\t}\n\treturn 0\n}\n\nfunc (x *AzureRepos) GetVisibility() Visibility {\n\tif x != nil {\n\t\treturn x.Visibility\n\t}\n\treturn Visibility_public\n}\n\nfunc (x *AzureRepos) GetProject() string {\n\tif x != nil {\n\t\treturn x.Project\n\t}\n\treturn \"\"\n}\n\nfunc (x *AzureRepos) GetOrganization() string {\n\tif x != nil {\n\t\treturn x.Organization\n\t}\n\treturn \"\"\n}\n\ntype Postman struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tLink            string              `protobuf:\"bytes,1,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tWorkspaceUuid   string              `protobuf:\"bytes,2,opt,name=workspace_uuid,json=workspaceUuid,proto3\" json:\"workspace_uuid,omitempty\"`\n\tWorkspaceName   string              `protobuf:\"bytes,3,opt,name=workspace_name,json=workspaceName,proto3\" json:\"workspace_name,omitempty\"`\n\tCollectionId    string              `protobuf:\"bytes,5,opt,name=collection_id,json=collectionId,proto3\" json:\"collection_id,omitempty\"`\n\tCollectionName  string              `protobuf:\"bytes,6,opt,name=collection_name,json=collectionName,proto3\" json:\"collection_name,omitempty\"`\n\tEnvironmentId   string              `protobuf:\"bytes,7,opt,name=environment_id,json=environmentId,proto3\" json:\"environment_id,omitempty\"`\n\tEnvironmentName string              `protobuf:\"bytes,8,opt,name=environment_name,json=environmentName,proto3\" json:\"environment_name,omitempty\"`\n\tRequestId       string              `protobuf:\"bytes,9,opt,name=request_id,json=requestId,proto3\" json:\"request_id,omitempty\"`\n\tRequestName     string              `protobuf:\"bytes,10,opt,name=request_name,json=requestName,proto3\" json:\"request_name,omitempty\"`\n\tFolderId        string              `protobuf:\"bytes,11,opt,name=folder_id,json=folderId,proto3\" json:\"folder_id,omitempty\"`\n\tFolderName      string              `protobuf:\"bytes,12,opt,name=folder_name,json=folderName,proto3\" json:\"folder_name,omitempty\"`\n\tFieldType       string              `protobuf:\"bytes,13,opt,name=field_type,json=fieldType,proto3\" json:\"field_type,omitempty\"` // Do not use this field for transport. It is only used for output to STDOUT.\n\tLocationType    PostmanLocationType `protobuf:\"varint,16,opt,name=location_type,json=locationType,proto3,enum=source_metadata.PostmanLocationType\" json:\"location_type,omitempty\"`\n\tResponseId      string              `protobuf:\"bytes,17,opt,name=response_id,json=responseId,proto3\" json:\"response_id,omitempty\"`\n\tResponseName    string              `protobuf:\"bytes,18,opt,name=response_name,json=responseName,proto3\" json:\"response_name,omitempty\"`\n}\n\nfunc (x *Postman) Reset() {\n\t*x = Postman{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[29]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Postman) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Postman) ProtoMessage() {}\n\nfunc (x *Postman) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[29]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Postman.ProtoReflect.Descriptor instead.\nfunc (*Postman) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{29}\n}\n\nfunc (x *Postman) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *Postman) GetWorkspaceUuid() string {\n\tif x != nil {\n\t\treturn x.WorkspaceUuid\n\t}\n\treturn \"\"\n}\n\nfunc (x *Postman) GetWorkspaceName() string {\n\tif x != nil {\n\t\treturn x.WorkspaceName\n\t}\n\treturn \"\"\n}\n\nfunc (x *Postman) GetCollectionId() string {\n\tif x != nil {\n\t\treturn x.CollectionId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Postman) GetCollectionName() string {\n\tif x != nil {\n\t\treturn x.CollectionName\n\t}\n\treturn \"\"\n}\n\nfunc (x *Postman) GetEnvironmentId() string {\n\tif x != nil {\n\t\treturn x.EnvironmentId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Postman) GetEnvironmentName() string {\n\tif x != nil {\n\t\treturn x.EnvironmentName\n\t}\n\treturn \"\"\n}\n\nfunc (x *Postman) GetRequestId() string {\n\tif x != nil {\n\t\treturn x.RequestId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Postman) GetRequestName() string {\n\tif x != nil {\n\t\treturn x.RequestName\n\t}\n\treturn \"\"\n}\n\nfunc (x *Postman) GetFolderId() string {\n\tif x != nil {\n\t\treturn x.FolderId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Postman) GetFolderName() string {\n\tif x != nil {\n\t\treturn x.FolderName\n\t}\n\treturn \"\"\n}\n\nfunc (x *Postman) GetFieldType() string {\n\tif x != nil {\n\t\treturn x.FieldType\n\t}\n\treturn \"\"\n}\n\nfunc (x *Postman) GetLocationType() PostmanLocationType {\n\tif x != nil {\n\t\treturn x.LocationType\n\t}\n\treturn PostmanLocationType_UNKNOWN_POSTMAN\n}\n\nfunc (x *Postman) GetResponseId() string {\n\tif x != nil {\n\t\treturn x.ResponseId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Postman) GetResponseName() string {\n\tif x != nil {\n\t\treturn x.ResponseName\n\t}\n\treturn \"\"\n}\n\ntype Vector struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tTimestamp  *timestamppb.Timestamp `protobuf:\"bytes,1,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\tSourceType string                 `protobuf:\"bytes,2,opt,name=source_type,json=sourceType,proto3\" json:\"source_type,omitempty\"`\n\tHost       string                 `protobuf:\"bytes,3,opt,name=host,proto3\" json:\"host,omitempty\"`\n\tLocator    string                 `protobuf:\"bytes,4,opt,name=locator,proto3\" json:\"locator,omitempty\"`\n\tLink       string                 `protobuf:\"bytes,5,opt,name=link,proto3\" json:\"link,omitempty\"`\n}\n\nfunc (x *Vector) Reset() {\n\t*x = Vector{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[30]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Vector) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Vector) ProtoMessage() {}\n\nfunc (x *Vector) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[30]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Vector.ProtoReflect.Descriptor instead.\nfunc (*Vector) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{30}\n}\n\nfunc (x *Vector) GetTimestamp() *timestamppb.Timestamp {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn nil\n}\n\nfunc (x *Vector) GetSourceType() string {\n\tif x != nil {\n\t\treturn x.SourceType\n\t}\n\treturn \"\"\n}\n\nfunc (x *Vector) GetHost() string {\n\tif x != nil {\n\t\treturn x.Host\n\t}\n\treturn \"\"\n}\n\nfunc (x *Vector) GetLocator() string {\n\tif x != nil {\n\t\treturn x.Locator\n\t}\n\treturn \"\"\n}\n\nfunc (x *Vector) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\ntype Webhook struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Data:\n\t//\n\t//\t*Webhook_Vector\n\tData isWebhook_Data `protobuf_oneof:\"data\"`\n}\n\nfunc (x *Webhook) Reset() {\n\t*x = Webhook{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[31]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Webhook) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Webhook) ProtoMessage() {}\n\nfunc (x *Webhook) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[31]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Webhook.ProtoReflect.Descriptor instead.\nfunc (*Webhook) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{31}\n}\n\nfunc (m *Webhook) GetData() isWebhook_Data {\n\tif m != nil {\n\t\treturn m.Data\n\t}\n\treturn nil\n}\n\nfunc (x *Webhook) GetVector() *Vector {\n\tif x, ok := x.GetData().(*Webhook_Vector); ok {\n\t\treturn x.Vector\n\t}\n\treturn nil\n}\n\ntype isWebhook_Data interface {\n\tisWebhook_Data()\n}\n\ntype Webhook_Vector struct {\n\tVector *Vector `protobuf:\"bytes,1,opt,name=vector,proto3,oneof\"`\n}\n\nfunc (*Webhook_Vector) isWebhook_Data() {}\n\ntype Elasticsearch struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tIndex      string `protobuf:\"bytes,1,opt,name=index,proto3\" json:\"index,omitempty\"`\n\tDocumentId string `protobuf:\"bytes,2,opt,name=document_id,json=documentId,proto3\" json:\"document_id,omitempty\"`\n\tTimestamp  string `protobuf:\"bytes,3,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n}\n\nfunc (x *Elasticsearch) Reset() {\n\t*x = Elasticsearch{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[32]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Elasticsearch) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Elasticsearch) ProtoMessage() {}\n\nfunc (x *Elasticsearch) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[32]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Elasticsearch.ProtoReflect.Descriptor instead.\nfunc (*Elasticsearch) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{32}\n}\n\nfunc (x *Elasticsearch) GetIndex() string {\n\tif x != nil {\n\t\treturn x.Index\n\t}\n\treturn \"\"\n}\n\nfunc (x *Elasticsearch) GetDocumentId() string {\n\tif x != nil {\n\t\treturn x.DocumentId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Elasticsearch) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\ntype Sentry struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEventId                 string `protobuf:\"bytes,1,opt,name=event_id,json=eventId,proto3\" json:\"event_id,omitempty\"`\n\tOrganizationId          string `protobuf:\"bytes,2,opt,name=organization_id,json=organizationId,proto3\" json:\"organization_id,omitempty\"`\n\tOrganizationSlug        string `protobuf:\"bytes,3,opt,name=organization_slug,json=organizationSlug,proto3\" json:\"organization_slug,omitempty\"`\n\tOrganizationDateCreated string `protobuf:\"bytes,4,opt,name=organization_date_created,json=organizationDateCreated,proto3\" json:\"organization_date_created,omitempty\"`\n\tProjectId               string `protobuf:\"bytes,5,opt,name=project_id,json=projectId,proto3\" json:\"project_id,omitempty\"`\n\tProjectSlug             string `protobuf:\"bytes,6,opt,name=project_slug,json=projectSlug,proto3\" json:\"project_slug,omitempty\"`\n\tIssueId                 string `protobuf:\"bytes,7,opt,name=issue_id,json=issueId,proto3\" json:\"issue_id,omitempty\"`\n\tDateCreated             string `protobuf:\"bytes,8,opt,name=date_created,json=dateCreated,proto3\" json:\"date_created,omitempty\"`\n\tLink                    string `protobuf:\"bytes,9,opt,name=link,proto3\" json:\"link,omitempty\"`\n}\n\nfunc (x *Sentry) Reset() {\n\t*x = Sentry{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[33]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Sentry) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Sentry) ProtoMessage() {}\n\nfunc (x *Sentry) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[33]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Sentry.ProtoReflect.Descriptor instead.\nfunc (*Sentry) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{33}\n}\n\nfunc (x *Sentry) GetEventId() string {\n\tif x != nil {\n\t\treturn x.EventId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Sentry) GetOrganizationId() string {\n\tif x != nil {\n\t\treturn x.OrganizationId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Sentry) GetOrganizationSlug() string {\n\tif x != nil {\n\t\treturn x.OrganizationSlug\n\t}\n\treturn \"\"\n}\n\nfunc (x *Sentry) GetOrganizationDateCreated() string {\n\tif x != nil {\n\t\treturn x.OrganizationDateCreated\n\t}\n\treturn \"\"\n}\n\nfunc (x *Sentry) GetProjectId() string {\n\tif x != nil {\n\t\treturn x.ProjectId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Sentry) GetProjectSlug() string {\n\tif x != nil {\n\t\treturn x.ProjectSlug\n\t}\n\treturn \"\"\n}\n\nfunc (x *Sentry) GetIssueId() string {\n\tif x != nil {\n\t\treturn x.IssueId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Sentry) GetDateCreated() string {\n\tif x != nil {\n\t\treturn x.DateCreated\n\t}\n\treturn \"\"\n}\n\nfunc (x *Sentry) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\ntype Stdin struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *Stdin) Reset() {\n\t*x = Stdin{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[34]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Stdin) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Stdin) ProtoMessage() {}\n\nfunc (x *Stdin) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[34]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Stdin.ProtoReflect.Descriptor instead.\nfunc (*Stdin) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{34}\n}\n\ntype SlackContinuous struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tChannelId   string     `protobuf:\"bytes,1,opt,name=channel_id,json=channelId,proto3\" json:\"channel_id,omitempty\"`\n\tChannelName string     `protobuf:\"bytes,2,opt,name=channel_name,json=channelName,proto3\" json:\"channel_name,omitempty\"`\n\tTimestamp   string     `protobuf:\"bytes,3,opt,name=timestamp,proto3\" json:\"timestamp,omitempty\"`\n\tUserId      string     `protobuf:\"bytes,4,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tLink        string     `protobuf:\"bytes,5,opt,name=link,proto3\" json:\"link,omitempty\"`\n\tFile        string     `protobuf:\"bytes,6,opt,name=file,proto3\" json:\"file,omitempty\"`\n\tEmail       string     `protobuf:\"bytes,7,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tVisibility  Visibility `protobuf:\"varint,8,opt,name=visibility,proto3,enum=source_metadata.Visibility\" json:\"visibility,omitempty\"`\n\tLocation    string     `protobuf:\"bytes,9,opt,name=location,proto3\" json:\"location,omitempty\"`\n\tWorkspaceId string     `protobuf:\"bytes,10,opt,name=workspace_id,json=workspaceId,proto3\" json:\"workspace_id,omitempty\"`\n}\n\nfunc (x *SlackContinuous) Reset() {\n\t*x = SlackContinuous{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[35]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SlackContinuous) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SlackContinuous) ProtoMessage() {}\n\nfunc (x *SlackContinuous) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[35]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SlackContinuous.ProtoReflect.Descriptor instead.\nfunc (*SlackContinuous) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{35}\n}\n\nfunc (x *SlackContinuous) GetChannelId() string {\n\tif x != nil {\n\t\treturn x.ChannelId\n\t}\n\treturn \"\"\n}\n\nfunc (x *SlackContinuous) GetChannelName() string {\n\tif x != nil {\n\t\treturn x.ChannelName\n\t}\n\treturn \"\"\n}\n\nfunc (x *SlackContinuous) GetTimestamp() string {\n\tif x != nil {\n\t\treturn x.Timestamp\n\t}\n\treturn \"\"\n}\n\nfunc (x *SlackContinuous) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *SlackContinuous) GetLink() string {\n\tif x != nil {\n\t\treturn x.Link\n\t}\n\treturn \"\"\n}\n\nfunc (x *SlackContinuous) GetFile() string {\n\tif x != nil {\n\t\treturn x.File\n\t}\n\treturn \"\"\n}\n\nfunc (x *SlackContinuous) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *SlackContinuous) GetVisibility() Visibility {\n\tif x != nil {\n\t\treturn x.Visibility\n\t}\n\treturn Visibility_public\n}\n\nfunc (x *SlackContinuous) GetLocation() string {\n\tif x != nil {\n\t\treturn x.Location\n\t}\n\treturn \"\"\n}\n\nfunc (x *SlackContinuous) GetWorkspaceId() string {\n\tif x != nil {\n\t\treturn x.WorkspaceId\n\t}\n\treturn \"\"\n}\n\ntype JSONEnumerator struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tMetadata string `protobuf:\"bytes,1,opt,name=metadata,proto3\" json:\"metadata,omitempty\"`\n}\n\nfunc (x *JSONEnumerator) Reset() {\n\t*x = JSONEnumerator{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[36]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *JSONEnumerator) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*JSONEnumerator) ProtoMessage() {}\n\nfunc (x *JSONEnumerator) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[36]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use JSONEnumerator.ProtoReflect.Descriptor instead.\nfunc (*JSONEnumerator) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{36}\n}\n\nfunc (x *JSONEnumerator) GetMetadata() string {\n\tif x != nil {\n\t\treturn x.Metadata\n\t}\n\treturn \"\"\n}\n\ntype MetaData struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Data:\n\t//\n\t//\t*MetaData_Azure\n\t//\t*MetaData_Bitbucket\n\t//\t*MetaData_Circleci\n\t//\t*MetaData_Confluence\n\t//\t*MetaData_Docker\n\t//\t*MetaData_Ecr\n\t//\t*MetaData_Gcs\n\t//\t*MetaData_Github\n\t//\t*MetaData_Gitlab\n\t//\t*MetaData_Jira\n\t//\t*MetaData_Npm\n\t//\t*MetaData_Pypi\n\t//\t*MetaData_S3\n\t//\t*MetaData_Slack\n\t//\t*MetaData_Filesystem\n\t//\t*MetaData_Git\n\t//\t*MetaData_Test\n\t//\t*MetaData_Buildkite\n\t//\t*MetaData_Gerrit\n\t//\t*MetaData_Jenkins\n\t//\t*MetaData_Teams\n\t//\t*MetaData_Artifactory\n\t//\t*MetaData_Syslog\n\t//\t*MetaData_Forager\n\t//\t*MetaData_Sharepoint\n\t//\t*MetaData_GoogleDrive\n\t//\t*MetaData_AzureRepos\n\t//\t*MetaData_TravisCI\n\t//\t*MetaData_Postman\n\t//\t*MetaData_Webhook\n\t//\t*MetaData_Elasticsearch\n\t//\t*MetaData_Huggingface\n\t//\t*MetaData_Sentry\n\t//\t*MetaData_Stdin\n\t//\t*MetaData_SlackContinuous\n\t//\t*MetaData_JsonEnumerator\n\tData isMetaData_Data `protobuf_oneof:\"data\"`\n}\n\nfunc (x *MetaData) Reset() {\n\t*x = MetaData{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_source_metadata_proto_msgTypes[37]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *MetaData) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*MetaData) ProtoMessage() {}\n\nfunc (x *MetaData) ProtoReflect() protoreflect.Message {\n\tmi := &file_source_metadata_proto_msgTypes[37]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use MetaData.ProtoReflect.Descriptor instead.\nfunc (*MetaData) Descriptor() ([]byte, []int) {\n\treturn file_source_metadata_proto_rawDescGZIP(), []int{37}\n}\n\nfunc (m *MetaData) GetData() isMetaData_Data {\n\tif m != nil {\n\t\treturn m.Data\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetAzure() *Azure {\n\tif x, ok := x.GetData().(*MetaData_Azure); ok {\n\t\treturn x.Azure\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetBitbucket() *Bitbucket {\n\tif x, ok := x.GetData().(*MetaData_Bitbucket); ok {\n\t\treturn x.Bitbucket\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetCircleci() *CircleCI {\n\tif x, ok := x.GetData().(*MetaData_Circleci); ok {\n\t\treturn x.Circleci\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetConfluence() *Confluence {\n\tif x, ok := x.GetData().(*MetaData_Confluence); ok {\n\t\treturn x.Confluence\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetDocker() *Docker {\n\tif x, ok := x.GetData().(*MetaData_Docker); ok {\n\t\treturn x.Docker\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetEcr() *ECR {\n\tif x, ok := x.GetData().(*MetaData_Ecr); ok {\n\t\treturn x.Ecr\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetGcs() *GCS {\n\tif x, ok := x.GetData().(*MetaData_Gcs); ok {\n\t\treturn x.Gcs\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetGithub() *Github {\n\tif x, ok := x.GetData().(*MetaData_Github); ok {\n\t\treturn x.Github\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetGitlab() *Gitlab {\n\tif x, ok := x.GetData().(*MetaData_Gitlab); ok {\n\t\treturn x.Gitlab\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetJira() *Jira {\n\tif x, ok := x.GetData().(*MetaData_Jira); ok {\n\t\treturn x.Jira\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetNpm() *NPM {\n\tif x, ok := x.GetData().(*MetaData_Npm); ok {\n\t\treturn x.Npm\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetPypi() *PyPi {\n\tif x, ok := x.GetData().(*MetaData_Pypi); ok {\n\t\treturn x.Pypi\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetS3() *S3 {\n\tif x, ok := x.GetData().(*MetaData_S3); ok {\n\t\treturn x.S3\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetSlack() *Slack {\n\tif x, ok := x.GetData().(*MetaData_Slack); ok {\n\t\treturn x.Slack\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetFilesystem() *Filesystem {\n\tif x, ok := x.GetData().(*MetaData_Filesystem); ok {\n\t\treturn x.Filesystem\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetGit() *Git {\n\tif x, ok := x.GetData().(*MetaData_Git); ok {\n\t\treturn x.Git\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetTest() *Test {\n\tif x, ok := x.GetData().(*MetaData_Test); ok {\n\t\treturn x.Test\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetBuildkite() *Buildkite {\n\tif x, ok := x.GetData().(*MetaData_Buildkite); ok {\n\t\treturn x.Buildkite\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetGerrit() *Gerrit {\n\tif x, ok := x.GetData().(*MetaData_Gerrit); ok {\n\t\treturn x.Gerrit\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetJenkins() *Jenkins {\n\tif x, ok := x.GetData().(*MetaData_Jenkins); ok {\n\t\treturn x.Jenkins\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetTeams() *Teams {\n\tif x, ok := x.GetData().(*MetaData_Teams); ok {\n\t\treturn x.Teams\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetArtifactory() *Artifactory {\n\tif x, ok := x.GetData().(*MetaData_Artifactory); ok {\n\t\treturn x.Artifactory\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetSyslog() *Syslog {\n\tif x, ok := x.GetData().(*MetaData_Syslog); ok {\n\t\treturn x.Syslog\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetForager() *Forager {\n\tif x, ok := x.GetData().(*MetaData_Forager); ok {\n\t\treturn x.Forager\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetSharepoint() *SharePoint {\n\tif x, ok := x.GetData().(*MetaData_Sharepoint); ok {\n\t\treturn x.Sharepoint\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetGoogleDrive() *GoogleDrive {\n\tif x, ok := x.GetData().(*MetaData_GoogleDrive); ok {\n\t\treturn x.GoogleDrive\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetAzureRepos() *AzureRepos {\n\tif x, ok := x.GetData().(*MetaData_AzureRepos); ok {\n\t\treturn x.AzureRepos\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetTravisCI() *TravisCI {\n\tif x, ok := x.GetData().(*MetaData_TravisCI); ok {\n\t\treturn x.TravisCI\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetPostman() *Postman {\n\tif x, ok := x.GetData().(*MetaData_Postman); ok {\n\t\treturn x.Postman\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetWebhook() *Webhook {\n\tif x, ok := x.GetData().(*MetaData_Webhook); ok {\n\t\treturn x.Webhook\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetElasticsearch() *Elasticsearch {\n\tif x, ok := x.GetData().(*MetaData_Elasticsearch); ok {\n\t\treturn x.Elasticsearch\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetHuggingface() *Huggingface {\n\tif x, ok := x.GetData().(*MetaData_Huggingface); ok {\n\t\treturn x.Huggingface\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetSentry() *Sentry {\n\tif x, ok := x.GetData().(*MetaData_Sentry); ok {\n\t\treturn x.Sentry\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetStdin() *Stdin {\n\tif x, ok := x.GetData().(*MetaData_Stdin); ok {\n\t\treturn x.Stdin\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetSlackContinuous() *SlackContinuous {\n\tif x, ok := x.GetData().(*MetaData_SlackContinuous); ok {\n\t\treturn x.SlackContinuous\n\t}\n\treturn nil\n}\n\nfunc (x *MetaData) GetJsonEnumerator() *JSONEnumerator {\n\tif x, ok := x.GetData().(*MetaData_JsonEnumerator); ok {\n\t\treturn x.JsonEnumerator\n\t}\n\treturn nil\n}\n\ntype isMetaData_Data interface {\n\tisMetaData_Data()\n}\n\ntype MetaData_Azure struct {\n\tAzure *Azure `protobuf:\"bytes,1,opt,name=azure,proto3,oneof\"`\n}\n\ntype MetaData_Bitbucket struct {\n\tBitbucket *Bitbucket `protobuf:\"bytes,2,opt,name=bitbucket,proto3,oneof\"`\n}\n\ntype MetaData_Circleci struct {\n\tCircleci *CircleCI `protobuf:\"bytes,3,opt,name=circleci,proto3,oneof\"`\n}\n\ntype MetaData_Confluence struct {\n\tConfluence *Confluence `protobuf:\"bytes,4,opt,name=confluence,proto3,oneof\"`\n}\n\ntype MetaData_Docker struct {\n\tDocker *Docker `protobuf:\"bytes,5,opt,name=docker,proto3,oneof\"`\n}\n\ntype MetaData_Ecr struct {\n\tEcr *ECR `protobuf:\"bytes,6,opt,name=ecr,proto3,oneof\"`\n}\n\ntype MetaData_Gcs struct {\n\tGcs *GCS `protobuf:\"bytes,7,opt,name=gcs,proto3,oneof\"`\n}\n\ntype MetaData_Github struct {\n\tGithub *Github `protobuf:\"bytes,8,opt,name=github,proto3,oneof\"`\n}\n\ntype MetaData_Gitlab struct {\n\tGitlab *Gitlab `protobuf:\"bytes,9,opt,name=gitlab,proto3,oneof\"`\n}\n\ntype MetaData_Jira struct {\n\tJira *Jira `protobuf:\"bytes,10,opt,name=jira,proto3,oneof\"`\n}\n\ntype MetaData_Npm struct {\n\tNpm *NPM `protobuf:\"bytes,11,opt,name=npm,proto3,oneof\"`\n}\n\ntype MetaData_Pypi struct {\n\tPypi *PyPi `protobuf:\"bytes,12,opt,name=pypi,proto3,oneof\"`\n}\n\ntype MetaData_S3 struct {\n\tS3 *S3 `protobuf:\"bytes,13,opt,name=s3,proto3,oneof\"`\n}\n\ntype MetaData_Slack struct {\n\tSlack *Slack `protobuf:\"bytes,14,opt,name=slack,proto3,oneof\"`\n}\n\ntype MetaData_Filesystem struct {\n\tFilesystem *Filesystem `protobuf:\"bytes,15,opt,name=filesystem,proto3,oneof\"`\n}\n\ntype MetaData_Git struct {\n\tGit *Git `protobuf:\"bytes,16,opt,name=git,proto3,oneof\"`\n}\n\ntype MetaData_Test struct {\n\tTest *Test `protobuf:\"bytes,17,opt,name=test,proto3,oneof\"`\n}\n\ntype MetaData_Buildkite struct {\n\tBuildkite *Buildkite `protobuf:\"bytes,18,opt,name=buildkite,proto3,oneof\"`\n}\n\ntype MetaData_Gerrit struct {\n\tGerrit *Gerrit `protobuf:\"bytes,19,opt,name=gerrit,proto3,oneof\"`\n}\n\ntype MetaData_Jenkins struct {\n\tJenkins *Jenkins `protobuf:\"bytes,20,opt,name=jenkins,proto3,oneof\"`\n}\n\ntype MetaData_Teams struct {\n\tTeams *Teams `protobuf:\"bytes,21,opt,name=teams,proto3,oneof\"`\n}\n\ntype MetaData_Artifactory struct {\n\tArtifactory *Artifactory `protobuf:\"bytes,22,opt,name=artifactory,proto3,oneof\"`\n}\n\ntype MetaData_Syslog struct {\n\tSyslog *Syslog `protobuf:\"bytes,23,opt,name=syslog,proto3,oneof\"`\n}\n\ntype MetaData_Forager struct {\n\tForager *Forager `protobuf:\"bytes,24,opt,name=forager,proto3,oneof\"`\n}\n\ntype MetaData_Sharepoint struct {\n\tSharepoint *SharePoint `protobuf:\"bytes,25,opt,name=sharepoint,proto3,oneof\"`\n}\n\ntype MetaData_GoogleDrive struct {\n\tGoogleDrive *GoogleDrive `protobuf:\"bytes,26,opt,name=googleDrive,proto3,oneof\"`\n}\n\ntype MetaData_AzureRepos struct {\n\tAzureRepos *AzureRepos `protobuf:\"bytes,27,opt,name=azureRepos,proto3,oneof\"`\n}\n\ntype MetaData_TravisCI struct {\n\tTravisCI *TravisCI `protobuf:\"bytes,28,opt,name=travisCI,proto3,oneof\"`\n}\n\ntype MetaData_Postman struct {\n\tPostman *Postman `protobuf:\"bytes,29,opt,name=postman,proto3,oneof\"`\n}\n\ntype MetaData_Webhook struct {\n\tWebhook *Webhook `protobuf:\"bytes,30,opt,name=webhook,proto3,oneof\"`\n}\n\ntype MetaData_Elasticsearch struct {\n\tElasticsearch *Elasticsearch `protobuf:\"bytes,31,opt,name=elasticsearch,proto3,oneof\"`\n}\n\ntype MetaData_Huggingface struct {\n\tHuggingface *Huggingface `protobuf:\"bytes,32,opt,name=huggingface,proto3,oneof\"`\n}\n\ntype MetaData_Sentry struct {\n\tSentry *Sentry `protobuf:\"bytes,33,opt,name=sentry,proto3,oneof\"`\n}\n\ntype MetaData_Stdin struct {\n\tStdin *Stdin `protobuf:\"bytes,34,opt,name=stdin,proto3,oneof\"`\n}\n\ntype MetaData_SlackContinuous struct {\n\tSlackContinuous *SlackContinuous `protobuf:\"bytes,35,opt,name=slackContinuous,proto3,oneof\"`\n}\n\ntype MetaData_JsonEnumerator struct {\n\tJsonEnumerator *JSONEnumerator `protobuf:\"bytes,36,opt,name=jsonEnumerator,proto3,oneof\"`\n}\n\nfunc (*MetaData_Azure) isMetaData_Data() {}\n\nfunc (*MetaData_Bitbucket) isMetaData_Data() {}\n\nfunc (*MetaData_Circleci) isMetaData_Data() {}\n\nfunc (*MetaData_Confluence) isMetaData_Data() {}\n\nfunc (*MetaData_Docker) isMetaData_Data() {}\n\nfunc (*MetaData_Ecr) isMetaData_Data() {}\n\nfunc (*MetaData_Gcs) isMetaData_Data() {}\n\nfunc (*MetaData_Github) isMetaData_Data() {}\n\nfunc (*MetaData_Gitlab) isMetaData_Data() {}\n\nfunc (*MetaData_Jira) isMetaData_Data() {}\n\nfunc (*MetaData_Npm) isMetaData_Data() {}\n\nfunc (*MetaData_Pypi) isMetaData_Data() {}\n\nfunc (*MetaData_S3) isMetaData_Data() {}\n\nfunc (*MetaData_Slack) isMetaData_Data() {}\n\nfunc (*MetaData_Filesystem) isMetaData_Data() {}\n\nfunc (*MetaData_Git) isMetaData_Data() {}\n\nfunc (*MetaData_Test) isMetaData_Data() {}\n\nfunc (*MetaData_Buildkite) isMetaData_Data() {}\n\nfunc (*MetaData_Gerrit) isMetaData_Data() {}\n\nfunc (*MetaData_Jenkins) isMetaData_Data() {}\n\nfunc (*MetaData_Teams) isMetaData_Data() {}\n\nfunc (*MetaData_Artifactory) isMetaData_Data() {}\n\nfunc (*MetaData_Syslog) isMetaData_Data() {}\n\nfunc (*MetaData_Forager) isMetaData_Data() {}\n\nfunc (*MetaData_Sharepoint) isMetaData_Data() {}\n\nfunc (*MetaData_GoogleDrive) isMetaData_Data() {}\n\nfunc (*MetaData_AzureRepos) isMetaData_Data() {}\n\nfunc (*MetaData_TravisCI) isMetaData_Data() {}\n\nfunc (*MetaData_Postman) isMetaData_Data() {}\n\nfunc (*MetaData_Webhook) isMetaData_Data() {}\n\nfunc (*MetaData_Elasticsearch) isMetaData_Data() {}\n\nfunc (*MetaData_Huggingface) isMetaData_Data() {}\n\nfunc (*MetaData_Sentry) isMetaData_Data() {}\n\nfunc (*MetaData_Stdin) isMetaData_Data() {}\n\nfunc (*MetaData_SlackContinuous) isMetaData_Data() {}\n\nfunc (*MetaData_JsonEnumerator) isMetaData_Data() {}\n\nvar File_source_metadata_proto protoreflect.FileDescriptor\n\nvar file_source_metadata_proto_rawDesc = []byte{\n\t0x0a, 0x15, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,\n\t0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f,\n\t0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,\n\t0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,\n\t0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x7f, 0x0a, 0x05, 0x41, 0x7a, 0x75,\n\t0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72,\n\t0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,\n\t0x66, 0x69, 0x6c, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64,\n\t0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64,\n\t0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x86, 0x02, 0x0a, 0x09, 0x42,\n\t0x69, 0x74, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x1e, 0x0a, 0x0a,\n\t0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1c, 0x0a, 0x09,\n\t0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x09, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6e,\n\t0x69, 0x70, 0x70, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,\n\t0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74,\n\t0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12,\n\t0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c,\n\t0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x12, 0x0a,\n\t0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e,\n\t0x6b, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x09,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12,\n\t0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x6c,\n\t0x69, 0x6e, 0x65, 0x22, 0xa4, 0x01, 0x0a, 0x09, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x6b, 0x69, 0x74,\n\t0x65, 0x12, 0x10, 0x0a, 0x03, 0x6f, 0x72, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,\n\t0x6f, 0x72, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12,\n\t0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c,\n\t0x69, 0x6e, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x04, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x75, 0x69,\n\t0x6c, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52,\n\t0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09,\n\t0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xcd, 0x01, 0x0a, 0x08, 0x43,\n\t0x69, 0x72, 0x63, 0x6c, 0x65, 0x43, 0x49, 0x12, 0x19, 0x0a, 0x08, 0x76, 0x63, 0x73, 0x5f, 0x74,\n\t0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x63, 0x73, 0x54, 0x79,\n\t0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e,\n\t0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x21,\n\t0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04,\n\t0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65,\n\t0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x18,\n\t0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x65, 0x70,\n\t0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0xb4, 0x01, 0x0a, 0x08, 0x54,\n\t0x72, 0x61, 0x76, 0x69, 0x73, 0x43, 0x49, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e,\n\t0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e,\n\t0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,\n\t0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,\n\t0x6f, 0x72, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6e, 0x75, 0x6d,\n\t0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64,\n\t0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x6a, 0x6f, 0x62, 0x5f, 0x6e, 0x75,\n\t0x6d, 0x62, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6a, 0x6f, 0x62, 0x4e,\n\t0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x06, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62,\n\t0x6c, 0x69, 0x63, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69,\n\t0x63, 0x22, 0xfb, 0x01, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x75, 0x65, 0x6e, 0x63, 0x65,\n\t0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,\n\t0x70, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65,\n\t0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72,\n\t0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69,\n\t0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1c,\n\t0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1a, 0x0a, 0x08,\n\t0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,\n\t0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65,\n\t0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04,\n\t0x75, 0x73, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72,\n\t0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0a,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22,\n\t0x5a, 0x0a, 0x06, 0x44, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c,\n\t0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x14, 0x0a,\n\t0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6d,\n\t0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67,\n\t0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x22, 0xa3, 0x01, 0x0a, 0x03,\n\t0x45, 0x43, 0x52, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x14, 0x0a,\n\t0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6d,\n\t0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x18,\n\t0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x12,\n\t0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18,\n\t0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x65,\n\t0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69,\n\t0x6c, 0x22, 0x5e, 0x0a, 0x0a, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12,\n\t0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66,\n\t0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c,\n\t0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x12, 0x0a,\n\t0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x6c, 0x69, 0x6e,\n\t0x65, 0x22, 0xcd, 0x01, 0x0a, 0x03, 0x47, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6d,\n\t0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69,\n\t0x74, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x03,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x72,\n\t0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x74,\n\t0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,\n\t0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e,\n\t0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x32, 0x0a,\n\t0x15, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x61,\n\t0x6c, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x72, 0x65,\n\t0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x61, 0x74,\n\t0x68, 0x22, 0xbd, 0x02, 0x0a, 0x06, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x12, 0x12, 0x0a, 0x04,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b,\n\t0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a,\n\t0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06,\n\t0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f,\n\t0x6d, 0x6d, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69,\n\t0x6c, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x1c,\n\t0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x12, 0x0a, 0x04,\n\t0x6c, 0x69, 0x6e, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x65,\n\t0x12, 0x3b, 0x0a, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x09,\n\t0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65,\n\t0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74,\n\t0x79, 0x52, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x32, 0x0a,\n\t0x15, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x61,\n\t0x6c, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x72, 0x65,\n\t0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x61, 0x74,\n\t0x68, 0x22, 0xcb, 0x02, 0x0a, 0x06, 0x47, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x12, 0x16, 0x0a, 0x06,\n\t0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f,\n\t0x6d, 0x6d, 0x69, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b,\n\t0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x14, 0x0a, 0x05,\n\t0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61,\n\t0x69, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79,\n\t0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,\n\t0x72, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18,\n\t0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,\n\t0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04,\n\t0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f,\n\t0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63,\n\t0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e,\n\t0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x6a, 0x65,\n\t0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63,\n\t0x74, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70,\n\t0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x32, 0x0a, 0x15, 0x72,\n\t0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f,\n\t0x70, 0x61, 0x74, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x72, 0x65, 0x70, 0x6f,\n\t0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x22,\n\t0xd8, 0x01, 0x0a, 0x03, 0x47, 0x43, 0x53, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x63, 0x6b, 0x65,\n\t0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12,\n\t0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c,\n\t0x69, 0x6e, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12,\n\t0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,\n\t0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,\n\t0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74,\n\t0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f,\n\t0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65,\n\t0x64, 0x41, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x63, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28,\n\t0x09, 0x52, 0x04, 0x61, 0x63, 0x6c, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65,\n\t0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63,\n\t0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xb3, 0x02, 0x0a, 0x0b, 0x48,\n\t0x75, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x66, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69,\n\t0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x1a,\n\t0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65,\n\t0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,\n\t0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f,\n\t0x6d, 0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d,\n\t0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65,\n\t0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x1c, 0x0a, 0x09,\n\t0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69,\n\t0x6e, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x3b,\n\t0x0a, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01,\n\t0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61,\n\t0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52,\n\t0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x72,\n\t0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0a, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65,\n\t0x22, 0x98, 0x01, 0x0a, 0x04, 0x4a, 0x69, 0x72, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x73, 0x73,\n\t0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x73, 0x73, 0x75, 0x65, 0x12,\n\t0x16, 0x0a, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18,\n\t0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x1a, 0x0a, 0x08, 0x6c,\n\t0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c,\n\t0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c,\n\t0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1c, 0x0a,\n\t0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x77, 0x0a, 0x03, 0x4e,\n\t0x50, 0x4d, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65,\n\t0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x07, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69,\n\t0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x14,\n\t0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65,\n\t0x6d, 0x61, 0x69, 0x6c, 0x22, 0x78, 0x0a, 0x04, 0x50, 0x79, 0x50, 0x69, 0x12, 0x12, 0x0a, 0x04,\n\t0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65,\n\t0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x07, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65,\n\t0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x6c,\n\t0x65, 0x61, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69,\n\t0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x78,\n\t0x0a, 0x02, 0x53, 0x33, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04,\n\t0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65,\n\t0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69,\n\t0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74,\n\t0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x97, 0x02, 0x0a, 0x05, 0x53, 0x6c, 0x61,\n\t0x63, 0x6b, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49,\n\t0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x6e, 0x61, 0x6d,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,\n\t0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,\n\t0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,\n\t0x6d, 0x70, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6c,\n\t0x69, 0x6e, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12,\n\t0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66,\n\t0x69, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x3b, 0x0a, 0x0a, 0x76, 0x69, 0x73,\n\t0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e,\n\t0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e,\n\t0x56, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x0a, 0x76, 0x69, 0x73, 0x69,\n\t0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x22, 0x96, 0x01, 0x0a, 0x06, 0x47, 0x65, 0x72, 0x72, 0x69, 0x74, 0x12, 0x16, 0x0a,\n\t0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63,\n\t0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61,\n\t0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12,\n\t0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d,\n\t0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x69,\n\t0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18,\n\t0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x22, 0x1a, 0x0a, 0x04, 0x54,\n\t0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x81, 0x01, 0x0a, 0x07, 0x4a, 0x65, 0x6e, 0x6b,\n\t0x69, 0x6e, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e,\n\t0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x6a, 0x65,\n\t0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f,\n\t0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x75,\n\t0x69, 0x6c, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e,\n\t0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x1c, 0x0a,\n\t0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x90, 0x02, 0x0a, 0x05,\n\t0x54, 0x65, 0x61, 0x6d, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c,\n\t0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x6e,\n\t0x65, 0x6c, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f,\n\t0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e,\n\t0x6e, 0x65, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73,\n\t0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65,\n\t0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64,\n\t0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12,\n\t0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69,\n\t0x6e, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18,\n\t0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08,\n\t0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,\n\t0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x61, 0x6d,\n\t0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x61,\n\t0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64,\n\t0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x22, 0x99,\n\t0x01, 0x0a, 0x0b, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x12,\n\t0x0a, 0x04, 0x72, 0x65, 0x70, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x65,\n\t0x70, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x03,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69,\n\t0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74,\n\t0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72,\n\t0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72,\n\t0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x06, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0xa8, 0x01, 0x0a, 0x06, 0x53,\n\t0x79, 0x73, 0x6c, 0x6f, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d,\n\t0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d,\n\t0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x70, 0x70, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70,\n\t0x72, 0x6f, 0x63, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x72, 0x6f,\n\t0x63, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,\n\t0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,\n\t0x70, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x61, 0x63,\n\t0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x61, 0x63,\n\t0x69, 0x6c, 0x69, 0x74, 0x79, 0x22, 0x9f, 0x01, 0x0a, 0x07, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65,\n\t0x72, 0x12, 0x31, 0x0a, 0x06, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x17, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64,\n\t0x61, 0x74, 0x61, 0x2e, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x48, 0x00, 0x52, 0x06, 0x67, 0x69,\n\t0x74, 0x68, 0x75, 0x62, 0x12, 0x28, 0x0a, 0x03, 0x6e, 0x70, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x14, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64,\n\t0x61, 0x74, 0x61, 0x2e, 0x4e, 0x50, 0x4d, 0x48, 0x00, 0x52, 0x03, 0x6e, 0x70, 0x6d, 0x12, 0x2b,\n\t0x0a, 0x04, 0x70, 0x79, 0x70, 0x69, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x73,\n\t0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x50,\n\t0x79, 0x50, 0x69, 0x48, 0x00, 0x52, 0x04, 0x70, 0x79, 0x70, 0x69, 0x42, 0x0a, 0x0a, 0x08, 0x6d,\n\t0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xae, 0x01, 0x0a, 0x0a, 0x53, 0x68, 0x61, 0x72,\n\t0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69,\n\t0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74,\n\t0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x75, 0x74, 0x68,\n\t0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72,\n\t0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x69, 0x65, 0x77, 0x73, 0x18,\n\t0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x69, 0x65, 0x77, 0x73, 0x12, 0x14, 0x0a, 0x05,\n\t0x64, 0x6f, 0x63, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x64, 0x6f, 0x63,\n\t0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0xbf, 0x01, 0x0a, 0x0b, 0x47, 0x6f, 0x6f,\n\t0x67, 0x6c, 0x65, 0x44, 0x72, 0x69, 0x76, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b,\n\t0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,\n\t0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73,\n\t0x74, 0x61, 0x6d, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x18, 0x05,\n\t0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x12, 0x28, 0x0a, 0x10,\n\t0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x62, 0x79,\n\t0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x69,\n\t0x66, 0x69, 0x65, 0x64, 0x42, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x07,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0xcb, 0x02, 0x0a, 0x0a, 0x41,\n\t0x7a, 0x75, 0x72, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e,\n\t0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x1a, 0x0a,\n\t0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x70,\n\t0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72,\n\t0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6d,\n\t0x6d, 0x69, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69,\n\t0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18,\n\t0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74,\n\t0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,\n\t0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e,\n\t0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x3b, 0x0a,\n\t0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28,\n\t0x0e, 0x32, 0x1b, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64,\n\t0x61, 0x74, 0x61, 0x2e, 0x56, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x0a,\n\t0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x72,\n\t0x6f, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x72, 0x6f,\n\t0x6a, 0x65, 0x63, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61,\n\t0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x72, 0x67, 0x61,\n\t0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xf4, 0x04, 0x0a, 0x07, 0x50, 0x6f, 0x73,\n\t0x74, 0x6d, 0x61, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b,\n\t0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x75, 0x75, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x55, 0x75, 0x69, 0x64, 0x12,\n\t0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d,\n\t0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61,\n\t0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63,\n\t0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x63,\n\t0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,\n\t0x4e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d,\n\t0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e,\n\t0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x65,\n\t0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18,\n\t0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65,\n\t0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,\n\t0x74, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75,\n\t0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,\n\t0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x71,\n\t0x75, 0x65, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x6f, 0x6c, 0x64,\n\t0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x6f, 0x6c,\n\t0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x5f,\n\t0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x6f, 0x6c, 0x64,\n\t0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f,\n\t0x74, 0x79, 0x70, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c,\n\t0x64, 0x54, 0x79, 0x70, 0x65, 0x12, 0x49, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f,\n\t0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x73,\n\t0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x50,\n\t0x6f, 0x73, 0x74, 0x6d, 0x61, 0x6e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79,\n\t0x70, 0x65, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65,\n\t0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x69, 0x64, 0x18,\n\t0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x49,\n\t0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x6e, 0x61,\n\t0x6d, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e,\n\t0x73, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x0e,\n\t0x10, 0x0f, 0x4a, 0x04, 0x08, 0x0f, 0x10, 0x10, 0x52, 0x0a, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c,\n\t0x73, 0x5f, 0x69, 0x64, 0x52, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65,\n\t0x52, 0x0d, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22,\n\t0xa5, 0x01, 0x0a, 0x06, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69,\n\t0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,\n\t0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,\n\t0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73,\n\t0x74, 0x61, 0x6d, 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74,\n\t0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63,\n\t0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6c, 0x6f, 0x63,\n\t0x61, 0x74, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x63, 0x61,\n\t0x74, 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22, 0x44, 0x0a, 0x07, 0x57, 0x65, 0x62, 0x68, 0x6f,\n\t0x6f, 0x6b, 0x12, 0x31, 0x0a, 0x06, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61,\n\t0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x06, 0x76,\n\t0x65, 0x63, 0x74, 0x6f, 0x72, 0x42, 0x06, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x64, 0x0a,\n\t0x0d, 0x45, 0x6c, 0x61, 0x73, 0x74, 0x69, 0x63, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x14,\n\t0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69,\n\t0x6e, 0x64, 0x65, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74,\n\t0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x6f, 0x63, 0x75, 0x6d,\n\t0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,\n\t0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,\n\t0x61, 0x6d, 0x70, 0x22, 0xc9, 0x02, 0x0a, 0x06, 0x53, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x19,\n\t0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x6f, 0x72, 0x67,\n\t0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x0e, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e,\n\t0x49, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x5f, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6f,\n\t0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x6c, 0x75, 0x67, 0x12,\n\t0x3a, 0x0a, 0x19, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f,\n\t0x64, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x17, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e,\n\t0x44, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70,\n\t0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x09, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72,\n\t0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x0b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x6c, 0x75, 0x67, 0x12, 0x19, 0x0a,\n\t0x08, 0x69, 0x73, 0x73, 0x75, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x07, 0x69, 0x73, 0x73, 0x75, 0x65, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x61, 0x74, 0x65,\n\t0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,\n\t0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6c,\n\t0x69, 0x6e, 0x6b, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x22,\n\t0x07, 0x0a, 0x05, 0x53, 0x74, 0x64, 0x69, 0x6e, 0x22, 0xc4, 0x02, 0x0a, 0x0f, 0x53, 0x6c, 0x61,\n\t0x63, 0x6b, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a,\n\t0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63,\n\t0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x0b, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c,\n\t0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x17, 0x0a, 0x07,\n\t0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75,\n\t0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x05, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c,\n\t0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x14, 0x0a,\n\t0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d,\n\t0x61, 0x69, 0x6c, 0x12, 0x3b, 0x0a, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74,\n\t0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,\n\t0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x56, 0x69, 0x73, 0x69, 0x62, 0x69,\n\t0x6c, 0x69, 0x74, 0x79, 0x52, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79,\n\t0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c,\n\t0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x22,\n\t0x2c, 0x0a, 0x0e, 0x4a, 0x53, 0x4f, 0x4e, 0x45, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x74, 0x6f,\n\t0x72, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xbf, 0x0f,\n\t0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x44, 0x61, 0x74, 0x61, 0x12, 0x2e, 0x0a, 0x05, 0x61, 0x7a,\n\t0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x73, 0x6f, 0x75, 0x72,\n\t0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x41, 0x7a, 0x75, 0x72,\n\t0x65, 0x48, 0x00, 0x52, 0x05, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x12, 0x3a, 0x0a, 0x09, 0x62, 0x69,\n\t0x74, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,\n\t0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e,\n\t0x42, 0x69, 0x74, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x48, 0x00, 0x52, 0x09, 0x62, 0x69, 0x74,\n\t0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x37, 0x0a, 0x08, 0x63, 0x69, 0x72, 0x63, 0x6c, 0x65,\n\t0x63, 0x69, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63,\n\t0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x43, 0x69, 0x72, 0x63, 0x6c,\n\t0x65, 0x43, 0x49, 0x48, 0x00, 0x52, 0x08, 0x63, 0x69, 0x72, 0x63, 0x6c, 0x65, 0x63, 0x69, 0x12,\n\t0x3d, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74,\n\t0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x75, 0x65, 0x6e, 0x63, 0x65,\n\t0x48, 0x00, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x31,\n\t0x0a, 0x06, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17,\n\t0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,\n\t0x2e, 0x44, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x48, 0x00, 0x52, 0x06, 0x64, 0x6f, 0x63, 0x6b, 0x65,\n\t0x72, 0x12, 0x28, 0x0a, 0x03, 0x65, 0x63, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14,\n\t0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,\n\t0x2e, 0x45, 0x43, 0x52, 0x48, 0x00, 0x52, 0x03, 0x65, 0x63, 0x72, 0x12, 0x28, 0x0a, 0x03, 0x67,\n\t0x63, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63,\n\t0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x43, 0x53, 0x48, 0x00,\n\t0x52, 0x03, 0x67, 0x63, 0x73, 0x12, 0x31, 0x0a, 0x06, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x18,\n\t0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d,\n\t0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x48, 0x00,\n\t0x52, 0x06, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x12, 0x31, 0x0a, 0x06, 0x67, 0x69, 0x74, 0x6c,\n\t0x61, 0x62, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63,\n\t0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x69, 0x74, 0x6c, 0x61,\n\t0x62, 0x48, 0x00, 0x52, 0x06, 0x67, 0x69, 0x74, 0x6c, 0x61, 0x62, 0x12, 0x2b, 0x0a, 0x04, 0x6a,\n\t0x69, 0x72, 0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x73, 0x6f, 0x75, 0x72,\n\t0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x4a, 0x69, 0x72, 0x61,\n\t0x48, 0x00, 0x52, 0x04, 0x6a, 0x69, 0x72, 0x61, 0x12, 0x28, 0x0a, 0x03, 0x6e, 0x70, 0x6d, 0x18,\n\t0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d,\n\t0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x4e, 0x50, 0x4d, 0x48, 0x00, 0x52, 0x03, 0x6e,\n\t0x70, 0x6d, 0x12, 0x2b, 0x0a, 0x04, 0x70, 0x79, 0x70, 0x69, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x15, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,\n\t0x74, 0x61, 0x2e, 0x50, 0x79, 0x50, 0x69, 0x48, 0x00, 0x52, 0x04, 0x70, 0x79, 0x70, 0x69, 0x12,\n\t0x25, 0x0a, 0x02, 0x73, 0x33, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x6f,\n\t0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, 0x33,\n\t0x48, 0x00, 0x52, 0x02, 0x73, 0x33, 0x12, 0x2e, 0x0a, 0x05, 0x73, 0x6c, 0x61, 0x63, 0x6b, 0x18,\n\t0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d,\n\t0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x48, 0x00, 0x52,\n\t0x05, 0x73, 0x6c, 0x61, 0x63, 0x6b, 0x12, 0x3d, 0x0a, 0x0a, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79,\n\t0x73, 0x74, 0x65, 0x6d, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x6f, 0x75,\n\t0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x46, 0x69, 0x6c,\n\t0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x48, 0x00, 0x52, 0x0a, 0x66, 0x69, 0x6c, 0x65, 0x73,\n\t0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x28, 0x0a, 0x03, 0x67, 0x69, 0x74, 0x18, 0x10, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61,\n\t0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x69, 0x74, 0x48, 0x00, 0x52, 0x03, 0x67, 0x69, 0x74, 0x12,\n\t0x2b, 0x0a, 0x04, 0x74, 0x65, 0x73, 0x74, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e,\n\t0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e,\n\t0x54, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x74, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x09,\n\t0x62, 0x75, 0x69, 0x6c, 0x64, 0x6b, 0x69, 0x74, 0x65, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x1a, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,\n\t0x61, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x6b, 0x69, 0x74, 0x65, 0x48, 0x00, 0x52, 0x09, 0x62,\n\t0x75, 0x69, 0x6c, 0x64, 0x6b, 0x69, 0x74, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x67, 0x65, 0x72, 0x72,\n\t0x69, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63,\n\t0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x47, 0x65, 0x72, 0x72, 0x69,\n\t0x74, 0x48, 0x00, 0x52, 0x06, 0x67, 0x65, 0x72, 0x72, 0x69, 0x74, 0x12, 0x34, 0x0a, 0x07, 0x6a,\n\t0x65, 0x6e, 0x6b, 0x69, 0x6e, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73,\n\t0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x4a,\n\t0x65, 0x6e, 0x6b, 0x69, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x07, 0x6a, 0x65, 0x6e, 0x6b, 0x69, 0x6e,\n\t0x73, 0x12, 0x2e, 0x0a, 0x05, 0x74, 0x65, 0x61, 0x6d, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x16, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,\n\t0x74, 0x61, 0x2e, 0x54, 0x65, 0x61, 0x6d, 0x73, 0x48, 0x00, 0x52, 0x05, 0x74, 0x65, 0x61, 0x6d,\n\t0x73, 0x12, 0x40, 0x0a, 0x0b, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x79,\n\t0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f,\n\t0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63,\n\t0x74, 0x6f, 0x72, 0x79, 0x48, 0x00, 0x52, 0x0b, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74,\n\t0x6f, 0x72, 0x79, 0x12, 0x31, 0x0a, 0x06, 0x73, 0x79, 0x73, 0x6c, 0x6f, 0x67, 0x18, 0x17, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74,\n\t0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, 0x79, 0x73, 0x6c, 0x6f, 0x67, 0x48, 0x00, 0x52, 0x06,\n\t0x73, 0x79, 0x73, 0x6c, 0x6f, 0x67, 0x12, 0x34, 0x0a, 0x07, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65,\n\t0x72, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,\n\t0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65,\n\t0x72, 0x48, 0x00, 0x52, 0x07, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x0a,\n\t0x73, 0x68, 0x61, 0x72, 0x65, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x1b, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,\n\t0x74, 0x61, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52,\n\t0x0a, 0x73, 0x68, 0x61, 0x72, 0x65, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x40, 0x0a, 0x0b, 0x67,\n\t0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x44, 0x72, 0x69, 0x76, 0x65, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x1c, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,\n\t0x74, 0x61, 0x2e, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x44, 0x72, 0x69, 0x76, 0x65, 0x48, 0x00,\n\t0x52, 0x0b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x44, 0x72, 0x69, 0x76, 0x65, 0x12, 0x3d, 0x0a,\n\t0x0a, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x18, 0x1b, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64,\n\t0x61, 0x74, 0x61, 0x2e, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x48, 0x00,\n\t0x52, 0x0a, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x12, 0x37, 0x0a, 0x08,\n\t0x74, 0x72, 0x61, 0x76, 0x69, 0x73, 0x43, 0x49, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19,\n\t0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,\n\t0x2e, 0x54, 0x72, 0x61, 0x76, 0x69, 0x73, 0x43, 0x49, 0x48, 0x00, 0x52, 0x08, 0x74, 0x72, 0x61,\n\t0x76, 0x69, 0x73, 0x43, 0x49, 0x12, 0x34, 0x0a, 0x07, 0x70, 0x6f, 0x73, 0x74, 0x6d, 0x61, 0x6e,\n\t0x18, 0x1d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f,\n\t0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x6d, 0x61, 0x6e,\n\t0x48, 0x00, 0x52, 0x07, 0x70, 0x6f, 0x73, 0x74, 0x6d, 0x61, 0x6e, 0x12, 0x34, 0x0a, 0x07, 0x77,\n\t0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73,\n\t0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x57,\n\t0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x48, 0x00, 0x52, 0x07, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f,\n\t0x6b, 0x12, 0x46, 0x0a, 0x0d, 0x65, 0x6c, 0x61, 0x73, 0x74, 0x69, 0x63, 0x73, 0x65, 0x61, 0x72,\n\t0x63, 0x68, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63,\n\t0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x45, 0x6c, 0x61, 0x73, 0x74,\n\t0x69, 0x63, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x48, 0x00, 0x52, 0x0d, 0x65, 0x6c, 0x61, 0x73,\n\t0x74, 0x69, 0x63, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x40, 0x0a, 0x0b, 0x68, 0x75, 0x67,\n\t0x67, 0x69, 0x6e, 0x67, 0x66, 0x61, 0x63, 0x65, 0x18, 0x20, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c,\n\t0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,\n\t0x2e, 0x48, 0x75, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x66, 0x61, 0x63, 0x65, 0x48, 0x00, 0x52, 0x0b,\n\t0x68, 0x75, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x66, 0x61, 0x63, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x73,\n\t0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x21, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x6f,\n\t0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, 0x65,\n\t0x6e, 0x74, 0x72, 0x79, 0x48, 0x00, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x2e,\n\t0x0a, 0x05, 0x73, 0x74, 0x64, 0x69, 0x6e, 0x18, 0x22, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e,\n\t0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e,\n\t0x53, 0x74, 0x64, 0x69, 0x6e, 0x48, 0x00, 0x52, 0x05, 0x73, 0x74, 0x64, 0x69, 0x6e, 0x12, 0x4c,\n\t0x0a, 0x0f, 0x73, 0x6c, 0x61, 0x63, 0x6b, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75,\n\t0x73, 0x18, 0x23, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,\n\t0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x43,\n\t0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x48, 0x00, 0x52, 0x0f, 0x73, 0x6c, 0x61,\n\t0x63, 0x6b, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x12, 0x49, 0x0a, 0x0e,\n\t0x6a, 0x73, 0x6f, 0x6e, 0x45, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x24,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x65,\n\t0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x4a, 0x53, 0x4f, 0x4e, 0x45, 0x6e, 0x75, 0x6d, 0x65,\n\t0x72, 0x61, 0x74, 0x6f, 0x72, 0x48, 0x00, 0x52, 0x0e, 0x6a, 0x73, 0x6f, 0x6e, 0x45, 0x6e, 0x75,\n\t0x6d, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x42, 0x06, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x2a,\n\t0x3e, 0x0a, 0x0a, 0x56, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x0a, 0x0a,\n\t0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x70, 0x72, 0x69,\n\t0x76, 0x61, 0x74, 0x65, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64,\n\t0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x75, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x03, 0x2a,\n\t0xc2, 0x03, 0x0a, 0x13, 0x50, 0x6f, 0x73, 0x74, 0x6d, 0x61, 0x6e, 0x4c, 0x6f, 0x63, 0x61, 0x74,\n\t0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f,\n\t0x57, 0x4e, 0x5f, 0x50, 0x4f, 0x53, 0x54, 0x4d, 0x41, 0x4e, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17,\n\t0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x59, 0x5f, 0x50, 0x41,\n\t0x52, 0x41, 0x4d, 0x45, 0x54, 0x45, 0x52, 0x10, 0x01, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x45, 0x51,\n\t0x55, 0x45, 0x53, 0x54, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x5a, 0x41, 0x54, 0x49,\n\t0x4f, 0x4e, 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f,\n\t0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x10, 0x03, 0x12, 0x1a, 0x0a, 0x16, 0x52, 0x45, 0x51, 0x55,\n\t0x45, 0x53, 0x54, 0x5f, 0x42, 0x4f, 0x44, 0x59, 0x5f, 0x46, 0x4f, 0x52, 0x4d, 0x5f, 0x44, 0x41,\n\t0x54, 0x41, 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f,\n\t0x42, 0x4f, 0x44, 0x59, 0x5f, 0x52, 0x41, 0x57, 0x10, 0x05, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45,\n\t0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x42, 0x4f, 0x44, 0x59, 0x5f, 0x55, 0x52, 0x4c, 0x5f, 0x45,\n\t0x4e, 0x43, 0x4f, 0x44, 0x45, 0x44, 0x10, 0x06, 0x12, 0x18, 0x0a, 0x14, 0x52, 0x45, 0x51, 0x55,\n\t0x45, 0x53, 0x54, 0x5f, 0x42, 0x4f, 0x44, 0x59, 0x5f, 0x47, 0x52, 0x41, 0x50, 0x48, 0x51, 0x4c,\n\t0x10, 0x07, 0x12, 0x12, 0x0a, 0x0e, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x5f, 0x53, 0x43,\n\t0x52, 0x49, 0x50, 0x54, 0x10, 0x08, 0x12, 0x0f, 0x0a, 0x0b, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53,\n\t0x54, 0x5f, 0x55, 0x52, 0x4c, 0x10, 0x09, 0x12, 0x18, 0x0a, 0x14, 0x45, 0x4e, 0x56, 0x49, 0x52,\n\t0x4f, 0x4e, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x56, 0x41, 0x52, 0x49, 0x41, 0x42, 0x4c, 0x45, 0x10,\n\t0x0a, 0x12, 0x18, 0x0a, 0x14, 0x46, 0x4f, 0x4c, 0x44, 0x45, 0x52, 0x5f, 0x41, 0x55, 0x54, 0x48,\n\t0x4f, 0x52, 0x49, 0x5a, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x0b, 0x12, 0x11, 0x0a, 0x0d, 0x46,\n\t0x4f, 0x4c, 0x44, 0x45, 0x52, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x10, 0x0c, 0x12, 0x15,\n\t0x0a, 0x11, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x43, 0x52,\n\t0x49, 0x50, 0x54, 0x10, 0x0d, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54,\n\t0x49, 0x4f, 0x4e, 0x5f, 0x56, 0x41, 0x52, 0x49, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x0e, 0x12, 0x1c,\n\t0x0a, 0x18, 0x43, 0x4f, 0x4c, 0x4c, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x55, 0x54,\n\t0x48, 0x4f, 0x52, 0x49, 0x5a, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x0f, 0x12, 0x11, 0x0a, 0x0d,\n\t0x52, 0x45, 0x53, 0x50, 0x4f, 0x4e, 0x53, 0x45, 0x5f, 0x42, 0x4f, 0x44, 0x59, 0x10, 0x10, 0x12,\n\t0x13, 0x0a, 0x0f, 0x52, 0x45, 0x53, 0x50, 0x4f, 0x4e, 0x53, 0x45, 0x5f, 0x48, 0x45, 0x41, 0x44,\n\t0x45, 0x52, 0x10, 0x11, 0x42, 0x43, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,\n\t0x6f, 0x6d, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69,\n\t0x74, 0x79, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x68, 0x6f, 0x67, 0x2f, 0x76, 0x33,\n\t0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62, 0x2f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d,\n\t0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x33,\n}\n\nvar (\n\tfile_source_metadata_proto_rawDescOnce sync.Once\n\tfile_source_metadata_proto_rawDescData = file_source_metadata_proto_rawDesc\n)\n\nfunc file_source_metadata_proto_rawDescGZIP() []byte {\n\tfile_source_metadata_proto_rawDescOnce.Do(func() {\n\t\tfile_source_metadata_proto_rawDescData = protoimpl.X.CompressGZIP(file_source_metadata_proto_rawDescData)\n\t})\n\treturn file_source_metadata_proto_rawDescData\n}\n\nvar file_source_metadata_proto_enumTypes = make([]protoimpl.EnumInfo, 2)\nvar file_source_metadata_proto_msgTypes = make([]protoimpl.MessageInfo, 38)\nvar file_source_metadata_proto_goTypes = []interface{}{\n\t(Visibility)(0),               // 0: source_metadata.Visibility\n\t(PostmanLocationType)(0),      // 1: source_metadata.PostmanLocationType\n\t(*Azure)(nil),                 // 2: source_metadata.Azure\n\t(*Bitbucket)(nil),             // 3: source_metadata.Bitbucket\n\t(*Buildkite)(nil),             // 4: source_metadata.Buildkite\n\t(*CircleCI)(nil),              // 5: source_metadata.CircleCI\n\t(*TravisCI)(nil),              // 6: source_metadata.TravisCI\n\t(*Confluence)(nil),            // 7: source_metadata.Confluence\n\t(*Docker)(nil),                // 8: source_metadata.Docker\n\t(*ECR)(nil),                   // 9: source_metadata.ECR\n\t(*Filesystem)(nil),            // 10: source_metadata.Filesystem\n\t(*Git)(nil),                   // 11: source_metadata.Git\n\t(*Github)(nil),                // 12: source_metadata.Github\n\t(*Gitlab)(nil),                // 13: source_metadata.Gitlab\n\t(*GCS)(nil),                   // 14: source_metadata.GCS\n\t(*Huggingface)(nil),           // 15: source_metadata.Huggingface\n\t(*Jira)(nil),                  // 16: source_metadata.Jira\n\t(*NPM)(nil),                   // 17: source_metadata.NPM\n\t(*PyPi)(nil),                  // 18: source_metadata.PyPi\n\t(*S3)(nil),                    // 19: source_metadata.S3\n\t(*Slack)(nil),                 // 20: source_metadata.Slack\n\t(*Gerrit)(nil),                // 21: source_metadata.Gerrit\n\t(*Test)(nil),                  // 22: source_metadata.Test\n\t(*Jenkins)(nil),               // 23: source_metadata.Jenkins\n\t(*Teams)(nil),                 // 24: source_metadata.Teams\n\t(*Artifactory)(nil),           // 25: source_metadata.Artifactory\n\t(*Syslog)(nil),                // 26: source_metadata.Syslog\n\t(*Forager)(nil),               // 27: source_metadata.Forager\n\t(*SharePoint)(nil),            // 28: source_metadata.SharePoint\n\t(*GoogleDrive)(nil),           // 29: source_metadata.GoogleDrive\n\t(*AzureRepos)(nil),            // 30: source_metadata.AzureRepos\n\t(*Postman)(nil),               // 31: source_metadata.Postman\n\t(*Vector)(nil),                // 32: source_metadata.Vector\n\t(*Webhook)(nil),               // 33: source_metadata.Webhook\n\t(*Elasticsearch)(nil),         // 34: source_metadata.Elasticsearch\n\t(*Sentry)(nil),                // 35: source_metadata.Sentry\n\t(*Stdin)(nil),                 // 36: source_metadata.Stdin\n\t(*SlackContinuous)(nil),       // 37: source_metadata.SlackContinuous\n\t(*JSONEnumerator)(nil),        // 38: source_metadata.JSONEnumerator\n\t(*MetaData)(nil),              // 39: source_metadata.MetaData\n\t(*timestamppb.Timestamp)(nil), // 40: google.protobuf.Timestamp\n}\nvar file_source_metadata_proto_depIdxs = []int32{\n\t0,  // 0: source_metadata.Github.visibility:type_name -> source_metadata.Visibility\n\t0,  // 1: source_metadata.Huggingface.visibility:type_name -> source_metadata.Visibility\n\t0,  // 2: source_metadata.Slack.visibility:type_name -> source_metadata.Visibility\n\t12, // 3: source_metadata.Forager.github:type_name -> source_metadata.Github\n\t17, // 4: source_metadata.Forager.npm:type_name -> source_metadata.NPM\n\t18, // 5: source_metadata.Forager.pypi:type_name -> source_metadata.PyPi\n\t0,  // 6: source_metadata.AzureRepos.visibility:type_name -> source_metadata.Visibility\n\t1,  // 7: source_metadata.Postman.location_type:type_name -> source_metadata.PostmanLocationType\n\t40, // 8: source_metadata.Vector.timestamp:type_name -> google.protobuf.Timestamp\n\t32, // 9: source_metadata.Webhook.vector:type_name -> source_metadata.Vector\n\t0,  // 10: source_metadata.SlackContinuous.visibility:type_name -> source_metadata.Visibility\n\t2,  // 11: source_metadata.MetaData.azure:type_name -> source_metadata.Azure\n\t3,  // 12: source_metadata.MetaData.bitbucket:type_name -> source_metadata.Bitbucket\n\t5,  // 13: source_metadata.MetaData.circleci:type_name -> source_metadata.CircleCI\n\t7,  // 14: source_metadata.MetaData.confluence:type_name -> source_metadata.Confluence\n\t8,  // 15: source_metadata.MetaData.docker:type_name -> source_metadata.Docker\n\t9,  // 16: source_metadata.MetaData.ecr:type_name -> source_metadata.ECR\n\t14, // 17: source_metadata.MetaData.gcs:type_name -> source_metadata.GCS\n\t12, // 18: source_metadata.MetaData.github:type_name -> source_metadata.Github\n\t13, // 19: source_metadata.MetaData.gitlab:type_name -> source_metadata.Gitlab\n\t16, // 20: source_metadata.MetaData.jira:type_name -> source_metadata.Jira\n\t17, // 21: source_metadata.MetaData.npm:type_name -> source_metadata.NPM\n\t18, // 22: source_metadata.MetaData.pypi:type_name -> source_metadata.PyPi\n\t19, // 23: source_metadata.MetaData.s3:type_name -> source_metadata.S3\n\t20, // 24: source_metadata.MetaData.slack:type_name -> source_metadata.Slack\n\t10, // 25: source_metadata.MetaData.filesystem:type_name -> source_metadata.Filesystem\n\t11, // 26: source_metadata.MetaData.git:type_name -> source_metadata.Git\n\t22, // 27: source_metadata.MetaData.test:type_name -> source_metadata.Test\n\t4,  // 28: source_metadata.MetaData.buildkite:type_name -> source_metadata.Buildkite\n\t21, // 29: source_metadata.MetaData.gerrit:type_name -> source_metadata.Gerrit\n\t23, // 30: source_metadata.MetaData.jenkins:type_name -> source_metadata.Jenkins\n\t24, // 31: source_metadata.MetaData.teams:type_name -> source_metadata.Teams\n\t25, // 32: source_metadata.MetaData.artifactory:type_name -> source_metadata.Artifactory\n\t26, // 33: source_metadata.MetaData.syslog:type_name -> source_metadata.Syslog\n\t27, // 34: source_metadata.MetaData.forager:type_name -> source_metadata.Forager\n\t28, // 35: source_metadata.MetaData.sharepoint:type_name -> source_metadata.SharePoint\n\t29, // 36: source_metadata.MetaData.googleDrive:type_name -> source_metadata.GoogleDrive\n\t30, // 37: source_metadata.MetaData.azureRepos:type_name -> source_metadata.AzureRepos\n\t6,  // 38: source_metadata.MetaData.travisCI:type_name -> source_metadata.TravisCI\n\t31, // 39: source_metadata.MetaData.postman:type_name -> source_metadata.Postman\n\t33, // 40: source_metadata.MetaData.webhook:type_name -> source_metadata.Webhook\n\t34, // 41: source_metadata.MetaData.elasticsearch:type_name -> source_metadata.Elasticsearch\n\t15, // 42: source_metadata.MetaData.huggingface:type_name -> source_metadata.Huggingface\n\t35, // 43: source_metadata.MetaData.sentry:type_name -> source_metadata.Sentry\n\t36, // 44: source_metadata.MetaData.stdin:type_name -> source_metadata.Stdin\n\t37, // 45: source_metadata.MetaData.slackContinuous:type_name -> source_metadata.SlackContinuous\n\t38, // 46: source_metadata.MetaData.jsonEnumerator:type_name -> source_metadata.JSONEnumerator\n\t47, // [47:47] is the sub-list for method output_type\n\t47, // [47:47] is the sub-list for method input_type\n\t47, // [47:47] is the sub-list for extension type_name\n\t47, // [47:47] is the sub-list for extension extendee\n\t0,  // [0:47] is the sub-list for field type_name\n}\n\nfunc init() { file_source_metadata_proto_init() }\nfunc file_source_metadata_proto_init() {\n\tif File_source_metadata_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_source_metadata_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Azure); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Bitbucket); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Buildkite); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CircleCI); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*TravisCI); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Confluence); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Docker); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ECR); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Filesystem); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Git); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Github); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Gitlab); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GCS); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Huggingface); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Jira); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*NPM); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*PyPi); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*S3); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Slack); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Gerrit); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Test); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Jenkins); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Teams); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Artifactory); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Syslog); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Forager); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SharePoint); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GoogleDrive); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*AzureRepos); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Postman); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Vector); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Webhook); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Elasticsearch); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Sentry); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Stdin); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SlackContinuous); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*JSONEnumerator); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_source_metadata_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*MetaData); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\tfile_source_metadata_proto_msgTypes[25].OneofWrappers = []interface{}{\n\t\t(*Forager_Github)(nil),\n\t\t(*Forager_Npm)(nil),\n\t\t(*Forager_Pypi)(nil),\n\t}\n\tfile_source_metadata_proto_msgTypes[31].OneofWrappers = []interface{}{\n\t\t(*Webhook_Vector)(nil),\n\t}\n\tfile_source_metadata_proto_msgTypes[37].OneofWrappers = []interface{}{\n\t\t(*MetaData_Azure)(nil),\n\t\t(*MetaData_Bitbucket)(nil),\n\t\t(*MetaData_Circleci)(nil),\n\t\t(*MetaData_Confluence)(nil),\n\t\t(*MetaData_Docker)(nil),\n\t\t(*MetaData_Ecr)(nil),\n\t\t(*MetaData_Gcs)(nil),\n\t\t(*MetaData_Github)(nil),\n\t\t(*MetaData_Gitlab)(nil),\n\t\t(*MetaData_Jira)(nil),\n\t\t(*MetaData_Npm)(nil),\n\t\t(*MetaData_Pypi)(nil),\n\t\t(*MetaData_S3)(nil),\n\t\t(*MetaData_Slack)(nil),\n\t\t(*MetaData_Filesystem)(nil),\n\t\t(*MetaData_Git)(nil),\n\t\t(*MetaData_Test)(nil),\n\t\t(*MetaData_Buildkite)(nil),\n\t\t(*MetaData_Gerrit)(nil),\n\t\t(*MetaData_Jenkins)(nil),\n\t\t(*MetaData_Teams)(nil),\n\t\t(*MetaData_Artifactory)(nil),\n\t\t(*MetaData_Syslog)(nil),\n\t\t(*MetaData_Forager)(nil),\n\t\t(*MetaData_Sharepoint)(nil),\n\t\t(*MetaData_GoogleDrive)(nil),\n\t\t(*MetaData_AzureRepos)(nil),\n\t\t(*MetaData_TravisCI)(nil),\n\t\t(*MetaData_Postman)(nil),\n\t\t(*MetaData_Webhook)(nil),\n\t\t(*MetaData_Elasticsearch)(nil),\n\t\t(*MetaData_Huggingface)(nil),\n\t\t(*MetaData_Sentry)(nil),\n\t\t(*MetaData_Stdin)(nil),\n\t\t(*MetaData_SlackContinuous)(nil),\n\t\t(*MetaData_JsonEnumerator)(nil),\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_source_metadata_proto_rawDesc,\n\t\t\tNumEnums:      2,\n\t\t\tNumMessages:   38,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_source_metadata_proto_goTypes,\n\t\tDependencyIndexes: file_source_metadata_proto_depIdxs,\n\t\tEnumInfos:         file_source_metadata_proto_enumTypes,\n\t\tMessageInfos:      file_source_metadata_proto_msgTypes,\n\t}.Build()\n\tFile_source_metadata_proto = out.File\n\tfile_source_metadata_proto_rawDesc = nil\n\tfile_source_metadata_proto_goTypes = nil\n\tfile_source_metadata_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pkg/pb/source_metadatapb/source_metadata.pb.validate.go",
    "content": "// Code generated by protoc-gen-validate. DO NOT EDIT.\n// source: source_metadata.proto\n\npackage source_metadatapb\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/mail\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode/utf8\"\n\n\t\"google.golang.org/protobuf/types/known/anypb\"\n)\n\n// ensure the imports are used\nvar (\n\t_ = bytes.MinRead\n\t_ = errors.New(\"\")\n\t_ = fmt.Print\n\t_ = utf8.UTFMax\n\t_ = (*regexp.Regexp)(nil)\n\t_ = (*strings.Reader)(nil)\n\t_ = net.IPv4len\n\t_ = time.Duration(0)\n\t_ = (*url.URL)(nil)\n\t_ = (*mail.Address)(nil)\n\t_ = anypb.Any{}\n\t_ = sort.Sort\n)\n\n// Validate checks the field values on Azure with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Azure) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Azure with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in AzureMultiError, or nil if none found.\nfunc (m *Azure) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Azure) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Container\n\n\t// no validation rules for File\n\n\t// no validation rules for Uploaded\n\n\t// no validation rules for Link\n\n\t// no validation rules for Email\n\n\tif len(errors) > 0 {\n\t\treturn AzureMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// AzureMultiError is an error wrapping multiple validation errors returned by\n// Azure.ValidateAll() if the designated constraints aren't met.\ntype AzureMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m AzureMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m AzureMultiError) AllErrors() []error { return m }\n\n// AzureValidationError is the validation error returned by Azure.Validate if\n// the designated constraints aren't met.\ntype AzureValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e AzureValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e AzureValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e AzureValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e AzureValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e AzureValidationError) ErrorName() string { return \"AzureValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e AzureValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sAzure.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = AzureValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = AzureValidationError{}\n\n// Validate checks the field values on Bitbucket with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Bitbucket) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Bitbucket with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in BitbucketMultiError, or nil\n// if none found.\nfunc (m *Bitbucket) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Bitbucket) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for File\n\n\t// no validation rules for Repository\n\n\t// no validation rules for Workspace\n\n\t// no validation rules for SnippetId\n\n\t// no validation rules for Title\n\n\t// no validation rules for Commit\n\n\t// no validation rules for Email\n\n\t// no validation rules for Link\n\n\t// no validation rules for Timestamp\n\n\t// no validation rules for Line\n\n\tif len(errors) > 0 {\n\t\treturn BitbucketMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// BitbucketMultiError is an error wrapping multiple validation errors returned\n// by Bitbucket.ValidateAll() if the designated constraints aren't met.\ntype BitbucketMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m BitbucketMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m BitbucketMultiError) AllErrors() []error { return m }\n\n// BitbucketValidationError is the validation error returned by\n// Bitbucket.Validate if the designated constraints aren't met.\ntype BitbucketValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e BitbucketValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e BitbucketValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e BitbucketValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e BitbucketValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e BitbucketValidationError) ErrorName() string { return \"BitbucketValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e BitbucketValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sBitbucket.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = BitbucketValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = BitbucketValidationError{}\n\n// Validate checks the field values on Buildkite with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Buildkite) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Buildkite with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in BuildkiteMultiError, or nil\n// if none found.\nfunc (m *Buildkite) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Buildkite) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Org\n\n\t// no validation rules for Pipeline\n\n\t// no validation rules for Link\n\n\t// no validation rules for Email\n\n\t// no validation rules for BuildNumber\n\n\t// no validation rules for Timestamp\n\n\tif len(errors) > 0 {\n\t\treturn BuildkiteMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// BuildkiteMultiError is an error wrapping multiple validation errors returned\n// by Buildkite.ValidateAll() if the designated constraints aren't met.\ntype BuildkiteMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m BuildkiteMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m BuildkiteMultiError) AllErrors() []error { return m }\n\n// BuildkiteValidationError is the validation error returned by\n// Buildkite.Validate if the designated constraints aren't met.\ntype BuildkiteValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e BuildkiteValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e BuildkiteValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e BuildkiteValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e BuildkiteValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e BuildkiteValidationError) ErrorName() string { return \"BuildkiteValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e BuildkiteValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sBuildkite.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = BuildkiteValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = BuildkiteValidationError{}\n\n// Validate checks the field values on CircleCI with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *CircleCI) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on CircleCI with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in CircleCIMultiError, or nil\n// if none found.\nfunc (m *CircleCI) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *CircleCI) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for VcsType\n\n\t// no validation rules for Username\n\n\t// no validation rules for Repository\n\n\t// no validation rules for BuildNumber\n\n\t// no validation rules for BuildStep\n\n\t// no validation rules for Link\n\n\t// no validation rules for Email\n\n\tif len(errors) > 0 {\n\t\treturn CircleCIMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// CircleCIMultiError is an error wrapping multiple validation errors returned\n// by CircleCI.ValidateAll() if the designated constraints aren't met.\ntype CircleCIMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m CircleCIMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m CircleCIMultiError) AllErrors() []error { return m }\n\n// CircleCIValidationError is the validation error returned by\n// CircleCI.Validate if the designated constraints aren't met.\ntype CircleCIValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e CircleCIValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e CircleCIValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e CircleCIValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e CircleCIValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e CircleCIValidationError) ErrorName() string { return \"CircleCIValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e CircleCIValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sCircleCI.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = CircleCIValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = CircleCIValidationError{}\n\n// Validate checks the field values on TravisCI with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *TravisCI) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on TravisCI with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in TravisCIMultiError, or nil\n// if none found.\nfunc (m *TravisCI) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *TravisCI) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Username\n\n\t// no validation rules for Repository\n\n\t// no validation rules for BuildNumber\n\n\t// no validation rules for JobNumber\n\n\t// no validation rules for Link\n\n\t// no validation rules for Public\n\n\tif len(errors) > 0 {\n\t\treturn TravisCIMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// TravisCIMultiError is an error wrapping multiple validation errors returned\n// by TravisCI.ValidateAll() if the designated constraints aren't met.\ntype TravisCIMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m TravisCIMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m TravisCIMultiError) AllErrors() []error { return m }\n\n// TravisCIValidationError is the validation error returned by\n// TravisCI.Validate if the designated constraints aren't met.\ntype TravisCIValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e TravisCIValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e TravisCIValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e TravisCIValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e TravisCIValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e TravisCIValidationError) ErrorName() string { return \"TravisCIValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e TravisCIValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sTravisCI.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = TravisCIValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = TravisCIValidationError{}\n\n// Validate checks the field values on Confluence with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Confluence) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Confluence with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in ConfluenceMultiError, or\n// nil if none found.\nfunc (m *Confluence) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Confluence) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Page\n\n\t// no validation rules for Space\n\n\t// no validation rules for Version\n\n\t// no validation rules for Link\n\n\t// no validation rules for Email\n\n\t// no validation rules for Timestamp\n\n\t// no validation rules for Location\n\n\t// no validation rules for File\n\n\t// no validation rules for User\n\n\t// no validation rules for CommentId\n\n\tif len(errors) > 0 {\n\t\treturn ConfluenceMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// ConfluenceMultiError is an error wrapping multiple validation errors\n// returned by Confluence.ValidateAll() if the designated constraints aren't met.\ntype ConfluenceMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m ConfluenceMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m ConfluenceMultiError) AllErrors() []error { return m }\n\n// ConfluenceValidationError is the validation error returned by\n// Confluence.Validate if the designated constraints aren't met.\ntype ConfluenceValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e ConfluenceValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e ConfluenceValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e ConfluenceValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e ConfluenceValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e ConfluenceValidationError) ErrorName() string { return \"ConfluenceValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e ConfluenceValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sConfluence.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = ConfluenceValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = ConfluenceValidationError{}\n\n// Validate checks the field values on Docker with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Docker) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Docker with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in DockerMultiError, or nil if none found.\nfunc (m *Docker) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Docker) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for File\n\n\t// no validation rules for Image\n\n\t// no validation rules for Layer\n\n\t// no validation rules for Tag\n\n\tif len(errors) > 0 {\n\t\treturn DockerMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// DockerMultiError is an error wrapping multiple validation errors returned by\n// Docker.ValidateAll() if the designated constraints aren't met.\ntype DockerMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m DockerMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m DockerMultiError) AllErrors() []error { return m }\n\n// DockerValidationError is the validation error returned by Docker.Validate if\n// the designated constraints aren't met.\ntype DockerValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e DockerValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e DockerValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e DockerValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e DockerValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e DockerValidationError) ErrorName() string { return \"DockerValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e DockerValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sDocker.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = DockerValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = DockerValidationError{}\n\n// Validate checks the field values on ECR with the rules defined in the proto\n// definition for this message. If any rules are violated, the first error\n// encountered is returned, or nil if there are no violations.\nfunc (m *ECR) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on ECR with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in ECRMultiError, or nil if none found.\nfunc (m *ECR) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *ECR) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for File\n\n\t// no validation rules for Layer\n\n\t// no validation rules for Image\n\n\t// no validation rules for Registry\n\n\t// no validation rules for Region\n\n\t// no validation rules for Link\n\n\t// no validation rules for Email\n\n\tif len(errors) > 0 {\n\t\treturn ECRMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// ECRMultiError is an error wrapping multiple validation errors returned by\n// ECR.ValidateAll() if the designated constraints aren't met.\ntype ECRMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m ECRMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m ECRMultiError) AllErrors() []error { return m }\n\n// ECRValidationError is the validation error returned by ECR.Validate if the\n// designated constraints aren't met.\ntype ECRValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e ECRValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e ECRValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e ECRValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e ECRValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e ECRValidationError) ErrorName() string { return \"ECRValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e ECRValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sECR.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = ECRValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = ECRValidationError{}\n\n// Validate checks the field values on Filesystem with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Filesystem) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Filesystem with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in FilesystemMultiError, or\n// nil if none found.\nfunc (m *Filesystem) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Filesystem) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for File\n\n\t// no validation rules for Link\n\n\t// no validation rules for Email\n\n\t// no validation rules for Line\n\n\tif len(errors) > 0 {\n\t\treturn FilesystemMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// FilesystemMultiError is an error wrapping multiple validation errors\n// returned by Filesystem.ValidateAll() if the designated constraints aren't met.\ntype FilesystemMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m FilesystemMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m FilesystemMultiError) AllErrors() []error { return m }\n\n// FilesystemValidationError is the validation error returned by\n// Filesystem.Validate if the designated constraints aren't met.\ntype FilesystemValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e FilesystemValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e FilesystemValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e FilesystemValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e FilesystemValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e FilesystemValidationError) ErrorName() string { return \"FilesystemValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e FilesystemValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sFilesystem.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = FilesystemValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = FilesystemValidationError{}\n\n// Validate checks the field values on Git with the rules defined in the proto\n// definition for this message. If any rules are violated, the first error\n// encountered is returned, or nil if there are no violations.\nfunc (m *Git) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Git with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in GitMultiError, or nil if none found.\nfunc (m *Git) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Git) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Commit\n\n\t// no validation rules for File\n\n\t// no validation rules for Email\n\n\t// no validation rules for Repository\n\n\t// no validation rules for Timestamp\n\n\t// no validation rules for Line\n\n\t// no validation rules for RepositoryLocalPath\n\n\tif len(errors) > 0 {\n\t\treturn GitMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// GitMultiError is an error wrapping multiple validation errors returned by\n// Git.ValidateAll() if the designated constraints aren't met.\ntype GitMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m GitMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m GitMultiError) AllErrors() []error { return m }\n\n// GitValidationError is the validation error returned by Git.Validate if the\n// designated constraints aren't met.\ntype GitValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e GitValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e GitValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e GitValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e GitValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e GitValidationError) ErrorName() string { return \"GitValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e GitValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sGit.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = GitValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = GitValidationError{}\n\n// Validate checks the field values on Github with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Github) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Github with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in GithubMultiError, or nil if none found.\nfunc (m *Github) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Github) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Link\n\n\t// no validation rules for Username\n\n\t// no validation rules for Repository\n\n\t// no validation rules for Commit\n\n\t// no validation rules for Email\n\n\t// no validation rules for File\n\n\t// no validation rules for Timestamp\n\n\t// no validation rules for Line\n\n\t// no validation rules for Visibility\n\n\t// no validation rules for RepositoryLocalPath\n\n\tif len(errors) > 0 {\n\t\treturn GithubMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// GithubMultiError is an error wrapping multiple validation errors returned by\n// Github.ValidateAll() if the designated constraints aren't met.\ntype GithubMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m GithubMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m GithubMultiError) AllErrors() []error { return m }\n\n// GithubValidationError is the validation error returned by Github.Validate if\n// the designated constraints aren't met.\ntype GithubValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e GithubValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e GithubValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e GithubValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e GithubValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e GithubValidationError) ErrorName() string { return \"GithubValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e GithubValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sGithub.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = GithubValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = GithubValidationError{}\n\n// Validate checks the field values on Gitlab with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Gitlab) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Gitlab with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in GitlabMultiError, or nil if none found.\nfunc (m *Gitlab) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Gitlab) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Commit\n\n\t// no validation rules for File\n\n\t// no validation rules for Link\n\n\t// no validation rules for Email\n\n\t// no validation rules for Repository\n\n\t// no validation rules for Timestamp\n\n\t// no validation rules for Line\n\n\t// no validation rules for ProjectId\n\n\t// no validation rules for ProjectName\n\n\t// no validation rules for ProjectOwner\n\n\t// no validation rules for RepositoryLocalPath\n\n\tif len(errors) > 0 {\n\t\treturn GitlabMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// GitlabMultiError is an error wrapping multiple validation errors returned by\n// Gitlab.ValidateAll() if the designated constraints aren't met.\ntype GitlabMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m GitlabMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m GitlabMultiError) AllErrors() []error { return m }\n\n// GitlabValidationError is the validation error returned by Gitlab.Validate if\n// the designated constraints aren't met.\ntype GitlabValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e GitlabValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e GitlabValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e GitlabValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e GitlabValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e GitlabValidationError) ErrorName() string { return \"GitlabValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e GitlabValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sGitlab.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = GitlabValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = GitlabValidationError{}\n\n// Validate checks the field values on GCS with the rules defined in the proto\n// definition for this message. If any rules are violated, the first error\n// encountered is returned, or nil if there are no violations.\nfunc (m *GCS) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on GCS with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in GCSMultiError, or nil if none found.\nfunc (m *GCS) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *GCS) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Bucket\n\n\t// no validation rules for Filename\n\n\t// no validation rules for Link\n\n\t// no validation rules for Email\n\n\t// no validation rules for CreatedAt\n\n\t// no validation rules for UpdatedAt\n\n\t// no validation rules for ContentType\n\n\tif len(errors) > 0 {\n\t\treturn GCSMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// GCSMultiError is an error wrapping multiple validation errors returned by\n// GCS.ValidateAll() if the designated constraints aren't met.\ntype GCSMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m GCSMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m GCSMultiError) AllErrors() []error { return m }\n\n// GCSValidationError is the validation error returned by GCS.Validate if the\n// designated constraints aren't met.\ntype GCSValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e GCSValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e GCSValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e GCSValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e GCSValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e GCSValidationError) ErrorName() string { return \"GCSValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e GCSValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sGCS.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = GCSValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = GCSValidationError{}\n\n// Validate checks the field values on Huggingface with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Huggingface) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Huggingface with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in HuggingfaceMultiError, or\n// nil if none found.\nfunc (m *Huggingface) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Huggingface) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Link\n\n\t// no validation rules for Username\n\n\t// no validation rules for Repository\n\n\t// no validation rules for Commit\n\n\t// no validation rules for Email\n\n\t// no validation rules for File\n\n\t// no validation rules for Timestamp\n\n\t// no validation rules for Line\n\n\t// no validation rules for Visibility\n\n\t// no validation rules for ResourceType\n\n\tif len(errors) > 0 {\n\t\treturn HuggingfaceMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// HuggingfaceMultiError is an error wrapping multiple validation errors\n// returned by Huggingface.ValidateAll() if the designated constraints aren't met.\ntype HuggingfaceMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m HuggingfaceMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m HuggingfaceMultiError) AllErrors() []error { return m }\n\n// HuggingfaceValidationError is the validation error returned by\n// Huggingface.Validate if the designated constraints aren't met.\ntype HuggingfaceValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e HuggingfaceValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e HuggingfaceValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e HuggingfaceValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e HuggingfaceValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e HuggingfaceValidationError) ErrorName() string { return \"HuggingfaceValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e HuggingfaceValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sHuggingface.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = HuggingfaceValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = HuggingfaceValidationError{}\n\n// Validate checks the field values on Jira with the rules defined in the proto\n// definition for this message. If any rules are violated, the first error\n// encountered is returned, or nil if there are no violations.\nfunc (m *Jira) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Jira with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in JiraMultiError, or nil if none found.\nfunc (m *Jira) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Jira) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Issue\n\n\t// no validation rules for Author\n\n\t// no validation rules for Link\n\n\t// no validation rules for Location\n\n\t// no validation rules for Email\n\n\t// no validation rules for Timestamp\n\n\tif len(errors) > 0 {\n\t\treturn JiraMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// JiraMultiError is an error wrapping multiple validation errors returned by\n// Jira.ValidateAll() if the designated constraints aren't met.\ntype JiraMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m JiraMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m JiraMultiError) AllErrors() []error { return m }\n\n// JiraValidationError is the validation error returned by Jira.Validate if the\n// designated constraints aren't met.\ntype JiraValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e JiraValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e JiraValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e JiraValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e JiraValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e JiraValidationError) ErrorName() string { return \"JiraValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e JiraValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sJira.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = JiraValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = JiraValidationError{}\n\n// Validate checks the field values on NPM with the rules defined in the proto\n// definition for this message. If any rules are violated, the first error\n// encountered is returned, or nil if there are no violations.\nfunc (m *NPM) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on NPM with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in NPMMultiError, or nil if none found.\nfunc (m *NPM) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *NPM) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for File\n\n\t// no validation rules for Package\n\n\t// no validation rules for Release\n\n\t// no validation rules for Link\n\n\t// no validation rules for Email\n\n\tif len(errors) > 0 {\n\t\treturn NPMMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// NPMMultiError is an error wrapping multiple validation errors returned by\n// NPM.ValidateAll() if the designated constraints aren't met.\ntype NPMMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m NPMMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m NPMMultiError) AllErrors() []error { return m }\n\n// NPMValidationError is the validation error returned by NPM.Validate if the\n// designated constraints aren't met.\ntype NPMValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e NPMValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e NPMValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e NPMValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e NPMValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e NPMValidationError) ErrorName() string { return \"NPMValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e NPMValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sNPM.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = NPMValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = NPMValidationError{}\n\n// Validate checks the field values on PyPi with the rules defined in the proto\n// definition for this message. If any rules are violated, the first error\n// encountered is returned, or nil if there are no violations.\nfunc (m *PyPi) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on PyPi with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in PyPiMultiError, or nil if none found.\nfunc (m *PyPi) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *PyPi) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for File\n\n\t// no validation rules for Package\n\n\t// no validation rules for Release\n\n\t// no validation rules for Link\n\n\t// no validation rules for Email\n\n\tif len(errors) > 0 {\n\t\treturn PyPiMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// PyPiMultiError is an error wrapping multiple validation errors returned by\n// PyPi.ValidateAll() if the designated constraints aren't met.\ntype PyPiMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m PyPiMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m PyPiMultiError) AllErrors() []error { return m }\n\n// PyPiValidationError is the validation error returned by PyPi.Validate if the\n// designated constraints aren't met.\ntype PyPiValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e PyPiValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e PyPiValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e PyPiValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e PyPiValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e PyPiValidationError) ErrorName() string { return \"PyPiValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e PyPiValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sPyPi.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = PyPiValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = PyPiValidationError{}\n\n// Validate checks the field values on S3 with the rules defined in the proto\n// definition for this message. If any rules are violated, the first error\n// encountered is returned, or nil if there are no violations.\nfunc (m *S3) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on S3 with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in S3MultiError, or nil if none found.\nfunc (m *S3) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *S3) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Bucket\n\n\t// no validation rules for File\n\n\t// no validation rules for Link\n\n\t// no validation rules for Email\n\n\t// no validation rules for Timestamp\n\n\tif len(errors) > 0 {\n\t\treturn S3MultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// S3MultiError is an error wrapping multiple validation errors returned by\n// S3.ValidateAll() if the designated constraints aren't met.\ntype S3MultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m S3MultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m S3MultiError) AllErrors() []error { return m }\n\n// S3ValidationError is the validation error returned by S3.Validate if the\n// designated constraints aren't met.\ntype S3ValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e S3ValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e S3ValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e S3ValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e S3ValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e S3ValidationError) ErrorName() string { return \"S3ValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e S3ValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sS3.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = S3ValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = S3ValidationError{}\n\n// Validate checks the field values on Slack with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Slack) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Slack with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in SlackMultiError, or nil if none found.\nfunc (m *Slack) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Slack) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for ChannelId\n\n\t// no validation rules for ChannelName\n\n\t// no validation rules for Timestamp\n\n\t// no validation rules for UserId\n\n\t// no validation rules for Link\n\n\t// no validation rules for File\n\n\t// no validation rules for Email\n\n\t// no validation rules for Visibility\n\n\t// no validation rules for Location\n\n\tif len(errors) > 0 {\n\t\treturn SlackMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// SlackMultiError is an error wrapping multiple validation errors returned by\n// Slack.ValidateAll() if the designated constraints aren't met.\ntype SlackMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m SlackMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m SlackMultiError) AllErrors() []error { return m }\n\n// SlackValidationError is the validation error returned by Slack.Validate if\n// the designated constraints aren't met.\ntype SlackValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e SlackValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e SlackValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e SlackValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e SlackValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e SlackValidationError) ErrorName() string { return \"SlackValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e SlackValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sSlack.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = SlackValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = SlackValidationError{}\n\n// Validate checks the field values on Gerrit with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Gerrit) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Gerrit with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in GerritMultiError, or nil if none found.\nfunc (m *Gerrit) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Gerrit) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Commit\n\n\t// no validation rules for File\n\n\t// no validation rules for Email\n\n\t// no validation rules for Project\n\n\t// no validation rules for Timestamp\n\n\t// no validation rules for Line\n\n\tif len(errors) > 0 {\n\t\treturn GerritMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// GerritMultiError is an error wrapping multiple validation errors returned by\n// Gerrit.ValidateAll() if the designated constraints aren't met.\ntype GerritMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m GerritMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m GerritMultiError) AllErrors() []error { return m }\n\n// GerritValidationError is the validation error returned by Gerrit.Validate if\n// the designated constraints aren't met.\ntype GerritValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e GerritValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e GerritValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e GerritValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e GerritValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e GerritValidationError) ErrorName() string { return \"GerritValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e GerritValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sGerrit.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = GerritValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = GerritValidationError{}\n\n// Validate checks the field values on Test with the rules defined in the proto\n// definition for this message. If any rules are violated, the first error\n// encountered is returned, or nil if there are no violations.\nfunc (m *Test) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Test with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in TestMultiError, or nil if none found.\nfunc (m *Test) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Test) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for File\n\n\tif len(errors) > 0 {\n\t\treturn TestMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// TestMultiError is an error wrapping multiple validation errors returned by\n// Test.ValidateAll() if the designated constraints aren't met.\ntype TestMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m TestMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m TestMultiError) AllErrors() []error { return m }\n\n// TestValidationError is the validation error returned by Test.Validate if the\n// designated constraints aren't met.\ntype TestValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e TestValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e TestValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e TestValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e TestValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e TestValidationError) ErrorName() string { return \"TestValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e TestValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sTest.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = TestValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = TestValidationError{}\n\n// Validate checks the field values on Jenkins with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Jenkins) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Jenkins with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in JenkinsMultiError, or nil if none found.\nfunc (m *Jenkins) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Jenkins) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for ProjectName\n\n\t// no validation rules for BuildNumber\n\n\t// no validation rules for Link\n\n\t// no validation rules for Timestamp\n\n\tif len(errors) > 0 {\n\t\treturn JenkinsMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// JenkinsMultiError is an error wrapping multiple validation errors returned\n// by Jenkins.ValidateAll() if the designated constraints aren't met.\ntype JenkinsMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m JenkinsMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m JenkinsMultiError) AllErrors() []error { return m }\n\n// JenkinsValidationError is the validation error returned by Jenkins.Validate\n// if the designated constraints aren't met.\ntype JenkinsValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e JenkinsValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e JenkinsValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e JenkinsValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e JenkinsValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e JenkinsValidationError) ErrorName() string { return \"JenkinsValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e JenkinsValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sJenkins.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = JenkinsValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = JenkinsValidationError{}\n\n// Validate checks the field values on Teams with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Teams) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Teams with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in TeamsMultiError, or nil if none found.\nfunc (m *Teams) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Teams) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for ChannelId\n\n\t// no validation rules for ChannelName\n\n\t// no validation rules for Timestamp\n\n\t// no validation rules for UserId\n\n\t// no validation rules for Link\n\n\t// no validation rules for File\n\n\t// no validation rules for Email\n\n\t// no validation rules for Location\n\n\t// no validation rules for TeamName\n\n\t// no validation rules for TeamId\n\n\tif len(errors) > 0 {\n\t\treturn TeamsMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// TeamsMultiError is an error wrapping multiple validation errors returned by\n// Teams.ValidateAll() if the designated constraints aren't met.\ntype TeamsMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m TeamsMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m TeamsMultiError) AllErrors() []error { return m }\n\n// TeamsValidationError is the validation error returned by Teams.Validate if\n// the designated constraints aren't met.\ntype TeamsValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e TeamsValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e TeamsValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e TeamsValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e TeamsValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e TeamsValidationError) ErrorName() string { return \"TeamsValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e TeamsValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sTeams.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = TeamsValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = TeamsValidationError{}\n\n// Validate checks the field values on Artifactory with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Artifactory) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Artifactory with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in ArtifactoryMultiError, or\n// nil if none found.\nfunc (m *Artifactory) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Artifactory) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Repo\n\n\t// no validation rules for Path\n\n\t// no validation rules for Link\n\n\t// no validation rules for Timestamp\n\n\t// no validation rules for Username\n\n\t// no validation rules for Email\n\n\tif len(errors) > 0 {\n\t\treturn ArtifactoryMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// ArtifactoryMultiError is an error wrapping multiple validation errors\n// returned by Artifactory.ValidateAll() if the designated constraints aren't met.\ntype ArtifactoryMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m ArtifactoryMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m ArtifactoryMultiError) AllErrors() []error { return m }\n\n// ArtifactoryValidationError is the validation error returned by\n// Artifactory.Validate if the designated constraints aren't met.\ntype ArtifactoryValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e ArtifactoryValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e ArtifactoryValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e ArtifactoryValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e ArtifactoryValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e ArtifactoryValidationError) ErrorName() string { return \"ArtifactoryValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e ArtifactoryValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sArtifactory.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = ArtifactoryValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = ArtifactoryValidationError{}\n\n// Validate checks the field values on Syslog with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Syslog) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Syslog with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in SyslogMultiError, or nil if none found.\nfunc (m *Syslog) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Syslog) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Hostname\n\n\t// no validation rules for Appname\n\n\t// no validation rules for Procid\n\n\t// no validation rules for Timestamp\n\n\t// no validation rules for Client\n\n\t// no validation rules for Facility\n\n\tif len(errors) > 0 {\n\t\treturn SyslogMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// SyslogMultiError is an error wrapping multiple validation errors returned by\n// Syslog.ValidateAll() if the designated constraints aren't met.\ntype SyslogMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m SyslogMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m SyslogMultiError) AllErrors() []error { return m }\n\n// SyslogValidationError is the validation error returned by Syslog.Validate if\n// the designated constraints aren't met.\ntype SyslogValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e SyslogValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e SyslogValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e SyslogValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e SyslogValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e SyslogValidationError) ErrorName() string { return \"SyslogValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e SyslogValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sSyslog.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = SyslogValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = SyslogValidationError{}\n\n// Validate checks the field values on Forager with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Forager) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Forager with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in ForagerMultiError, or nil if none found.\nfunc (m *Forager) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Forager) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tswitch v := m.Metadata.(type) {\n\tcase *Forager_Github:\n\t\tif v == nil {\n\t\t\terr := ForagerValidationError{\n\t\t\t\tfield:  \"Metadata\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetGithub()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, ForagerValidationError{\n\t\t\t\t\t\tfield:  \"Github\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, ForagerValidationError{\n\t\t\t\t\t\tfield:  \"Github\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetGithub()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn ForagerValidationError{\n\t\t\t\t\tfield:  \"Github\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *Forager_Npm:\n\t\tif v == nil {\n\t\t\terr := ForagerValidationError{\n\t\t\t\tfield:  \"Metadata\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetNpm()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, ForagerValidationError{\n\t\t\t\t\t\tfield:  \"Npm\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, ForagerValidationError{\n\t\t\t\t\t\tfield:  \"Npm\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetNpm()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn ForagerValidationError{\n\t\t\t\t\tfield:  \"Npm\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *Forager_Pypi:\n\t\tif v == nil {\n\t\t\terr := ForagerValidationError{\n\t\t\t\tfield:  \"Metadata\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetPypi()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, ForagerValidationError{\n\t\t\t\t\t\tfield:  \"Pypi\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, ForagerValidationError{\n\t\t\t\t\t\tfield:  \"Pypi\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetPypi()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn ForagerValidationError{\n\t\t\t\t\tfield:  \"Pypi\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn ForagerMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// ForagerMultiError is an error wrapping multiple validation errors returned\n// by Forager.ValidateAll() if the designated constraints aren't met.\ntype ForagerMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m ForagerMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m ForagerMultiError) AllErrors() []error { return m }\n\n// ForagerValidationError is the validation error returned by Forager.Validate\n// if the designated constraints aren't met.\ntype ForagerValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e ForagerValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e ForagerValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e ForagerValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e ForagerValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e ForagerValidationError) ErrorName() string { return \"ForagerValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e ForagerValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sForager.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = ForagerValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = ForagerValidationError{}\n\n// Validate checks the field values on SharePoint with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *SharePoint) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on SharePoint with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in SharePointMultiError, or\n// nil if none found.\nfunc (m *SharePoint) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *SharePoint) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Link\n\n\t// no validation rules for Timestamp\n\n\t// no validation rules for Author\n\n\t// no validation rules for Title\n\n\t// no validation rules for Views\n\n\t// no validation rules for Docid\n\n\t// no validation rules for Email\n\n\tif len(errors) > 0 {\n\t\treturn SharePointMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// SharePointMultiError is an error wrapping multiple validation errors\n// returned by SharePoint.ValidateAll() if the designated constraints aren't met.\ntype SharePointMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m SharePointMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m SharePointMultiError) AllErrors() []error { return m }\n\n// SharePointValidationError is the validation error returned by\n// SharePoint.Validate if the designated constraints aren't met.\ntype SharePointValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e SharePointValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e SharePointValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e SharePointValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e SharePointValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e SharePointValidationError) ErrorName() string { return \"SharePointValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e SharePointValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sSharePoint.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = SharePointValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = SharePointValidationError{}\n\n// Validate checks the field values on GoogleDrive with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *GoogleDrive) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on GoogleDrive with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in GoogleDriveMultiError, or\n// nil if none found.\nfunc (m *GoogleDrive) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *GoogleDrive) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for File\n\n\t// no validation rules for Link\n\n\t// no validation rules for Email\n\n\t// no validation rules for Timestamp\n\n\t// no validation rules for Shared\n\n\t// no validation rules for LastModifiedBy\n\n\t// no validation rules for Path\n\n\tif len(errors) > 0 {\n\t\treturn GoogleDriveMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// GoogleDriveMultiError is an error wrapping multiple validation errors\n// returned by GoogleDrive.ValidateAll() if the designated constraints aren't met.\ntype GoogleDriveMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m GoogleDriveMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m GoogleDriveMultiError) AllErrors() []error { return m }\n\n// GoogleDriveValidationError is the validation error returned by\n// GoogleDrive.Validate if the designated constraints aren't met.\ntype GoogleDriveValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e GoogleDriveValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e GoogleDriveValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e GoogleDriveValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e GoogleDriveValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e GoogleDriveValidationError) ErrorName() string { return \"GoogleDriveValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e GoogleDriveValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sGoogleDrive.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = GoogleDriveValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = GoogleDriveValidationError{}\n\n// Validate checks the field values on AzureRepos with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *AzureRepos) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on AzureRepos with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in AzureReposMultiError, or\n// nil if none found.\nfunc (m *AzureRepos) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *AzureRepos) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Link\n\n\t// no validation rules for Username\n\n\t// no validation rules for Repository\n\n\t// no validation rules for Commit\n\n\t// no validation rules for Email\n\n\t// no validation rules for File\n\n\t// no validation rules for Timestamp\n\n\t// no validation rules for Line\n\n\t// no validation rules for Visibility\n\n\t// no validation rules for Project\n\n\t// no validation rules for Organization\n\n\tif len(errors) > 0 {\n\t\treturn AzureReposMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// AzureReposMultiError is an error wrapping multiple validation errors\n// returned by AzureRepos.ValidateAll() if the designated constraints aren't met.\ntype AzureReposMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m AzureReposMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m AzureReposMultiError) AllErrors() []error { return m }\n\n// AzureReposValidationError is the validation error returned by\n// AzureRepos.Validate if the designated constraints aren't met.\ntype AzureReposValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e AzureReposValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e AzureReposValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e AzureReposValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e AzureReposValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e AzureReposValidationError) ErrorName() string { return \"AzureReposValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e AzureReposValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sAzureRepos.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = AzureReposValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = AzureReposValidationError{}\n\n// Validate checks the field values on Postman with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Postman) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Postman with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in PostmanMultiError, or nil if none found.\nfunc (m *Postman) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Postman) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Link\n\n\t// no validation rules for WorkspaceUuid\n\n\t// no validation rules for WorkspaceName\n\n\t// no validation rules for CollectionId\n\n\t// no validation rules for CollectionName\n\n\t// no validation rules for EnvironmentId\n\n\t// no validation rules for EnvironmentName\n\n\t// no validation rules for RequestId\n\n\t// no validation rules for RequestName\n\n\t// no validation rules for FolderId\n\n\t// no validation rules for FolderName\n\n\t// no validation rules for FieldType\n\n\t// no validation rules for LocationType\n\n\t// no validation rules for ResponseId\n\n\t// no validation rules for ResponseName\n\n\tif len(errors) > 0 {\n\t\treturn PostmanMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// PostmanMultiError is an error wrapping multiple validation errors returned\n// by Postman.ValidateAll() if the designated constraints aren't met.\ntype PostmanMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m PostmanMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m PostmanMultiError) AllErrors() []error { return m }\n\n// PostmanValidationError is the validation error returned by Postman.Validate\n// if the designated constraints aren't met.\ntype PostmanValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e PostmanValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e PostmanValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e PostmanValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e PostmanValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e PostmanValidationError) ErrorName() string { return \"PostmanValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e PostmanValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sPostman.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = PostmanValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = PostmanValidationError{}\n\n// Validate checks the field values on Vector with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Vector) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Vector with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in VectorMultiError, or nil if none found.\nfunc (m *Vector) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Vector) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif all {\n\t\tswitch v := interface{}(m.GetTimestamp()).(type) {\n\t\tcase interface{ ValidateAll() error }:\n\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\terrors = append(errors, VectorValidationError{\n\t\t\t\t\tfield:  \"Timestamp\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t})\n\t\t\t}\n\t\tcase interface{ Validate() error }:\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\terrors = append(errors, VectorValidationError{\n\t\t\t\t\tfield:  \"Timestamp\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t} else if v, ok := interface{}(m.GetTimestamp()).(interface{ Validate() error }); ok {\n\t\tif err := v.Validate(); err != nil {\n\t\t\treturn VectorValidationError{\n\t\t\t\tfield:  \"Timestamp\",\n\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\tcause:  err,\n\t\t\t}\n\t\t}\n\t}\n\n\t// no validation rules for SourceType\n\n\t// no validation rules for Host\n\n\t// no validation rules for Locator\n\n\t// no validation rules for Link\n\n\tif len(errors) > 0 {\n\t\treturn VectorMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// VectorMultiError is an error wrapping multiple validation errors returned by\n// Vector.ValidateAll() if the designated constraints aren't met.\ntype VectorMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m VectorMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m VectorMultiError) AllErrors() []error { return m }\n\n// VectorValidationError is the validation error returned by Vector.Validate if\n// the designated constraints aren't met.\ntype VectorValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e VectorValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e VectorValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e VectorValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e VectorValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e VectorValidationError) ErrorName() string { return \"VectorValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e VectorValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sVector.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = VectorValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = VectorValidationError{}\n\n// Validate checks the field values on Webhook with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Webhook) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Webhook with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in WebhookMultiError, or nil if none found.\nfunc (m *Webhook) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Webhook) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tswitch v := m.Data.(type) {\n\tcase *Webhook_Vector:\n\t\tif v == nil {\n\t\t\terr := WebhookValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetVector()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, WebhookValidationError{\n\t\t\t\t\t\tfield:  \"Vector\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, WebhookValidationError{\n\t\t\t\t\t\tfield:  \"Vector\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetVector()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn WebhookValidationError{\n\t\t\t\t\tfield:  \"Vector\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn WebhookMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// WebhookMultiError is an error wrapping multiple validation errors returned\n// by Webhook.ValidateAll() if the designated constraints aren't met.\ntype WebhookMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m WebhookMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m WebhookMultiError) AllErrors() []error { return m }\n\n// WebhookValidationError is the validation error returned by Webhook.Validate\n// if the designated constraints aren't met.\ntype WebhookValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e WebhookValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e WebhookValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e WebhookValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e WebhookValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e WebhookValidationError) ErrorName() string { return \"WebhookValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e WebhookValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sWebhook.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = WebhookValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = WebhookValidationError{}\n\n// Validate checks the field values on Elasticsearch with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Elasticsearch) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Elasticsearch with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in ElasticsearchMultiError, or\n// nil if none found.\nfunc (m *Elasticsearch) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Elasticsearch) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Index\n\n\t// no validation rules for DocumentId\n\n\t// no validation rules for Timestamp\n\n\tif len(errors) > 0 {\n\t\treturn ElasticsearchMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// ElasticsearchMultiError is an error wrapping multiple validation errors\n// returned by Elasticsearch.ValidateAll() if the designated constraints\n// aren't met.\ntype ElasticsearchMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m ElasticsearchMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m ElasticsearchMultiError) AllErrors() []error { return m }\n\n// ElasticsearchValidationError is the validation error returned by\n// Elasticsearch.Validate if the designated constraints aren't met.\ntype ElasticsearchValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e ElasticsearchValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e ElasticsearchValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e ElasticsearchValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e ElasticsearchValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e ElasticsearchValidationError) ErrorName() string { return \"ElasticsearchValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e ElasticsearchValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sElasticsearch.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = ElasticsearchValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = ElasticsearchValidationError{}\n\n// Validate checks the field values on Sentry with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Sentry) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Sentry with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in SentryMultiError, or nil if none found.\nfunc (m *Sentry) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Sentry) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for EventId\n\n\t// no validation rules for OrganizationId\n\n\t// no validation rules for OrganizationSlug\n\n\t// no validation rules for OrganizationDateCreated\n\n\t// no validation rules for ProjectId\n\n\t// no validation rules for ProjectSlug\n\n\t// no validation rules for IssueId\n\n\t// no validation rules for DateCreated\n\n\t// no validation rules for Link\n\n\tif len(errors) > 0 {\n\t\treturn SentryMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// SentryMultiError is an error wrapping multiple validation errors returned by\n// Sentry.ValidateAll() if the designated constraints aren't met.\ntype SentryMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m SentryMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m SentryMultiError) AllErrors() []error { return m }\n\n// SentryValidationError is the validation error returned by Sentry.Validate if\n// the designated constraints aren't met.\ntype SentryValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e SentryValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e SentryValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e SentryValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e SentryValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e SentryValidationError) ErrorName() string { return \"SentryValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e SentryValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sSentry.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = SentryValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = SentryValidationError{}\n\n// Validate checks the field values on Stdin with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Stdin) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Stdin with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in StdinMultiError, or nil if none found.\nfunc (m *Stdin) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Stdin) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif len(errors) > 0 {\n\t\treturn StdinMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// StdinMultiError is an error wrapping multiple validation errors returned by\n// Stdin.ValidateAll() if the designated constraints aren't met.\ntype StdinMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m StdinMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m StdinMultiError) AllErrors() []error { return m }\n\n// StdinValidationError is the validation error returned by Stdin.Validate if\n// the designated constraints aren't met.\ntype StdinValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e StdinValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e StdinValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e StdinValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e StdinValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e StdinValidationError) ErrorName() string { return \"StdinValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e StdinValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sStdin.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = StdinValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = StdinValidationError{}\n\n// Validate checks the field values on SlackContinuous with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// first error encountered is returned, or nil if there are no violations.\nfunc (m *SlackContinuous) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on SlackContinuous with the rules\n// defined in the proto definition for this message. If any rules are\n// violated, the result is a list of violation errors wrapped in\n// SlackContinuousMultiError, or nil if none found.\nfunc (m *SlackContinuous) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *SlackContinuous) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for ChannelId\n\n\t// no validation rules for ChannelName\n\n\t// no validation rules for Timestamp\n\n\t// no validation rules for UserId\n\n\t// no validation rules for Link\n\n\t// no validation rules for File\n\n\t// no validation rules for Email\n\n\t// no validation rules for Visibility\n\n\t// no validation rules for Location\n\n\t// no validation rules for WorkspaceId\n\n\tif len(errors) > 0 {\n\t\treturn SlackContinuousMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// SlackContinuousMultiError is an error wrapping multiple validation errors\n// returned by SlackContinuous.ValidateAll() if the designated constraints\n// aren't met.\ntype SlackContinuousMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m SlackContinuousMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m SlackContinuousMultiError) AllErrors() []error { return m }\n\n// SlackContinuousValidationError is the validation error returned by\n// SlackContinuous.Validate if the designated constraints aren't met.\ntype SlackContinuousValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e SlackContinuousValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e SlackContinuousValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e SlackContinuousValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e SlackContinuousValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e SlackContinuousValidationError) ErrorName() string { return \"SlackContinuousValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e SlackContinuousValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sSlackContinuous.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = SlackContinuousValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = SlackContinuousValidationError{}\n\n// Validate checks the field values on JSONEnumerator with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *JSONEnumerator) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on JSONEnumerator with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in JSONEnumeratorMultiError,\n// or nil if none found.\nfunc (m *JSONEnumerator) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *JSONEnumerator) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Metadata\n\n\tif len(errors) > 0 {\n\t\treturn JSONEnumeratorMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// JSONEnumeratorMultiError is an error wrapping multiple validation errors\n// returned by JSONEnumerator.ValidateAll() if the designated constraints\n// aren't met.\ntype JSONEnumeratorMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m JSONEnumeratorMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m JSONEnumeratorMultiError) AllErrors() []error { return m }\n\n// JSONEnumeratorValidationError is the validation error returned by\n// JSONEnumerator.Validate if the designated constraints aren't met.\ntype JSONEnumeratorValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e JSONEnumeratorValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e JSONEnumeratorValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e JSONEnumeratorValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e JSONEnumeratorValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e JSONEnumeratorValidationError) ErrorName() string { return \"JSONEnumeratorValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e JSONEnumeratorValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sJSONEnumerator.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = JSONEnumeratorValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = JSONEnumeratorValidationError{}\n\n// Validate checks the field values on MetaData with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *MetaData) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on MetaData with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in MetaDataMultiError, or nil\n// if none found.\nfunc (m *MetaData) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *MetaData) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tswitch v := m.Data.(type) {\n\tcase *MetaData_Azure:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetAzure()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Azure\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Azure\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetAzure()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Azure\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Bitbucket:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetBitbucket()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Bitbucket\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Bitbucket\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetBitbucket()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Bitbucket\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Circleci:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetCircleci()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Circleci\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Circleci\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetCircleci()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Circleci\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Confluence:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetConfluence()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Confluence\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Confluence\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetConfluence()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Confluence\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Docker:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetDocker()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Docker\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Docker\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetDocker()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Docker\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Ecr:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetEcr()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Ecr\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Ecr\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetEcr()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Ecr\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Gcs:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetGcs()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Gcs\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Gcs\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetGcs()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Gcs\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Github:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetGithub()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Github\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Github\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetGithub()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Github\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Gitlab:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetGitlab()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Gitlab\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Gitlab\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetGitlab()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Gitlab\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Jira:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetJira()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Jira\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Jira\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetJira()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Jira\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Npm:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetNpm()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Npm\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Npm\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetNpm()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Npm\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Pypi:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetPypi()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Pypi\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Pypi\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetPypi()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Pypi\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_S3:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetS3()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"S3\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"S3\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetS3()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"S3\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Slack:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetSlack()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Slack\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Slack\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetSlack()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Slack\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Filesystem:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetFilesystem()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Filesystem\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Filesystem\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetFilesystem()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Filesystem\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Git:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetGit()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Git\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Git\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetGit()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Git\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Test:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetTest()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Test\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Test\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetTest()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Test\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Buildkite:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetBuildkite()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Buildkite\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Buildkite\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetBuildkite()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Buildkite\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Gerrit:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetGerrit()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Gerrit\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Gerrit\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetGerrit()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Gerrit\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Jenkins:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetJenkins()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Jenkins\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Jenkins\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetJenkins()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Jenkins\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Teams:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetTeams()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Teams\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Teams\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetTeams()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Teams\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Artifactory:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetArtifactory()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Artifactory\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Artifactory\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetArtifactory()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Artifactory\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Syslog:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetSyslog()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Syslog\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Syslog\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetSyslog()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Syslog\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Forager:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetForager()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Forager\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Forager\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetForager()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Forager\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Sharepoint:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetSharepoint()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Sharepoint\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Sharepoint\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetSharepoint()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Sharepoint\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_GoogleDrive:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetGoogleDrive()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"GoogleDrive\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"GoogleDrive\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetGoogleDrive()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"GoogleDrive\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_AzureRepos:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetAzureRepos()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"AzureRepos\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"AzureRepos\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetAzureRepos()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"AzureRepos\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_TravisCI:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetTravisCI()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"TravisCI\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"TravisCI\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetTravisCI()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"TravisCI\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Postman:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetPostman()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Postman\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Postman\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetPostman()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Postman\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Webhook:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetWebhook()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Webhook\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Webhook\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetWebhook()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Webhook\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Elasticsearch:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetElasticsearch()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Elasticsearch\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Elasticsearch\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetElasticsearch()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Elasticsearch\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Huggingface:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetHuggingface()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Huggingface\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Huggingface\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetHuggingface()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Huggingface\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Sentry:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetSentry()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Sentry\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Sentry\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetSentry()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Sentry\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_Stdin:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetStdin()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Stdin\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"Stdin\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetStdin()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"Stdin\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_SlackContinuous:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetSlackContinuous()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"SlackContinuous\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"SlackContinuous\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetSlackContinuous()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"SlackContinuous\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *MetaData_JsonEnumerator:\n\t\tif v == nil {\n\t\t\terr := MetaDataValidationError{\n\t\t\t\tfield:  \"Data\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetJsonEnumerator()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"JsonEnumerator\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, MetaDataValidationError{\n\t\t\t\t\t\tfield:  \"JsonEnumerator\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetJsonEnumerator()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn MetaDataValidationError{\n\t\t\t\t\tfield:  \"JsonEnumerator\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn MetaDataMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// MetaDataMultiError is an error wrapping multiple validation errors returned\n// by MetaData.ValidateAll() if the designated constraints aren't met.\ntype MetaDataMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m MetaDataMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m MetaDataMultiError) AllErrors() []error { return m }\n\n// MetaDataValidationError is the validation error returned by\n// MetaData.Validate if the designated constraints aren't met.\ntype MetaDataValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e MetaDataValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e MetaDataValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e MetaDataValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e MetaDataValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e MetaDataValidationError) ErrorName() string { return \"MetaDataValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e MetaDataValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sMetaData.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = MetaDataValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = MetaDataValidationError{}\n"
  },
  {
    "path": "pkg/pb/sourcespb/sources.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.33.0\n// \tprotoc        v4.25.3\n// source: sources.proto\n\npackage sourcespb\n\nimport (\n\t_ \"github.com/envoyproxy/protoc-gen-validate/validate\"\n\tcredentialspb \"github.com/trufflesecurity/trufflehog/v3/pkg/pb/credentialspb\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\tanypb \"google.golang.org/protobuf/types/known/anypb\"\n\tdurationpb \"google.golang.org/protobuf/types/known/durationpb\"\n\ttimestamppb \"google.golang.org/protobuf/types/known/timestamppb\"\n\treflect \"reflect\"\n\tsync \"sync\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype SourceType int32\n\nconst (\n\tSourceType_SOURCE_TYPE_AZURE_STORAGE              SourceType = 0\n\tSourceType_SOURCE_TYPE_BITBUCKET                  SourceType = 1\n\tSourceType_SOURCE_TYPE_CIRCLECI                   SourceType = 2\n\tSourceType_SOURCE_TYPE_CONFLUENCE                 SourceType = 3\n\tSourceType_SOURCE_TYPE_DOCKER                     SourceType = 4\n\tSourceType_SOURCE_TYPE_ECR                        SourceType = 5\n\tSourceType_SOURCE_TYPE_GCS                        SourceType = 6\n\tSourceType_SOURCE_TYPE_GITHUB                     SourceType = 7\n\tSourceType_SOURCE_TYPE_PUBLIC_GIT                 SourceType = 8\n\tSourceType_SOURCE_TYPE_GITLAB                     SourceType = 9\n\tSourceType_SOURCE_TYPE_JIRA                       SourceType = 10\n\tSourceType_SOURCE_TYPE_NPM_UNAUTHD_PACKAGES       SourceType = 11\n\tSourceType_SOURCE_TYPE_PYPI_UNAUTHD_PACKAGES      SourceType = 12\n\tSourceType_SOURCE_TYPE_S3                         SourceType = 13\n\tSourceType_SOURCE_TYPE_SLACK                      SourceType = 14\n\tSourceType_SOURCE_TYPE_FILESYSTEM                 SourceType = 15\n\tSourceType_SOURCE_TYPE_GIT                        SourceType = 16\n\tSourceType_SOURCE_TYPE_TEST                       SourceType = 17\n\tSourceType_SOURCE_TYPE_S3_UNAUTHED                SourceType = 18\n\tSourceType_SOURCE_TYPE_GITHUB_UNAUTHENTICATED_ORG SourceType = 19\n\tSourceType_SOURCE_TYPE_BUILDKITE                  SourceType = 20\n\tSourceType_SOURCE_TYPE_GERRIT                     SourceType = 21\n\tSourceType_SOURCE_TYPE_JENKINS                    SourceType = 22\n\tSourceType_SOURCE_TYPE_TEAMS                      SourceType = 23\n\tSourceType_SOURCE_TYPE_JFROG_ARTIFACTORY          SourceType = 24\n\tSourceType_SOURCE_TYPE_SYSLOG                     SourceType = 25\n\tSourceType_SOURCE_TYPE_PUBLIC_EVENT_MONITORING    SourceType = 26\n\tSourceType_SOURCE_TYPE_SLACK_REALTIME             SourceType = 27\n\tSourceType_SOURCE_TYPE_GOOGLE_DRIVE               SourceType = 28\n\tSourceType_SOURCE_TYPE_SHAREPOINT                 SourceType = 29\n\tSourceType_SOURCE_TYPE_GCS_UNAUTHED               SourceType = 30\n\tSourceType_SOURCE_TYPE_AZURE_REPOS                SourceType = 31\n\tSourceType_SOURCE_TYPE_TRAVISCI                   SourceType = 32\n\tSourceType_SOURCE_TYPE_POSTMAN                    SourceType = 33\n\tSourceType_SOURCE_TYPE_WEBHOOK                    SourceType = 34\n\tSourceType_SOURCE_TYPE_ELASTICSEARCH              SourceType = 35\n\tSourceType_SOURCE_TYPE_HUGGINGFACE                SourceType = 36\n\tSourceType_SOURCE_TYPE_GITHUB_EXPERIMENTAL        SourceType = 37\n\tSourceType_SOURCE_TYPE_SENTRY                     SourceType = 38\n\tSourceType_SOURCE_TYPE_GITHUB_REALTIME            SourceType = 39\n\tSourceType_SOURCE_TYPE_STDIN                      SourceType = 40\n\tSourceType_SOURCE_TYPE_SLACK_CONTINUOUS           SourceType = 41\n\tSourceType_SOURCE_TYPE_JSON_ENUMERATOR            SourceType = 42\n)\n\n// Enum value maps for SourceType.\nvar (\n\tSourceType_name = map[int32]string{\n\t\t0:  \"SOURCE_TYPE_AZURE_STORAGE\",\n\t\t1:  \"SOURCE_TYPE_BITBUCKET\",\n\t\t2:  \"SOURCE_TYPE_CIRCLECI\",\n\t\t3:  \"SOURCE_TYPE_CONFLUENCE\",\n\t\t4:  \"SOURCE_TYPE_DOCKER\",\n\t\t5:  \"SOURCE_TYPE_ECR\",\n\t\t6:  \"SOURCE_TYPE_GCS\",\n\t\t7:  \"SOURCE_TYPE_GITHUB\",\n\t\t8:  \"SOURCE_TYPE_PUBLIC_GIT\",\n\t\t9:  \"SOURCE_TYPE_GITLAB\",\n\t\t10: \"SOURCE_TYPE_JIRA\",\n\t\t11: \"SOURCE_TYPE_NPM_UNAUTHD_PACKAGES\",\n\t\t12: \"SOURCE_TYPE_PYPI_UNAUTHD_PACKAGES\",\n\t\t13: \"SOURCE_TYPE_S3\",\n\t\t14: \"SOURCE_TYPE_SLACK\",\n\t\t15: \"SOURCE_TYPE_FILESYSTEM\",\n\t\t16: \"SOURCE_TYPE_GIT\",\n\t\t17: \"SOURCE_TYPE_TEST\",\n\t\t18: \"SOURCE_TYPE_S3_UNAUTHED\",\n\t\t19: \"SOURCE_TYPE_GITHUB_UNAUTHENTICATED_ORG\",\n\t\t20: \"SOURCE_TYPE_BUILDKITE\",\n\t\t21: \"SOURCE_TYPE_GERRIT\",\n\t\t22: \"SOURCE_TYPE_JENKINS\",\n\t\t23: \"SOURCE_TYPE_TEAMS\",\n\t\t24: \"SOURCE_TYPE_JFROG_ARTIFACTORY\",\n\t\t25: \"SOURCE_TYPE_SYSLOG\",\n\t\t26: \"SOURCE_TYPE_PUBLIC_EVENT_MONITORING\",\n\t\t27: \"SOURCE_TYPE_SLACK_REALTIME\",\n\t\t28: \"SOURCE_TYPE_GOOGLE_DRIVE\",\n\t\t29: \"SOURCE_TYPE_SHAREPOINT\",\n\t\t30: \"SOURCE_TYPE_GCS_UNAUTHED\",\n\t\t31: \"SOURCE_TYPE_AZURE_REPOS\",\n\t\t32: \"SOURCE_TYPE_TRAVISCI\",\n\t\t33: \"SOURCE_TYPE_POSTMAN\",\n\t\t34: \"SOURCE_TYPE_WEBHOOK\",\n\t\t35: \"SOURCE_TYPE_ELASTICSEARCH\",\n\t\t36: \"SOURCE_TYPE_HUGGINGFACE\",\n\t\t37: \"SOURCE_TYPE_GITHUB_EXPERIMENTAL\",\n\t\t38: \"SOURCE_TYPE_SENTRY\",\n\t\t39: \"SOURCE_TYPE_GITHUB_REALTIME\",\n\t\t40: \"SOURCE_TYPE_STDIN\",\n\t\t41: \"SOURCE_TYPE_SLACK_CONTINUOUS\",\n\t\t42: \"SOURCE_TYPE_JSON_ENUMERATOR\",\n\t}\n\tSourceType_value = map[string]int32{\n\t\t\"SOURCE_TYPE_AZURE_STORAGE\":              0,\n\t\t\"SOURCE_TYPE_BITBUCKET\":                  1,\n\t\t\"SOURCE_TYPE_CIRCLECI\":                   2,\n\t\t\"SOURCE_TYPE_CONFLUENCE\":                 3,\n\t\t\"SOURCE_TYPE_DOCKER\":                     4,\n\t\t\"SOURCE_TYPE_ECR\":                        5,\n\t\t\"SOURCE_TYPE_GCS\":                        6,\n\t\t\"SOURCE_TYPE_GITHUB\":                     7,\n\t\t\"SOURCE_TYPE_PUBLIC_GIT\":                 8,\n\t\t\"SOURCE_TYPE_GITLAB\":                     9,\n\t\t\"SOURCE_TYPE_JIRA\":                       10,\n\t\t\"SOURCE_TYPE_NPM_UNAUTHD_PACKAGES\":       11,\n\t\t\"SOURCE_TYPE_PYPI_UNAUTHD_PACKAGES\":      12,\n\t\t\"SOURCE_TYPE_S3\":                         13,\n\t\t\"SOURCE_TYPE_SLACK\":                      14,\n\t\t\"SOURCE_TYPE_FILESYSTEM\":                 15,\n\t\t\"SOURCE_TYPE_GIT\":                        16,\n\t\t\"SOURCE_TYPE_TEST\":                       17,\n\t\t\"SOURCE_TYPE_S3_UNAUTHED\":                18,\n\t\t\"SOURCE_TYPE_GITHUB_UNAUTHENTICATED_ORG\": 19,\n\t\t\"SOURCE_TYPE_BUILDKITE\":                  20,\n\t\t\"SOURCE_TYPE_GERRIT\":                     21,\n\t\t\"SOURCE_TYPE_JENKINS\":                    22,\n\t\t\"SOURCE_TYPE_TEAMS\":                      23,\n\t\t\"SOURCE_TYPE_JFROG_ARTIFACTORY\":          24,\n\t\t\"SOURCE_TYPE_SYSLOG\":                     25,\n\t\t\"SOURCE_TYPE_PUBLIC_EVENT_MONITORING\":    26,\n\t\t\"SOURCE_TYPE_SLACK_REALTIME\":             27,\n\t\t\"SOURCE_TYPE_GOOGLE_DRIVE\":               28,\n\t\t\"SOURCE_TYPE_SHAREPOINT\":                 29,\n\t\t\"SOURCE_TYPE_GCS_UNAUTHED\":               30,\n\t\t\"SOURCE_TYPE_AZURE_REPOS\":                31,\n\t\t\"SOURCE_TYPE_TRAVISCI\":                   32,\n\t\t\"SOURCE_TYPE_POSTMAN\":                    33,\n\t\t\"SOURCE_TYPE_WEBHOOK\":                    34,\n\t\t\"SOURCE_TYPE_ELASTICSEARCH\":              35,\n\t\t\"SOURCE_TYPE_HUGGINGFACE\":                36,\n\t\t\"SOURCE_TYPE_GITHUB_EXPERIMENTAL\":        37,\n\t\t\"SOURCE_TYPE_SENTRY\":                     38,\n\t\t\"SOURCE_TYPE_GITHUB_REALTIME\":            39,\n\t\t\"SOURCE_TYPE_STDIN\":                      40,\n\t\t\"SOURCE_TYPE_SLACK_CONTINUOUS\":           41,\n\t\t\"SOURCE_TYPE_JSON_ENUMERATOR\":            42,\n\t}\n)\n\nfunc (x SourceType) Enum() *SourceType {\n\tp := new(SourceType)\n\t*p = x\n\treturn p\n}\n\nfunc (x SourceType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (SourceType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_sources_proto_enumTypes[0].Descriptor()\n}\n\nfunc (SourceType) Type() protoreflect.EnumType {\n\treturn &file_sources_proto_enumTypes[0]\n}\n\nfunc (x SourceType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use SourceType.Descriptor instead.\nfunc (SourceType) EnumDescriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{0}\n}\n\ntype BitbucketInstallationType int32\n\nconst (\n\tBitbucketInstallationType_AUTODETECT  BitbucketInstallationType = 0\n\tBitbucketInstallationType_CLOUD       BitbucketInstallationType = 1\n\tBitbucketInstallationType_DATA_CENTER BitbucketInstallationType = 2\n)\n\n// Enum value maps for BitbucketInstallationType.\nvar (\n\tBitbucketInstallationType_name = map[int32]string{\n\t\t0: \"AUTODETECT\",\n\t\t1: \"CLOUD\",\n\t\t2: \"DATA_CENTER\",\n\t}\n\tBitbucketInstallationType_value = map[string]int32{\n\t\t\"AUTODETECT\":  0,\n\t\t\"CLOUD\":       1,\n\t\t\"DATA_CENTER\": 2,\n\t}\n)\n\nfunc (x BitbucketInstallationType) Enum() *BitbucketInstallationType {\n\tp := new(BitbucketInstallationType)\n\t*p = x\n\treturn p\n}\n\nfunc (x BitbucketInstallationType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (BitbucketInstallationType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_sources_proto_enumTypes[1].Descriptor()\n}\n\nfunc (BitbucketInstallationType) Type() protoreflect.EnumType {\n\treturn &file_sources_proto_enumTypes[1]\n}\n\nfunc (x BitbucketInstallationType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use BitbucketInstallationType.Descriptor instead.\nfunc (BitbucketInstallationType) EnumDescriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{1}\n}\n\ntype JiraInstallationType int32\n\nconst (\n\tJiraInstallationType_JIRA_INSTALLATION_TYPE_AUTODETECT  JiraInstallationType = 0\n\tJiraInstallationType_JIRA_INSTALLATION_TYPE_CLOUD       JiraInstallationType = 1\n\tJiraInstallationType_JIRA_INSTALLATION_TYPE_DATA_CENTER JiraInstallationType = 2\n)\n\n// Enum value maps for JiraInstallationType.\nvar (\n\tJiraInstallationType_name = map[int32]string{\n\t\t0: \"JIRA_INSTALLATION_TYPE_AUTODETECT\",\n\t\t1: \"JIRA_INSTALLATION_TYPE_CLOUD\",\n\t\t2: \"JIRA_INSTALLATION_TYPE_DATA_CENTER\",\n\t}\n\tJiraInstallationType_value = map[string]int32{\n\t\t\"JIRA_INSTALLATION_TYPE_AUTODETECT\":  0,\n\t\t\"JIRA_INSTALLATION_TYPE_CLOUD\":       1,\n\t\t\"JIRA_INSTALLATION_TYPE_DATA_CENTER\": 2,\n\t}\n)\n\nfunc (x JiraInstallationType) Enum() *JiraInstallationType {\n\tp := new(JiraInstallationType)\n\t*p = x\n\treturn p\n}\n\nfunc (x JiraInstallationType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (JiraInstallationType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_sources_proto_enumTypes[2].Descriptor()\n}\n\nfunc (JiraInstallationType) Type() protoreflect.EnumType {\n\treturn &file_sources_proto_enumTypes[2]\n}\n\nfunc (x JiraInstallationType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use JiraInstallationType.Descriptor instead.\nfunc (JiraInstallationType) EnumDescriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{2}\n}\n\ntype Confluence_GetAllSpacesScope int32\n\nconst (\n\tConfluence_ALL      Confluence_GetAllSpacesScope = 0\n\tConfluence_GLOBAL   Confluence_GetAllSpacesScope = 1\n\tConfluence_PERSONAL Confluence_GetAllSpacesScope = 2\n)\n\n// Enum value maps for Confluence_GetAllSpacesScope.\nvar (\n\tConfluence_GetAllSpacesScope_name = map[int32]string{\n\t\t0: \"ALL\",\n\t\t1: \"GLOBAL\",\n\t\t2: \"PERSONAL\",\n\t}\n\tConfluence_GetAllSpacesScope_value = map[string]int32{\n\t\t\"ALL\":      0,\n\t\t\"GLOBAL\":   1,\n\t\t\"PERSONAL\": 2,\n\t}\n)\n\nfunc (x Confluence_GetAllSpacesScope) Enum() *Confluence_GetAllSpacesScope {\n\tp := new(Confluence_GetAllSpacesScope)\n\t*p = x\n\treturn p\n}\n\nfunc (x Confluence_GetAllSpacesScope) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (Confluence_GetAllSpacesScope) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_sources_proto_enumTypes[3].Descriptor()\n}\n\nfunc (Confluence_GetAllSpacesScope) Type() protoreflect.EnumType {\n\treturn &file_sources_proto_enumTypes[3]\n}\n\nfunc (x Confluence_GetAllSpacesScope) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use Confluence_GetAllSpacesScope.Descriptor instead.\nfunc (Confluence_GetAllSpacesScope) EnumDescriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{6, 0}\n}\n\ntype LocalSource struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tType string `protobuf:\"bytes,1,opt,name=type,proto3\" json:\"type,omitempty\"`\n\tName string `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// DEPRECATED: scan_interval is deprecated and can be removed when we no longer depend on the name.\n\t// Deprecating in favor of scan_period due to the fact that scan_interval is a duration\n\t// which is a fixed-length span of time represented as a count of seconds and fractions of seconds\n\t// at nanosecond resolution. Most of the time, we want to be able to specify a scan interval in\n\t// human-readable format (e.g. 45s, 30m, 12h, etc.) which is not possible with a duration.\n\t// https://protobuf.dev/reference/protobuf/google.protobuf/#duration\n\t//\n\t// Deprecated: Marked as deprecated in sources.proto.\n\tScanInterval *durationpb.Duration `protobuf:\"bytes,3,opt,name=scan_interval,json=scanInterval,proto3\" json:\"scan_interval,omitempty\"`\n\tVerify       bool                 `protobuf:\"varint,4,opt,name=verify,proto3\" json:\"verify,omitempty\"`\n\tConnection   *anypb.Any           `protobuf:\"bytes,5,opt,name=connection,proto3\" json:\"connection,omitempty\"`\n\tScanPeriod   string               `protobuf:\"bytes,6,opt,name=scan_period,json=scanPeriod,proto3\" json:\"scan_period,omitempty\"`\n}\n\nfunc (x *LocalSource) Reset() {\n\t*x = LocalSource{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[0]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *LocalSource) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LocalSource) ProtoMessage() {}\n\nfunc (x *LocalSource) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[0]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LocalSource.ProtoReflect.Descriptor instead.\nfunc (*LocalSource) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *LocalSource) GetType() string {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn \"\"\n}\n\nfunc (x *LocalSource) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\n// Deprecated: Marked as deprecated in sources.proto.\nfunc (x *LocalSource) GetScanInterval() *durationpb.Duration {\n\tif x != nil {\n\t\treturn x.ScanInterval\n\t}\n\treturn nil\n}\n\nfunc (x *LocalSource) GetVerify() bool {\n\tif x != nil {\n\t\treturn x.Verify\n\t}\n\treturn false\n}\n\nfunc (x *LocalSource) GetConnection() *anypb.Any {\n\tif x != nil {\n\t\treturn x.Connection\n\t}\n\treturn nil\n}\n\nfunc (x *LocalSource) GetScanPeriod() string {\n\tif x != nil {\n\t\treturn x.ScanPeriod\n\t}\n\treturn \"\"\n}\n\n// https://www.jfrog.com/confluence/display/JFROG/Artifactory+REST+API#ArtifactoryRESTAPI-RetrieveFolderorRepositoryArchive\ntype Artifactory struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEndpoint string `protobuf:\"bytes,1,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*Artifactory_BasicAuth\n\t//\t*Artifactory_AccessToken\n\t//\t*Artifactory_Unauthenticated\n\tCredential   isArtifactory_Credential `protobuf_oneof:\"credential\"`\n\tRepositories []string                 `protobuf:\"bytes,4,rep,name=repositories,proto3\" json:\"repositories,omitempty\"`\n\tIncludePaths []string                 `protobuf:\"bytes,5,rep,name=include_paths,json=includePaths,proto3\" json:\"include_paths,omitempty\"`\n\tIgnorePaths  []string                 `protobuf:\"bytes,6,rep,name=ignore_paths,json=ignorePaths,proto3\" json:\"ignore_paths,omitempty\"`\n}\n\nfunc (x *Artifactory) Reset() {\n\t*x = Artifactory{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[1]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Artifactory) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Artifactory) ProtoMessage() {}\n\nfunc (x *Artifactory) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[1]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Artifactory.ProtoReflect.Descriptor instead.\nfunc (*Artifactory) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Artifactory) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (m *Artifactory) GetCredential() isArtifactory_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *Artifactory) GetBasicAuth() *credentialspb.BasicAuth {\n\tif x, ok := x.GetCredential().(*Artifactory_BasicAuth); ok {\n\t\treturn x.BasicAuth\n\t}\n\treturn nil\n}\n\nfunc (x *Artifactory) GetAccessToken() string {\n\tif x, ok := x.GetCredential().(*Artifactory_AccessToken); ok {\n\t\treturn x.AccessToken\n\t}\n\treturn \"\"\n}\n\nfunc (x *Artifactory) GetUnauthenticated() *credentialspb.Unauthenticated {\n\tif x, ok := x.GetCredential().(*Artifactory_Unauthenticated); ok {\n\t\treturn x.Unauthenticated\n\t}\n\treturn nil\n}\n\nfunc (x *Artifactory) GetRepositories() []string {\n\tif x != nil {\n\t\treturn x.Repositories\n\t}\n\treturn nil\n}\n\nfunc (x *Artifactory) GetIncludePaths() []string {\n\tif x != nil {\n\t\treturn x.IncludePaths\n\t}\n\treturn nil\n}\n\nfunc (x *Artifactory) GetIgnorePaths() []string {\n\tif x != nil {\n\t\treturn x.IgnorePaths\n\t}\n\treturn nil\n}\n\ntype isArtifactory_Credential interface {\n\tisArtifactory_Credential()\n}\n\ntype Artifactory_BasicAuth struct {\n\tBasicAuth *credentialspb.BasicAuth `protobuf:\"bytes,2,opt,name=basic_auth,json=basicAuth,proto3,oneof\"`\n}\n\ntype Artifactory_AccessToken struct {\n\tAccessToken string `protobuf:\"bytes,3,opt,name=access_token,json=accessToken,proto3,oneof\"`\n}\n\ntype Artifactory_Unauthenticated struct {\n\tUnauthenticated *credentialspb.Unauthenticated `protobuf:\"bytes,7,opt,name=unauthenticated,proto3,oneof\"`\n}\n\nfunc (*Artifactory_BasicAuth) isArtifactory_Credential() {}\n\nfunc (*Artifactory_AccessToken) isArtifactory_Credential() {}\n\nfunc (*Artifactory_Unauthenticated) isArtifactory_Credential() {}\n\ntype AzureStorage struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*AzureStorage_ConnectionString\n\t//\t*AzureStorage_BasicAuth\n\t//\t*AzureStorage_ClientCertificate\n\t//\t*AzureStorage_Unauthenticated\n\tCredential        isAzureStorage_Credential `protobuf_oneof:\"credential\"`\n\tStorageContainers []string                  `protobuf:\"bytes,5,rep,name=storage_containers,json=storageContainers,proto3\" json:\"storage_containers,omitempty\"`\n}\n\nfunc (x *AzureStorage) Reset() {\n\t*x = AzureStorage{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[2]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AzureStorage) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AzureStorage) ProtoMessage() {}\n\nfunc (x *AzureStorage) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[2]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AzureStorage.ProtoReflect.Descriptor instead.\nfunc (*AzureStorage) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (m *AzureStorage) GetCredential() isAzureStorage_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *AzureStorage) GetConnectionString() string {\n\tif x, ok := x.GetCredential().(*AzureStorage_ConnectionString); ok {\n\t\treturn x.ConnectionString\n\t}\n\treturn \"\"\n}\n\nfunc (x *AzureStorage) GetBasicAuth() *credentialspb.BasicAuth {\n\tif x, ok := x.GetCredential().(*AzureStorage_BasicAuth); ok {\n\t\treturn x.BasicAuth\n\t}\n\treturn nil\n}\n\nfunc (x *AzureStorage) GetClientCertificate() string {\n\tif x, ok := x.GetCredential().(*AzureStorage_ClientCertificate); ok {\n\t\treturn x.ClientCertificate\n\t}\n\treturn \"\"\n}\n\nfunc (x *AzureStorage) GetUnauthenticated() *credentialspb.Unauthenticated {\n\tif x, ok := x.GetCredential().(*AzureStorage_Unauthenticated); ok {\n\t\treturn x.Unauthenticated\n\t}\n\treturn nil\n}\n\nfunc (x *AzureStorage) GetStorageContainers() []string {\n\tif x != nil {\n\t\treturn x.StorageContainers\n\t}\n\treturn nil\n}\n\ntype isAzureStorage_Credential interface {\n\tisAzureStorage_Credential()\n}\n\ntype AzureStorage_ConnectionString struct {\n\tConnectionString string `protobuf:\"bytes,1,opt,name=connection_string,json=connectionString,proto3,oneof\"`\n}\n\ntype AzureStorage_BasicAuth struct {\n\tBasicAuth *credentialspb.BasicAuth `protobuf:\"bytes,2,opt,name=basic_auth,json=basicAuth,proto3,oneof\"`\n}\n\ntype AzureStorage_ClientCertificate struct {\n\tClientCertificate string `protobuf:\"bytes,3,opt,name=client_certificate,json=clientCertificate,proto3,oneof\"`\n}\n\ntype AzureStorage_Unauthenticated struct {\n\tUnauthenticated *credentialspb.Unauthenticated `protobuf:\"bytes,4,opt,name=unauthenticated,proto3,oneof\"`\n}\n\nfunc (*AzureStorage_ConnectionString) isAzureStorage_Credential() {}\n\nfunc (*AzureStorage_BasicAuth) isAzureStorage_Credential() {}\n\nfunc (*AzureStorage_ClientCertificate) isAzureStorage_Credential() {}\n\nfunc (*AzureStorage_Unauthenticated) isAzureStorage_Credential() {}\n\ntype Bitbucket struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEndpoint string `protobuf:\"bytes,1,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*Bitbucket_Token\n\t//\t*Bitbucket_Oauth\n\t//\t*Bitbucket_BasicAuth\n\tCredential                 isBitbucket_Credential    `protobuf_oneof:\"credential\"`\n\tRepositories               []string                  `protobuf:\"bytes,5,rep,name=repositories,proto3\" json:\"repositories,omitempty\"`\n\tIgnoreRepos                []string                  `protobuf:\"bytes,6,rep,name=ignore_repos,json=ignoreRepos,proto3\" json:\"ignore_repos,omitempty\"`\n\tSkipBinaries               bool                      `protobuf:\"varint,7,opt,name=skip_binaries,json=skipBinaries,proto3\" json:\"skip_binaries,omitempty\"`\n\tSkipArchives               bool                      `protobuf:\"varint,8,opt,name=skip_archives,json=skipArchives,proto3\" json:\"skip_archives,omitempty\"`\n\tInstallationType           BitbucketInstallationType `protobuf:\"varint,9,opt,name=installation_type,json=installationType,proto3,enum=sources.BitbucketInstallationType\" json:\"installation_type,omitempty\"`\n\tOauthAuthorizationEndpoint string                    `protobuf:\"bytes,10,opt,name=oauth_authorization_endpoint,json=oauthAuthorizationEndpoint,proto3\" json:\"oauth_authorization_endpoint,omitempty\"` // endpoint for OAuth authorization flow in Bitbucket Data Center application links\n\tOauthTokenEndpoint         string                    `protobuf:\"bytes,11,opt,name=oauth_token_endpoint,json=oauthTokenEndpoint,proto3\" json:\"oauth_token_endpoint,omitempty\"`                         // endpoint for getting OAuth access/refresh tokens in Bitbucket Data Center application links\n\tOauthScopes                []string                  `protobuf:\"bytes,12,rep,name=oauth_scopes,json=oauthScopes,proto3\" json:\"oauth_scopes,omitempty\"`                                                // list of OAuth scopes that the access token is provided\n\tAllowSecretsManagerWrite   bool                      `protobuf:\"varint,13,opt,name=allow_secrets_manager_write,json=allowSecretsManagerWrite,proto3\" json:\"allow_secrets_manager_write,omitempty\"`    // flag to explicitly allow TruffleHog to write to the user's secrets manager\n}\n\nfunc (x *Bitbucket) Reset() {\n\t*x = Bitbucket{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[3]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Bitbucket) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Bitbucket) ProtoMessage() {}\n\nfunc (x *Bitbucket) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[3]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Bitbucket.ProtoReflect.Descriptor instead.\nfunc (*Bitbucket) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *Bitbucket) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (m *Bitbucket) GetCredential() isBitbucket_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *Bitbucket) GetToken() string {\n\tif x, ok := x.GetCredential().(*Bitbucket_Token); ok {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\nfunc (x *Bitbucket) GetOauth() *credentialspb.Oauth2 {\n\tif x, ok := x.GetCredential().(*Bitbucket_Oauth); ok {\n\t\treturn x.Oauth\n\t}\n\treturn nil\n}\n\nfunc (x *Bitbucket) GetBasicAuth() *credentialspb.BasicAuth {\n\tif x, ok := x.GetCredential().(*Bitbucket_BasicAuth); ok {\n\t\treturn x.BasicAuth\n\t}\n\treturn nil\n}\n\nfunc (x *Bitbucket) GetRepositories() []string {\n\tif x != nil {\n\t\treturn x.Repositories\n\t}\n\treturn nil\n}\n\nfunc (x *Bitbucket) GetIgnoreRepos() []string {\n\tif x != nil {\n\t\treturn x.IgnoreRepos\n\t}\n\treturn nil\n}\n\nfunc (x *Bitbucket) GetSkipBinaries() bool {\n\tif x != nil {\n\t\treturn x.SkipBinaries\n\t}\n\treturn false\n}\n\nfunc (x *Bitbucket) GetSkipArchives() bool {\n\tif x != nil {\n\t\treturn x.SkipArchives\n\t}\n\treturn false\n}\n\nfunc (x *Bitbucket) GetInstallationType() BitbucketInstallationType {\n\tif x != nil {\n\t\treturn x.InstallationType\n\t}\n\treturn BitbucketInstallationType_AUTODETECT\n}\n\nfunc (x *Bitbucket) GetOauthAuthorizationEndpoint() string {\n\tif x != nil {\n\t\treturn x.OauthAuthorizationEndpoint\n\t}\n\treturn \"\"\n}\n\nfunc (x *Bitbucket) GetOauthTokenEndpoint() string {\n\tif x != nil {\n\t\treturn x.OauthTokenEndpoint\n\t}\n\treturn \"\"\n}\n\nfunc (x *Bitbucket) GetOauthScopes() []string {\n\tif x != nil {\n\t\treturn x.OauthScopes\n\t}\n\treturn nil\n}\n\nfunc (x *Bitbucket) GetAllowSecretsManagerWrite() bool {\n\tif x != nil {\n\t\treturn x.AllowSecretsManagerWrite\n\t}\n\treturn false\n}\n\ntype isBitbucket_Credential interface {\n\tisBitbucket_Credential()\n}\n\ntype Bitbucket_Token struct {\n\tToken string `protobuf:\"bytes,2,opt,name=token,proto3,oneof\"`\n}\n\ntype Bitbucket_Oauth struct {\n\tOauth *credentialspb.Oauth2 `protobuf:\"bytes,3,opt,name=oauth,proto3,oneof\"`\n}\n\ntype Bitbucket_BasicAuth struct {\n\tBasicAuth *credentialspb.BasicAuth `protobuf:\"bytes,4,opt,name=basic_auth,json=basicAuth,proto3,oneof\"`\n}\n\nfunc (*Bitbucket_Token) isBitbucket_Credential() {}\n\nfunc (*Bitbucket_Oauth) isBitbucket_Credential() {}\n\nfunc (*Bitbucket_BasicAuth) isBitbucket_Credential() {}\n\ntype CircleCI struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEndpoint string `protobuf:\"bytes,1,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*CircleCI_Token\n\tCredential isCircleCI_Credential `protobuf_oneof:\"credential\"`\n}\n\nfunc (x *CircleCI) Reset() {\n\t*x = CircleCI{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[4]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *CircleCI) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CircleCI) ProtoMessage() {}\n\nfunc (x *CircleCI) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[4]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CircleCI.ProtoReflect.Descriptor instead.\nfunc (*CircleCI) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *CircleCI) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (m *CircleCI) GetCredential() isCircleCI_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *CircleCI) GetToken() string {\n\tif x, ok := x.GetCredential().(*CircleCI_Token); ok {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\ntype isCircleCI_Credential interface {\n\tisCircleCI_Credential()\n}\n\ntype CircleCI_Token struct {\n\tToken string `protobuf:\"bytes,2,opt,name=token,proto3,oneof\"`\n}\n\nfunc (*CircleCI_Token) isCircleCI_Credential() {}\n\ntype TravisCI struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEndpoint string `protobuf:\"bytes,1,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*TravisCI_Token\n\tCredential isTravisCI_Credential `protobuf_oneof:\"credential\"`\n}\n\nfunc (x *TravisCI) Reset() {\n\t*x = TravisCI{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[5]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *TravisCI) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TravisCI) ProtoMessage() {}\n\nfunc (x *TravisCI) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[5]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TravisCI.ProtoReflect.Descriptor instead.\nfunc (*TravisCI) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *TravisCI) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (m *TravisCI) GetCredential() isTravisCI_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *TravisCI) GetToken() string {\n\tif x, ok := x.GetCredential().(*TravisCI_Token); ok {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\ntype isTravisCI_Credential interface {\n\tisTravisCI_Credential()\n}\n\ntype TravisCI_Token struct {\n\tToken string `protobuf:\"bytes,2,opt,name=token,proto3,oneof\"`\n}\n\nfunc (*TravisCI_Token) isTravisCI_Credential() {}\n\ntype Confluence struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEndpoint string `protobuf:\"bytes,1,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*Confluence_Unauthenticated\n\t//\t*Confluence_BasicAuth\n\t//\t*Confluence_Token\n\tCredential            isConfluence_Credential      `protobuf_oneof:\"credential\"`\n\tSpacesScope           Confluence_GetAllSpacesScope `protobuf:\"varint,5,opt,name=spaces_scope,json=spacesScope,proto3,enum=sources.Confluence_GetAllSpacesScope\" json:\"spaces_scope,omitempty\"`\n\tInsecureSkipVerifyTls bool                         `protobuf:\"varint,6,opt,name=insecure_skip_verify_tls,json=insecureSkipVerifyTls,proto3\" json:\"insecure_skip_verify_tls,omitempty\"`\n\tSpaces                []string                     `protobuf:\"bytes,7,rep,name=spaces,proto3\" json:\"spaces,omitempty\"`\n\tIgnoreSpaces          []string                     `protobuf:\"bytes,8,rep,name=ignore_spaces,json=ignoreSpaces,proto3\" json:\"ignore_spaces,omitempty\"`\n\tIncludeAttachments    bool                         `protobuf:\"varint,9,opt,name=include_attachments,json=includeAttachments,proto3\" json:\"include_attachments,omitempty\"`\n\tSkipHistory           bool                         `protobuf:\"varint,10,opt,name=skip_history,json=skipHistory,proto3\" json:\"skip_history,omitempty\"`\n\tIncludeComments       bool                         `protobuf:\"varint,11,opt,name=include_comments,json=includeComments,proto3\" json:\"include_comments,omitempty\"`\n}\n\nfunc (x *Confluence) Reset() {\n\t*x = Confluence{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[6]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Confluence) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Confluence) ProtoMessage() {}\n\nfunc (x *Confluence) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[6]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Confluence.ProtoReflect.Descriptor instead.\nfunc (*Confluence) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *Confluence) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (m *Confluence) GetCredential() isConfluence_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *Confluence) GetUnauthenticated() *credentialspb.Unauthenticated {\n\tif x, ok := x.GetCredential().(*Confluence_Unauthenticated); ok {\n\t\treturn x.Unauthenticated\n\t}\n\treturn nil\n}\n\nfunc (x *Confluence) GetBasicAuth() *credentialspb.BasicAuth {\n\tif x, ok := x.GetCredential().(*Confluence_BasicAuth); ok {\n\t\treturn x.BasicAuth\n\t}\n\treturn nil\n}\n\nfunc (x *Confluence) GetToken() string {\n\tif x, ok := x.GetCredential().(*Confluence_Token); ok {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\nfunc (x *Confluence) GetSpacesScope() Confluence_GetAllSpacesScope {\n\tif x != nil {\n\t\treturn x.SpacesScope\n\t}\n\treturn Confluence_ALL\n}\n\nfunc (x *Confluence) GetInsecureSkipVerifyTls() bool {\n\tif x != nil {\n\t\treturn x.InsecureSkipVerifyTls\n\t}\n\treturn false\n}\n\nfunc (x *Confluence) GetSpaces() []string {\n\tif x != nil {\n\t\treturn x.Spaces\n\t}\n\treturn nil\n}\n\nfunc (x *Confluence) GetIgnoreSpaces() []string {\n\tif x != nil {\n\t\treturn x.IgnoreSpaces\n\t}\n\treturn nil\n}\n\nfunc (x *Confluence) GetIncludeAttachments() bool {\n\tif x != nil {\n\t\treturn x.IncludeAttachments\n\t}\n\treturn false\n}\n\nfunc (x *Confluence) GetSkipHistory() bool {\n\tif x != nil {\n\t\treturn x.SkipHistory\n\t}\n\treturn false\n}\n\nfunc (x *Confluence) GetIncludeComments() bool {\n\tif x != nil {\n\t\treturn x.IncludeComments\n\t}\n\treturn false\n}\n\ntype isConfluence_Credential interface {\n\tisConfluence_Credential()\n}\n\ntype Confluence_Unauthenticated struct {\n\tUnauthenticated *credentialspb.Unauthenticated `protobuf:\"bytes,2,opt,name=unauthenticated,proto3,oneof\"`\n}\n\ntype Confluence_BasicAuth struct {\n\tBasicAuth *credentialspb.BasicAuth `protobuf:\"bytes,3,opt,name=basic_auth,json=basicAuth,proto3,oneof\"`\n}\n\ntype Confluence_Token struct {\n\tToken string `protobuf:\"bytes,4,opt,name=token,proto3,oneof\"`\n}\n\nfunc (*Confluence_Unauthenticated) isConfluence_Credential() {}\n\nfunc (*Confluence_BasicAuth) isConfluence_Credential() {}\n\nfunc (*Confluence_Token) isConfluence_Credential() {}\n\ntype Docker struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*Docker_Unauthenticated\n\t//\t*Docker_BasicAuth\n\t//\t*Docker_BearerToken\n\t//\t*Docker_DockerKeychain\n\tCredential    isDocker_Credential `protobuf_oneof:\"credential\"`\n\tImages        []string            `protobuf:\"bytes,5,rep,name=images,proto3\" json:\"images,omitempty\"`\n\tExcludePaths  []string            `protobuf:\"bytes,6,rep,name=exclude_paths,json=excludePaths,proto3\" json:\"exclude_paths,omitempty\"`\n\tNamespace     string              `protobuf:\"bytes,7,opt,name=namespace,proto3\" json:\"namespace,omitempty\"`\n\tRegistryToken string              `protobuf:\"bytes,8,opt,name=registry_token,json=registryToken,proto3\" json:\"registry_token,omitempty\"`\n}\n\nfunc (x *Docker) Reset() {\n\t*x = Docker{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[7]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Docker) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Docker) ProtoMessage() {}\n\nfunc (x *Docker) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[7]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Docker.ProtoReflect.Descriptor instead.\nfunc (*Docker) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (m *Docker) GetCredential() isDocker_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *Docker) GetUnauthenticated() *credentialspb.Unauthenticated {\n\tif x, ok := x.GetCredential().(*Docker_Unauthenticated); ok {\n\t\treturn x.Unauthenticated\n\t}\n\treturn nil\n}\n\nfunc (x *Docker) GetBasicAuth() *credentialspb.BasicAuth {\n\tif x, ok := x.GetCredential().(*Docker_BasicAuth); ok {\n\t\treturn x.BasicAuth\n\t}\n\treturn nil\n}\n\nfunc (x *Docker) GetBearerToken() string {\n\tif x, ok := x.GetCredential().(*Docker_BearerToken); ok {\n\t\treturn x.BearerToken\n\t}\n\treturn \"\"\n}\n\nfunc (x *Docker) GetDockerKeychain() bool {\n\tif x, ok := x.GetCredential().(*Docker_DockerKeychain); ok {\n\t\treturn x.DockerKeychain\n\t}\n\treturn false\n}\n\nfunc (x *Docker) GetImages() []string {\n\tif x != nil {\n\t\treturn x.Images\n\t}\n\treturn nil\n}\n\nfunc (x *Docker) GetExcludePaths() []string {\n\tif x != nil {\n\t\treturn x.ExcludePaths\n\t}\n\treturn nil\n}\n\nfunc (x *Docker) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *Docker) GetRegistryToken() string {\n\tif x != nil {\n\t\treturn x.RegistryToken\n\t}\n\treturn \"\"\n}\n\ntype isDocker_Credential interface {\n\tisDocker_Credential()\n}\n\ntype Docker_Unauthenticated struct {\n\tUnauthenticated *credentialspb.Unauthenticated `protobuf:\"bytes,1,opt,name=unauthenticated,proto3,oneof\"`\n}\n\ntype Docker_BasicAuth struct {\n\tBasicAuth *credentialspb.BasicAuth `protobuf:\"bytes,2,opt,name=basic_auth,json=basicAuth,proto3,oneof\"`\n}\n\ntype Docker_BearerToken struct {\n\tBearerToken string `protobuf:\"bytes,3,opt,name=bearer_token,json=bearerToken,proto3,oneof\"`\n}\n\ntype Docker_DockerKeychain struct {\n\tDockerKeychain bool `protobuf:\"varint,4,opt,name=docker_keychain,json=dockerKeychain,proto3,oneof\"`\n}\n\nfunc (*Docker_Unauthenticated) isDocker_Credential() {}\n\nfunc (*Docker_BasicAuth) isDocker_Credential() {}\n\nfunc (*Docker_BearerToken) isDocker_Credential() {}\n\nfunc (*Docker_DockerKeychain) isDocker_Credential() {}\n\ntype ECR struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*ECR_AccessKey\n\tCredential isECR_Credential `protobuf_oneof:\"credential\"`\n\tRegistries []string         `protobuf:\"bytes,2,rep,name=registries,proto3\" json:\"registries,omitempty\"`\n}\n\nfunc (x *ECR) Reset() {\n\t*x = ECR{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[8]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *ECR) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ECR) ProtoMessage() {}\n\nfunc (x *ECR) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[8]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ECR.ProtoReflect.Descriptor instead.\nfunc (*ECR) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (m *ECR) GetCredential() isECR_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *ECR) GetAccessKey() *credentialspb.KeySecret {\n\tif x, ok := x.GetCredential().(*ECR_AccessKey); ok {\n\t\treturn x.AccessKey\n\t}\n\treturn nil\n}\n\nfunc (x *ECR) GetRegistries() []string {\n\tif x != nil {\n\t\treturn x.Registries\n\t}\n\treturn nil\n}\n\ntype isECR_Credential interface {\n\tisECR_Credential()\n}\n\ntype ECR_AccessKey struct {\n\tAccessKey *credentialspb.KeySecret `protobuf:\"bytes,1,opt,name=access_key,json=accessKey,proto3,oneof\"`\n}\n\nfunc (*ECR_AccessKey) isECR_Credential() {}\n\ntype Filesystem struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// DEPRECATED: directories is deprecated and can be removed / renamed to\n\t// paths when we no longer depend on the name in enterprise configs.\n\tDirectories      []string `protobuf:\"bytes,1,rep,name=directories,proto3\" json:\"directories,omitempty\"`\n\tPaths            []string `protobuf:\"bytes,2,rep,name=paths,proto3\" json:\"paths,omitempty\"`\n\tIncludePathsFile string   `protobuf:\"bytes,3,opt,name=include_paths_file,json=includePathsFile,proto3\" json:\"include_paths_file,omitempty\"` // path to file containing newline separated list of paths\n\tExcludePathsFile string   `protobuf:\"bytes,4,opt,name=exclude_paths_file,json=excludePathsFile,proto3\" json:\"exclude_paths_file,omitempty\"` // path to file containing newline separated list of paths\n\tSkipBinaries     bool     `protobuf:\"varint,5,opt,name=skip_binaries,json=skipBinaries,proto3\" json:\"skip_binaries,omitempty\"`              // allows skipping binary files from the scan\n\tMaxSymlinkDepth  int32    `protobuf:\"varint,6,opt,name=max_symlink_depth,json=maxSymlinkDepth,proto3\" json:\"max_symlink_depth,omitempty\"`   // allows following symlinks upto specified max depth\n}\n\nfunc (x *Filesystem) Reset() {\n\t*x = Filesystem{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[9]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Filesystem) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Filesystem) ProtoMessage() {}\n\nfunc (x *Filesystem) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[9]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Filesystem.ProtoReflect.Descriptor instead.\nfunc (*Filesystem) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *Filesystem) GetDirectories() []string {\n\tif x != nil {\n\t\treturn x.Directories\n\t}\n\treturn nil\n}\n\nfunc (x *Filesystem) GetPaths() []string {\n\tif x != nil {\n\t\treturn x.Paths\n\t}\n\treturn nil\n}\n\nfunc (x *Filesystem) GetIncludePathsFile() string {\n\tif x != nil {\n\t\treturn x.IncludePathsFile\n\t}\n\treturn \"\"\n}\n\nfunc (x *Filesystem) GetExcludePathsFile() string {\n\tif x != nil {\n\t\treturn x.ExcludePathsFile\n\t}\n\treturn \"\"\n}\n\nfunc (x *Filesystem) GetSkipBinaries() bool {\n\tif x != nil {\n\t\treturn x.SkipBinaries\n\t}\n\treturn false\n}\n\nfunc (x *Filesystem) GetMaxSymlinkDepth() int32 {\n\tif x != nil {\n\t\treturn x.MaxSymlinkDepth\n\t}\n\treturn 0\n}\n\ntype GCS struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*GCS_JsonServiceAccount\n\t//\t*GCS_ApiKey\n\t//\t*GCS_Unauthenticated\n\t//\t*GCS_Adc\n\t//\t*GCS_ServiceAccountFile\n\t//\t*GCS_Oauth\n\tCredential     isGCS_Credential `protobuf_oneof:\"credential\"`\n\tProjectId      string           `protobuf:\"bytes,5,opt,name=project_id,json=projectId,proto3\" json:\"project_id,omitempty\"`\n\tIncludeBuckets []string         `protobuf:\"bytes,6,rep,name=include_buckets,json=includeBuckets,proto3\" json:\"include_buckets,omitempty\"`\n\tExcludeBuckets []string         `protobuf:\"bytes,7,rep,name=exclude_buckets,json=excludeBuckets,proto3\" json:\"exclude_buckets,omitempty\"`\n\tIncludeObjects []string         `protobuf:\"bytes,8,rep,name=include_objects,json=includeObjects,proto3\" json:\"include_objects,omitempty\"`\n\tExcludeObjects []string         `protobuf:\"bytes,9,rep,name=exclude_objects,json=excludeObjects,proto3\" json:\"exclude_objects,omitempty\"`\n\tMaxObjectSize  int64            `protobuf:\"varint,10,opt,name=max_object_size,json=maxObjectSize,proto3\" json:\"max_object_size,omitempty\"`\n}\n\nfunc (x *GCS) Reset() {\n\t*x = GCS{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[10]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GCS) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GCS) ProtoMessage() {}\n\nfunc (x *GCS) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[10]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GCS.ProtoReflect.Descriptor instead.\nfunc (*GCS) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (m *GCS) GetCredential() isGCS_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *GCS) GetJsonServiceAccount() string {\n\tif x, ok := x.GetCredential().(*GCS_JsonServiceAccount); ok {\n\t\treturn x.JsonServiceAccount\n\t}\n\treturn \"\"\n}\n\nfunc (x *GCS) GetApiKey() string {\n\tif x, ok := x.GetCredential().(*GCS_ApiKey); ok {\n\t\treturn x.ApiKey\n\t}\n\treturn \"\"\n}\n\nfunc (x *GCS) GetUnauthenticated() *credentialspb.Unauthenticated {\n\tif x, ok := x.GetCredential().(*GCS_Unauthenticated); ok {\n\t\treturn x.Unauthenticated\n\t}\n\treturn nil\n}\n\nfunc (x *GCS) GetAdc() *credentialspb.CloudEnvironment {\n\tif x, ok := x.GetCredential().(*GCS_Adc); ok {\n\t\treturn x.Adc\n\t}\n\treturn nil\n}\n\nfunc (x *GCS) GetServiceAccountFile() string {\n\tif x, ok := x.GetCredential().(*GCS_ServiceAccountFile); ok {\n\t\treturn x.ServiceAccountFile\n\t}\n\treturn \"\"\n}\n\nfunc (x *GCS) GetOauth() *credentialspb.Oauth2 {\n\tif x, ok := x.GetCredential().(*GCS_Oauth); ok {\n\t\treturn x.Oauth\n\t}\n\treturn nil\n}\n\nfunc (x *GCS) GetProjectId() string {\n\tif x != nil {\n\t\treturn x.ProjectId\n\t}\n\treturn \"\"\n}\n\nfunc (x *GCS) GetIncludeBuckets() []string {\n\tif x != nil {\n\t\treturn x.IncludeBuckets\n\t}\n\treturn nil\n}\n\nfunc (x *GCS) GetExcludeBuckets() []string {\n\tif x != nil {\n\t\treturn x.ExcludeBuckets\n\t}\n\treturn nil\n}\n\nfunc (x *GCS) GetIncludeObjects() []string {\n\tif x != nil {\n\t\treturn x.IncludeObjects\n\t}\n\treturn nil\n}\n\nfunc (x *GCS) GetExcludeObjects() []string {\n\tif x != nil {\n\t\treturn x.ExcludeObjects\n\t}\n\treturn nil\n}\n\nfunc (x *GCS) GetMaxObjectSize() int64 {\n\tif x != nil {\n\t\treturn x.MaxObjectSize\n\t}\n\treturn 0\n}\n\ntype isGCS_Credential interface {\n\tisGCS_Credential()\n}\n\ntype GCS_JsonServiceAccount struct {\n\tJsonServiceAccount string `protobuf:\"bytes,1,opt,name=json_service_account,json=jsonServiceAccount,proto3,oneof\"`\n}\n\ntype GCS_ApiKey struct {\n\tApiKey string `protobuf:\"bytes,2,opt,name=api_key,json=apiKey,proto3,oneof\"`\n}\n\ntype GCS_Unauthenticated struct {\n\tUnauthenticated *credentialspb.Unauthenticated `protobuf:\"bytes,3,opt,name=unauthenticated,proto3,oneof\"`\n}\n\ntype GCS_Adc struct {\n\tAdc *credentialspb.CloudEnvironment `protobuf:\"bytes,4,opt,name=adc,proto3,oneof\"`\n}\n\ntype GCS_ServiceAccountFile struct {\n\tServiceAccountFile string `protobuf:\"bytes,11,opt,name=service_account_file,json=serviceAccountFile,proto3,oneof\"`\n}\n\ntype GCS_Oauth struct {\n\tOauth *credentialspb.Oauth2 `protobuf:\"bytes,12,opt,name=oauth,proto3,oneof\"`\n}\n\nfunc (*GCS_JsonServiceAccount) isGCS_Credential() {}\n\nfunc (*GCS_ApiKey) isGCS_Credential() {}\n\nfunc (*GCS_Unauthenticated) isGCS_Credential() {}\n\nfunc (*GCS_Adc) isGCS_Credential() {}\n\nfunc (*GCS_ServiceAccountFile) isGCS_Credential() {}\n\nfunc (*GCS_Oauth) isGCS_Credential() {}\n\ntype Git struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*Git_BasicAuth\n\t//\t*Git_Unauthenticated\n\t//\t*Git_SshAuth\n\tCredential       isGit_Credential `protobuf_oneof:\"credential\"`\n\tDirectories      []string         `protobuf:\"bytes,3,rep,name=directories,proto3\" json:\"directories,omitempty\"`\n\tRepositories     []string         `protobuf:\"bytes,4,rep,name=repositories,proto3\" json:\"repositories,omitempty\"`\n\tHead             string           `protobuf:\"bytes,6,opt,name=head,proto3\" json:\"head,omitempty\"`\n\tBase             string           `protobuf:\"bytes,7,opt,name=base,proto3\" json:\"base,omitempty\"`\n\tBare             bool             `protobuf:\"varint,8,opt,name=bare,proto3\" json:\"bare,omitempty\"`\n\tIncludePathsFile string           `protobuf:\"bytes,9,opt,name=include_paths_file,json=includePathsFile,proto3\" json:\"include_paths_file,omitempty\"`  // path to file containing newline separated list of paths\n\tExcludePathsFile string           `protobuf:\"bytes,10,opt,name=exclude_paths_file,json=excludePathsFile,proto3\" json:\"exclude_paths_file,omitempty\"` // path to file containing newline separated list of paths\n\tExcludeGlobs     string           `protobuf:\"bytes,11,opt,name=exclude_globs,json=excludeGlobs,proto3\" json:\"exclude_globs,omitempty\"`               // comma separated list of globs\n\tMaxDepth         int64            `protobuf:\"varint,12,opt,name=max_depth,json=maxDepth,proto3\" json:\"max_depth,omitempty\"`\n\t// This field is generally used by the CLI or within CI/CD systems to specify a single repository,\n\t// whereas the repositories field is used by the enterprise config to specify multiple repositories.\n\t// Passing a single repository via the uri field also allows for additional options to be specified\n\t// like head, base, bare, etc.\n\tUri                 string `protobuf:\"bytes,13,opt,name=uri,proto3\" json:\"uri,omitempty\"` // repository URL. https://, file://, or ssh://\n\tSkipBinaries        bool   `protobuf:\"varint,14,opt,name=skip_binaries,json=skipBinaries,proto3\" json:\"skip_binaries,omitempty\"`\n\tSkipArchives        bool   `protobuf:\"varint,15,opt,name=skip_archives,json=skipArchives,proto3\" json:\"skip_archives,omitempty\"`\n\tClonePath           string `protobuf:\"bytes,16,opt,name=clone_path,json=clonePath,proto3\" json:\"clone_path,omitempty\"`\n\tNoCleanup           bool   `protobuf:\"varint,17,opt,name=no_cleanup,json=noCleanup,proto3\" json:\"no_cleanup,omitempty\"`\n\tPrintLegacyJson     bool   `protobuf:\"varint,18,opt,name=print_legacy_json,json=printLegacyJson,proto3\" json:\"print_legacy_json,omitempty\"`\n\tTrustLocalGitConfig bool   `protobuf:\"varint,19,opt,name=trust_local_git_config,json=trustLocalGitConfig,proto3\" json:\"trust_local_git_config,omitempty\"` // Trust local git configurations\n}\n\nfunc (x *Git) Reset() {\n\t*x = Git{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[11]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Git) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Git) ProtoMessage() {}\n\nfunc (x *Git) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[11]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Git.ProtoReflect.Descriptor instead.\nfunc (*Git) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (m *Git) GetCredential() isGit_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *Git) GetBasicAuth() *credentialspb.BasicAuth {\n\tif x, ok := x.GetCredential().(*Git_BasicAuth); ok {\n\t\treturn x.BasicAuth\n\t}\n\treturn nil\n}\n\nfunc (x *Git) GetUnauthenticated() *credentialspb.Unauthenticated {\n\tif x, ok := x.GetCredential().(*Git_Unauthenticated); ok {\n\t\treturn x.Unauthenticated\n\t}\n\treturn nil\n}\n\nfunc (x *Git) GetSshAuth() *credentialspb.SSHAuth {\n\tif x, ok := x.GetCredential().(*Git_SshAuth); ok {\n\t\treturn x.SshAuth\n\t}\n\treturn nil\n}\n\nfunc (x *Git) GetDirectories() []string {\n\tif x != nil {\n\t\treturn x.Directories\n\t}\n\treturn nil\n}\n\nfunc (x *Git) GetRepositories() []string {\n\tif x != nil {\n\t\treturn x.Repositories\n\t}\n\treturn nil\n}\n\nfunc (x *Git) GetHead() string {\n\tif x != nil {\n\t\treturn x.Head\n\t}\n\treturn \"\"\n}\n\nfunc (x *Git) GetBase() string {\n\tif x != nil {\n\t\treturn x.Base\n\t}\n\treturn \"\"\n}\n\nfunc (x *Git) GetBare() bool {\n\tif x != nil {\n\t\treturn x.Bare\n\t}\n\treturn false\n}\n\nfunc (x *Git) GetIncludePathsFile() string {\n\tif x != nil {\n\t\treturn x.IncludePathsFile\n\t}\n\treturn \"\"\n}\n\nfunc (x *Git) GetExcludePathsFile() string {\n\tif x != nil {\n\t\treturn x.ExcludePathsFile\n\t}\n\treturn \"\"\n}\n\nfunc (x *Git) GetExcludeGlobs() string {\n\tif x != nil {\n\t\treturn x.ExcludeGlobs\n\t}\n\treturn \"\"\n}\n\nfunc (x *Git) GetMaxDepth() int64 {\n\tif x != nil {\n\t\treturn x.MaxDepth\n\t}\n\treturn 0\n}\n\nfunc (x *Git) GetUri() string {\n\tif x != nil {\n\t\treturn x.Uri\n\t}\n\treturn \"\"\n}\n\nfunc (x *Git) GetSkipBinaries() bool {\n\tif x != nil {\n\t\treturn x.SkipBinaries\n\t}\n\treturn false\n}\n\nfunc (x *Git) GetSkipArchives() bool {\n\tif x != nil {\n\t\treturn x.SkipArchives\n\t}\n\treturn false\n}\n\nfunc (x *Git) GetClonePath() string {\n\tif x != nil {\n\t\treturn x.ClonePath\n\t}\n\treturn \"\"\n}\n\nfunc (x *Git) GetNoCleanup() bool {\n\tif x != nil {\n\t\treturn x.NoCleanup\n\t}\n\treturn false\n}\n\nfunc (x *Git) GetPrintLegacyJson() bool {\n\tif x != nil {\n\t\treturn x.PrintLegacyJson\n\t}\n\treturn false\n}\n\nfunc (x *Git) GetTrustLocalGitConfig() bool {\n\tif x != nil {\n\t\treturn x.TrustLocalGitConfig\n\t}\n\treturn false\n}\n\ntype isGit_Credential interface {\n\tisGit_Credential()\n}\n\ntype Git_BasicAuth struct {\n\tBasicAuth *credentialspb.BasicAuth `protobuf:\"bytes,1,opt,name=basic_auth,json=basicAuth,proto3,oneof\"`\n}\n\ntype Git_Unauthenticated struct {\n\tUnauthenticated *credentialspb.Unauthenticated `protobuf:\"bytes,2,opt,name=unauthenticated,proto3,oneof\"`\n}\n\ntype Git_SshAuth struct {\n\tSshAuth *credentialspb.SSHAuth `protobuf:\"bytes,5,opt,name=ssh_auth,json=sshAuth,proto3,oneof\"`\n}\n\nfunc (*Git_BasicAuth) isGit_Credential() {}\n\nfunc (*Git_Unauthenticated) isGit_Credential() {}\n\nfunc (*Git_SshAuth) isGit_Credential() {}\n\ntype GitLab struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEndpoint string `protobuf:\"bytes,1,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*GitLab_Token\n\t//\t*GitLab_Oauth\n\t//\t*GitLab_BasicAuth\n\tCredential                      isGitLab_Credential `protobuf_oneof:\"credential\"`\n\tRepositories                    []string            `protobuf:\"bytes,5,rep,name=repositories,proto3\" json:\"repositories,omitempty\"`\n\tIgnoreRepos                     []string            `protobuf:\"bytes,6,rep,name=ignore_repos,json=ignoreRepos,proto3\" json:\"ignore_repos,omitempty\"`\n\tSkipBinaries                    bool                `protobuf:\"varint,7,opt,name=skip_binaries,json=skipBinaries,proto3\" json:\"skip_binaries,omitempty\"`\n\tSkipArchives                    bool                `protobuf:\"varint,8,opt,name=skip_archives,json=skipArchives,proto3\" json:\"skip_archives,omitempty\"`\n\tIncludeRepos                    []string            `protobuf:\"bytes,9,rep,name=include_repos,json=includeRepos,proto3\" json:\"include_repos,omitempty\"`\n\tExcludeProjectsSharedIntoGroups bool                `protobuf:\"varint,10,opt,name=exclude_projects_shared_into_groups,json=excludeProjectsSharedIntoGroups,proto3\" json:\"exclude_projects_shared_into_groups,omitempty\"`\n\tRemoveAuthInUrl                 bool                `protobuf:\"varint,11,opt,name=remove_auth_in_url,json=removeAuthInUrl,proto3\" json:\"remove_auth_in_url,omitempty\"`\n\tGroupIds                        []string            `protobuf:\"bytes,12,rep,name=group_ids,json=groupIds,proto3\" json:\"group_ids,omitempty\"`\n\tClonePath                       string              `protobuf:\"bytes,13,opt,name=clone_path,json=clonePath,proto3\" json:\"clone_path,omitempty\"`\n\tNoCleanup                       bool                `protobuf:\"varint,14,opt,name=no_cleanup,json=noCleanup,proto3\" json:\"no_cleanup,omitempty\"`\n\tPrintLegacyJson                 bool                `protobuf:\"varint,15,opt,name=print_legacy_json,json=printLegacyJson,proto3\" json:\"print_legacy_json,omitempty\"`\n}\n\nfunc (x *GitLab) Reset() {\n\t*x = GitLab{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[12]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GitLab) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GitLab) ProtoMessage() {}\n\nfunc (x *GitLab) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[12]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GitLab.ProtoReflect.Descriptor instead.\nfunc (*GitLab) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{12}\n}\n\nfunc (x *GitLab) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (m *GitLab) GetCredential() isGitLab_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *GitLab) GetToken() string {\n\tif x, ok := x.GetCredential().(*GitLab_Token); ok {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\nfunc (x *GitLab) GetOauth() *credentialspb.Oauth2 {\n\tif x, ok := x.GetCredential().(*GitLab_Oauth); ok {\n\t\treturn x.Oauth\n\t}\n\treturn nil\n}\n\nfunc (x *GitLab) GetBasicAuth() *credentialspb.BasicAuth {\n\tif x, ok := x.GetCredential().(*GitLab_BasicAuth); ok {\n\t\treturn x.BasicAuth\n\t}\n\treturn nil\n}\n\nfunc (x *GitLab) GetRepositories() []string {\n\tif x != nil {\n\t\treturn x.Repositories\n\t}\n\treturn nil\n}\n\nfunc (x *GitLab) GetIgnoreRepos() []string {\n\tif x != nil {\n\t\treturn x.IgnoreRepos\n\t}\n\treturn nil\n}\n\nfunc (x *GitLab) GetSkipBinaries() bool {\n\tif x != nil {\n\t\treturn x.SkipBinaries\n\t}\n\treturn false\n}\n\nfunc (x *GitLab) GetSkipArchives() bool {\n\tif x != nil {\n\t\treturn x.SkipArchives\n\t}\n\treturn false\n}\n\nfunc (x *GitLab) GetIncludeRepos() []string {\n\tif x != nil {\n\t\treturn x.IncludeRepos\n\t}\n\treturn nil\n}\n\nfunc (x *GitLab) GetExcludeProjectsSharedIntoGroups() bool {\n\tif x != nil {\n\t\treturn x.ExcludeProjectsSharedIntoGroups\n\t}\n\treturn false\n}\n\nfunc (x *GitLab) GetRemoveAuthInUrl() bool {\n\tif x != nil {\n\t\treturn x.RemoveAuthInUrl\n\t}\n\treturn false\n}\n\nfunc (x *GitLab) GetGroupIds() []string {\n\tif x != nil {\n\t\treturn x.GroupIds\n\t}\n\treturn nil\n}\n\nfunc (x *GitLab) GetClonePath() string {\n\tif x != nil {\n\t\treturn x.ClonePath\n\t}\n\treturn \"\"\n}\n\nfunc (x *GitLab) GetNoCleanup() bool {\n\tif x != nil {\n\t\treturn x.NoCleanup\n\t}\n\treturn false\n}\n\nfunc (x *GitLab) GetPrintLegacyJson() bool {\n\tif x != nil {\n\t\treturn x.PrintLegacyJson\n\t}\n\treturn false\n}\n\ntype isGitLab_Credential interface {\n\tisGitLab_Credential()\n}\n\ntype GitLab_Token struct {\n\tToken string `protobuf:\"bytes,2,opt,name=token,proto3,oneof\"`\n}\n\ntype GitLab_Oauth struct {\n\tOauth *credentialspb.Oauth2 `protobuf:\"bytes,3,opt,name=oauth,proto3,oneof\"`\n}\n\ntype GitLab_BasicAuth struct {\n\tBasicAuth *credentialspb.BasicAuth `protobuf:\"bytes,4,opt,name=basic_auth,json=basicAuth,proto3,oneof\"`\n}\n\nfunc (*GitLab_Token) isGitLab_Credential() {}\n\nfunc (*GitLab_Oauth) isGitLab_Credential() {}\n\nfunc (*GitLab_BasicAuth) isGitLab_Credential() {}\n\ntype GitHub struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEndpoint string `protobuf:\"bytes,1,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*GitHub_GithubApp\n\t//\t*GitHub_Token\n\t//\t*GitHub_Unauthenticated\n\t//\t*GitHub_BasicAuth\n\tCredential                 isGitHub_Credential `protobuf_oneof:\"credential\"`\n\tRepositories               []string            `protobuf:\"bytes,5,rep,name=repositories,proto3\" json:\"repositories,omitempty\"`\n\tOrganizations              []string            `protobuf:\"bytes,6,rep,name=organizations,proto3\" json:\"organizations,omitempty\"`\n\tScanUsers                  bool                `protobuf:\"varint,7,opt,name=scanUsers,proto3\" json:\"scanUsers,omitempty\"`\n\tIncludeForks               bool                `protobuf:\"varint,8,opt,name=includeForks,proto3\" json:\"includeForks,omitempty\"`\n\tHead                       string              `protobuf:\"bytes,9,opt,name=head,proto3\" json:\"head,omitempty\"`\n\tBase                       string              `protobuf:\"bytes,10,opt,name=base,proto3\" json:\"base,omitempty\"`\n\tIgnoreRepos                []string            `protobuf:\"bytes,11,rep,name=ignore_repos,json=ignoreRepos,proto3\" json:\"ignore_repos,omitempty\"`\n\tIncludeRepos               []string            `protobuf:\"bytes,12,rep,name=include_repos,json=includeRepos,proto3\" json:\"include_repos,omitempty\"`\n\tIncludePullRequestComments bool                `protobuf:\"varint,14,opt,name=include_pull_request_comments,json=includePullRequestComments,proto3\" json:\"include_pull_request_comments,omitempty\"`\n\tIncludeIssueComments       bool                `protobuf:\"varint,15,opt,name=include_issue_comments,json=includeIssueComments,proto3\" json:\"include_issue_comments,omitempty\"`\n\tIncludeGistComments        bool                `protobuf:\"varint,16,opt,name=include_gist_comments,json=includeGistComments,proto3\" json:\"include_gist_comments,omitempty\"`\n\tSkipBinaries               bool                `protobuf:\"varint,17,opt,name=skip_binaries,json=skipBinaries,proto3\" json:\"skip_binaries,omitempty\"`\n\tSkipArchives               bool                `protobuf:\"varint,18,opt,name=skip_archives,json=skipArchives,proto3\" json:\"skip_archives,omitempty\"`\n\tIncludeWikis               bool                `protobuf:\"varint,19,opt,name=include_wikis,json=includeWikis,proto3\" json:\"include_wikis,omitempty\"`\n\tCommentsTimeframeDays      uint32              `protobuf:\"varint,20,opt,name=comments_timeframe_days,json=commentsTimeframeDays,proto3\" json:\"comments_timeframe_days,omitempty\"`\n\tRemoveAuthInUrl            bool                `protobuf:\"varint,21,opt,name=remove_auth_in_url,json=removeAuthInUrl,proto3\" json:\"remove_auth_in_url,omitempty\"`\n\tClonePath                  string              `protobuf:\"bytes,22,opt,name=clone_path,json=clonePath,proto3\" json:\"clone_path,omitempty\"`\n\tNoCleanup                  bool                `protobuf:\"varint,23,opt,name=no_cleanup,json=noCleanup,proto3\" json:\"no_cleanup,omitempty\"`\n\tIgnoreGists                bool                `protobuf:\"varint,24,opt,name=ignore_gists,json=ignoreGists,proto3\" json:\"ignore_gists,omitempty\"`\n\tPrintLegacyJson            bool                `protobuf:\"varint,25,opt,name=print_legacy_json,json=printLegacyJson,proto3\" json:\"print_legacy_json,omitempty\"`\n}\n\nfunc (x *GitHub) Reset() {\n\t*x = GitHub{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[13]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GitHub) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GitHub) ProtoMessage() {}\n\nfunc (x *GitHub) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[13]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GitHub.ProtoReflect.Descriptor instead.\nfunc (*GitHub) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{13}\n}\n\nfunc (x *GitHub) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (m *GitHub) GetCredential() isGitHub_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *GitHub) GetGithubApp() *credentialspb.GitHubApp {\n\tif x, ok := x.GetCredential().(*GitHub_GithubApp); ok {\n\t\treturn x.GithubApp\n\t}\n\treturn nil\n}\n\nfunc (x *GitHub) GetToken() string {\n\tif x, ok := x.GetCredential().(*GitHub_Token); ok {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\nfunc (x *GitHub) GetUnauthenticated() *credentialspb.Unauthenticated {\n\tif x, ok := x.GetCredential().(*GitHub_Unauthenticated); ok {\n\t\treturn x.Unauthenticated\n\t}\n\treturn nil\n}\n\nfunc (x *GitHub) GetBasicAuth() *credentialspb.BasicAuth {\n\tif x, ok := x.GetCredential().(*GitHub_BasicAuth); ok {\n\t\treturn x.BasicAuth\n\t}\n\treturn nil\n}\n\nfunc (x *GitHub) GetRepositories() []string {\n\tif x != nil {\n\t\treturn x.Repositories\n\t}\n\treturn nil\n}\n\nfunc (x *GitHub) GetOrganizations() []string {\n\tif x != nil {\n\t\treturn x.Organizations\n\t}\n\treturn nil\n}\n\nfunc (x *GitHub) GetScanUsers() bool {\n\tif x != nil {\n\t\treturn x.ScanUsers\n\t}\n\treturn false\n}\n\nfunc (x *GitHub) GetIncludeForks() bool {\n\tif x != nil {\n\t\treturn x.IncludeForks\n\t}\n\treturn false\n}\n\nfunc (x *GitHub) GetHead() string {\n\tif x != nil {\n\t\treturn x.Head\n\t}\n\treturn \"\"\n}\n\nfunc (x *GitHub) GetBase() string {\n\tif x != nil {\n\t\treturn x.Base\n\t}\n\treturn \"\"\n}\n\nfunc (x *GitHub) GetIgnoreRepos() []string {\n\tif x != nil {\n\t\treturn x.IgnoreRepos\n\t}\n\treturn nil\n}\n\nfunc (x *GitHub) GetIncludeRepos() []string {\n\tif x != nil {\n\t\treturn x.IncludeRepos\n\t}\n\treturn nil\n}\n\nfunc (x *GitHub) GetIncludePullRequestComments() bool {\n\tif x != nil {\n\t\treturn x.IncludePullRequestComments\n\t}\n\treturn false\n}\n\nfunc (x *GitHub) GetIncludeIssueComments() bool {\n\tif x != nil {\n\t\treturn x.IncludeIssueComments\n\t}\n\treturn false\n}\n\nfunc (x *GitHub) GetIncludeGistComments() bool {\n\tif x != nil {\n\t\treturn x.IncludeGistComments\n\t}\n\treturn false\n}\n\nfunc (x *GitHub) GetSkipBinaries() bool {\n\tif x != nil {\n\t\treturn x.SkipBinaries\n\t}\n\treturn false\n}\n\nfunc (x *GitHub) GetSkipArchives() bool {\n\tif x != nil {\n\t\treturn x.SkipArchives\n\t}\n\treturn false\n}\n\nfunc (x *GitHub) GetIncludeWikis() bool {\n\tif x != nil {\n\t\treturn x.IncludeWikis\n\t}\n\treturn false\n}\n\nfunc (x *GitHub) GetCommentsTimeframeDays() uint32 {\n\tif x != nil {\n\t\treturn x.CommentsTimeframeDays\n\t}\n\treturn 0\n}\n\nfunc (x *GitHub) GetRemoveAuthInUrl() bool {\n\tif x != nil {\n\t\treturn x.RemoveAuthInUrl\n\t}\n\treturn false\n}\n\nfunc (x *GitHub) GetClonePath() string {\n\tif x != nil {\n\t\treturn x.ClonePath\n\t}\n\treturn \"\"\n}\n\nfunc (x *GitHub) GetNoCleanup() bool {\n\tif x != nil {\n\t\treturn x.NoCleanup\n\t}\n\treturn false\n}\n\nfunc (x *GitHub) GetIgnoreGists() bool {\n\tif x != nil {\n\t\treturn x.IgnoreGists\n\t}\n\treturn false\n}\n\nfunc (x *GitHub) GetPrintLegacyJson() bool {\n\tif x != nil {\n\t\treturn x.PrintLegacyJson\n\t}\n\treturn false\n}\n\ntype isGitHub_Credential interface {\n\tisGitHub_Credential()\n}\n\ntype GitHub_GithubApp struct {\n\tGithubApp *credentialspb.GitHubApp `protobuf:\"bytes,2,opt,name=github_app,json=githubApp,proto3,oneof\"`\n}\n\ntype GitHub_Token struct {\n\tToken string `protobuf:\"bytes,3,opt,name=token,proto3,oneof\"`\n}\n\ntype GitHub_Unauthenticated struct {\n\tUnauthenticated *credentialspb.Unauthenticated `protobuf:\"bytes,4,opt,name=unauthenticated,proto3,oneof\"`\n}\n\ntype GitHub_BasicAuth struct {\n\tBasicAuth *credentialspb.BasicAuth `protobuf:\"bytes,13,opt,name=basic_auth,json=basicAuth,proto3,oneof\"`\n}\n\nfunc (*GitHub_GithubApp) isGitHub_Credential() {}\n\nfunc (*GitHub_Token) isGitHub_Credential() {}\n\nfunc (*GitHub_Unauthenticated) isGitHub_Credential() {}\n\nfunc (*GitHub_BasicAuth) isGitHub_Credential() {}\n\ntype GitHubExperimental struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tRepository string `protobuf:\"bytes,1,opt,name=repository,proto3\" json:\"repository,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*GitHubExperimental_Token\n\tCredential         isGitHubExperimental_Credential `protobuf_oneof:\"credential\"`\n\tObjectDiscovery    bool                            `protobuf:\"varint,3,opt,name=object_discovery,json=objectDiscovery,proto3\" json:\"object_discovery,omitempty\"`\n\tCollisionThreshold int64                           `protobuf:\"varint,4,opt,name=collision_threshold,json=collisionThreshold,proto3\" json:\"collision_threshold,omitempty\"`\n\tDeleteCachedData   bool                            `protobuf:\"varint,5,opt,name=delete_cached_data,json=deleteCachedData,proto3\" json:\"delete_cached_data,omitempty\"`\n}\n\nfunc (x *GitHubExperimental) Reset() {\n\t*x = GitHubExperimental{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[14]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GitHubExperimental) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GitHubExperimental) ProtoMessage() {}\n\nfunc (x *GitHubExperimental) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[14]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GitHubExperimental.ProtoReflect.Descriptor instead.\nfunc (*GitHubExperimental) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{14}\n}\n\nfunc (x *GitHubExperimental) GetRepository() string {\n\tif x != nil {\n\t\treturn x.Repository\n\t}\n\treturn \"\"\n}\n\nfunc (m *GitHubExperimental) GetCredential() isGitHubExperimental_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *GitHubExperimental) GetToken() string {\n\tif x, ok := x.GetCredential().(*GitHubExperimental_Token); ok {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\nfunc (x *GitHubExperimental) GetObjectDiscovery() bool {\n\tif x != nil {\n\t\treturn x.ObjectDiscovery\n\t}\n\treturn false\n}\n\nfunc (x *GitHubExperimental) GetCollisionThreshold() int64 {\n\tif x != nil {\n\t\treturn x.CollisionThreshold\n\t}\n\treturn 0\n}\n\nfunc (x *GitHubExperimental) GetDeleteCachedData() bool {\n\tif x != nil {\n\t\treturn x.DeleteCachedData\n\t}\n\treturn false\n}\n\ntype isGitHubExperimental_Credential interface {\n\tisGitHubExperimental_Credential()\n}\n\ntype GitHubExperimental_Token struct {\n\tToken string `protobuf:\"bytes,2,opt,name=token,proto3,oneof\"`\n}\n\nfunc (*GitHubExperimental_Token) isGitHubExperimental_Credential() {}\n\ntype GitHubRealtime struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tListenerPort  uint32 `protobuf:\"varint,1,opt,name=listener_port,json=listenerPort,proto3\" json:\"listener_port,omitempty\"`\n\tWebhookSecret string `protobuf:\"bytes,2,opt,name=webhook_secret,json=webhookSecret,proto3\" json:\"webhook_secret,omitempty\"`\n\tEndpoint      string `protobuf:\"bytes,3,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*GitHubRealtime_GithubApp\n\t//\t*GitHubRealtime_Token\n\t//\t*GitHubRealtime_Unauthenticated\n\t//\t*GitHubRealtime_BasicAuth\n\tCredential isGitHubRealtime_Credential `protobuf_oneof:\"credential\"`\n}\n\nfunc (x *GitHubRealtime) Reset() {\n\t*x = GitHubRealtime{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[15]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GitHubRealtime) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GitHubRealtime) ProtoMessage() {}\n\nfunc (x *GitHubRealtime) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[15]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GitHubRealtime.ProtoReflect.Descriptor instead.\nfunc (*GitHubRealtime) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{15}\n}\n\nfunc (x *GitHubRealtime) GetListenerPort() uint32 {\n\tif x != nil {\n\t\treturn x.ListenerPort\n\t}\n\treturn 0\n}\n\nfunc (x *GitHubRealtime) GetWebhookSecret() string {\n\tif x != nil {\n\t\treturn x.WebhookSecret\n\t}\n\treturn \"\"\n}\n\nfunc (x *GitHubRealtime) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (m *GitHubRealtime) GetCredential() isGitHubRealtime_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *GitHubRealtime) GetGithubApp() *credentialspb.GitHubApp {\n\tif x, ok := x.GetCredential().(*GitHubRealtime_GithubApp); ok {\n\t\treturn x.GithubApp\n\t}\n\treturn nil\n}\n\nfunc (x *GitHubRealtime) GetToken() string {\n\tif x, ok := x.GetCredential().(*GitHubRealtime_Token); ok {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\nfunc (x *GitHubRealtime) GetUnauthenticated() *credentialspb.Unauthenticated {\n\tif x, ok := x.GetCredential().(*GitHubRealtime_Unauthenticated); ok {\n\t\treturn x.Unauthenticated\n\t}\n\treturn nil\n}\n\nfunc (x *GitHubRealtime) GetBasicAuth() *credentialspb.BasicAuth {\n\tif x, ok := x.GetCredential().(*GitHubRealtime_BasicAuth); ok {\n\t\treturn x.BasicAuth\n\t}\n\treturn nil\n}\n\ntype isGitHubRealtime_Credential interface {\n\tisGitHubRealtime_Credential()\n}\n\ntype GitHubRealtime_GithubApp struct {\n\tGithubApp *credentialspb.GitHubApp `protobuf:\"bytes,4,opt,name=github_app,json=githubApp,proto3,oneof\"`\n}\n\ntype GitHubRealtime_Token struct {\n\tToken string `protobuf:\"bytes,5,opt,name=token,proto3,oneof\"`\n}\n\ntype GitHubRealtime_Unauthenticated struct {\n\tUnauthenticated *credentialspb.Unauthenticated `protobuf:\"bytes,6,opt,name=unauthenticated,proto3,oneof\"`\n}\n\ntype GitHubRealtime_BasicAuth struct {\n\tBasicAuth *credentialspb.BasicAuth `protobuf:\"bytes,7,opt,name=basic_auth,json=basicAuth,proto3,oneof\"`\n}\n\nfunc (*GitHubRealtime_GithubApp) isGitHubRealtime_Credential() {}\n\nfunc (*GitHubRealtime_Token) isGitHubRealtime_Credential() {}\n\nfunc (*GitHubRealtime_Unauthenticated) isGitHubRealtime_Credential() {}\n\nfunc (*GitHubRealtime_BasicAuth) isGitHubRealtime_Credential() {}\n\ntype GoogleDrive struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*GoogleDrive_RefreshToken\n\t//\t*GoogleDrive_UseTokenService\n\t//\t*GoogleDrive_Oauth\n\t//\t*GoogleDrive_Dwd\n\tCredential isGoogleDrive_Credential `protobuf_oneof:\"credential\"`\n}\n\nfunc (x *GoogleDrive) Reset() {\n\t*x = GoogleDrive{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[16]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *GoogleDrive) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GoogleDrive) ProtoMessage() {}\n\nfunc (x *GoogleDrive) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[16]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GoogleDrive.ProtoReflect.Descriptor instead.\nfunc (*GoogleDrive) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{16}\n}\n\nfunc (m *GoogleDrive) GetCredential() isGoogleDrive_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *GoogleDrive) GetRefreshToken() string {\n\tif x, ok := x.GetCredential().(*GoogleDrive_RefreshToken); ok {\n\t\treturn x.RefreshToken\n\t}\n\treturn \"\"\n}\n\nfunc (x *GoogleDrive) GetUseTokenService() bool {\n\tif x, ok := x.GetCredential().(*GoogleDrive_UseTokenService); ok {\n\t\treturn x.UseTokenService\n\t}\n\treturn false\n}\n\nfunc (x *GoogleDrive) GetOauth() *credentialspb.Oauth2 {\n\tif x, ok := x.GetCredential().(*GoogleDrive_Oauth); ok {\n\t\treturn x.Oauth\n\t}\n\treturn nil\n}\n\nfunc (x *GoogleDrive) GetDwd() *credentialspb.GoogleDriveDWD {\n\tif x, ok := x.GetCredential().(*GoogleDrive_Dwd); ok {\n\t\treturn x.Dwd\n\t}\n\treturn nil\n}\n\ntype isGoogleDrive_Credential interface {\n\tisGoogleDrive_Credential()\n}\n\ntype GoogleDrive_RefreshToken struct {\n\tRefreshToken string `protobuf:\"bytes,1,opt,name=refresh_token,json=refreshToken,proto3,oneof\"`\n}\n\ntype GoogleDrive_UseTokenService struct {\n\tUseTokenService bool `protobuf:\"varint,2,opt,name=use_token_service,json=useTokenService,proto3,oneof\"`\n}\n\ntype GoogleDrive_Oauth struct {\n\tOauth *credentialspb.Oauth2 `protobuf:\"bytes,3,opt,name=oauth,proto3,oneof\"` // on-prem scanning\n}\n\ntype GoogleDrive_Dwd struct {\n\tDwd *credentialspb.GoogleDriveDWD `protobuf:\"bytes,4,opt,name=dwd,proto3,oneof\"` // Domain-Wide Delegation\n}\n\nfunc (*GoogleDrive_RefreshToken) isGoogleDrive_Credential() {}\n\nfunc (*GoogleDrive_UseTokenService) isGoogleDrive_Credential() {}\n\nfunc (*GoogleDrive_Oauth) isGoogleDrive_Credential() {}\n\nfunc (*GoogleDrive_Dwd) isGoogleDrive_Credential() {}\n\ntype Huggingface struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEndpoint string `protobuf:\"bytes,1,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*Huggingface_Token\n\t//\t*Huggingface_Unauthenticated\n\tCredential         isHuggingface_Credential `protobuf_oneof:\"credential\"`\n\tModels             []string                 `protobuf:\"bytes,4,rep,name=models,proto3\" json:\"models,omitempty\"`\n\tSpaces             []string                 `protobuf:\"bytes,5,rep,name=spaces,proto3\" json:\"spaces,omitempty\"`\n\tDatasets           []string                 `protobuf:\"bytes,12,rep,name=datasets,proto3\" json:\"datasets,omitempty\"`\n\tOrganizations      []string                 `protobuf:\"bytes,6,rep,name=organizations,proto3\" json:\"organizations,omitempty\"`\n\tUsers              []string                 `protobuf:\"bytes,15,rep,name=users,proto3\" json:\"users,omitempty\"`\n\tIgnoreModels       []string                 `protobuf:\"bytes,7,rep,name=ignore_models,json=ignoreModels,proto3\" json:\"ignore_models,omitempty\"`\n\tIncludeModels      []string                 `protobuf:\"bytes,8,rep,name=include_models,json=includeModels,proto3\" json:\"include_models,omitempty\"`\n\tIgnoreSpaces       []string                 `protobuf:\"bytes,9,rep,name=ignore_spaces,json=ignoreSpaces,proto3\" json:\"ignore_spaces,omitempty\"`\n\tIncludeSpaces      []string                 `protobuf:\"bytes,10,rep,name=include_spaces,json=includeSpaces,proto3\" json:\"include_spaces,omitempty\"`\n\tIgnoreDatasets     []string                 `protobuf:\"bytes,13,rep,name=ignore_datasets,json=ignoreDatasets,proto3\" json:\"ignore_datasets,omitempty\"`\n\tIncludeDatasets    []string                 `protobuf:\"bytes,14,rep,name=include_datasets,json=includeDatasets,proto3\" json:\"include_datasets,omitempty\"`\n\tSkipAllModels      bool                     `protobuf:\"varint,16,opt,name=skip_all_models,json=skipAllModels,proto3\" json:\"skip_all_models,omitempty\"`\n\tSkipAllSpaces      bool                     `protobuf:\"varint,17,opt,name=skip_all_spaces,json=skipAllSpaces,proto3\" json:\"skip_all_spaces,omitempty\"`\n\tSkipAllDatasets    bool                     `protobuf:\"varint,18,opt,name=skip_all_datasets,json=skipAllDatasets,proto3\" json:\"skip_all_datasets,omitempty\"`\n\tIncludeDiscussions bool                     `protobuf:\"varint,11,opt,name=include_discussions,json=includeDiscussions,proto3\" json:\"include_discussions,omitempty\"`\n\tIncludePrs         bool                     `protobuf:\"varint,19,opt,name=include_prs,json=includePrs,proto3\" json:\"include_prs,omitempty\"`\n}\n\nfunc (x *Huggingface) Reset() {\n\t*x = Huggingface{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[17]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Huggingface) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Huggingface) ProtoMessage() {}\n\nfunc (x *Huggingface) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[17]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Huggingface.ProtoReflect.Descriptor instead.\nfunc (*Huggingface) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{17}\n}\n\nfunc (x *Huggingface) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (m *Huggingface) GetCredential() isHuggingface_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *Huggingface) GetToken() string {\n\tif x, ok := x.GetCredential().(*Huggingface_Token); ok {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\nfunc (x *Huggingface) GetUnauthenticated() *credentialspb.Unauthenticated {\n\tif x, ok := x.GetCredential().(*Huggingface_Unauthenticated); ok {\n\t\treturn x.Unauthenticated\n\t}\n\treturn nil\n}\n\nfunc (x *Huggingface) GetModels() []string {\n\tif x != nil {\n\t\treturn x.Models\n\t}\n\treturn nil\n}\n\nfunc (x *Huggingface) GetSpaces() []string {\n\tif x != nil {\n\t\treturn x.Spaces\n\t}\n\treturn nil\n}\n\nfunc (x *Huggingface) GetDatasets() []string {\n\tif x != nil {\n\t\treturn x.Datasets\n\t}\n\treturn nil\n}\n\nfunc (x *Huggingface) GetOrganizations() []string {\n\tif x != nil {\n\t\treturn x.Organizations\n\t}\n\treturn nil\n}\n\nfunc (x *Huggingface) GetUsers() []string {\n\tif x != nil {\n\t\treturn x.Users\n\t}\n\treturn nil\n}\n\nfunc (x *Huggingface) GetIgnoreModels() []string {\n\tif x != nil {\n\t\treturn x.IgnoreModels\n\t}\n\treturn nil\n}\n\nfunc (x *Huggingface) GetIncludeModels() []string {\n\tif x != nil {\n\t\treturn x.IncludeModels\n\t}\n\treturn nil\n}\n\nfunc (x *Huggingface) GetIgnoreSpaces() []string {\n\tif x != nil {\n\t\treturn x.IgnoreSpaces\n\t}\n\treturn nil\n}\n\nfunc (x *Huggingface) GetIncludeSpaces() []string {\n\tif x != nil {\n\t\treturn x.IncludeSpaces\n\t}\n\treturn nil\n}\n\nfunc (x *Huggingface) GetIgnoreDatasets() []string {\n\tif x != nil {\n\t\treturn x.IgnoreDatasets\n\t}\n\treturn nil\n}\n\nfunc (x *Huggingface) GetIncludeDatasets() []string {\n\tif x != nil {\n\t\treturn x.IncludeDatasets\n\t}\n\treturn nil\n}\n\nfunc (x *Huggingface) GetSkipAllModels() bool {\n\tif x != nil {\n\t\treturn x.SkipAllModels\n\t}\n\treturn false\n}\n\nfunc (x *Huggingface) GetSkipAllSpaces() bool {\n\tif x != nil {\n\t\treturn x.SkipAllSpaces\n\t}\n\treturn false\n}\n\nfunc (x *Huggingface) GetSkipAllDatasets() bool {\n\tif x != nil {\n\t\treturn x.SkipAllDatasets\n\t}\n\treturn false\n}\n\nfunc (x *Huggingface) GetIncludeDiscussions() bool {\n\tif x != nil {\n\t\treturn x.IncludeDiscussions\n\t}\n\treturn false\n}\n\nfunc (x *Huggingface) GetIncludePrs() bool {\n\tif x != nil {\n\t\treturn x.IncludePrs\n\t}\n\treturn false\n}\n\ntype isHuggingface_Credential interface {\n\tisHuggingface_Credential()\n}\n\ntype Huggingface_Token struct {\n\tToken string `protobuf:\"bytes,2,opt,name=token,proto3,oneof\"`\n}\n\ntype Huggingface_Unauthenticated struct {\n\tUnauthenticated *credentialspb.Unauthenticated `protobuf:\"bytes,3,opt,name=unauthenticated,proto3,oneof\"`\n}\n\nfunc (*Huggingface_Token) isHuggingface_Credential() {}\n\nfunc (*Huggingface_Unauthenticated) isHuggingface_Credential() {}\n\ntype JIRA struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEndpoint string `protobuf:\"bytes,1,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*JIRA_BasicAuth\n\t//\t*JIRA_Unauthenticated\n\t//\t*JIRA_Oauth\n\t//\t*JIRA_Token\n\tCredential            isJIRA_Credential `protobuf_oneof:\"credential\"`\n\tProjects              []string          `protobuf:\"bytes,5,rep,name=projects,proto3\" json:\"projects,omitempty\"`\n\tIgnoreProjects        []string          `protobuf:\"bytes,7,rep,name=ignore_projects,json=ignoreProjects,proto3\" json:\"ignore_projects,omitempty\"`\n\tInsecureSkipVerifyTls bool              `protobuf:\"varint,8,opt,name=insecure_skip_verify_tls,json=insecureSkipVerifyTls,proto3\" json:\"insecure_skip_verify_tls,omitempty\"`\n\t// Override autodetection of Jira installation type.\n\t// Defaults to AUTODETECT which checks for cloud-only endpoints.\n\tInstallationType JiraInstallationType `protobuf:\"varint,9,opt,name=installation_type,json=installationType,proto3,enum=sources.JiraInstallationType\" json:\"installation_type,omitempty\"`\n}\n\nfunc (x *JIRA) Reset() {\n\t*x = JIRA{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[18]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *JIRA) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*JIRA) ProtoMessage() {}\n\nfunc (x *JIRA) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[18]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use JIRA.ProtoReflect.Descriptor instead.\nfunc (*JIRA) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{18}\n}\n\nfunc (x *JIRA) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (m *JIRA) GetCredential() isJIRA_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *JIRA) GetBasicAuth() *credentialspb.BasicAuth {\n\tif x, ok := x.GetCredential().(*JIRA_BasicAuth); ok {\n\t\treturn x.BasicAuth\n\t}\n\treturn nil\n}\n\nfunc (x *JIRA) GetUnauthenticated() *credentialspb.Unauthenticated {\n\tif x, ok := x.GetCredential().(*JIRA_Unauthenticated); ok {\n\t\treturn x.Unauthenticated\n\t}\n\treturn nil\n}\n\nfunc (x *JIRA) GetOauth() *credentialspb.Oauth2 {\n\tif x, ok := x.GetCredential().(*JIRA_Oauth); ok {\n\t\treturn x.Oauth\n\t}\n\treturn nil\n}\n\nfunc (x *JIRA) GetToken() string {\n\tif x, ok := x.GetCredential().(*JIRA_Token); ok {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\nfunc (x *JIRA) GetProjects() []string {\n\tif x != nil {\n\t\treturn x.Projects\n\t}\n\treturn nil\n}\n\nfunc (x *JIRA) GetIgnoreProjects() []string {\n\tif x != nil {\n\t\treturn x.IgnoreProjects\n\t}\n\treturn nil\n}\n\nfunc (x *JIRA) GetInsecureSkipVerifyTls() bool {\n\tif x != nil {\n\t\treturn x.InsecureSkipVerifyTls\n\t}\n\treturn false\n}\n\nfunc (x *JIRA) GetInstallationType() JiraInstallationType {\n\tif x != nil {\n\t\treturn x.InstallationType\n\t}\n\treturn JiraInstallationType_JIRA_INSTALLATION_TYPE_AUTODETECT\n}\n\ntype isJIRA_Credential interface {\n\tisJIRA_Credential()\n}\n\ntype JIRA_BasicAuth struct {\n\tBasicAuth *credentialspb.BasicAuth `protobuf:\"bytes,2,opt,name=basic_auth,json=basicAuth,proto3,oneof\"`\n}\n\ntype JIRA_Unauthenticated struct {\n\tUnauthenticated *credentialspb.Unauthenticated `protobuf:\"bytes,3,opt,name=unauthenticated,proto3,oneof\"`\n}\n\ntype JIRA_Oauth struct {\n\tOauth *credentialspb.Oauth2 `protobuf:\"bytes,4,opt,name=oauth,proto3,oneof\"`\n}\n\ntype JIRA_Token struct {\n\tToken string `protobuf:\"bytes,6,opt,name=token,proto3,oneof\"`\n}\n\nfunc (*JIRA_BasicAuth) isJIRA_Credential() {}\n\nfunc (*JIRA_Unauthenticated) isJIRA_Credential() {}\n\nfunc (*JIRA_Oauth) isJIRA_Credential() {}\n\nfunc (*JIRA_Token) isJIRA_Credential() {}\n\ntype NPMUnauthenticatedPackage struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*NPMUnauthenticatedPackage_Unauthenticated\n\tCredential isNPMUnauthenticatedPackage_Credential `protobuf_oneof:\"credential\"`\n}\n\nfunc (x *NPMUnauthenticatedPackage) Reset() {\n\t*x = NPMUnauthenticatedPackage{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[19]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *NPMUnauthenticatedPackage) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NPMUnauthenticatedPackage) ProtoMessage() {}\n\nfunc (x *NPMUnauthenticatedPackage) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[19]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NPMUnauthenticatedPackage.ProtoReflect.Descriptor instead.\nfunc (*NPMUnauthenticatedPackage) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{19}\n}\n\nfunc (m *NPMUnauthenticatedPackage) GetCredential() isNPMUnauthenticatedPackage_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *NPMUnauthenticatedPackage) GetUnauthenticated() *credentialspb.Unauthenticated {\n\tif x, ok := x.GetCredential().(*NPMUnauthenticatedPackage_Unauthenticated); ok {\n\t\treturn x.Unauthenticated\n\t}\n\treturn nil\n}\n\ntype isNPMUnauthenticatedPackage_Credential interface {\n\tisNPMUnauthenticatedPackage_Credential()\n}\n\ntype NPMUnauthenticatedPackage_Unauthenticated struct {\n\tUnauthenticated *credentialspb.Unauthenticated `protobuf:\"bytes,1,opt,name=unauthenticated,proto3,oneof\"`\n}\n\nfunc (*NPMUnauthenticatedPackage_Unauthenticated) isNPMUnauthenticatedPackage_Credential() {}\n\ntype PyPIUnauthenticatedPackage struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*PyPIUnauthenticatedPackage_Unauthenticated\n\tCredential isPyPIUnauthenticatedPackage_Credential `protobuf_oneof:\"credential\"`\n}\n\nfunc (x *PyPIUnauthenticatedPackage) Reset() {\n\t*x = PyPIUnauthenticatedPackage{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[20]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *PyPIUnauthenticatedPackage) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PyPIUnauthenticatedPackage) ProtoMessage() {}\n\nfunc (x *PyPIUnauthenticatedPackage) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[20]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PyPIUnauthenticatedPackage.ProtoReflect.Descriptor instead.\nfunc (*PyPIUnauthenticatedPackage) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{20}\n}\n\nfunc (m *PyPIUnauthenticatedPackage) GetCredential() isPyPIUnauthenticatedPackage_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *PyPIUnauthenticatedPackage) GetUnauthenticated() *credentialspb.Unauthenticated {\n\tif x, ok := x.GetCredential().(*PyPIUnauthenticatedPackage_Unauthenticated); ok {\n\t\treturn x.Unauthenticated\n\t}\n\treturn nil\n}\n\ntype isPyPIUnauthenticatedPackage_Credential interface {\n\tisPyPIUnauthenticatedPackage_Credential()\n}\n\ntype PyPIUnauthenticatedPackage_Unauthenticated struct {\n\tUnauthenticated *credentialspb.Unauthenticated `protobuf:\"bytes,1,opt,name=unauthenticated,proto3,oneof\"`\n}\n\nfunc (*PyPIUnauthenticatedPackage_Unauthenticated) isPyPIUnauthenticatedPackage_Credential() {}\n\ntype S3 struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*S3_AccessKey\n\t//\t*S3_Unauthenticated\n\t//\t*S3_CloudEnvironment\n\t//\t*S3_SessionToken\n\tCredential    isS3_Credential `protobuf_oneof:\"credential\"`\n\tBuckets       []string        `protobuf:\"bytes,3,rep,name=buckets,proto3\" json:\"buckets,omitempty\"`\n\tMaxObjectSize int64           `protobuf:\"varint,6,opt,name=max_object_size,json=maxObjectSize,proto3\" json:\"max_object_size,omitempty\"`\n\tRoles         []string        `protobuf:\"bytes,7,rep,name=roles,proto3\" json:\"roles,omitempty\"`\n\tIgnoreBuckets []string        `protobuf:\"bytes,8,rep,name=ignore_buckets,json=ignoreBuckets,proto3\" json:\"ignore_buckets,omitempty\"`\n\t// Deprecated: Marked as deprecated in sources.proto.\n\tEnableResumption bool `protobuf:\"varint,9,opt,name=enable_resumption,json=enableResumption,proto3\" json:\"enable_resumption,omitempty\"`\n}\n\nfunc (x *S3) Reset() {\n\t*x = S3{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[21]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *S3) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*S3) ProtoMessage() {}\n\nfunc (x *S3) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[21]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use S3.ProtoReflect.Descriptor instead.\nfunc (*S3) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{21}\n}\n\nfunc (m *S3) GetCredential() isS3_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *S3) GetAccessKey() *credentialspb.KeySecret {\n\tif x, ok := x.GetCredential().(*S3_AccessKey); ok {\n\t\treturn x.AccessKey\n\t}\n\treturn nil\n}\n\nfunc (x *S3) GetUnauthenticated() *credentialspb.Unauthenticated {\n\tif x, ok := x.GetCredential().(*S3_Unauthenticated); ok {\n\t\treturn x.Unauthenticated\n\t}\n\treturn nil\n}\n\nfunc (x *S3) GetCloudEnvironment() *credentialspb.CloudEnvironment {\n\tif x, ok := x.GetCredential().(*S3_CloudEnvironment); ok {\n\t\treturn x.CloudEnvironment\n\t}\n\treturn nil\n}\n\nfunc (x *S3) GetSessionToken() *credentialspb.AWSSessionTokenSecret {\n\tif x, ok := x.GetCredential().(*S3_SessionToken); ok {\n\t\treturn x.SessionToken\n\t}\n\treturn nil\n}\n\nfunc (x *S3) GetBuckets() []string {\n\tif x != nil {\n\t\treturn x.Buckets\n\t}\n\treturn nil\n}\n\nfunc (x *S3) GetMaxObjectSize() int64 {\n\tif x != nil {\n\t\treturn x.MaxObjectSize\n\t}\n\treturn 0\n}\n\nfunc (x *S3) GetRoles() []string {\n\tif x != nil {\n\t\treturn x.Roles\n\t}\n\treturn nil\n}\n\nfunc (x *S3) GetIgnoreBuckets() []string {\n\tif x != nil {\n\t\treturn x.IgnoreBuckets\n\t}\n\treturn nil\n}\n\n// Deprecated: Marked as deprecated in sources.proto.\nfunc (x *S3) GetEnableResumption() bool {\n\tif x != nil {\n\t\treturn x.EnableResumption\n\t}\n\treturn false\n}\n\ntype isS3_Credential interface {\n\tisS3_Credential()\n}\n\ntype S3_AccessKey struct {\n\tAccessKey *credentialspb.KeySecret `protobuf:\"bytes,1,opt,name=access_key,json=accessKey,proto3,oneof\"`\n}\n\ntype S3_Unauthenticated struct {\n\tUnauthenticated *credentialspb.Unauthenticated `protobuf:\"bytes,2,opt,name=unauthenticated,proto3,oneof\"`\n}\n\ntype S3_CloudEnvironment struct {\n\tCloudEnvironment *credentialspb.CloudEnvironment `protobuf:\"bytes,4,opt,name=cloud_environment,json=cloudEnvironment,proto3,oneof\"`\n}\n\ntype S3_SessionToken struct {\n\tSessionToken *credentialspb.AWSSessionTokenSecret `protobuf:\"bytes,5,opt,name=session_token,json=sessionToken,proto3,oneof\"`\n}\n\nfunc (*S3_AccessKey) isS3_Credential() {}\n\nfunc (*S3_Unauthenticated) isS3_Credential() {}\n\nfunc (*S3_CloudEnvironment) isS3_Credential() {}\n\nfunc (*S3_SessionToken) isS3_Credential() {}\n\ntype Slack struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEndpoint string `protobuf:\"bytes,1,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*Slack_Token\n\t//\t*Slack_Tokens\n\tCredential isSlack_Credential `protobuf_oneof:\"credential\"`\n\tChannels   []string           `protobuf:\"bytes,3,rep,name=channels,proto3\" json:\"channels,omitempty\"`\n\tIgnoreList []string           `protobuf:\"bytes,4,rep,name=ignore_list,json=ignoreList,proto3\" json:\"ignore_list,omitempty\"`\n}\n\nfunc (x *Slack) Reset() {\n\t*x = Slack{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[22]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Slack) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Slack) ProtoMessage() {}\n\nfunc (x *Slack) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[22]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Slack.ProtoReflect.Descriptor instead.\nfunc (*Slack) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{22}\n}\n\nfunc (x *Slack) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (m *Slack) GetCredential() isSlack_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *Slack) GetToken() string {\n\tif x, ok := x.GetCredential().(*Slack_Token); ok {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\nfunc (x *Slack) GetTokens() *credentialspb.SlackTokens {\n\tif x, ok := x.GetCredential().(*Slack_Tokens); ok {\n\t\treturn x.Tokens\n\t}\n\treturn nil\n}\n\nfunc (x *Slack) GetChannels() []string {\n\tif x != nil {\n\t\treturn x.Channels\n\t}\n\treturn nil\n}\n\nfunc (x *Slack) GetIgnoreList() []string {\n\tif x != nil {\n\t\treturn x.IgnoreList\n\t}\n\treturn nil\n}\n\ntype isSlack_Credential interface {\n\tisSlack_Credential()\n}\n\ntype Slack_Token struct {\n\tToken string `protobuf:\"bytes,2,opt,name=token,proto3,oneof\"`\n}\n\ntype Slack_Tokens struct {\n\tTokens *credentialspb.SlackTokens `protobuf:\"bytes,5,opt,name=tokens,proto3,oneof\"`\n}\n\nfunc (*Slack_Token) isSlack_Credential() {}\n\nfunc (*Slack_Tokens) isSlack_Credential() {}\n\ntype Test struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *Test) Reset() {\n\t*x = Test{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[23]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Test) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Test) ProtoMessage() {}\n\nfunc (x *Test) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[23]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Test.ProtoReflect.Descriptor instead.\nfunc (*Test) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{23}\n}\n\ntype Buildkite struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*Buildkite_Token\n\tCredential isBuildkite_Credential `protobuf_oneof:\"credential\"`\n}\n\nfunc (x *Buildkite) Reset() {\n\t*x = Buildkite{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[24]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Buildkite) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Buildkite) ProtoMessage() {}\n\nfunc (x *Buildkite) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[24]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Buildkite.ProtoReflect.Descriptor instead.\nfunc (*Buildkite) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{24}\n}\n\nfunc (m *Buildkite) GetCredential() isBuildkite_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *Buildkite) GetToken() string {\n\tif x, ok := x.GetCredential().(*Buildkite_Token); ok {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\ntype isBuildkite_Credential interface {\n\tisBuildkite_Credential()\n}\n\ntype Buildkite_Token struct {\n\tToken string `protobuf:\"bytes,1,opt,name=token,proto3,oneof\"`\n}\n\nfunc (*Buildkite_Token) isBuildkite_Credential() {}\n\ntype Gerrit struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEndpoint string `protobuf:\"bytes,1,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*Gerrit_BasicAuth\n\t//\t*Gerrit_Unauthenticated\n\tCredential   isGerrit_Credential `protobuf_oneof:\"credential\"`\n\tProjects     []string            `protobuf:\"bytes,4,rep,name=projects,proto3\" json:\"projects,omitempty\"`\n\tSkipBinaries bool                `protobuf:\"varint,5,opt,name=skip_binaries,json=skipBinaries,proto3\" json:\"skip_binaries,omitempty\"`\n\tSkipArchives bool                `protobuf:\"varint,6,opt,name=skip_archives,json=skipArchives,proto3\" json:\"skip_archives,omitempty\"`\n}\n\nfunc (x *Gerrit) Reset() {\n\t*x = Gerrit{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[25]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Gerrit) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Gerrit) ProtoMessage() {}\n\nfunc (x *Gerrit) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[25]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Gerrit.ProtoReflect.Descriptor instead.\nfunc (*Gerrit) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{25}\n}\n\nfunc (x *Gerrit) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (m *Gerrit) GetCredential() isGerrit_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *Gerrit) GetBasicAuth() *credentialspb.BasicAuth {\n\tif x, ok := x.GetCredential().(*Gerrit_BasicAuth); ok {\n\t\treturn x.BasicAuth\n\t}\n\treturn nil\n}\n\nfunc (x *Gerrit) GetUnauthenticated() *credentialspb.Unauthenticated {\n\tif x, ok := x.GetCredential().(*Gerrit_Unauthenticated); ok {\n\t\treturn x.Unauthenticated\n\t}\n\treturn nil\n}\n\nfunc (x *Gerrit) GetProjects() []string {\n\tif x != nil {\n\t\treturn x.Projects\n\t}\n\treturn nil\n}\n\nfunc (x *Gerrit) GetSkipBinaries() bool {\n\tif x != nil {\n\t\treturn x.SkipBinaries\n\t}\n\treturn false\n}\n\nfunc (x *Gerrit) GetSkipArchives() bool {\n\tif x != nil {\n\t\treturn x.SkipArchives\n\t}\n\treturn false\n}\n\ntype isGerrit_Credential interface {\n\tisGerrit_Credential()\n}\n\ntype Gerrit_BasicAuth struct {\n\tBasicAuth *credentialspb.BasicAuth `protobuf:\"bytes,2,opt,name=basic_auth,json=basicAuth,proto3,oneof\"`\n}\n\ntype Gerrit_Unauthenticated struct {\n\tUnauthenticated *credentialspb.Unauthenticated `protobuf:\"bytes,3,opt,name=unauthenticated,proto3,oneof\"`\n}\n\nfunc (*Gerrit_BasicAuth) isGerrit_Credential() {}\n\nfunc (*Gerrit_Unauthenticated) isGerrit_Credential() {}\n\ntype Jenkins struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEndpoint string `protobuf:\"bytes,1,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*Jenkins_BasicAuth\n\t//\t*Jenkins_Header\n\t//\t*Jenkins_Unauthenticated\n\tCredential            isJenkins_Credential `protobuf_oneof:\"credential\"`\n\tInsecureSkipVerifyTls bool                 `protobuf:\"varint,4,opt,name=insecure_skip_verify_tls,json=insecureSkipVerifyTls,proto3\" json:\"insecure_skip_verify_tls,omitempty\"`\n}\n\nfunc (x *Jenkins) Reset() {\n\t*x = Jenkins{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[26]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Jenkins) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Jenkins) ProtoMessage() {}\n\nfunc (x *Jenkins) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[26]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Jenkins.ProtoReflect.Descriptor instead.\nfunc (*Jenkins) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{26}\n}\n\nfunc (x *Jenkins) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (m *Jenkins) GetCredential() isJenkins_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *Jenkins) GetBasicAuth() *credentialspb.BasicAuth {\n\tif x, ok := x.GetCredential().(*Jenkins_BasicAuth); ok {\n\t\treturn x.BasicAuth\n\t}\n\treturn nil\n}\n\nfunc (x *Jenkins) GetHeader() *credentialspb.Header {\n\tif x, ok := x.GetCredential().(*Jenkins_Header); ok {\n\t\treturn x.Header\n\t}\n\treturn nil\n}\n\nfunc (x *Jenkins) GetUnauthenticated() *credentialspb.Unauthenticated {\n\tif x, ok := x.GetCredential().(*Jenkins_Unauthenticated); ok {\n\t\treturn x.Unauthenticated\n\t}\n\treturn nil\n}\n\nfunc (x *Jenkins) GetInsecureSkipVerifyTls() bool {\n\tif x != nil {\n\t\treturn x.InsecureSkipVerifyTls\n\t}\n\treturn false\n}\n\ntype isJenkins_Credential interface {\n\tisJenkins_Credential()\n}\n\ntype Jenkins_BasicAuth struct {\n\tBasicAuth *credentialspb.BasicAuth `protobuf:\"bytes,2,opt,name=basic_auth,json=basicAuth,proto3,oneof\"`\n}\n\ntype Jenkins_Header struct {\n\tHeader *credentialspb.Header `protobuf:\"bytes,3,opt,name=header,proto3,oneof\"`\n}\n\ntype Jenkins_Unauthenticated struct {\n\tUnauthenticated *credentialspb.Unauthenticated `protobuf:\"bytes,5,opt,name=unauthenticated,proto3,oneof\"`\n}\n\nfunc (*Jenkins_BasicAuth) isJenkins_Credential() {}\n\nfunc (*Jenkins_Header) isJenkins_Credential() {}\n\nfunc (*Jenkins_Unauthenticated) isJenkins_Credential() {}\n\ntype Teams struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEndpoint string `protobuf:\"bytes,1,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*Teams_Token\n\t//\t*Teams_Authenticated\n\t//\t*Teams_Oauth\n\tCredential isTeams_Credential `protobuf_oneof:\"credential\"`\n\tChannels   []string           `protobuf:\"bytes,4,rep,name=channels,proto3\" json:\"channels,omitempty\"`\n\tIgnoreList []string           `protobuf:\"bytes,5,rep,name=ignore_list,json=ignoreList,proto3\" json:\"ignore_list,omitempty\"`\n\tTeamIds    []string           `protobuf:\"bytes,6,rep,name=team_ids,json=teamIds,proto3\" json:\"team_ids,omitempty\"`\n\tTenantId   string             `protobuf:\"bytes,8,opt,name=tenant_id,json=tenantId,proto3\" json:\"tenant_id,omitempty\"`\n}\n\nfunc (x *Teams) Reset() {\n\t*x = Teams{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[27]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Teams) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Teams) ProtoMessage() {}\n\nfunc (x *Teams) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[27]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Teams.ProtoReflect.Descriptor instead.\nfunc (*Teams) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{27}\n}\n\nfunc (x *Teams) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (m *Teams) GetCredential() isTeams_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *Teams) GetToken() string {\n\tif x, ok := x.GetCredential().(*Teams_Token); ok {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\nfunc (x *Teams) GetAuthenticated() *credentialspb.ClientCredentials {\n\tif x, ok := x.GetCredential().(*Teams_Authenticated); ok {\n\t\treturn x.Authenticated\n\t}\n\treturn nil\n}\n\nfunc (x *Teams) GetOauth() *credentialspb.Oauth2 {\n\tif x, ok := x.GetCredential().(*Teams_Oauth); ok {\n\t\treturn x.Oauth\n\t}\n\treturn nil\n}\n\nfunc (x *Teams) GetChannels() []string {\n\tif x != nil {\n\t\treturn x.Channels\n\t}\n\treturn nil\n}\n\nfunc (x *Teams) GetIgnoreList() []string {\n\tif x != nil {\n\t\treturn x.IgnoreList\n\t}\n\treturn nil\n}\n\nfunc (x *Teams) GetTeamIds() []string {\n\tif x != nil {\n\t\treturn x.TeamIds\n\t}\n\treturn nil\n}\n\nfunc (x *Teams) GetTenantId() string {\n\tif x != nil {\n\t\treturn x.TenantId\n\t}\n\treturn \"\"\n}\n\ntype isTeams_Credential interface {\n\tisTeams_Credential()\n}\n\ntype Teams_Token struct {\n\tToken string `protobuf:\"bytes,2,opt,name=token,proto3,oneof\"`\n}\n\ntype Teams_Authenticated struct {\n\tAuthenticated *credentialspb.ClientCredentials `protobuf:\"bytes,3,opt,name=authenticated,proto3,oneof\"`\n}\n\ntype Teams_Oauth struct {\n\tOauth *credentialspb.Oauth2 `protobuf:\"bytes,7,opt,name=oauth,proto3,oneof\"`\n}\n\nfunc (*Teams_Token) isTeams_Credential() {}\n\nfunc (*Teams_Authenticated) isTeams_Credential() {}\n\nfunc (*Teams_Oauth) isTeams_Credential() {}\n\ntype Syslog struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tProtocol      string `protobuf:\"bytes,1,opt,name=protocol,proto3\" json:\"protocol,omitempty\"`\n\tListenAddress string `protobuf:\"bytes,2,opt,name=listen_address,json=listenAddress,proto3\" json:\"listen_address,omitempty\"`\n\tTlsCert       string `protobuf:\"bytes,3,opt,name=tlsCert,proto3\" json:\"tlsCert,omitempty\"`\n\tTlsKey        string `protobuf:\"bytes,4,opt,name=tlsKey,proto3\" json:\"tlsKey,omitempty\"`\n\tFormat        string `protobuf:\"bytes,5,opt,name=format,proto3\" json:\"format,omitempty\"`\n}\n\nfunc (x *Syslog) Reset() {\n\t*x = Syslog{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[28]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Syslog) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Syslog) ProtoMessage() {}\n\nfunc (x *Syslog) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[28]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Syslog.ProtoReflect.Descriptor instead.\nfunc (*Syslog) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{28}\n}\n\nfunc (x *Syslog) GetProtocol() string {\n\tif x != nil {\n\t\treturn x.Protocol\n\t}\n\treturn \"\"\n}\n\nfunc (x *Syslog) GetListenAddress() string {\n\tif x != nil {\n\t\treturn x.ListenAddress\n\t}\n\treturn \"\"\n}\n\nfunc (x *Syslog) GetTlsCert() string {\n\tif x != nil {\n\t\treturn x.TlsCert\n\t}\n\treturn \"\"\n}\n\nfunc (x *Syslog) GetTlsKey() string {\n\tif x != nil {\n\t\treturn x.TlsKey\n\t}\n\treturn \"\"\n}\n\nfunc (x *Syslog) GetFormat() string {\n\tif x != nil {\n\t\treturn x.Format\n\t}\n\treturn \"\"\n}\n\ntype Forager struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*Forager_Unauthenticated\n\tCredential isForager_Credential   `protobuf_oneof:\"credential\"`\n\tDomains    []string               `protobuf:\"bytes,2,rep,name=domains,proto3\" json:\"domains,omitempty\"`\n\tMaxDepth   int64                  `protobuf:\"varint,3,opt,name=max_depth,json=maxDepth,proto3\" json:\"max_depth,omitempty\"`\n\tSince      *timestamppb.Timestamp `protobuf:\"bytes,4,opt,name=since,proto3\" json:\"since,omitempty\"`\n}\n\nfunc (x *Forager) Reset() {\n\t*x = Forager{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[29]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Forager) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Forager) ProtoMessage() {}\n\nfunc (x *Forager) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[29]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Forager.ProtoReflect.Descriptor instead.\nfunc (*Forager) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{29}\n}\n\nfunc (m *Forager) GetCredential() isForager_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *Forager) GetUnauthenticated() *credentialspb.Unauthenticated {\n\tif x, ok := x.GetCredential().(*Forager_Unauthenticated); ok {\n\t\treturn x.Unauthenticated\n\t}\n\treturn nil\n}\n\nfunc (x *Forager) GetDomains() []string {\n\tif x != nil {\n\t\treturn x.Domains\n\t}\n\treturn nil\n}\n\nfunc (x *Forager) GetMaxDepth() int64 {\n\tif x != nil {\n\t\treturn x.MaxDepth\n\t}\n\treturn 0\n}\n\nfunc (x *Forager) GetSince() *timestamppb.Timestamp {\n\tif x != nil {\n\t\treturn x.Since\n\t}\n\treturn nil\n}\n\ntype isForager_Credential interface {\n\tisForager_Credential()\n}\n\ntype Forager_Unauthenticated struct {\n\tUnauthenticated *credentialspb.Unauthenticated `protobuf:\"bytes,1,opt,name=unauthenticated,proto3,oneof\"`\n}\n\nfunc (*Forager_Unauthenticated) isForager_Credential() {}\n\ntype SlackRealtime struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*SlackRealtime_Tokens\n\tCredential isSlackRealtime_Credential `protobuf_oneof:\"credential\"`\n}\n\nfunc (x *SlackRealtime) Reset() {\n\t*x = SlackRealtime{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[30]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SlackRealtime) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SlackRealtime) ProtoMessage() {}\n\nfunc (x *SlackRealtime) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[30]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SlackRealtime.ProtoReflect.Descriptor instead.\nfunc (*SlackRealtime) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{30}\n}\n\nfunc (m *SlackRealtime) GetCredential() isSlackRealtime_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *SlackRealtime) GetTokens() *credentialspb.SlackTokens {\n\tif x, ok := x.GetCredential().(*SlackRealtime_Tokens); ok {\n\t\treturn x.Tokens\n\t}\n\treturn nil\n}\n\ntype isSlackRealtime_Credential interface {\n\tisSlackRealtime_Credential()\n}\n\ntype SlackRealtime_Tokens struct {\n\tTokens *credentialspb.SlackTokens `protobuf:\"bytes,1,opt,name=tokens,proto3,oneof\"`\n}\n\nfunc (*SlackRealtime_Tokens) isSlackRealtime_Credential() {}\n\ntype Sharepoint struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*Sharepoint_Oauth\n\tCredential isSharepoint_Credential `protobuf_oneof:\"credential\"`\n\tSiteUrl    string                  `protobuf:\"bytes,2,opt,name=site_url,json=siteUrl,proto3\" json:\"site_url,omitempty\"`\n}\n\nfunc (x *Sharepoint) Reset() {\n\t*x = Sharepoint{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[31]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Sharepoint) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Sharepoint) ProtoMessage() {}\n\nfunc (x *Sharepoint) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[31]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Sharepoint.ProtoReflect.Descriptor instead.\nfunc (*Sharepoint) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{31}\n}\n\nfunc (m *Sharepoint) GetCredential() isSharepoint_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *Sharepoint) GetOauth() *credentialspb.Oauth2 {\n\tif x, ok := x.GetCredential().(*Sharepoint_Oauth); ok {\n\t\treturn x.Oauth\n\t}\n\treturn nil\n}\n\nfunc (x *Sharepoint) GetSiteUrl() string {\n\tif x != nil {\n\t\treturn x.SiteUrl\n\t}\n\treturn \"\"\n}\n\ntype isSharepoint_Credential interface {\n\tisSharepoint_Credential()\n}\n\ntype Sharepoint_Oauth struct {\n\tOauth *credentialspb.Oauth2 `protobuf:\"bytes,1,opt,name=oauth,proto3,oneof\"`\n}\n\nfunc (*Sharepoint_Oauth) isSharepoint_Credential() {}\n\ntype AzureRepos struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEndpoint string `protobuf:\"bytes,1,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*AzureRepos_Token\n\t//\t*AzureRepos_Oauth\n\tCredential      isAzureRepos_Credential `protobuf_oneof:\"credential\"`\n\tRepositories    []string                `protobuf:\"bytes,4,rep,name=repositories,proto3\" json:\"repositories,omitempty\"`\n\tOrganizations   []string                `protobuf:\"bytes,5,rep,name=organizations,proto3\" json:\"organizations,omitempty\"`\n\tProjects        []string                `protobuf:\"bytes,6,rep,name=projects,proto3\" json:\"projects,omitempty\"`\n\tIncludeForks    bool                    `protobuf:\"varint,7,opt,name=include_forks,json=includeForks,proto3\" json:\"include_forks,omitempty\"`\n\tIgnoreRepos     []string                `protobuf:\"bytes,8,rep,name=ignore_repos,json=ignoreRepos,proto3\" json:\"ignore_repos,omitempty\"`\n\tIncludeRepos    []string                `protobuf:\"bytes,9,rep,name=include_repos,json=includeRepos,proto3\" json:\"include_repos,omitempty\"`\n\tIncludeProjects []string                `protobuf:\"bytes,10,rep,name=include_projects,json=includeProjects,proto3\" json:\"include_projects,omitempty\"`\n\tIgnoreProjects  []string                `protobuf:\"bytes,11,rep,name=ignore_projects,json=ignoreProjects,proto3\" json:\"ignore_projects,omitempty\"`\n\tSkipBinaries    bool                    `protobuf:\"varint,12,opt,name=skip_binaries,json=skipBinaries,proto3\" json:\"skip_binaries,omitempty\"`\n\tSkipArchives    bool                    `protobuf:\"varint,13,opt,name=skip_archives,json=skipArchives,proto3\" json:\"skip_archives,omitempty\"`\n}\n\nfunc (x *AzureRepos) Reset() {\n\t*x = AzureRepos{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[32]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *AzureRepos) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AzureRepos) ProtoMessage() {}\n\nfunc (x *AzureRepos) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[32]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AzureRepos.ProtoReflect.Descriptor instead.\nfunc (*AzureRepos) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{32}\n}\n\nfunc (x *AzureRepos) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (m *AzureRepos) GetCredential() isAzureRepos_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *AzureRepos) GetToken() string {\n\tif x, ok := x.GetCredential().(*AzureRepos_Token); ok {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\nfunc (x *AzureRepos) GetOauth() *credentialspb.Oauth2 {\n\tif x, ok := x.GetCredential().(*AzureRepos_Oauth); ok {\n\t\treturn x.Oauth\n\t}\n\treturn nil\n}\n\nfunc (x *AzureRepos) GetRepositories() []string {\n\tif x != nil {\n\t\treturn x.Repositories\n\t}\n\treturn nil\n}\n\nfunc (x *AzureRepos) GetOrganizations() []string {\n\tif x != nil {\n\t\treturn x.Organizations\n\t}\n\treturn nil\n}\n\nfunc (x *AzureRepos) GetProjects() []string {\n\tif x != nil {\n\t\treturn x.Projects\n\t}\n\treturn nil\n}\n\nfunc (x *AzureRepos) GetIncludeForks() bool {\n\tif x != nil {\n\t\treturn x.IncludeForks\n\t}\n\treturn false\n}\n\nfunc (x *AzureRepos) GetIgnoreRepos() []string {\n\tif x != nil {\n\t\treturn x.IgnoreRepos\n\t}\n\treturn nil\n}\n\nfunc (x *AzureRepos) GetIncludeRepos() []string {\n\tif x != nil {\n\t\treturn x.IncludeRepos\n\t}\n\treturn nil\n}\n\nfunc (x *AzureRepos) GetIncludeProjects() []string {\n\tif x != nil {\n\t\treturn x.IncludeProjects\n\t}\n\treturn nil\n}\n\nfunc (x *AzureRepos) GetIgnoreProjects() []string {\n\tif x != nil {\n\t\treturn x.IgnoreProjects\n\t}\n\treturn nil\n}\n\nfunc (x *AzureRepos) GetSkipBinaries() bool {\n\tif x != nil {\n\t\treturn x.SkipBinaries\n\t}\n\treturn false\n}\n\nfunc (x *AzureRepos) GetSkipArchives() bool {\n\tif x != nil {\n\t\treturn x.SkipArchives\n\t}\n\treturn false\n}\n\ntype isAzureRepos_Credential interface {\n\tisAzureRepos_Credential()\n}\n\ntype AzureRepos_Token struct {\n\tToken string `protobuf:\"bytes,2,opt,name=token,proto3,oneof\"`\n}\n\ntype AzureRepos_Oauth struct {\n\tOauth *credentialspb.Oauth2 `protobuf:\"bytes,3,opt,name=oauth,proto3,oneof\"`\n}\n\nfunc (*AzureRepos_Token) isAzureRepos_Credential() {}\n\nfunc (*AzureRepos_Oauth) isAzureRepos_Credential() {}\n\ntype Postman struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*Postman_Unauthenticated\n\t//\t*Postman_Token\n\tCredential          isPostman_Credential `protobuf_oneof:\"credential\"`\n\tWorkspaces          []string             `protobuf:\"bytes,3,rep,name=workspaces,proto3\" json:\"workspaces,omitempty\"`\n\tCollections         []string             `protobuf:\"bytes,4,rep,name=collections,proto3\" json:\"collections,omitempty\"`\n\tEnvironments        []string             `protobuf:\"bytes,5,rep,name=environments,proto3\" json:\"environments,omitempty\"`\n\tExcludeCollections  []string             `protobuf:\"bytes,6,rep,name=exclude_collections,json=excludeCollections,proto3\" json:\"exclude_collections,omitempty\"`\n\tExcludeEnvironments []string             `protobuf:\"bytes,7,rep,name=exclude_environments,json=excludeEnvironments,proto3\" json:\"exclude_environments,omitempty\"`\n\tIncludeCollections  []string             `protobuf:\"bytes,8,rep,name=include_collections,json=includeCollections,proto3\" json:\"include_collections,omitempty\"`\n\tIncludeEnvironments []string             `protobuf:\"bytes,9,rep,name=include_environments,json=includeEnvironments,proto3\" json:\"include_environments,omitempty\"`\n\tDetectorKeywords    []string             `protobuf:\"bytes,10,rep,name=detector_keywords,json=detectorKeywords,proto3\" json:\"detector_keywords,omitempty\"`\n\tWorkspacePaths      []string             `protobuf:\"bytes,11,rep,name=workspace_paths,json=workspacePaths,proto3\" json:\"workspace_paths,omitempty\"`\n\tCollectionPaths     []string             `protobuf:\"bytes,12,rep,name=collection_paths,json=collectionPaths,proto3\" json:\"collection_paths,omitempty\"`\n\tEnvironmentPaths    []string             `protobuf:\"bytes,13,rep,name=environment_paths,json=environmentPaths,proto3\" json:\"environment_paths,omitempty\"`\n}\n\nfunc (x *Postman) Reset() {\n\t*x = Postman{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[33]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Postman) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Postman) ProtoMessage() {}\n\nfunc (x *Postman) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[33]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Postman.ProtoReflect.Descriptor instead.\nfunc (*Postman) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{33}\n}\n\nfunc (m *Postman) GetCredential() isPostman_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *Postman) GetUnauthenticated() *credentialspb.Unauthenticated {\n\tif x, ok := x.GetCredential().(*Postman_Unauthenticated); ok {\n\t\treturn x.Unauthenticated\n\t}\n\treturn nil\n}\n\nfunc (x *Postman) GetToken() string {\n\tif x, ok := x.GetCredential().(*Postman_Token); ok {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\nfunc (x *Postman) GetWorkspaces() []string {\n\tif x != nil {\n\t\treturn x.Workspaces\n\t}\n\treturn nil\n}\n\nfunc (x *Postman) GetCollections() []string {\n\tif x != nil {\n\t\treturn x.Collections\n\t}\n\treturn nil\n}\n\nfunc (x *Postman) GetEnvironments() []string {\n\tif x != nil {\n\t\treturn x.Environments\n\t}\n\treturn nil\n}\n\nfunc (x *Postman) GetExcludeCollections() []string {\n\tif x != nil {\n\t\treturn x.ExcludeCollections\n\t}\n\treturn nil\n}\n\nfunc (x *Postman) GetExcludeEnvironments() []string {\n\tif x != nil {\n\t\treturn x.ExcludeEnvironments\n\t}\n\treturn nil\n}\n\nfunc (x *Postman) GetIncludeCollections() []string {\n\tif x != nil {\n\t\treturn x.IncludeCollections\n\t}\n\treturn nil\n}\n\nfunc (x *Postman) GetIncludeEnvironments() []string {\n\tif x != nil {\n\t\treturn x.IncludeEnvironments\n\t}\n\treturn nil\n}\n\nfunc (x *Postman) GetDetectorKeywords() []string {\n\tif x != nil {\n\t\treturn x.DetectorKeywords\n\t}\n\treturn nil\n}\n\nfunc (x *Postman) GetWorkspacePaths() []string {\n\tif x != nil {\n\t\treturn x.WorkspacePaths\n\t}\n\treturn nil\n}\n\nfunc (x *Postman) GetCollectionPaths() []string {\n\tif x != nil {\n\t\treturn x.CollectionPaths\n\t}\n\treturn nil\n}\n\nfunc (x *Postman) GetEnvironmentPaths() []string {\n\tif x != nil {\n\t\treturn x.EnvironmentPaths\n\t}\n\treturn nil\n}\n\ntype isPostman_Credential interface {\n\tisPostman_Credential()\n}\n\ntype Postman_Unauthenticated struct {\n\tUnauthenticated *credentialspb.Unauthenticated `protobuf:\"bytes,1,opt,name=unauthenticated,proto3,oneof\"`\n}\n\ntype Postman_Token struct {\n\tToken string `protobuf:\"bytes,2,opt,name=token,proto3,oneof\"`\n}\n\nfunc (*Postman_Unauthenticated) isPostman_Credential() {}\n\nfunc (*Postman_Token) isPostman_Credential() {}\n\ntype Webhook struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tListenAddress string `protobuf:\"bytes,1,opt,name=listen_address,json=listenAddress,proto3\" json:\"listen_address,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*Webhook_Header\n\tCredential isWebhook_Credential `protobuf_oneof:\"credential\"`\n\t// Types that are assignable to Variant:\n\t//\n\t//\t*Webhook_Vector\n\tVariant isWebhook_Variant `protobuf_oneof:\"variant\"`\n}\n\nfunc (x *Webhook) Reset() {\n\t*x = Webhook{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[34]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Webhook) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Webhook) ProtoMessage() {}\n\nfunc (x *Webhook) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[34]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Webhook.ProtoReflect.Descriptor instead.\nfunc (*Webhook) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{34}\n}\n\nfunc (x *Webhook) GetListenAddress() string {\n\tif x != nil {\n\t\treturn x.ListenAddress\n\t}\n\treturn \"\"\n}\n\nfunc (m *Webhook) GetCredential() isWebhook_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *Webhook) GetHeader() *credentialspb.Header {\n\tif x, ok := x.GetCredential().(*Webhook_Header); ok {\n\t\treturn x.Header\n\t}\n\treturn nil\n}\n\nfunc (m *Webhook) GetVariant() isWebhook_Variant {\n\tif m != nil {\n\t\treturn m.Variant\n\t}\n\treturn nil\n}\n\nfunc (x *Webhook) GetVector() *Vector {\n\tif x, ok := x.GetVariant().(*Webhook_Vector); ok {\n\t\treturn x.Vector\n\t}\n\treturn nil\n}\n\ntype isWebhook_Credential interface {\n\tisWebhook_Credential()\n}\n\ntype Webhook_Header struct {\n\tHeader *credentialspb.Header `protobuf:\"bytes,2,opt,name=header,proto3,oneof\"`\n}\n\nfunc (*Webhook_Header) isWebhook_Credential() {}\n\ntype isWebhook_Variant interface {\n\tisWebhook_Variant()\n}\n\ntype Webhook_Vector struct {\n\tVector *Vector `protobuf:\"bytes,4,opt,name=vector,proto3,oneof\"`\n}\n\nfunc (*Webhook_Vector) isWebhook_Variant() {}\n\ntype Vector struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tLocatorField string `protobuf:\"bytes,1,opt,name=locator_field,json=locatorField,proto3\" json:\"locator_field,omitempty\"`\n\tLinkFormat   string `protobuf:\"bytes,2,opt,name=link_format,json=linkFormat,proto3\" json:\"link_format,omitempty\"`\n}\n\nfunc (x *Vector) Reset() {\n\t*x = Vector{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[35]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Vector) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Vector) ProtoMessage() {}\n\nfunc (x *Vector) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[35]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Vector.ProtoReflect.Descriptor instead.\nfunc (*Vector) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{35}\n}\n\nfunc (x *Vector) GetLocatorField() string {\n\tif x != nil {\n\t\treturn x.LocatorField\n\t}\n\treturn \"\"\n}\n\nfunc (x *Vector) GetLinkFormat() string {\n\tif x != nil {\n\t\treturn x.LinkFormat\n\t}\n\treturn \"\"\n}\n\ntype Elasticsearch struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tNodes          []string `protobuf:\"bytes,1,rep,name=nodes,proto3\" json:\"nodes,omitempty\"`\n\tUsername       string   `protobuf:\"bytes,2,opt,name=username,proto3\" json:\"username,omitempty\"`\n\tPassword       string   `protobuf:\"bytes,3,opt,name=password,proto3\" json:\"password,omitempty\"`\n\tCloudId        string   `protobuf:\"bytes,4,opt,name=cloud_id,json=cloudId,proto3\" json:\"cloud_id,omitempty\"`\n\tApiKey         string   `protobuf:\"bytes,5,opt,name=api_key,json=apiKey,proto3\" json:\"api_key,omitempty\"`\n\tServiceToken   string   `protobuf:\"bytes,6,opt,name=service_token,json=serviceToken,proto3\" json:\"service_token,omitempty\"`\n\tIndexPattern   string   `protobuf:\"bytes,7,opt,name=index_pattern,json=indexPattern,proto3\" json:\"index_pattern,omitempty\"`\n\tQueryJson      string   `protobuf:\"bytes,8,opt,name=query_json,json=queryJson,proto3\" json:\"query_json,omitempty\"`\n\tSinceTimestamp string   `protobuf:\"bytes,9,opt,name=since_timestamp,json=sinceTimestamp,proto3\" json:\"since_timestamp,omitempty\"`\n\tBestEffortScan bool     `protobuf:\"varint,10,opt,name=best_effort_scan,json=bestEffortScan,proto3\" json:\"best_effort_scan,omitempty\"`\n}\n\nfunc (x *Elasticsearch) Reset() {\n\t*x = Elasticsearch{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[36]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Elasticsearch) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Elasticsearch) ProtoMessage() {}\n\nfunc (x *Elasticsearch) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[36]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Elasticsearch.ProtoReflect.Descriptor instead.\nfunc (*Elasticsearch) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{36}\n}\n\nfunc (x *Elasticsearch) GetNodes() []string {\n\tif x != nil {\n\t\treturn x.Nodes\n\t}\n\treturn nil\n}\n\nfunc (x *Elasticsearch) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *Elasticsearch) GetPassword() string {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn \"\"\n}\n\nfunc (x *Elasticsearch) GetCloudId() string {\n\tif x != nil {\n\t\treturn x.CloudId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Elasticsearch) GetApiKey() string {\n\tif x != nil {\n\t\treturn x.ApiKey\n\t}\n\treturn \"\"\n}\n\nfunc (x *Elasticsearch) GetServiceToken() string {\n\tif x != nil {\n\t\treturn x.ServiceToken\n\t}\n\treturn \"\"\n}\n\nfunc (x *Elasticsearch) GetIndexPattern() string {\n\tif x != nil {\n\t\treturn x.IndexPattern\n\t}\n\treturn \"\"\n}\n\nfunc (x *Elasticsearch) GetQueryJson() string {\n\tif x != nil {\n\t\treturn x.QueryJson\n\t}\n\treturn \"\"\n}\n\nfunc (x *Elasticsearch) GetSinceTimestamp() string {\n\tif x != nil {\n\t\treturn x.SinceTimestamp\n\t}\n\treturn \"\"\n}\n\nfunc (x *Elasticsearch) GetBestEffortScan() bool {\n\tif x != nil {\n\t\treturn x.BestEffortScan\n\t}\n\treturn false\n}\n\ntype Sentry struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tEndpoint string `protobuf:\"bytes,1,opt,name=endpoint,proto3\" json:\"endpoint,omitempty\"`\n\t// Types that are assignable to Credential:\n\t//\n\t//\t*Sentry_AuthToken\n\t//\t*Sentry_DsnKey\n\t//\t*Sentry_ApiKey\n\tCredential            isSentry_Credential `protobuf_oneof:\"credential\"`\n\tInsecureSkipVerifyTls bool                `protobuf:\"varint,5,opt,name=insecure_skip_verify_tls,json=insecureSkipVerifyTls,proto3\" json:\"insecure_skip_verify_tls,omitempty\"`\n\tProjects              string              `protobuf:\"bytes,6,opt,name=projects,proto3\" json:\"projects,omitempty\"`\n}\n\nfunc (x *Sentry) Reset() {\n\t*x = Sentry{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[37]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Sentry) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Sentry) ProtoMessage() {}\n\nfunc (x *Sentry) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[37]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Sentry.ProtoReflect.Descriptor instead.\nfunc (*Sentry) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{37}\n}\n\nfunc (x *Sentry) GetEndpoint() string {\n\tif x != nil {\n\t\treturn x.Endpoint\n\t}\n\treturn \"\"\n}\n\nfunc (m *Sentry) GetCredential() isSentry_Credential {\n\tif m != nil {\n\t\treturn m.Credential\n\t}\n\treturn nil\n}\n\nfunc (x *Sentry) GetAuthToken() string {\n\tif x, ok := x.GetCredential().(*Sentry_AuthToken); ok {\n\t\treturn x.AuthToken\n\t}\n\treturn \"\"\n}\n\nfunc (x *Sentry) GetDsnKey() string {\n\tif x, ok := x.GetCredential().(*Sentry_DsnKey); ok {\n\t\treturn x.DsnKey\n\t}\n\treturn \"\"\n}\n\nfunc (x *Sentry) GetApiKey() string {\n\tif x, ok := x.GetCredential().(*Sentry_ApiKey); ok {\n\t\treturn x.ApiKey\n\t}\n\treturn \"\"\n}\n\nfunc (x *Sentry) GetInsecureSkipVerifyTls() bool {\n\tif x != nil {\n\t\treturn x.InsecureSkipVerifyTls\n\t}\n\treturn false\n}\n\nfunc (x *Sentry) GetProjects() string {\n\tif x != nil {\n\t\treturn x.Projects\n\t}\n\treturn \"\"\n}\n\ntype isSentry_Credential interface {\n\tisSentry_Credential()\n}\n\ntype Sentry_AuthToken struct {\n\tAuthToken string `protobuf:\"bytes,2,opt,name=auth_token,json=authToken,proto3,oneof\"`\n}\n\ntype Sentry_DsnKey struct {\n\tDsnKey string `protobuf:\"bytes,3,opt,name=dsn_key,json=dsnKey,proto3,oneof\"`\n}\n\ntype Sentry_ApiKey struct {\n\tApiKey string `protobuf:\"bytes,4,opt,name=api_key,json=apiKey,proto3,oneof\"`\n}\n\nfunc (*Sentry_AuthToken) isSentry_Credential() {}\n\nfunc (*Sentry_DsnKey) isSentry_Credential() {}\n\nfunc (*Sentry_ApiKey) isSentry_Credential() {}\n\ntype Stdin struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n}\n\nfunc (x *Stdin) Reset() {\n\t*x = Stdin{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[38]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *Stdin) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Stdin) ProtoMessage() {}\n\nfunc (x *Stdin) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[38]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Stdin.ProtoReflect.Descriptor instead.\nfunc (*Stdin) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{38}\n}\n\ntype SlackContinuous struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tNamespace string `protobuf:\"bytes,1,opt,name=namespace,proto3\" json:\"namespace,omitempty\"`\n\tProjectId string `protobuf:\"bytes,2,opt,name=project_id,json=projectId,proto3\" json:\"project_id,omitempty\"`\n}\n\nfunc (x *SlackContinuous) Reset() {\n\t*x = SlackContinuous{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[39]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *SlackContinuous) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SlackContinuous) ProtoMessage() {}\n\nfunc (x *SlackContinuous) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[39]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SlackContinuous.ProtoReflect.Descriptor instead.\nfunc (*SlackContinuous) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{39}\n}\n\nfunc (x *SlackContinuous) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *SlackContinuous) GetProjectId() string {\n\tif x != nil {\n\t\treturn x.ProjectId\n\t}\n\treturn \"\"\n}\n\ntype JSONEnumerator struct {\n\tstate         protoimpl.MessageState\n\tsizeCache     protoimpl.SizeCache\n\tunknownFields protoimpl.UnknownFields\n\n\tPaths []string `protobuf:\"bytes,1,rep,name=paths,proto3\" json:\"paths,omitempty\"`\n}\n\nfunc (x *JSONEnumerator) Reset() {\n\t*x = JSONEnumerator{}\n\tif protoimpl.UnsafeEnabled {\n\t\tmi := &file_sources_proto_msgTypes[40]\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tms.StoreMessageInfo(mi)\n\t}\n}\n\nfunc (x *JSONEnumerator) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*JSONEnumerator) ProtoMessage() {}\n\nfunc (x *JSONEnumerator) ProtoReflect() protoreflect.Message {\n\tmi := &file_sources_proto_msgTypes[40]\n\tif protoimpl.UnsafeEnabled && x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use JSONEnumerator.ProtoReflect.Descriptor instead.\nfunc (*JSONEnumerator) Descriptor() ([]byte, []int) {\n\treturn file_sources_proto_rawDescGZIP(), []int{40}\n}\n\nfunc (x *JSONEnumerator) GetPaths() []string {\n\tif x != nil {\n\t\treturn x.Paths\n\t}\n\treturn nil\n}\n\nvar File_sources_proto protoreflect.FileDescriptor\n\nvar file_sources_proto_rawDesc = []byte{\n\t0x0a, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,\n\t0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x1a, 0x17, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61,\n\t0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,\n\t0x6f, 0x1a, 0x11, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x70,\n\t0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,\n\t0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,\n\t0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,\n\t0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,\n\t0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x22, 0xe8, 0x01, 0x0a, 0x0b, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65,\n\t0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,\n\t0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x42, 0x0a, 0x0d, 0x73, 0x63, 0x61, 0x6e,\n\t0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,\n\t0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c,\n\t0x73, 0x63, 0x61, 0x6e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06,\n\t0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x76, 0x65,\n\t0x72, 0x69, 0x66, 0x79, 0x12, 0x34, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69,\n\t0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,\n\t0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x0a,\n\t0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x63,\n\t0x61, 0x6e, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x0a, 0x73, 0x63, 0x61, 0x6e, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x22, 0xd5, 0x02, 0x0a, 0x0b,\n\t0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x24, 0x0a, 0x08, 0x65,\n\t0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa,\n\t0x42, 0x05, 0x72, 0x03, 0x90, 0x01, 0x01, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e,\n\t0x74, 0x12, 0x37, 0x0a, 0x0a, 0x62, 0x61, 0x73, 0x69, 0x63, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,\n\t0x61, 0x6c, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x48, 0x00, 0x52,\n\t0x09, 0x62, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0c, 0x61, 0x63,\n\t0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,\n\t0x48, 0x00, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12,\n\t0x48, 0x0a, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74,\n\t0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65,\n\t0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74,\n\t0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68,\n\t0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x70,\n\t0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52,\n\t0x0c, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x23, 0x0a,\n\t0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x05,\n\t0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x61, 0x74,\n\t0x68, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x70, 0x61, 0x74,\n\t0x68, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65,\n\t0x50, 0x61, 0x74, 0x68, 0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74,\n\t0x69, 0x61, 0x6c, 0x22, 0xae, 0x02, 0x0a, 0x0c, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x53, 0x74, 0x6f,\n\t0x72, 0x61, 0x67, 0x65, 0x12, 0x2d, 0x0a, 0x11, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69,\n\t0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48,\n\t0x00, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72,\n\t0x69, 0x6e, 0x67, 0x12, 0x37, 0x0a, 0x0a, 0x62, 0x61, 0x73, 0x69, 0x63, 0x5f, 0x61, 0x75, 0x74,\n\t0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e,\n\t0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x48,\n\t0x00, 0x52, 0x09, 0x62, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x12, 0x2f, 0x0a, 0x12,\n\t0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,\n\t0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x11, 0x63, 0x6c, 0x69, 0x65,\n\t0x6e, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x48, 0x0a,\n\t0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64,\n\t0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74,\n\t0x69, 0x61, 0x6c, 0x73, 0x2e, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63,\n\t0x61, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e,\n\t0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x73, 0x74, 0x6f, 0x72, 0x61,\n\t0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20,\n\t0x03, 0x28, 0x09, 0x52, 0x11, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74,\n\t0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e,\n\t0x74, 0x69, 0x61, 0x6c, 0x22, 0xf5, 0x04, 0x0a, 0x09, 0x42, 0x69, 0x74, 0x62, 0x75, 0x63, 0x6b,\n\t0x65, 0x74, 0x12, 0x24, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x90, 0x01, 0x01, 0x52, 0x08,\n\t0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65,\n\t0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e,\n\t0x12, 0x2b, 0x0a, 0x05, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x13, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x4f, 0x61,\n\t0x75, 0x74, 0x68, 0x32, 0x48, 0x00, 0x52, 0x05, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x12, 0x37, 0x0a,\n\t0x0a, 0x62, 0x61, 0x73, 0x69, 0x63, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x16, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e,\n\t0x42, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x48, 0x00, 0x52, 0x09, 0x62, 0x61, 0x73,\n\t0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69,\n\t0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65,\n\t0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x67,\n\t0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09,\n\t0x52, 0x0b, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x12, 0x23, 0x0a,\n\t0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0x07,\n\t0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x69,\n\t0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69,\n\t0x76, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x41,\n\t0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x73, 0x12, 0x4f, 0x0a, 0x11, 0x69, 0x6e, 0x73, 0x74, 0x61,\n\t0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01,\n\t0x28, 0x0e, 0x32, 0x22, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x42, 0x69, 0x74,\n\t0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x10, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61,\n\t0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x40, 0x0a, 0x1c, 0x6f, 0x61, 0x75, 0x74,\n\t0x68, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f,\n\t0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a,\n\t0x6f, 0x61, 0x75, 0x74, 0x68, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x6f, 0x61,\n\t0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69,\n\t0x6e, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x54,\n\t0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c,\n\t0x6f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x03,\n\t0x28, 0x09, 0x52, 0x0b, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12,\n\t0x3d, 0x0a, 0x1b, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73,\n\t0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x18, 0x0d,\n\t0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x63, 0x72, 0x65,\n\t0x74, 0x73, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x57, 0x72, 0x69, 0x74, 0x65, 0x42, 0x0c,\n\t0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22, 0x56, 0x0a, 0x08,\n\t0x43, 0x69, 0x72, 0x63, 0x6c, 0x65, 0x43, 0x49, 0x12, 0x24, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70,\n\t0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72,\n\t0x03, 0x90, 0x01, 0x01, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x16,\n\t0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52,\n\t0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e,\n\t0x74, 0x69, 0x61, 0x6c, 0x22, 0x56, 0x0a, 0x08, 0x54, 0x72, 0x61, 0x76, 0x69, 0x73, 0x43, 0x49,\n\t0x12, 0x24, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x90, 0x01, 0x01, 0x52, 0x08, 0x65, 0x6e,\n\t0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x0c,\n\t0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22, 0xd2, 0x04, 0x0a,\n\t0x0a, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x24, 0x0a, 0x08, 0x65,\n\t0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa,\n\t0x42, 0x05, 0x72, 0x03, 0x90, 0x01, 0x01, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e,\n\t0x74, 0x12, 0x48, 0x0a, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63,\n\t0x61, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x72, 0x65,\n\t0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65,\n\t0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0f, 0x75, 0x6e, 0x61, 0x75,\n\t0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x37, 0x0a, 0x0a, 0x62,\n\t0x61, 0x73, 0x69, 0x63, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x16, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x42, 0x61,\n\t0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x48, 0x00, 0x52, 0x09, 0x62, 0x61, 0x73, 0x69, 0x63,\n\t0x41, 0x75, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20,\n\t0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x48, 0x0a, 0x0c,\n\t0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01,\n\t0x28, 0x0e, 0x32, 0x25, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x43, 0x6f, 0x6e,\n\t0x66, 0x6c, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x70,\n\t0x61, 0x63, 0x65, 0x73, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x0b, 0x73, 0x70, 0x61, 0x63, 0x65,\n\t0x73, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x37, 0x0a, 0x18, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75,\n\t0x72, 0x65, 0x5f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x74,\n\t0x6c, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75,\n\t0x72, 0x65, 0x53, 0x6b, 0x69, 0x70, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x54, 0x6c, 0x73, 0x12,\n\t0x16, 0x0a, 0x06, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52,\n\t0x06, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72,\n\t0x65, 0x5f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c,\n\t0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x53, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x13,\n\t0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65,\n\t0x6e, 0x74, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75,\n\t0x64, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x21, 0x0a,\n\t0x0c, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x0a, 0x20,\n\t0x01, 0x28, 0x08, 0x52, 0x0b, 0x73, 0x6b, 0x69, 0x70, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79,\n\t0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x6d,\n\t0x65, 0x6e, 0x74, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c,\n\t0x75, 0x64, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x36, 0x0a, 0x11, 0x47,\n\t0x65, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x70, 0x61, 0x63, 0x65, 0x73, 0x53, 0x63, 0x6f, 0x70, 0x65,\n\t0x12, 0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x47, 0x4c, 0x4f,\n\t0x42, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x45, 0x52, 0x53, 0x4f, 0x4e, 0x41,\n\t0x4c, 0x10, 0x02, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61,\n\t0x6c, 0x22, 0xeb, 0x02, 0x0a, 0x06, 0x44, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x48, 0x0a, 0x0f,\n\t0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,\n\t0x61, 0x6c, 0x73, 0x2e, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61,\n\t0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74,\n\t0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x37, 0x0a, 0x0a, 0x62, 0x61, 0x73, 0x69, 0x63, 0x5f,\n\t0x61, 0x75, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x72, 0x65,\n\t0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75,\n\t0x74, 0x68, 0x48, 0x00, 0x52, 0x09, 0x62, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x12,\n\t0x23, 0x0a, 0x0c, 0x62, 0x65, 0x61, 0x72, 0x65, 0x72, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,\n\t0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x62, 0x65, 0x61, 0x72, 0x65, 0x72, 0x54,\n\t0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x0f, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x5f, 0x6b,\n\t0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52,\n\t0x0e, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12,\n\t0x16, 0x0a, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52,\n\t0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x63, 0x6c, 0x75,\n\t0x64, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c,\n\t0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x1c, 0x0a, 0x09,\n\t0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65,\n\t0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x54, 0x6f, 0x6b, 0x65,\n\t0x6e, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22,\n\t0x6c, 0x0a, 0x03, 0x45, 0x43, 0x52, 0x12, 0x37, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73,\n\t0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x72, 0x65,\n\t0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x4b, 0x65, 0x79, 0x53, 0x65, 0x63, 0x72,\n\t0x65, 0x74, 0x48, 0x00, 0x52, 0x09, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x12,\n\t0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20,\n\t0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x69, 0x65, 0x73, 0x42,\n\t0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22, 0xf1, 0x01,\n\t0x0a, 0x0a, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x20, 0x0a, 0x0b,\n\t0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,\n\t0x09, 0x52, 0x0b, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x14,\n\t0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x70,\n\t0x61, 0x74, 0x68, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f,\n\t0x70, 0x61, 0x74, 0x68, 0x73, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x61, 0x74, 0x68, 0x73, 0x46, 0x69,\n\t0x6c, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x61,\n\t0x74, 0x68, 0x73, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10,\n\t0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x61, 0x74, 0x68, 0x73, 0x46, 0x69, 0x6c, 0x65,\n\t0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x69, 0x65,\n\t0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x69, 0x6e,\n\t0x61, 0x72, 0x69, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x79, 0x6d,\n\t0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05,\n\t0x52, 0x0f, 0x6d, 0x61, 0x78, 0x53, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b, 0x44, 0x65, 0x70, 0x74,\n\t0x68, 0x22, 0xab, 0x04, 0x0a, 0x03, 0x47, 0x43, 0x53, 0x12, 0x32, 0x0a, 0x14, 0x6a, 0x73, 0x6f,\n\t0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e,\n\t0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x12, 0x6a, 0x73, 0x6f, 0x6e, 0x53,\n\t0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a,\n\t0x07, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00,\n\t0x52, 0x06, 0x61, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x12, 0x48, 0x0a, 0x0f, 0x75, 0x6e, 0x61, 0x75,\n\t0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e,\n\t0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x48,\n\t0x00, 0x52, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74,\n\t0x65, 0x64, 0x12, 0x31, 0x0a, 0x03, 0x61, 0x64, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x1d, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x43, 0x6c,\n\t0x6f, 0x75, 0x64, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00,\n\t0x52, 0x03, 0x61, 0x64, 0x63, 0x12, 0x32, 0x0a, 0x14, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,\n\t0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x0b, 0x20,\n\t0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x12, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x63,\n\t0x63, 0x6f, 0x75, 0x6e, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x6f, 0x61, 0x75,\n\t0x74, 0x68, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65,\n\t0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x48, 0x00, 0x52,\n\t0x05, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63,\n\t0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x6a,\n\t0x65, 0x63, 0x74, 0x49, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65,\n\t0x5f, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e,\n\t0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x27,\n\t0x0a, 0x0f, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74,\n\t0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65,\n\t0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75,\n\t0x64, 0x65, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09,\n\t0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73,\n\t0x12, 0x27, 0x0a, 0x0f, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x6f, 0x62, 0x6a, 0x65,\n\t0x63, 0x74, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, 0x63, 0x6c, 0x75,\n\t0x64, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x78,\n\t0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x0a, 0x20, 0x01,\n\t0x28, 0x03, 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x69, 0x7a,\n\t0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22,\n\t0xe4, 0x05, 0x0a, 0x03, 0x47, 0x69, 0x74, 0x12, 0x37, 0x0a, 0x0a, 0x62, 0x61, 0x73, 0x69, 0x63,\n\t0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x72,\n\t0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x69, 0x63, 0x41,\n\t0x75, 0x74, 0x68, 0x48, 0x00, 0x52, 0x09, 0x62, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68,\n\t0x12, 0x48, 0x0a, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61,\n\t0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x72, 0x65, 0x64,\n\t0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e,\n\t0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74,\n\t0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x31, 0x0a, 0x08, 0x73, 0x73,\n\t0x68, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63,\n\t0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x53, 0x53, 0x48, 0x41, 0x75,\n\t0x74, 0x68, 0x48, 0x00, 0x52, 0x07, 0x73, 0x73, 0x68, 0x41, 0x75, 0x74, 0x68, 0x12, 0x20, 0x0a,\n\t0x0b, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03,\n\t0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12,\n\t0x22, 0x0a, 0x0c, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18,\n\t0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72,\n\t0x69, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, 0x61, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x04, 0x68, 0x65, 0x61, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18,\n\t0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x61, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62,\n\t0x61, 0x72, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x62, 0x61, 0x72, 0x65, 0x12,\n\t0x2c, 0x0a, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73,\n\t0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x69, 0x6e, 0x63,\n\t0x6c, 0x75, 0x64, 0x65, 0x50, 0x61, 0x74, 0x68, 0x73, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x2c, 0x0a,\n\t0x12, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x5f, 0x66,\n\t0x69, 0x6c, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x65, 0x78, 0x63, 0x6c, 0x75,\n\t0x64, 0x65, 0x50, 0x61, 0x74, 0x68, 0x73, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65,\n\t0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x67, 0x6c, 0x6f, 0x62, 0x73, 0x18, 0x0b, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x0c, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x47, 0x6c, 0x6f, 0x62, 0x73,\n\t0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x64, 0x65, 0x70, 0x74, 0x68, 0x18, 0x0c, 0x20,\n\t0x01, 0x28, 0x03, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x44, 0x65, 0x70, 0x74, 0x68, 0x12, 0x10, 0x0a,\n\t0x03, 0x75, 0x72, 0x69, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12,\n\t0x23, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x69, 0x65, 0x73,\n\t0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x69, 0x6e, 0x61,\n\t0x72, 0x69, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x61, 0x72, 0x63,\n\t0x68, 0x69, 0x76, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69,\n\t0x70, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x6f,\n\t0x6e, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63,\n\t0x6c, 0x6f, 0x6e, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x6f, 0x5f, 0x63,\n\t0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6e, 0x6f,\n\t0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x72, 0x69, 0x6e, 0x74,\n\t0x5f, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x12, 0x20, 0x01,\n\t0x28, 0x08, 0x52, 0x0f, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4a,\n\t0x73, 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x16, 0x74, 0x72, 0x75, 0x73, 0x74, 0x5f, 0x6c, 0x6f, 0x63,\n\t0x61, 0x6c, 0x5f, 0x67, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x13, 0x20,\n\t0x01, 0x28, 0x08, 0x52, 0x13, 0x74, 0x72, 0x75, 0x73, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x47,\n\t0x69, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64,\n\t0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22, 0xf2, 0x04, 0x0a, 0x06, 0x47, 0x69, 0x74, 0x4c, 0x61,\n\t0x62, 0x12, 0x24, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x90, 0x01, 0x01, 0x52, 0x08, 0x65,\n\t0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12,\n\t0x2b, 0x0a, 0x05, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13,\n\t0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x4f, 0x61, 0x75,\n\t0x74, 0x68, 0x32, 0x48, 0x00, 0x52, 0x05, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x12, 0x37, 0x0a, 0x0a,\n\t0x62, 0x61, 0x73, 0x69, 0x63, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x16, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x42,\n\t0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x48, 0x00, 0x52, 0x09, 0x62, 0x61, 0x73, 0x69,\n\t0x63, 0x41, 0x75, 0x74, 0x68, 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,\n\t0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x70,\n\t0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x67, 0x6e,\n\t0x6f, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52,\n\t0x0b, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x12, 0x23, 0x0a, 0x0d,\n\t0x73, 0x6b, 0x69, 0x70, 0x5f, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0x07, 0x20,\n\t0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x69, 0x65,\n\t0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76,\n\t0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x41, 0x72,\n\t0x63, 0x68, 0x69, 0x76, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64,\n\t0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x69,\n\t0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x12, 0x4c, 0x0a, 0x23, 0x65,\n\t0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x5f,\n\t0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x6f, 0x5f, 0x67, 0x72, 0x6f, 0x75,\n\t0x70, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1f, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64,\n\t0x65, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x49,\n\t0x6e, 0x74, 0x6f, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x72, 0x65, 0x6d,\n\t0x6f, 0x76, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x69, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18,\n\t0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x41, 0x75, 0x74,\n\t0x68, 0x49, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f,\n\t0x69, 0x64, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x67, 0x72, 0x6f, 0x75, 0x70,\n\t0x49, 0x64, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x6f, 0x6e, 0x65, 0x5f, 0x70, 0x61, 0x74,\n\t0x68, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x6f, 0x6e, 0x65, 0x50, 0x61,\n\t0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x6f, 0x5f, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70,\n\t0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6e, 0x6f, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75,\n\t0x70, 0x12, 0x2a, 0x0a, 0x11, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x5f, 0x6c, 0x65, 0x67, 0x61, 0x63,\n\t0x79, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x70, 0x72,\n\t0x69, 0x6e, 0x74, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4a, 0x73, 0x6f, 0x6e, 0x42, 0x0c, 0x0a,\n\t0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22, 0x9a, 0x08, 0x0a, 0x06,\n\t0x47, 0x69, 0x74, 0x48, 0x75, 0x62, 0x12, 0x24, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69,\n\t0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x90,\n\t0x01, 0x01, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x37, 0x0a, 0x0a,\n\t0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x5f, 0x61, 0x70, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x16, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x47,\n\t0x69, 0x74, 0x48, 0x75, 0x62, 0x41, 0x70, 0x70, 0x48, 0x00, 0x52, 0x09, 0x67, 0x69, 0x74, 0x68,\n\t0x75, 0x62, 0x41, 0x70, 0x70, 0x12, 0x16, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03,\n\t0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x48, 0x0a,\n\t0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64,\n\t0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74,\n\t0x69, 0x61, 0x6c, 0x73, 0x2e, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63,\n\t0x61, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e,\n\t0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x37, 0x0a, 0x0a, 0x62, 0x61, 0x73, 0x69, 0x63,\n\t0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x72,\n\t0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x69, 0x63, 0x41,\n\t0x75, 0x74, 0x68, 0x48, 0x00, 0x52, 0x09, 0x62, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68,\n\t0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73,\n\t0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f,\n\t0x72, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61,\n\t0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x6f, 0x72, 0x67,\n\t0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x63,\n\t0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73,\n\t0x63, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x69, 0x6e, 0x63, 0x6c,\n\t0x75, 0x64, 0x65, 0x46, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c,\n\t0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x46, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x12, 0x0a, 0x04,\n\t0x68, 0x65, 0x61, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x65, 0x61, 0x64,\n\t0x12, 0x12, 0x0a, 0x04, 0x62, 0x61, 0x73, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,\n\t0x62, 0x61, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x72,\n\t0x65, 0x70, 0x6f, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x67, 0x6e, 0x6f,\n\t0x72, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75,\n\t0x64, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c,\n\t0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x12, 0x41, 0x0a, 0x1d,\n\t0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x72, 0x65, 0x71,\n\t0x75, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0e, 0x20,\n\t0x01, 0x28, 0x08, 0x52, 0x1a, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x75, 0x6c, 0x6c,\n\t0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12,\n\t0x34, 0x0a, 0x16, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x69, 0x73, 0x73, 0x75, 0x65,\n\t0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52,\n\t0x14, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x49, 0x73, 0x73, 0x75, 0x65, 0x43, 0x6f, 0x6d,\n\t0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65,\n\t0x5f, 0x67, 0x69, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x10,\n\t0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x47, 0x69, 0x73,\n\t0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6b, 0x69,\n\t0x70, 0x5f, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08,\n\t0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x69, 0x65, 0x73, 0x12, 0x23,\n\t0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x73, 0x18,\n\t0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x41, 0x72, 0x63, 0x68, 0x69,\n\t0x76, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x77,\n\t0x69, 0x6b, 0x69, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c,\n\t0x75, 0x64, 0x65, 0x57, 0x69, 0x6b, 0x69, 0x73, 0x12, 0x36, 0x0a, 0x17, 0x63, 0x6f, 0x6d, 0x6d,\n\t0x65, 0x6e, 0x74, 0x73, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x64,\n\t0x61, 0x79, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x15, 0x63, 0x6f, 0x6d, 0x6d, 0x65,\n\t0x6e, 0x74, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x44, 0x61, 0x79, 0x73,\n\t0x12, 0x2b, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f,\n\t0x69, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x15, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x72, 0x65,\n\t0x6d, 0x6f, 0x76, 0x65, 0x41, 0x75, 0x74, 0x68, 0x49, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x1d, 0x0a,\n\t0x0a, 0x63, 0x6c, 0x6f, 0x6e, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x16, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x09, 0x63, 0x6c, 0x6f, 0x6e, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a,\n\t0x6e, 0x6f, 0x5f, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x18, 0x17, 0x20, 0x01, 0x28, 0x08,\n\t0x52, 0x09, 0x6e, 0x6f, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, 0x12, 0x21, 0x0a, 0x0c, 0x69,\n\t0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x67, 0x69, 0x73, 0x74, 0x73, 0x18, 0x18, 0x20, 0x01, 0x28,\n\t0x08, 0x52, 0x0b, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x47, 0x69, 0x73, 0x74, 0x73, 0x12, 0x2a,\n\t0x0a, 0x11, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x5f, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x5f, 0x6a,\n\t0x73, 0x6f, 0x6e, 0x18, 0x19, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x70, 0x72, 0x69, 0x6e, 0x74,\n\t0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4a, 0x73, 0x6f, 0x6e, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72,\n\t0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22, 0xe4, 0x01, 0x0a, 0x12, 0x47, 0x69, 0x74,\n\t0x48, 0x75, 0x62, 0x45, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x12,\n\t0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x79, 0x12,\n\t0x16, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00,\n\t0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x6f, 0x62, 0x6a, 0x65, 0x63,\n\t0x74, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28,\n\t0x08, 0x52, 0x0f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65,\n\t0x72, 0x79, 0x12, 0x2f, 0x0a, 0x13, 0x63, 0x6f, 0x6c, 0x6c, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x5f,\n\t0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52,\n\t0x12, 0x63, 0x6f, 0x6c, 0x6c, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68,\n\t0x6f, 0x6c, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x63, 0x61,\n\t0x63, 0x68, 0x65, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52,\n\t0x10, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64, 0x44, 0x61, 0x74,\n\t0x61, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22,\n\t0xe4, 0x02, 0x0a, 0x0e, 0x47, 0x69, 0x74, 0x48, 0x75, 0x62, 0x52, 0x65, 0x61, 0x6c, 0x74, 0x69,\n\t0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x5f, 0x70,\n\t0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6c, 0x69, 0x73, 0x74, 0x65,\n\t0x6e, 0x65, 0x72, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x65, 0x62, 0x68, 0x6f,\n\t0x6f, 0x6b, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x0d, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x24,\n\t0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,\n\t0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x90, 0x01, 0x01, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70,\n\t0x6f, 0x69, 0x6e, 0x74, 0x12, 0x37, 0x0a, 0x0a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x5f, 0x61,\n\t0x70, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65,\n\t0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x47, 0x69, 0x74, 0x48, 0x75, 0x62, 0x41, 0x70, 0x70,\n\t0x48, 0x00, 0x52, 0x09, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x41, 0x70, 0x70, 0x12, 0x16, 0x0a,\n\t0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05,\n\t0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x48, 0x0a, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65,\n\t0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c,\n\t0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x55, 0x6e, 0x61,\n\t0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0f,\n\t0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12,\n\t0x37, 0x0a, 0x0a, 0x62, 0x61, 0x73, 0x69, 0x63, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x07, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,\n\t0x73, 0x2e, 0x42, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x48, 0x00, 0x52, 0x09, 0x62,\n\t0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64,\n\t0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22, 0xce, 0x01, 0x0a, 0x0b, 0x47, 0x6f, 0x6f, 0x67, 0x6c,\n\t0x65, 0x44, 0x72, 0x69, 0x76, 0x65, 0x12, 0x25, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73,\n\t0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52,\n\t0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2c, 0x0a,\n\t0x11, 0x75, 0x73, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69,\n\t0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0f, 0x75, 0x73, 0x65, 0x54,\n\t0x6f, 0x6b, 0x65, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x6f,\n\t0x61, 0x75, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x72, 0x65,\n\t0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x48,\n\t0x00, 0x52, 0x05, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x12, 0x2f, 0x0a, 0x03, 0x64, 0x77, 0x64, 0x18,\n\t0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,\n\t0x61, 0x6c, 0x73, 0x2e, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x44, 0x72, 0x69, 0x76, 0x65, 0x44,\n\t0x57, 0x44, 0x48, 0x00, 0x52, 0x03, 0x64, 0x77, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65,\n\t0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22, 0xe5, 0x05, 0x0a, 0x0b, 0x48, 0x75, 0x67, 0x67,\n\t0x69, 0x6e, 0x67, 0x66, 0x61, 0x63, 0x65, 0x12, 0x24, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f,\n\t0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03,\n\t0x90, 0x01, 0x01, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a,\n\t0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05,\n\t0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x48, 0x0a, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65,\n\t0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c,\n\t0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x55, 0x6e, 0x61,\n\t0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0f,\n\t0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12,\n\t0x16, 0x0a, 0x06, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52,\n\t0x06, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x70, 0x61, 0x63, 0x65,\n\t0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12,\n\t0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28,\n\t0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6f,\n\t0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x03,\n\t0x28, 0x09, 0x52, 0x0d, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e,\n\t0x73, 0x12, 0x14, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x09,\n\t0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72,\n\t0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c,\n\t0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x12, 0x25, 0x0a, 0x0e,\n\t0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x18, 0x08,\n\t0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x4d, 0x6f, 0x64,\n\t0x65, 0x6c, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x73, 0x70,\n\t0x61, 0x63, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x67, 0x6e, 0x6f,\n\t0x72, 0x65, 0x53, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x63, 0x6c,\n\t0x75, 0x64, 0x65, 0x5f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09,\n\t0x52, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x53, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12,\n\t0x27, 0x0a, 0x0f, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65,\n\t0x74, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65,\n\t0x44, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c,\n\t0x75, 0x64, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x73, 0x18, 0x0e, 0x20, 0x03,\n\t0x28, 0x09, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x44, 0x61, 0x74, 0x61, 0x73,\n\t0x65, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x61, 0x6c, 0x6c, 0x5f,\n\t0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x6b,\n\t0x69, 0x70, 0x41, 0x6c, 0x6c, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x73,\n\t0x6b, 0x69, 0x70, 0x5f, 0x61, 0x6c, 0x6c, 0x5f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x18, 0x11,\n\t0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x41, 0x6c, 0x6c, 0x53, 0x70, 0x61,\n\t0x63, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x61, 0x6c, 0x6c, 0x5f,\n\t0x64, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f,\n\t0x73, 0x6b, 0x69, 0x70, 0x41, 0x6c, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x73, 0x65, 0x74, 0x73, 0x12,\n\t0x2f, 0x0a, 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x75,\n\t0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x6e,\n\t0x63, 0x6c, 0x75, 0x64, 0x65, 0x44, 0x69, 0x73, 0x63, 0x75, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73,\n\t0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x72, 0x73, 0x18,\n\t0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x72,\n\t0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22,\n\t0xcc, 0x03, 0x0a, 0x04, 0x4a, 0x49, 0x52, 0x41, 0x12, 0x24, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70,\n\t0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72,\n\t0x03, 0x90, 0x01, 0x01, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x37,\n\t0x0a, 0x0a, 0x62, 0x61, 0x73, 0x69, 0x63, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73,\n\t0x2e, 0x42, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x48, 0x00, 0x52, 0x09, 0x62, 0x61,\n\t0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x12, 0x48, 0x0a, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74,\n\t0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x1c, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x55,\n\t0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x48, 0x00,\n\t0x52, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65,\n\t0x64, 0x12, 0x2b, 0x0a, 0x05, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x13, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x4f,\n\t0x61, 0x75, 0x74, 0x68, 0x32, 0x48, 0x00, 0x52, 0x05, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x12, 0x16,\n\t0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52,\n\t0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63,\n\t0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63,\n\t0x74, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x70, 0x72, 0x6f,\n\t0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x67, 0x6e,\n\t0x6f, 0x72, 0x65, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x37, 0x0a, 0x18, 0x69,\n\t0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x76, 0x65, 0x72,\n\t0x69, 0x66, 0x79, 0x5f, 0x74, 0x6c, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x69,\n\t0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x53, 0x6b, 0x69, 0x70, 0x56, 0x65, 0x72, 0x69, 0x66,\n\t0x79, 0x54, 0x6c, 0x73, 0x12, 0x4a, 0x0a, 0x11, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61,\n\t0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32,\n\t0x1d, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x4a, 0x69, 0x72, 0x61, 0x49, 0x6e,\n\t0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x10,\n\t0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65,\n\t0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22, 0x73,\n\t0x0a, 0x19, 0x4e, 0x50, 0x4d, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63,\n\t0x61, 0x74, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x48, 0x0a, 0x0f, 0x75,\n\t0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61,\n\t0x6c, 0x73, 0x2e, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74,\n\t0x65, 0x64, 0x48, 0x00, 0x52, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69,\n\t0x63, 0x61, 0x74, 0x65, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74,\n\t0x69, 0x61, 0x6c, 0x22, 0x74, 0x0a, 0x1a, 0x50, 0x79, 0x50, 0x49, 0x55, 0x6e, 0x61, 0x75, 0x74,\n\t0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67,\n\t0x65, 0x12, 0x48, 0x0a, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63,\n\t0x61, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x72, 0x65,\n\t0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65,\n\t0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0f, 0x75, 0x6e, 0x61, 0x75,\n\t0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x63,\n\t0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22, 0xde, 0x03, 0x0a, 0x02, 0x53, 0x33,\n\t0x12, 0x37, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61,\n\t0x6c, 0x73, 0x2e, 0x4b, 0x65, 0x79, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x48, 0x00, 0x52, 0x09,\n\t0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x48, 0x0a, 0x0f, 0x75, 0x6e, 0x61,\n\t0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73,\n\t0x2e, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64,\n\t0x48, 0x00, 0x52, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61,\n\t0x74, 0x65, 0x64, 0x12, 0x4c, 0x0a, 0x11, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x65, 0x6e, 0x76,\n\t0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d,\n\t0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x43, 0x6c, 0x6f,\n\t0x75, 0x64, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52,\n\t0x10, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e,\n\t0x74, 0x12, 0x49, 0x0a, 0x0d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x6f, 0x6b,\n\t0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65,\n\t0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x41, 0x57, 0x53, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f,\n\t0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x48, 0x00, 0x52, 0x0c,\n\t0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x18, 0x0a, 0x07,\n\t0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x62,\n\t0x75, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x6f, 0x62,\n\t0x6a, 0x65, 0x63, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52,\n\t0x0d, 0x6d, 0x61, 0x78, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x14,\n\t0x0a, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x72,\n\t0x6f, 0x6c, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x62,\n\t0x75, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x69, 0x67,\n\t0x6e, 0x6f, 0x72, 0x65, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x2f, 0x0a, 0x11, 0x65,\n\t0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6d, 0x70, 0x74, 0x69, 0x6f, 0x6e,\n\t0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x10, 0x65, 0x6e, 0x61, 0x62,\n\t0x6c, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0c, 0x0a, 0x0a,\n\t0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22, 0xc4, 0x01, 0x0a, 0x05, 0x53,\n\t0x6c, 0x61, 0x63, 0x6b, 0x12, 0x24, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x90, 0x01, 0x01,\n\t0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x05, 0x74, 0x6f,\n\t0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x74, 0x6f, 0x6b,\n\t0x65, 0x6e, 0x12, 0x32, 0x0a, 0x06, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73,\n\t0x2e, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x06,\n\t0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65,\n\t0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65,\n\t0x6c, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x6c, 0x69, 0x73,\n\t0x74, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c,\n\t0x69, 0x73, 0x74, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61,\n\t0x6c, 0x22, 0x06, 0x0a, 0x04, 0x54, 0x65, 0x73, 0x74, 0x22, 0x31, 0x0a, 0x09, 0x42, 0x75, 0x69,\n\t0x6c, 0x64, 0x6b, 0x69, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0x0c,\n\t0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22, 0xa5, 0x02, 0x0a,\n\t0x06, 0x47, 0x65, 0x72, 0x72, 0x69, 0x74, 0x12, 0x24, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f,\n\t0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03,\n\t0x90, 0x01, 0x01, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x37, 0x0a,\n\t0x0a, 0x62, 0x61, 0x73, 0x69, 0x63, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x0b, 0x32, 0x16, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e,\n\t0x42, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x48, 0x00, 0x52, 0x09, 0x62, 0x61, 0x73,\n\t0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x12, 0x48, 0x0a, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68,\n\t0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x1c, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x55, 0x6e,\n\t0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52,\n\t0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64,\n\t0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03,\n\t0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x0d,\n\t0x73, 0x6b, 0x69, 0x70, 0x5f, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0x05, 0x20,\n\t0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x69, 0x65,\n\t0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76,\n\t0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x41, 0x72,\n\t0x63, 0x68, 0x69, 0x76, 0x65, 0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e,\n\t0x74, 0x69, 0x61, 0x6c, 0x22, 0xa8, 0x02, 0x0a, 0x07, 0x4a, 0x65, 0x6e, 0x6b, 0x69, 0x6e, 0x73,\n\t0x12, 0x24, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x90, 0x01, 0x01, 0x52, 0x08, 0x65, 0x6e,\n\t0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x37, 0x0a, 0x0a, 0x62, 0x61, 0x73, 0x69, 0x63, 0x5f,\n\t0x61, 0x75, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x72, 0x65,\n\t0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75,\n\t0x74, 0x68, 0x48, 0x00, 0x52, 0x09, 0x62, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x12,\n\t0x2d, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,\n\t0x13, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x48, 0x65,\n\t0x61, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x48,\n\t0x0a, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65,\n\t0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e,\n\t0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69,\n\t0x63, 0x61, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65,\n\t0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x37, 0x0a, 0x18, 0x69, 0x6e, 0x73, 0x65,\n\t0x63, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79,\n\t0x5f, 0x74, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x69, 0x6e, 0x73, 0x65,\n\t0x63, 0x75, 0x72, 0x65, 0x53, 0x6b, 0x69, 0x70, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x54, 0x6c,\n\t0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22,\n\t0xbd, 0x02, 0x0a, 0x05, 0x54, 0x65, 0x61, 0x6d, 0x73, 0x12, 0x24, 0x0a, 0x08, 0x65, 0x6e, 0x64,\n\t0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05,\n\t0x72, 0x03, 0x90, 0x01, 0x01, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12,\n\t0x16, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00,\n\t0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x46, 0x0a, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x65,\n\t0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e,\n\t0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x43, 0x6c, 0x69,\n\t0x65, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x48, 0x00,\n\t0x52, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12,\n\t0x2b, 0x0a, 0x05, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13,\n\t0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x4f, 0x61, 0x75,\n\t0x74, 0x68, 0x32, 0x48, 0x00, 0x52, 0x05, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08,\n\t0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08,\n\t0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x67, 0x6e, 0x6f,\n\t0x72, 0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x69,\n\t0x67, 0x6e, 0x6f, 0x72, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x65, 0x61,\n\t0x6d, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x74, 0x65, 0x61,\n\t0x6d, 0x49, 0x64, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69,\n\t0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49,\n\t0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22,\n\t0x95, 0x01, 0x0a, 0x06, 0x53, 0x79, 0x73, 0x6c, 0x6f, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72,\n\t0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e,\n\t0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,\n\t0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x0a,\n\t0x07, 0x74, 0x6c, 0x73, 0x43, 0x65, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,\n\t0x74, 0x6c, 0x73, 0x43, 0x65, 0x72, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x6c, 0x73, 0x4b, 0x65,\n\t0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x6c, 0x73, 0x4b, 0x65, 0x79, 0x12,\n\t0x16, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x22, 0xca, 0x01, 0x0a, 0x07, 0x46, 0x6f, 0x72, 0x61,\n\t0x67, 0x65, 0x72, 0x12, 0x48, 0x0a, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74,\n\t0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63,\n\t0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x55, 0x6e, 0x61, 0x75, 0x74,\n\t0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0f, 0x75, 0x6e,\n\t0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a,\n\t0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07,\n\t0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x64,\n\t0x65, 0x70, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x44,\n\t0x65, 0x70, 0x74, 0x68, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,\n\t0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e,\n\t0x74, 0x69, 0x61, 0x6c, 0x22, 0x51, 0x0a, 0x0d, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x61,\n\t0x6c, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x32, 0x0a, 0x06, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69,\n\t0x61, 0x6c, 0x73, 0x2e, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x48,\n\t0x00, 0x52, 0x06, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65,\n\t0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22, 0x62, 0x0a, 0x0a, 0x53, 0x68, 0x61, 0x72, 0x65,\n\t0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2b, 0x0a, 0x05, 0x6f, 0x61, 0x75, 0x74, 0x68, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61,\n\t0x6c, 0x73, 0x2e, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x48, 0x00, 0x52, 0x05, 0x6f, 0x61, 0x75,\n\t0x74, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x69, 0x74, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x69, 0x74, 0x65, 0x55, 0x72, 0x6c, 0x42, 0x0c, 0x0a,\n\t0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22, 0xf6, 0x03, 0x0a, 0x0a,\n\t0x41, 0x7a, 0x75, 0x72, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x12, 0x24, 0x0a, 0x08, 0x65, 0x6e,\n\t0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42,\n\t0x05, 0x72, 0x03, 0x90, 0x01, 0x01, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,\n\t0x12, 0x16, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48,\n\t0x00, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2b, 0x0a, 0x05, 0x6f, 0x61, 0x75, 0x74,\n\t0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e,\n\t0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x4f, 0x61, 0x75, 0x74, 0x68, 0x32, 0x48, 0x00, 0x52, 0x05,\n\t0x6f, 0x61, 0x75, 0x74, 0x68, 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74,\n\t0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x70,\n\t0x6f, 0x73, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6f, 0x72, 0x67,\n\t0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09,\n\t0x52, 0x0d, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12,\n\t0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28,\n\t0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69,\n\t0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x07, 0x20, 0x01,\n\t0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x46, 0x6f, 0x72, 0x6b, 0x73,\n\t0x12, 0x21, 0x0a, 0x0c, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x73,\n\t0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x52, 0x65,\n\t0x70, 0x6f, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x72,\n\t0x65, 0x70, 0x6f, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c,\n\t0x75, 0x64, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c,\n\t0x75, 0x64, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x03,\n\t0x28, 0x09, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x72, 0x6f, 0x6a, 0x65,\n\t0x63, 0x74, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x70, 0x72,\n\t0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x69, 0x67,\n\t0x6e, 0x6f, 0x72, 0x65, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x0d,\n\t0x73, 0x6b, 0x69, 0x70, 0x5f, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0x0c, 0x20,\n\t0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x69, 0x65,\n\t0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76,\n\t0x65, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x41, 0x72,\n\t0x63, 0x68, 0x69, 0x76, 0x65, 0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e,\n\t0x74, 0x69, 0x61, 0x6c, 0x22, 0xd5, 0x04, 0x0a, 0x07, 0x50, 0x6f, 0x73, 0x74, 0x6d, 0x61, 0x6e,\n\t0x12, 0x48, 0x0a, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61,\n\t0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x72, 0x65, 0x64,\n\t0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e,\n\t0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0f, 0x75, 0x6e, 0x61, 0x75, 0x74,\n\t0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x05, 0x74, 0x6f,\n\t0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x74, 0x6f, 0x6b,\n\t0x65, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73,\n\t0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,\n\t0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,\n\t0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74,\n\t0x69, 0x6f, 0x6e, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d,\n\t0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x6e, 0x76, 0x69,\n\t0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x65, 0x78, 0x63, 0x6c,\n\t0x75, 0x64, 0x65, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18,\n\t0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x43, 0x6f,\n\t0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x31, 0x0a, 0x14, 0x65, 0x78, 0x63,\n\t0x6c, 0x75, 0x64, 0x65, 0x5f, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74,\n\t0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65,\n\t0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2f, 0x0a, 0x13,\n\t0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69,\n\t0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75,\n\t0x64, 0x65, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x31, 0x0a,\n\t0x14, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e,\n\t0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x69, 0x6e, 0x63,\n\t0x6c, 0x75, 0x64, 0x65, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x73,\n\t0x12, 0x2b, 0x0a, 0x11, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x6b, 0x65, 0x79,\n\t0x77, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x64, 0x65, 0x74,\n\t0x65, 0x63, 0x74, 0x6f, 0x72, 0x4b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x27, 0x0a,\n\t0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73,\n\t0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,\n\t0x65, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63,\n\t0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09,\n\t0x52, 0x0f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68,\n\t0x73, 0x12, 0x2b, 0x0a, 0x11, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74,\n\t0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x65, 0x6e,\n\t0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x73, 0x42, 0x0c,\n\t0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22, 0xac, 0x01, 0x0a,\n\t0x07, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x12, 0x2e, 0x0a, 0x0e, 0x6c, 0x69, 0x73, 0x74,\n\t0x65, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x68, 0x01, 0x52, 0x0d, 0x6c, 0x69, 0x73, 0x74, 0x65,\n\t0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2d, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64,\n\t0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65,\n\t0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52,\n\t0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x06, 0x76, 0x65, 0x63, 0x74, 0x6f,\n\t0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,\n\t0x73, 0x2e, 0x56, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x48, 0x01, 0x52, 0x06, 0x76, 0x65, 0x63, 0x74,\n\t0x6f, 0x72, 0x42, 0x0c, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,\n\t0x42, 0x09, 0x0a, 0x07, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x22, 0x4e, 0x0a, 0x06, 0x56,\n\t0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72,\n\t0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x6f,\n\t0x63, 0x61, 0x74, 0x6f, 0x72, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x69,\n\t0x6e, 0x6b, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x0a, 0x6c, 0x69, 0x6e, 0x6b, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x22, 0xcd, 0x02, 0x0a, 0x0d,\n\t0x45, 0x6c, 0x61, 0x73, 0x74, 0x69, 0x63, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x14, 0x0a,\n\t0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x6f,\n\t0x64, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12,\n\t0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63,\n\t0x6c, 0x6f, 0x75, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63,\n\t0x6c, 0x6f, 0x75, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65,\n\t0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x12,\n\t0x23, 0x0a, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e,\n\t0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x54,\n\t0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x70, 0x61,\n\t0x74, 0x74, 0x65, 0x72, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x64,\n\t0x65, 0x78, 0x50, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x71, 0x75, 0x65,\n\t0x72, 0x79, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x71,\n\t0x75, 0x65, 0x72, 0x79, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x69, 0x6e, 0x63,\n\t0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x09, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x0e, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,\n\t0x70, 0x12, 0x28, 0x0a, 0x10, 0x62, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x66, 0x66, 0x6f, 0x72, 0x74,\n\t0x5f, 0x73, 0x63, 0x61, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x62, 0x65, 0x73,\n\t0x74, 0x45, 0x66, 0x66, 0x6f, 0x72, 0x74, 0x53, 0x63, 0x61, 0x6e, 0x22, 0xde, 0x01, 0x0a, 0x06,\n\t0x53, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69,\n\t0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69,\n\t0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x61, 0x75, 0x74, 0x68, 0x54, 0x6f,\n\t0x6b, 0x65, 0x6e, 0x12, 0x19, 0x0a, 0x07, 0x64, 0x73, 0x6e, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03,\n\t0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x64, 0x73, 0x6e, 0x4b, 0x65, 0x79, 0x12, 0x19,\n\t0x0a, 0x07, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48,\n\t0x00, 0x52, 0x06, 0x61, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x12, 0x37, 0x0a, 0x18, 0x69, 0x6e, 0x73,\n\t0x65, 0x63, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66,\n\t0x79, 0x5f, 0x74, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x69, 0x6e, 0x73,\n\t0x65, 0x63, 0x75, 0x72, 0x65, 0x53, 0x6b, 0x69, 0x70, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x54,\n\t0x6c, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x06,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x42, 0x0c,\n\t0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x22, 0x07, 0x0a, 0x05,\n\t0x53, 0x74, 0x64, 0x69, 0x6e, 0x22, 0x4e, 0x0a, 0x0f, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x43, 0x6f,\n\t0x6e, 0x74, 0x69, 0x6e, 0x75, 0x6f, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65,\n\t0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d,\n\t0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63,\n\t0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x6a,\n\t0x65, 0x63, 0x74, 0x49, 0x64, 0x22, 0x26, 0x0a, 0x0e, 0x4a, 0x53, 0x4f, 0x4e, 0x45, 0x6e, 0x75,\n\t0x6d, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73,\n\t0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x2a, 0xc4, 0x09,\n\t0x0a, 0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x19,\n\t0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x5a, 0x55, 0x52,\n\t0x45, 0x5f, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x53,\n\t0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x42, 0x49, 0x54, 0x42, 0x55,\n\t0x43, 0x4b, 0x45, 0x54, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45,\n\t0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x49, 0x52, 0x43, 0x4c, 0x45, 0x43, 0x49, 0x10, 0x02,\n\t0x12, 0x1a, 0x0a, 0x16, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,\n\t0x43, 0x4f, 0x4e, 0x46, 0x4c, 0x55, 0x45, 0x4e, 0x43, 0x45, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12,\n\t0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x4f, 0x43, 0x4b,\n\t0x45, 0x52, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54,\n\t0x59, 0x50, 0x45, 0x5f, 0x45, 0x43, 0x52, 0x10, 0x05, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x4f, 0x55,\n\t0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, 0x43, 0x53, 0x10, 0x06, 0x12, 0x16,\n\t0x0a, 0x12, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, 0x49,\n\t0x54, 0x48, 0x55, 0x42, 0x10, 0x07, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45,\n\t0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x47, 0x49, 0x54,\n\t0x10, 0x08, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50,\n\t0x45, 0x5f, 0x47, 0x49, 0x54, 0x4c, 0x41, 0x42, 0x10, 0x09, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x4f,\n\t0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4a, 0x49, 0x52, 0x41, 0x10, 0x0a,\n\t0x12, 0x24, 0x0a, 0x20, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,\n\t0x4e, 0x50, 0x4d, 0x5f, 0x55, 0x4e, 0x41, 0x55, 0x54, 0x48, 0x44, 0x5f, 0x50, 0x41, 0x43, 0x4b,\n\t0x41, 0x47, 0x45, 0x53, 0x10, 0x0b, 0x12, 0x25, 0x0a, 0x21, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45,\n\t0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x59, 0x50, 0x49, 0x5f, 0x55, 0x4e, 0x41, 0x55, 0x54,\n\t0x48, 0x44, 0x5f, 0x50, 0x41, 0x43, 0x4b, 0x41, 0x47, 0x45, 0x53, 0x10, 0x0c, 0x12, 0x12, 0x0a,\n\t0x0e, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x33, 0x10,\n\t0x0d, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45,\n\t0x5f, 0x53, 0x4c, 0x41, 0x43, 0x4b, 0x10, 0x0e, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x4f, 0x55, 0x52,\n\t0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x53, 0x59, 0x53, 0x54,\n\t0x45, 0x4d, 0x10, 0x0f, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54,\n\t0x59, 0x50, 0x45, 0x5f, 0x47, 0x49, 0x54, 0x10, 0x10, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x4f, 0x55,\n\t0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, 0x45, 0x53, 0x54, 0x10, 0x11, 0x12,\n\t0x1b, 0x0a, 0x17, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53,\n\t0x33, 0x5f, 0x55, 0x4e, 0x41, 0x55, 0x54, 0x48, 0x45, 0x44, 0x10, 0x12, 0x12, 0x2a, 0x0a, 0x26,\n\t0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, 0x49, 0x54, 0x48,\n\t0x55, 0x42, 0x5f, 0x55, 0x4e, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54,\n\t0x45, 0x44, 0x5f, 0x4f, 0x52, 0x47, 0x10, 0x13, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x4f, 0x55, 0x52,\n\t0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x4b, 0x49, 0x54,\n\t0x45, 0x10, 0x14, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59,\n\t0x50, 0x45, 0x5f, 0x47, 0x45, 0x52, 0x52, 0x49, 0x54, 0x10, 0x15, 0x12, 0x17, 0x0a, 0x13, 0x53,\n\t0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4a, 0x45, 0x4e, 0x4b, 0x49,\n\t0x4e, 0x53, 0x10, 0x16, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54,\n\t0x59, 0x50, 0x45, 0x5f, 0x54, 0x45, 0x41, 0x4d, 0x53, 0x10, 0x17, 0x12, 0x21, 0x0a, 0x1d, 0x53,\n\t0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4a, 0x46, 0x52, 0x4f, 0x47,\n\t0x5f, 0x41, 0x52, 0x54, 0x49, 0x46, 0x41, 0x43, 0x54, 0x4f, 0x52, 0x59, 0x10, 0x18, 0x12, 0x16,\n\t0x0a, 0x12, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x59,\n\t0x53, 0x4c, 0x4f, 0x47, 0x10, 0x19, 0x12, 0x27, 0x0a, 0x23, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45,\n\t0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x45, 0x56, 0x45,\n\t0x4e, 0x54, 0x5f, 0x4d, 0x4f, 0x4e, 0x49, 0x54, 0x4f, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x1a, 0x12,\n\t0x1e, 0x0a, 0x1a, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53,\n\t0x4c, 0x41, 0x43, 0x4b, 0x5f, 0x52, 0x45, 0x41, 0x4c, 0x54, 0x49, 0x4d, 0x45, 0x10, 0x1b, 0x12,\n\t0x1c, 0x0a, 0x18, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47,\n\t0x4f, 0x4f, 0x47, 0x4c, 0x45, 0x5f, 0x44, 0x52, 0x49, 0x56, 0x45, 0x10, 0x1c, 0x12, 0x1a, 0x0a,\n\t0x16, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x48, 0x41,\n\t0x52, 0x45, 0x50, 0x4f, 0x49, 0x4e, 0x54, 0x10, 0x1d, 0x12, 0x1c, 0x0a, 0x18, 0x53, 0x4f, 0x55,\n\t0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, 0x43, 0x53, 0x5f, 0x55, 0x4e, 0x41,\n\t0x55, 0x54, 0x48, 0x45, 0x44, 0x10, 0x1e, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x4f, 0x55, 0x52, 0x43,\n\t0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x5a, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x50,\n\t0x4f, 0x53, 0x10, 0x1f, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54,\n\t0x59, 0x50, 0x45, 0x5f, 0x54, 0x52, 0x41, 0x56, 0x49, 0x53, 0x43, 0x49, 0x10, 0x20, 0x12, 0x17,\n\t0x0a, 0x13, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x4f,\n\t0x53, 0x54, 0x4d, 0x41, 0x4e, 0x10, 0x21, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x4f, 0x55, 0x52, 0x43,\n\t0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x45, 0x42, 0x48, 0x4f, 0x4f, 0x4b, 0x10, 0x22,\n\t0x12, 0x1d, 0x0a, 0x19, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,\n\t0x45, 0x4c, 0x41, 0x53, 0x54, 0x49, 0x43, 0x53, 0x45, 0x41, 0x52, 0x43, 0x48, 0x10, 0x23, 0x12,\n\t0x1b, 0x0a, 0x17, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x48,\n\t0x55, 0x47, 0x47, 0x49, 0x4e, 0x47, 0x46, 0x41, 0x43, 0x45, 0x10, 0x24, 0x12, 0x23, 0x0a, 0x1f,\n\t0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, 0x49, 0x54, 0x48,\n\t0x55, 0x42, 0x5f, 0x45, 0x58, 0x50, 0x45, 0x52, 0x49, 0x4d, 0x45, 0x4e, 0x54, 0x41, 0x4c, 0x10,\n\t0x25, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45,\n\t0x5f, 0x53, 0x45, 0x4e, 0x54, 0x52, 0x59, 0x10, 0x26, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x4f, 0x55,\n\t0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, 0x49, 0x54, 0x48, 0x55, 0x42, 0x5f,\n\t0x52, 0x45, 0x41, 0x4c, 0x54, 0x49, 0x4d, 0x45, 0x10, 0x27, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x4f,\n\t0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x44, 0x49, 0x4e, 0x10,\n\t0x28, 0x12, 0x20, 0x0a, 0x1c, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45,\n\t0x5f, 0x53, 0x4c, 0x41, 0x43, 0x4b, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x49, 0x4e, 0x55, 0x4f, 0x55,\n\t0x53, 0x10, 0x29, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59,\n\t0x50, 0x45, 0x5f, 0x4a, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x45, 0x52, 0x41, 0x54,\n\t0x4f, 0x52, 0x10, 0x2a, 0x2a, 0x47, 0x0a, 0x19, 0x42, 0x69, 0x74, 0x62, 0x75, 0x63, 0x6b, 0x65,\n\t0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70,\n\t0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x41, 0x55, 0x54, 0x4f, 0x44, 0x45, 0x54, 0x45, 0x43, 0x54, 0x10,\n\t0x00, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x4f, 0x55, 0x44, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b,\n\t0x44, 0x41, 0x54, 0x41, 0x5f, 0x43, 0x45, 0x4e, 0x54, 0x45, 0x52, 0x10, 0x02, 0x2a, 0x87, 0x01,\n\t0x0a, 0x14, 0x4a, 0x69, 0x72, 0x61, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x61, 0x74, 0x69,\n\t0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x21, 0x4a, 0x49, 0x52, 0x41, 0x5f, 0x49,\n\t0x4e, 0x53, 0x54, 0x41, 0x4c, 0x4c, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45,\n\t0x5f, 0x41, 0x55, 0x54, 0x4f, 0x44, 0x45, 0x54, 0x45, 0x43, 0x54, 0x10, 0x00, 0x12, 0x20, 0x0a,\n\t0x1c, 0x4a, 0x49, 0x52, 0x41, 0x5f, 0x49, 0x4e, 0x53, 0x54, 0x41, 0x4c, 0x4c, 0x41, 0x54, 0x49,\n\t0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4c, 0x4f, 0x55, 0x44, 0x10, 0x01, 0x12,\n\t0x26, 0x0a, 0x22, 0x4a, 0x49, 0x52, 0x41, 0x5f, 0x49, 0x4e, 0x53, 0x54, 0x41, 0x4c, 0x4c, 0x41,\n\t0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x43,\n\t0x45, 0x4e, 0x54, 0x45, 0x52, 0x10, 0x02, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75,\n\t0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x73, 0x65, 0x63,\n\t0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x68, 0x6f, 0x67,\n\t0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62, 0x2f, 0x73, 0x6f, 0x75, 0x72, 0x63,\n\t0x65, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n}\n\nvar (\n\tfile_sources_proto_rawDescOnce sync.Once\n\tfile_sources_proto_rawDescData = file_sources_proto_rawDesc\n)\n\nfunc file_sources_proto_rawDescGZIP() []byte {\n\tfile_sources_proto_rawDescOnce.Do(func() {\n\t\tfile_sources_proto_rawDescData = protoimpl.X.CompressGZIP(file_sources_proto_rawDescData)\n\t})\n\treturn file_sources_proto_rawDescData\n}\n\nvar file_sources_proto_enumTypes = make([]protoimpl.EnumInfo, 4)\nvar file_sources_proto_msgTypes = make([]protoimpl.MessageInfo, 41)\nvar file_sources_proto_goTypes = []interface{}{\n\t(SourceType)(0),                             // 0: sources.SourceType\n\t(BitbucketInstallationType)(0),              // 1: sources.BitbucketInstallationType\n\t(JiraInstallationType)(0),                   // 2: sources.JiraInstallationType\n\t(Confluence_GetAllSpacesScope)(0),           // 3: sources.Confluence.GetAllSpacesScope\n\t(*LocalSource)(nil),                         // 4: sources.LocalSource\n\t(*Artifactory)(nil),                         // 5: sources.Artifactory\n\t(*AzureStorage)(nil),                        // 6: sources.AzureStorage\n\t(*Bitbucket)(nil),                           // 7: sources.Bitbucket\n\t(*CircleCI)(nil),                            // 8: sources.CircleCI\n\t(*TravisCI)(nil),                            // 9: sources.TravisCI\n\t(*Confluence)(nil),                          // 10: sources.Confluence\n\t(*Docker)(nil),                              // 11: sources.Docker\n\t(*ECR)(nil),                                 // 12: sources.ECR\n\t(*Filesystem)(nil),                          // 13: sources.Filesystem\n\t(*GCS)(nil),                                 // 14: sources.GCS\n\t(*Git)(nil),                                 // 15: sources.Git\n\t(*GitLab)(nil),                              // 16: sources.GitLab\n\t(*GitHub)(nil),                              // 17: sources.GitHub\n\t(*GitHubExperimental)(nil),                  // 18: sources.GitHubExperimental\n\t(*GitHubRealtime)(nil),                      // 19: sources.GitHubRealtime\n\t(*GoogleDrive)(nil),                         // 20: sources.GoogleDrive\n\t(*Huggingface)(nil),                         // 21: sources.Huggingface\n\t(*JIRA)(nil),                                // 22: sources.JIRA\n\t(*NPMUnauthenticatedPackage)(nil),           // 23: sources.NPMUnauthenticatedPackage\n\t(*PyPIUnauthenticatedPackage)(nil),          // 24: sources.PyPIUnauthenticatedPackage\n\t(*S3)(nil),                                  // 25: sources.S3\n\t(*Slack)(nil),                               // 26: sources.Slack\n\t(*Test)(nil),                                // 27: sources.Test\n\t(*Buildkite)(nil),                           // 28: sources.Buildkite\n\t(*Gerrit)(nil),                              // 29: sources.Gerrit\n\t(*Jenkins)(nil),                             // 30: sources.Jenkins\n\t(*Teams)(nil),                               // 31: sources.Teams\n\t(*Syslog)(nil),                              // 32: sources.Syslog\n\t(*Forager)(nil),                             // 33: sources.Forager\n\t(*SlackRealtime)(nil),                       // 34: sources.SlackRealtime\n\t(*Sharepoint)(nil),                          // 35: sources.Sharepoint\n\t(*AzureRepos)(nil),                          // 36: sources.AzureRepos\n\t(*Postman)(nil),                             // 37: sources.Postman\n\t(*Webhook)(nil),                             // 38: sources.Webhook\n\t(*Vector)(nil),                              // 39: sources.Vector\n\t(*Elasticsearch)(nil),                       // 40: sources.Elasticsearch\n\t(*Sentry)(nil),                              // 41: sources.Sentry\n\t(*Stdin)(nil),                               // 42: sources.Stdin\n\t(*SlackContinuous)(nil),                     // 43: sources.SlackContinuous\n\t(*JSONEnumerator)(nil),                      // 44: sources.JSONEnumerator\n\t(*durationpb.Duration)(nil),                 // 45: google.protobuf.Duration\n\t(*anypb.Any)(nil),                           // 46: google.protobuf.Any\n\t(*credentialspb.BasicAuth)(nil),             // 47: credentials.BasicAuth\n\t(*credentialspb.Unauthenticated)(nil),       // 48: credentials.Unauthenticated\n\t(*credentialspb.Oauth2)(nil),                // 49: credentials.Oauth2\n\t(*credentialspb.KeySecret)(nil),             // 50: credentials.KeySecret\n\t(*credentialspb.CloudEnvironment)(nil),      // 51: credentials.CloudEnvironment\n\t(*credentialspb.SSHAuth)(nil),               // 52: credentials.SSHAuth\n\t(*credentialspb.GitHubApp)(nil),             // 53: credentials.GitHubApp\n\t(*credentialspb.GoogleDriveDWD)(nil),        // 54: credentials.GoogleDriveDWD\n\t(*credentialspb.AWSSessionTokenSecret)(nil), // 55: credentials.AWSSessionTokenSecret\n\t(*credentialspb.SlackTokens)(nil),           // 56: credentials.SlackTokens\n\t(*credentialspb.Header)(nil),                // 57: credentials.Header\n\t(*credentialspb.ClientCredentials)(nil),     // 58: credentials.ClientCredentials\n\t(*timestamppb.Timestamp)(nil),               // 59: google.protobuf.Timestamp\n}\nvar file_sources_proto_depIdxs = []int32{\n\t45, // 0: sources.LocalSource.scan_interval:type_name -> google.protobuf.Duration\n\t46, // 1: sources.LocalSource.connection:type_name -> google.protobuf.Any\n\t47, // 2: sources.Artifactory.basic_auth:type_name -> credentials.BasicAuth\n\t48, // 3: sources.Artifactory.unauthenticated:type_name -> credentials.Unauthenticated\n\t47, // 4: sources.AzureStorage.basic_auth:type_name -> credentials.BasicAuth\n\t48, // 5: sources.AzureStorage.unauthenticated:type_name -> credentials.Unauthenticated\n\t49, // 6: sources.Bitbucket.oauth:type_name -> credentials.Oauth2\n\t47, // 7: sources.Bitbucket.basic_auth:type_name -> credentials.BasicAuth\n\t1,  // 8: sources.Bitbucket.installation_type:type_name -> sources.BitbucketInstallationType\n\t48, // 9: sources.Confluence.unauthenticated:type_name -> credentials.Unauthenticated\n\t47, // 10: sources.Confluence.basic_auth:type_name -> credentials.BasicAuth\n\t3,  // 11: sources.Confluence.spaces_scope:type_name -> sources.Confluence.GetAllSpacesScope\n\t48, // 12: sources.Docker.unauthenticated:type_name -> credentials.Unauthenticated\n\t47, // 13: sources.Docker.basic_auth:type_name -> credentials.BasicAuth\n\t50, // 14: sources.ECR.access_key:type_name -> credentials.KeySecret\n\t48, // 15: sources.GCS.unauthenticated:type_name -> credentials.Unauthenticated\n\t51, // 16: sources.GCS.adc:type_name -> credentials.CloudEnvironment\n\t49, // 17: sources.GCS.oauth:type_name -> credentials.Oauth2\n\t47, // 18: sources.Git.basic_auth:type_name -> credentials.BasicAuth\n\t48, // 19: sources.Git.unauthenticated:type_name -> credentials.Unauthenticated\n\t52, // 20: sources.Git.ssh_auth:type_name -> credentials.SSHAuth\n\t49, // 21: sources.GitLab.oauth:type_name -> credentials.Oauth2\n\t47, // 22: sources.GitLab.basic_auth:type_name -> credentials.BasicAuth\n\t53, // 23: sources.GitHub.github_app:type_name -> credentials.GitHubApp\n\t48, // 24: sources.GitHub.unauthenticated:type_name -> credentials.Unauthenticated\n\t47, // 25: sources.GitHub.basic_auth:type_name -> credentials.BasicAuth\n\t53, // 26: sources.GitHubRealtime.github_app:type_name -> credentials.GitHubApp\n\t48, // 27: sources.GitHubRealtime.unauthenticated:type_name -> credentials.Unauthenticated\n\t47, // 28: sources.GitHubRealtime.basic_auth:type_name -> credentials.BasicAuth\n\t49, // 29: sources.GoogleDrive.oauth:type_name -> credentials.Oauth2\n\t54, // 30: sources.GoogleDrive.dwd:type_name -> credentials.GoogleDriveDWD\n\t48, // 31: sources.Huggingface.unauthenticated:type_name -> credentials.Unauthenticated\n\t47, // 32: sources.JIRA.basic_auth:type_name -> credentials.BasicAuth\n\t48, // 33: sources.JIRA.unauthenticated:type_name -> credentials.Unauthenticated\n\t49, // 34: sources.JIRA.oauth:type_name -> credentials.Oauth2\n\t2,  // 35: sources.JIRA.installation_type:type_name -> sources.JiraInstallationType\n\t48, // 36: sources.NPMUnauthenticatedPackage.unauthenticated:type_name -> credentials.Unauthenticated\n\t48, // 37: sources.PyPIUnauthenticatedPackage.unauthenticated:type_name -> credentials.Unauthenticated\n\t50, // 38: sources.S3.access_key:type_name -> credentials.KeySecret\n\t48, // 39: sources.S3.unauthenticated:type_name -> credentials.Unauthenticated\n\t51, // 40: sources.S3.cloud_environment:type_name -> credentials.CloudEnvironment\n\t55, // 41: sources.S3.session_token:type_name -> credentials.AWSSessionTokenSecret\n\t56, // 42: sources.Slack.tokens:type_name -> credentials.SlackTokens\n\t47, // 43: sources.Gerrit.basic_auth:type_name -> credentials.BasicAuth\n\t48, // 44: sources.Gerrit.unauthenticated:type_name -> credentials.Unauthenticated\n\t47, // 45: sources.Jenkins.basic_auth:type_name -> credentials.BasicAuth\n\t57, // 46: sources.Jenkins.header:type_name -> credentials.Header\n\t48, // 47: sources.Jenkins.unauthenticated:type_name -> credentials.Unauthenticated\n\t58, // 48: sources.Teams.authenticated:type_name -> credentials.ClientCredentials\n\t49, // 49: sources.Teams.oauth:type_name -> credentials.Oauth2\n\t48, // 50: sources.Forager.unauthenticated:type_name -> credentials.Unauthenticated\n\t59, // 51: sources.Forager.since:type_name -> google.protobuf.Timestamp\n\t56, // 52: sources.SlackRealtime.tokens:type_name -> credentials.SlackTokens\n\t49, // 53: sources.Sharepoint.oauth:type_name -> credentials.Oauth2\n\t49, // 54: sources.AzureRepos.oauth:type_name -> credentials.Oauth2\n\t48, // 55: sources.Postman.unauthenticated:type_name -> credentials.Unauthenticated\n\t57, // 56: sources.Webhook.header:type_name -> credentials.Header\n\t39, // 57: sources.Webhook.vector:type_name -> sources.Vector\n\t58, // [58:58] is the sub-list for method output_type\n\t58, // [58:58] is the sub-list for method input_type\n\t58, // [58:58] is the sub-list for extension type_name\n\t58, // [58:58] is the sub-list for extension extendee\n\t0,  // [0:58] is the sub-list for field type_name\n}\n\nfunc init() { file_sources_proto_init() }\nfunc file_sources_proto_init() {\n\tif File_sources_proto != nil {\n\t\treturn\n\t}\n\tif !protoimpl.UnsafeEnabled {\n\t\tfile_sources_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*LocalSource); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Artifactory); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*AzureStorage); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Bitbucket); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*CircleCI); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*TravisCI); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Confluence); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Docker); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*ECR); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Filesystem); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GCS); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Git); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GitLab); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GitHub); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GitHubExperimental); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GitHubRealtime); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*GoogleDrive); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Huggingface); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*JIRA); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*NPMUnauthenticatedPackage); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*PyPIUnauthenticatedPackage); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*S3); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Slack); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Test); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Buildkite); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Gerrit); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Jenkins); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Teams); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Syslog); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Forager); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SlackRealtime); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Sharepoint); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*AzureRepos); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Postman); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Webhook); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Vector); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Elasticsearch); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Sentry); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*Stdin); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*SlackContinuous); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\tfile_sources_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} {\n\t\t\tswitch v := v.(*JSONEnumerator); i {\n\t\t\tcase 0:\n\t\t\t\treturn &v.state\n\t\t\tcase 1:\n\t\t\t\treturn &v.sizeCache\n\t\t\tcase 2:\n\t\t\t\treturn &v.unknownFields\n\t\t\tdefault:\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\tfile_sources_proto_msgTypes[1].OneofWrappers = []interface{}{\n\t\t(*Artifactory_BasicAuth)(nil),\n\t\t(*Artifactory_AccessToken)(nil),\n\t\t(*Artifactory_Unauthenticated)(nil),\n\t}\n\tfile_sources_proto_msgTypes[2].OneofWrappers = []interface{}{\n\t\t(*AzureStorage_ConnectionString)(nil),\n\t\t(*AzureStorage_BasicAuth)(nil),\n\t\t(*AzureStorage_ClientCertificate)(nil),\n\t\t(*AzureStorage_Unauthenticated)(nil),\n\t}\n\tfile_sources_proto_msgTypes[3].OneofWrappers = []interface{}{\n\t\t(*Bitbucket_Token)(nil),\n\t\t(*Bitbucket_Oauth)(nil),\n\t\t(*Bitbucket_BasicAuth)(nil),\n\t}\n\tfile_sources_proto_msgTypes[4].OneofWrappers = []interface{}{\n\t\t(*CircleCI_Token)(nil),\n\t}\n\tfile_sources_proto_msgTypes[5].OneofWrappers = []interface{}{\n\t\t(*TravisCI_Token)(nil),\n\t}\n\tfile_sources_proto_msgTypes[6].OneofWrappers = []interface{}{\n\t\t(*Confluence_Unauthenticated)(nil),\n\t\t(*Confluence_BasicAuth)(nil),\n\t\t(*Confluence_Token)(nil),\n\t}\n\tfile_sources_proto_msgTypes[7].OneofWrappers = []interface{}{\n\t\t(*Docker_Unauthenticated)(nil),\n\t\t(*Docker_BasicAuth)(nil),\n\t\t(*Docker_BearerToken)(nil),\n\t\t(*Docker_DockerKeychain)(nil),\n\t}\n\tfile_sources_proto_msgTypes[8].OneofWrappers = []interface{}{\n\t\t(*ECR_AccessKey)(nil),\n\t}\n\tfile_sources_proto_msgTypes[10].OneofWrappers = []interface{}{\n\t\t(*GCS_JsonServiceAccount)(nil),\n\t\t(*GCS_ApiKey)(nil),\n\t\t(*GCS_Unauthenticated)(nil),\n\t\t(*GCS_Adc)(nil),\n\t\t(*GCS_ServiceAccountFile)(nil),\n\t\t(*GCS_Oauth)(nil),\n\t}\n\tfile_sources_proto_msgTypes[11].OneofWrappers = []interface{}{\n\t\t(*Git_BasicAuth)(nil),\n\t\t(*Git_Unauthenticated)(nil),\n\t\t(*Git_SshAuth)(nil),\n\t}\n\tfile_sources_proto_msgTypes[12].OneofWrappers = []interface{}{\n\t\t(*GitLab_Token)(nil),\n\t\t(*GitLab_Oauth)(nil),\n\t\t(*GitLab_BasicAuth)(nil),\n\t}\n\tfile_sources_proto_msgTypes[13].OneofWrappers = []interface{}{\n\t\t(*GitHub_GithubApp)(nil),\n\t\t(*GitHub_Token)(nil),\n\t\t(*GitHub_Unauthenticated)(nil),\n\t\t(*GitHub_BasicAuth)(nil),\n\t}\n\tfile_sources_proto_msgTypes[14].OneofWrappers = []interface{}{\n\t\t(*GitHubExperimental_Token)(nil),\n\t}\n\tfile_sources_proto_msgTypes[15].OneofWrappers = []interface{}{\n\t\t(*GitHubRealtime_GithubApp)(nil),\n\t\t(*GitHubRealtime_Token)(nil),\n\t\t(*GitHubRealtime_Unauthenticated)(nil),\n\t\t(*GitHubRealtime_BasicAuth)(nil),\n\t}\n\tfile_sources_proto_msgTypes[16].OneofWrappers = []interface{}{\n\t\t(*GoogleDrive_RefreshToken)(nil),\n\t\t(*GoogleDrive_UseTokenService)(nil),\n\t\t(*GoogleDrive_Oauth)(nil),\n\t\t(*GoogleDrive_Dwd)(nil),\n\t}\n\tfile_sources_proto_msgTypes[17].OneofWrappers = []interface{}{\n\t\t(*Huggingface_Token)(nil),\n\t\t(*Huggingface_Unauthenticated)(nil),\n\t}\n\tfile_sources_proto_msgTypes[18].OneofWrappers = []interface{}{\n\t\t(*JIRA_BasicAuth)(nil),\n\t\t(*JIRA_Unauthenticated)(nil),\n\t\t(*JIRA_Oauth)(nil),\n\t\t(*JIRA_Token)(nil),\n\t}\n\tfile_sources_proto_msgTypes[19].OneofWrappers = []interface{}{\n\t\t(*NPMUnauthenticatedPackage_Unauthenticated)(nil),\n\t}\n\tfile_sources_proto_msgTypes[20].OneofWrappers = []interface{}{\n\t\t(*PyPIUnauthenticatedPackage_Unauthenticated)(nil),\n\t}\n\tfile_sources_proto_msgTypes[21].OneofWrappers = []interface{}{\n\t\t(*S3_AccessKey)(nil),\n\t\t(*S3_Unauthenticated)(nil),\n\t\t(*S3_CloudEnvironment)(nil),\n\t\t(*S3_SessionToken)(nil),\n\t}\n\tfile_sources_proto_msgTypes[22].OneofWrappers = []interface{}{\n\t\t(*Slack_Token)(nil),\n\t\t(*Slack_Tokens)(nil),\n\t}\n\tfile_sources_proto_msgTypes[24].OneofWrappers = []interface{}{\n\t\t(*Buildkite_Token)(nil),\n\t}\n\tfile_sources_proto_msgTypes[25].OneofWrappers = []interface{}{\n\t\t(*Gerrit_BasicAuth)(nil),\n\t\t(*Gerrit_Unauthenticated)(nil),\n\t}\n\tfile_sources_proto_msgTypes[26].OneofWrappers = []interface{}{\n\t\t(*Jenkins_BasicAuth)(nil),\n\t\t(*Jenkins_Header)(nil),\n\t\t(*Jenkins_Unauthenticated)(nil),\n\t}\n\tfile_sources_proto_msgTypes[27].OneofWrappers = []interface{}{\n\t\t(*Teams_Token)(nil),\n\t\t(*Teams_Authenticated)(nil),\n\t\t(*Teams_Oauth)(nil),\n\t}\n\tfile_sources_proto_msgTypes[29].OneofWrappers = []interface{}{\n\t\t(*Forager_Unauthenticated)(nil),\n\t}\n\tfile_sources_proto_msgTypes[30].OneofWrappers = []interface{}{\n\t\t(*SlackRealtime_Tokens)(nil),\n\t}\n\tfile_sources_proto_msgTypes[31].OneofWrappers = []interface{}{\n\t\t(*Sharepoint_Oauth)(nil),\n\t}\n\tfile_sources_proto_msgTypes[32].OneofWrappers = []interface{}{\n\t\t(*AzureRepos_Token)(nil),\n\t\t(*AzureRepos_Oauth)(nil),\n\t}\n\tfile_sources_proto_msgTypes[33].OneofWrappers = []interface{}{\n\t\t(*Postman_Unauthenticated)(nil),\n\t\t(*Postman_Token)(nil),\n\t}\n\tfile_sources_proto_msgTypes[34].OneofWrappers = []interface{}{\n\t\t(*Webhook_Header)(nil),\n\t\t(*Webhook_Vector)(nil),\n\t}\n\tfile_sources_proto_msgTypes[37].OneofWrappers = []interface{}{\n\t\t(*Sentry_AuthToken)(nil),\n\t\t(*Sentry_DsnKey)(nil),\n\t\t(*Sentry_ApiKey)(nil),\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: file_sources_proto_rawDesc,\n\t\t\tNumEnums:      4,\n\t\t\tNumMessages:   41,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_sources_proto_goTypes,\n\t\tDependencyIndexes: file_sources_proto_depIdxs,\n\t\tEnumInfos:         file_sources_proto_enumTypes,\n\t\tMessageInfos:      file_sources_proto_msgTypes,\n\t}.Build()\n\tFile_sources_proto = out.File\n\tfile_sources_proto_rawDesc = nil\n\tfile_sources_proto_goTypes = nil\n\tfile_sources_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "pkg/pb/sourcespb/sources.pb.validate.go",
    "content": "// Code generated by protoc-gen-validate. DO NOT EDIT.\n// source: sources.proto\n\npackage sourcespb\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/mail\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode/utf8\"\n\n\t\"google.golang.org/protobuf/types/known/anypb\"\n)\n\n// ensure the imports are used\nvar (\n\t_ = bytes.MinRead\n\t_ = errors.New(\"\")\n\t_ = fmt.Print\n\t_ = utf8.UTFMax\n\t_ = (*regexp.Regexp)(nil)\n\t_ = (*strings.Reader)(nil)\n\t_ = net.IPv4len\n\t_ = time.Duration(0)\n\t_ = (*url.URL)(nil)\n\t_ = (*mail.Address)(nil)\n\t_ = anypb.Any{}\n\t_ = sort.Sort\n)\n\n// Validate checks the field values on LocalSource with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *LocalSource) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on LocalSource with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in LocalSourceMultiError, or\n// nil if none found.\nfunc (m *LocalSource) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *LocalSource) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Type\n\n\t// no validation rules for Name\n\n\tif all {\n\t\tswitch v := interface{}(m.GetScanInterval()).(type) {\n\t\tcase interface{ ValidateAll() error }:\n\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\terrors = append(errors, LocalSourceValidationError{\n\t\t\t\t\tfield:  \"ScanInterval\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t})\n\t\t\t}\n\t\tcase interface{ Validate() error }:\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\terrors = append(errors, LocalSourceValidationError{\n\t\t\t\t\tfield:  \"ScanInterval\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t} else if v, ok := interface{}(m.GetScanInterval()).(interface{ Validate() error }); ok {\n\t\tif err := v.Validate(); err != nil {\n\t\t\treturn LocalSourceValidationError{\n\t\t\t\tfield:  \"ScanInterval\",\n\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\tcause:  err,\n\t\t\t}\n\t\t}\n\t}\n\n\t// no validation rules for Verify\n\n\tif all {\n\t\tswitch v := interface{}(m.GetConnection()).(type) {\n\t\tcase interface{ ValidateAll() error }:\n\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\terrors = append(errors, LocalSourceValidationError{\n\t\t\t\t\tfield:  \"Connection\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t})\n\t\t\t}\n\t\tcase interface{ Validate() error }:\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\terrors = append(errors, LocalSourceValidationError{\n\t\t\t\t\tfield:  \"Connection\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t} else if v, ok := interface{}(m.GetConnection()).(interface{ Validate() error }); ok {\n\t\tif err := v.Validate(); err != nil {\n\t\t\treturn LocalSourceValidationError{\n\t\t\t\tfield:  \"Connection\",\n\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\tcause:  err,\n\t\t\t}\n\t\t}\n\t}\n\n\t// no validation rules for ScanPeriod\n\n\tif len(errors) > 0 {\n\t\treturn LocalSourceMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// LocalSourceMultiError is an error wrapping multiple validation errors\n// returned by LocalSource.ValidateAll() if the designated constraints aren't met.\ntype LocalSourceMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m LocalSourceMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m LocalSourceMultiError) AllErrors() []error { return m }\n\n// LocalSourceValidationError is the validation error returned by\n// LocalSource.Validate if the designated constraints aren't met.\ntype LocalSourceValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e LocalSourceValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e LocalSourceValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e LocalSourceValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e LocalSourceValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e LocalSourceValidationError) ErrorName() string { return \"LocalSourceValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e LocalSourceValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sLocalSource.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = LocalSourceValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = LocalSourceValidationError{}\n\n// Validate checks the field values on Artifactory with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Artifactory) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Artifactory with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in ArtifactoryMultiError, or\n// nil if none found.\nfunc (m *Artifactory) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Artifactory) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif _, err := url.Parse(m.GetEndpoint()); err != nil {\n\t\terr = ArtifactoryValidationError{\n\t\t\tfield:  \"Endpoint\",\n\t\t\treason: \"value must be a valid URI\",\n\t\t\tcause:  err,\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\tswitch v := m.Credential.(type) {\n\tcase *Artifactory_BasicAuth:\n\t\tif v == nil {\n\t\t\terr := ArtifactoryValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetBasicAuth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, ArtifactoryValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, ArtifactoryValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetBasicAuth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn ArtifactoryValidationError{\n\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *Artifactory_AccessToken:\n\t\tif v == nil {\n\t\t\terr := ArtifactoryValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for AccessToken\n\tcase *Artifactory_Unauthenticated:\n\t\tif v == nil {\n\t\t\terr := ArtifactoryValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetUnauthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, ArtifactoryValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, ArtifactoryValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetUnauthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn ArtifactoryValidationError{\n\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn ArtifactoryMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// ArtifactoryMultiError is an error wrapping multiple validation errors\n// returned by Artifactory.ValidateAll() if the designated constraints aren't met.\ntype ArtifactoryMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m ArtifactoryMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m ArtifactoryMultiError) AllErrors() []error { return m }\n\n// ArtifactoryValidationError is the validation error returned by\n// Artifactory.Validate if the designated constraints aren't met.\ntype ArtifactoryValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e ArtifactoryValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e ArtifactoryValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e ArtifactoryValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e ArtifactoryValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e ArtifactoryValidationError) ErrorName() string { return \"ArtifactoryValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e ArtifactoryValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sArtifactory.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = ArtifactoryValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = ArtifactoryValidationError{}\n\n// Validate checks the field values on AzureStorage with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *AzureStorage) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on AzureStorage with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in AzureStorageMultiError, or\n// nil if none found.\nfunc (m *AzureStorage) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *AzureStorage) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tswitch v := m.Credential.(type) {\n\tcase *AzureStorage_ConnectionString:\n\t\tif v == nil {\n\t\t\terr := AzureStorageValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for ConnectionString\n\tcase *AzureStorage_BasicAuth:\n\t\tif v == nil {\n\t\t\terr := AzureStorageValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetBasicAuth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, AzureStorageValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, AzureStorageValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetBasicAuth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn AzureStorageValidationError{\n\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *AzureStorage_ClientCertificate:\n\t\tif v == nil {\n\t\t\terr := AzureStorageValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for ClientCertificate\n\tcase *AzureStorage_Unauthenticated:\n\t\tif v == nil {\n\t\t\terr := AzureStorageValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetUnauthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, AzureStorageValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, AzureStorageValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetUnauthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn AzureStorageValidationError{\n\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn AzureStorageMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// AzureStorageMultiError is an error wrapping multiple validation errors\n// returned by AzureStorage.ValidateAll() if the designated constraints aren't met.\ntype AzureStorageMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m AzureStorageMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m AzureStorageMultiError) AllErrors() []error { return m }\n\n// AzureStorageValidationError is the validation error returned by\n// AzureStorage.Validate if the designated constraints aren't met.\ntype AzureStorageValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e AzureStorageValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e AzureStorageValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e AzureStorageValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e AzureStorageValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e AzureStorageValidationError) ErrorName() string { return \"AzureStorageValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e AzureStorageValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sAzureStorage.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = AzureStorageValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = AzureStorageValidationError{}\n\n// Validate checks the field values on Bitbucket with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Bitbucket) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Bitbucket with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in BitbucketMultiError, or nil\n// if none found.\nfunc (m *Bitbucket) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Bitbucket) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif _, err := url.Parse(m.GetEndpoint()); err != nil {\n\t\terr = BitbucketValidationError{\n\t\t\tfield:  \"Endpoint\",\n\t\t\treason: \"value must be a valid URI\",\n\t\t\tcause:  err,\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\t// no validation rules for SkipBinaries\n\n\t// no validation rules for SkipArchives\n\n\t// no validation rules for InstallationType\n\n\t// no validation rules for OauthAuthorizationEndpoint\n\n\t// no validation rules for OauthTokenEndpoint\n\n\t// no validation rules for AllowSecretsManagerWrite\n\n\tswitch v := m.Credential.(type) {\n\tcase *Bitbucket_Token:\n\t\tif v == nil {\n\t\t\terr := BitbucketValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for Token\n\tcase *Bitbucket_Oauth:\n\t\tif v == nil {\n\t\t\terr := BitbucketValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetOauth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, BitbucketValidationError{\n\t\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, BitbucketValidationError{\n\t\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetOauth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn BitbucketValidationError{\n\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *Bitbucket_BasicAuth:\n\t\tif v == nil {\n\t\t\terr := BitbucketValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetBasicAuth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, BitbucketValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, BitbucketValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetBasicAuth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn BitbucketValidationError{\n\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn BitbucketMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// BitbucketMultiError is an error wrapping multiple validation errors returned\n// by Bitbucket.ValidateAll() if the designated constraints aren't met.\ntype BitbucketMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m BitbucketMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m BitbucketMultiError) AllErrors() []error { return m }\n\n// BitbucketValidationError is the validation error returned by\n// Bitbucket.Validate if the designated constraints aren't met.\ntype BitbucketValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e BitbucketValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e BitbucketValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e BitbucketValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e BitbucketValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e BitbucketValidationError) ErrorName() string { return \"BitbucketValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e BitbucketValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sBitbucket.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = BitbucketValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = BitbucketValidationError{}\n\n// Validate checks the field values on CircleCI with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *CircleCI) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on CircleCI with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in CircleCIMultiError, or nil\n// if none found.\nfunc (m *CircleCI) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *CircleCI) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif _, err := url.Parse(m.GetEndpoint()); err != nil {\n\t\terr = CircleCIValidationError{\n\t\t\tfield:  \"Endpoint\",\n\t\t\treason: \"value must be a valid URI\",\n\t\t\tcause:  err,\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\tswitch v := m.Credential.(type) {\n\tcase *CircleCI_Token:\n\t\tif v == nil {\n\t\t\terr := CircleCIValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for Token\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn CircleCIMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// CircleCIMultiError is an error wrapping multiple validation errors returned\n// by CircleCI.ValidateAll() if the designated constraints aren't met.\ntype CircleCIMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m CircleCIMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m CircleCIMultiError) AllErrors() []error { return m }\n\n// CircleCIValidationError is the validation error returned by\n// CircleCI.Validate if the designated constraints aren't met.\ntype CircleCIValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e CircleCIValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e CircleCIValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e CircleCIValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e CircleCIValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e CircleCIValidationError) ErrorName() string { return \"CircleCIValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e CircleCIValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sCircleCI.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = CircleCIValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = CircleCIValidationError{}\n\n// Validate checks the field values on TravisCI with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *TravisCI) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on TravisCI with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in TravisCIMultiError, or nil\n// if none found.\nfunc (m *TravisCI) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *TravisCI) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif _, err := url.Parse(m.GetEndpoint()); err != nil {\n\t\terr = TravisCIValidationError{\n\t\t\tfield:  \"Endpoint\",\n\t\t\treason: \"value must be a valid URI\",\n\t\t\tcause:  err,\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\tswitch v := m.Credential.(type) {\n\tcase *TravisCI_Token:\n\t\tif v == nil {\n\t\t\terr := TravisCIValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for Token\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn TravisCIMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// TravisCIMultiError is an error wrapping multiple validation errors returned\n// by TravisCI.ValidateAll() if the designated constraints aren't met.\ntype TravisCIMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m TravisCIMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m TravisCIMultiError) AllErrors() []error { return m }\n\n// TravisCIValidationError is the validation error returned by\n// TravisCI.Validate if the designated constraints aren't met.\ntype TravisCIValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e TravisCIValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e TravisCIValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e TravisCIValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e TravisCIValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e TravisCIValidationError) ErrorName() string { return \"TravisCIValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e TravisCIValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sTravisCI.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = TravisCIValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = TravisCIValidationError{}\n\n// Validate checks the field values on Confluence with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Confluence) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Confluence with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in ConfluenceMultiError, or\n// nil if none found.\nfunc (m *Confluence) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Confluence) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif _, err := url.Parse(m.GetEndpoint()); err != nil {\n\t\terr = ConfluenceValidationError{\n\t\t\tfield:  \"Endpoint\",\n\t\t\treason: \"value must be a valid URI\",\n\t\t\tcause:  err,\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\t// no validation rules for SpacesScope\n\n\t// no validation rules for InsecureSkipVerifyTls\n\n\t// no validation rules for IncludeAttachments\n\n\t// no validation rules for SkipHistory\n\n\t// no validation rules for IncludeComments\n\n\tswitch v := m.Credential.(type) {\n\tcase *Confluence_Unauthenticated:\n\t\tif v == nil {\n\t\t\terr := ConfluenceValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetUnauthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, ConfluenceValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, ConfluenceValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetUnauthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn ConfluenceValidationError{\n\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *Confluence_BasicAuth:\n\t\tif v == nil {\n\t\t\terr := ConfluenceValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetBasicAuth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, ConfluenceValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, ConfluenceValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetBasicAuth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn ConfluenceValidationError{\n\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *Confluence_Token:\n\t\tif v == nil {\n\t\t\terr := ConfluenceValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for Token\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn ConfluenceMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// ConfluenceMultiError is an error wrapping multiple validation errors\n// returned by Confluence.ValidateAll() if the designated constraints aren't met.\ntype ConfluenceMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m ConfluenceMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m ConfluenceMultiError) AllErrors() []error { return m }\n\n// ConfluenceValidationError is the validation error returned by\n// Confluence.Validate if the designated constraints aren't met.\ntype ConfluenceValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e ConfluenceValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e ConfluenceValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e ConfluenceValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e ConfluenceValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e ConfluenceValidationError) ErrorName() string { return \"ConfluenceValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e ConfluenceValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sConfluence.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = ConfluenceValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = ConfluenceValidationError{}\n\n// Validate checks the field values on Docker with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Docker) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Docker with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in DockerMultiError, or nil if none found.\nfunc (m *Docker) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Docker) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Namespace\n\n\t// no validation rules for RegistryToken\n\n\tswitch v := m.Credential.(type) {\n\tcase *Docker_Unauthenticated:\n\t\tif v == nil {\n\t\t\terr := DockerValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetUnauthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, DockerValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, DockerValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetUnauthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn DockerValidationError{\n\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *Docker_BasicAuth:\n\t\tif v == nil {\n\t\t\terr := DockerValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetBasicAuth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, DockerValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, DockerValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetBasicAuth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn DockerValidationError{\n\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *Docker_BearerToken:\n\t\tif v == nil {\n\t\t\terr := DockerValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for BearerToken\n\tcase *Docker_DockerKeychain:\n\t\tif v == nil {\n\t\t\terr := DockerValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for DockerKeychain\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn DockerMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// DockerMultiError is an error wrapping multiple validation errors returned by\n// Docker.ValidateAll() if the designated constraints aren't met.\ntype DockerMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m DockerMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m DockerMultiError) AllErrors() []error { return m }\n\n// DockerValidationError is the validation error returned by Docker.Validate if\n// the designated constraints aren't met.\ntype DockerValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e DockerValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e DockerValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e DockerValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e DockerValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e DockerValidationError) ErrorName() string { return \"DockerValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e DockerValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sDocker.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = DockerValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = DockerValidationError{}\n\n// Validate checks the field values on ECR with the rules defined in the proto\n// definition for this message. If any rules are violated, the first error\n// encountered is returned, or nil if there are no violations.\nfunc (m *ECR) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on ECR with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in ECRMultiError, or nil if none found.\nfunc (m *ECR) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *ECR) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tswitch v := m.Credential.(type) {\n\tcase *ECR_AccessKey:\n\t\tif v == nil {\n\t\t\terr := ECRValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetAccessKey()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, ECRValidationError{\n\t\t\t\t\t\tfield:  \"AccessKey\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, ECRValidationError{\n\t\t\t\t\t\tfield:  \"AccessKey\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetAccessKey()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn ECRValidationError{\n\t\t\t\t\tfield:  \"AccessKey\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn ECRMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// ECRMultiError is an error wrapping multiple validation errors returned by\n// ECR.ValidateAll() if the designated constraints aren't met.\ntype ECRMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m ECRMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m ECRMultiError) AllErrors() []error { return m }\n\n// ECRValidationError is the validation error returned by ECR.Validate if the\n// designated constraints aren't met.\ntype ECRValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e ECRValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e ECRValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e ECRValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e ECRValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e ECRValidationError) ErrorName() string { return \"ECRValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e ECRValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sECR.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = ECRValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = ECRValidationError{}\n\n// Validate checks the field values on Filesystem with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Filesystem) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Filesystem with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in FilesystemMultiError, or\n// nil if none found.\nfunc (m *Filesystem) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Filesystem) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for IncludePathsFile\n\n\t// no validation rules for ExcludePathsFile\n\n\t// no validation rules for SkipBinaries\n\n\t// no validation rules for MaxSymlinkDepth\n\n\tif len(errors) > 0 {\n\t\treturn FilesystemMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// FilesystemMultiError is an error wrapping multiple validation errors\n// returned by Filesystem.ValidateAll() if the designated constraints aren't met.\ntype FilesystemMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m FilesystemMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m FilesystemMultiError) AllErrors() []error { return m }\n\n// FilesystemValidationError is the validation error returned by\n// Filesystem.Validate if the designated constraints aren't met.\ntype FilesystemValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e FilesystemValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e FilesystemValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e FilesystemValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e FilesystemValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e FilesystemValidationError) ErrorName() string { return \"FilesystemValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e FilesystemValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sFilesystem.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = FilesystemValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = FilesystemValidationError{}\n\n// Validate checks the field values on GCS with the rules defined in the proto\n// definition for this message. If any rules are violated, the first error\n// encountered is returned, or nil if there are no violations.\nfunc (m *GCS) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on GCS with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in GCSMultiError, or nil if none found.\nfunc (m *GCS) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *GCS) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for ProjectId\n\n\t// no validation rules for MaxObjectSize\n\n\tswitch v := m.Credential.(type) {\n\tcase *GCS_JsonServiceAccount:\n\t\tif v == nil {\n\t\t\terr := GCSValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for JsonServiceAccount\n\tcase *GCS_ApiKey:\n\t\tif v == nil {\n\t\t\terr := GCSValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for ApiKey\n\tcase *GCS_Unauthenticated:\n\t\tif v == nil {\n\t\t\terr := GCSValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetUnauthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GCSValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GCSValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetUnauthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GCSValidationError{\n\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *GCS_Adc:\n\t\tif v == nil {\n\t\t\terr := GCSValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetAdc()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GCSValidationError{\n\t\t\t\t\t\tfield:  \"Adc\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GCSValidationError{\n\t\t\t\t\t\tfield:  \"Adc\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetAdc()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GCSValidationError{\n\t\t\t\t\tfield:  \"Adc\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *GCS_ServiceAccountFile:\n\t\tif v == nil {\n\t\t\terr := GCSValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for ServiceAccountFile\n\tcase *GCS_Oauth:\n\t\tif v == nil {\n\t\t\terr := GCSValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetOauth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GCSValidationError{\n\t\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GCSValidationError{\n\t\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetOauth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GCSValidationError{\n\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn GCSMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// GCSMultiError is an error wrapping multiple validation errors returned by\n// GCS.ValidateAll() if the designated constraints aren't met.\ntype GCSMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m GCSMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m GCSMultiError) AllErrors() []error { return m }\n\n// GCSValidationError is the validation error returned by GCS.Validate if the\n// designated constraints aren't met.\ntype GCSValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e GCSValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e GCSValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e GCSValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e GCSValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e GCSValidationError) ErrorName() string { return \"GCSValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e GCSValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sGCS.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = GCSValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = GCSValidationError{}\n\n// Validate checks the field values on Git with the rules defined in the proto\n// definition for this message. If any rules are violated, the first error\n// encountered is returned, or nil if there are no violations.\nfunc (m *Git) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Git with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in GitMultiError, or nil if none found.\nfunc (m *Git) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Git) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Head\n\n\t// no validation rules for Base\n\n\t// no validation rules for Bare\n\n\t// no validation rules for IncludePathsFile\n\n\t// no validation rules for ExcludePathsFile\n\n\t// no validation rules for ExcludeGlobs\n\n\t// no validation rules for MaxDepth\n\n\t// no validation rules for Uri\n\n\t// no validation rules for SkipBinaries\n\n\t// no validation rules for SkipArchives\n\n\t// no validation rules for ClonePath\n\n\t// no validation rules for NoCleanup\n\n\t// no validation rules for PrintLegacyJson\n\n\t// no validation rules for TrustLocalGitConfig\n\n\tswitch v := m.Credential.(type) {\n\tcase *Git_BasicAuth:\n\t\tif v == nil {\n\t\t\terr := GitValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetBasicAuth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GitValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GitValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetBasicAuth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GitValidationError{\n\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *Git_Unauthenticated:\n\t\tif v == nil {\n\t\t\terr := GitValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetUnauthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GitValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GitValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetUnauthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GitValidationError{\n\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *Git_SshAuth:\n\t\tif v == nil {\n\t\t\terr := GitValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetSshAuth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GitValidationError{\n\t\t\t\t\t\tfield:  \"SshAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GitValidationError{\n\t\t\t\t\t\tfield:  \"SshAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetSshAuth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GitValidationError{\n\t\t\t\t\tfield:  \"SshAuth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn GitMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// GitMultiError is an error wrapping multiple validation errors returned by\n// Git.ValidateAll() if the designated constraints aren't met.\ntype GitMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m GitMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m GitMultiError) AllErrors() []error { return m }\n\n// GitValidationError is the validation error returned by Git.Validate if the\n// designated constraints aren't met.\ntype GitValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e GitValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e GitValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e GitValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e GitValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e GitValidationError) ErrorName() string { return \"GitValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e GitValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sGit.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = GitValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = GitValidationError{}\n\n// Validate checks the field values on GitLab with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *GitLab) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on GitLab with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in GitLabMultiError, or nil if none found.\nfunc (m *GitLab) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *GitLab) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif _, err := url.Parse(m.GetEndpoint()); err != nil {\n\t\terr = GitLabValidationError{\n\t\t\tfield:  \"Endpoint\",\n\t\t\treason: \"value must be a valid URI\",\n\t\t\tcause:  err,\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\t// no validation rules for SkipBinaries\n\n\t// no validation rules for SkipArchives\n\n\t// no validation rules for ExcludeProjectsSharedIntoGroups\n\n\t// no validation rules for RemoveAuthInUrl\n\n\t// no validation rules for ClonePath\n\n\t// no validation rules for NoCleanup\n\n\t// no validation rules for PrintLegacyJson\n\n\tswitch v := m.Credential.(type) {\n\tcase *GitLab_Token:\n\t\tif v == nil {\n\t\t\terr := GitLabValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for Token\n\tcase *GitLab_Oauth:\n\t\tif v == nil {\n\t\t\terr := GitLabValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetOauth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GitLabValidationError{\n\t\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GitLabValidationError{\n\t\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetOauth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GitLabValidationError{\n\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *GitLab_BasicAuth:\n\t\tif v == nil {\n\t\t\terr := GitLabValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetBasicAuth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GitLabValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GitLabValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetBasicAuth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GitLabValidationError{\n\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn GitLabMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// GitLabMultiError is an error wrapping multiple validation errors returned by\n// GitLab.ValidateAll() if the designated constraints aren't met.\ntype GitLabMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m GitLabMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m GitLabMultiError) AllErrors() []error { return m }\n\n// GitLabValidationError is the validation error returned by GitLab.Validate if\n// the designated constraints aren't met.\ntype GitLabValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e GitLabValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e GitLabValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e GitLabValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e GitLabValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e GitLabValidationError) ErrorName() string { return \"GitLabValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e GitLabValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sGitLab.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = GitLabValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = GitLabValidationError{}\n\n// Validate checks the field values on GitHub with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *GitHub) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on GitHub with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in GitHubMultiError, or nil if none found.\nfunc (m *GitHub) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *GitHub) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif _, err := url.Parse(m.GetEndpoint()); err != nil {\n\t\terr = GitHubValidationError{\n\t\t\tfield:  \"Endpoint\",\n\t\t\treason: \"value must be a valid URI\",\n\t\t\tcause:  err,\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\t// no validation rules for ScanUsers\n\n\t// no validation rules for IncludeForks\n\n\t// no validation rules for Head\n\n\t// no validation rules for Base\n\n\t// no validation rules for IncludePullRequestComments\n\n\t// no validation rules for IncludeIssueComments\n\n\t// no validation rules for IncludeGistComments\n\n\t// no validation rules for SkipBinaries\n\n\t// no validation rules for SkipArchives\n\n\t// no validation rules for IncludeWikis\n\n\t// no validation rules for CommentsTimeframeDays\n\n\t// no validation rules for RemoveAuthInUrl\n\n\t// no validation rules for ClonePath\n\n\t// no validation rules for NoCleanup\n\n\t// no validation rules for IgnoreGists\n\n\t// no validation rules for PrintLegacyJson\n\n\tswitch v := m.Credential.(type) {\n\tcase *GitHub_GithubApp:\n\t\tif v == nil {\n\t\t\terr := GitHubValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetGithubApp()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GitHubValidationError{\n\t\t\t\t\t\tfield:  \"GithubApp\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GitHubValidationError{\n\t\t\t\t\t\tfield:  \"GithubApp\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetGithubApp()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GitHubValidationError{\n\t\t\t\t\tfield:  \"GithubApp\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *GitHub_Token:\n\t\tif v == nil {\n\t\t\terr := GitHubValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for Token\n\tcase *GitHub_Unauthenticated:\n\t\tif v == nil {\n\t\t\terr := GitHubValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetUnauthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GitHubValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GitHubValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetUnauthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GitHubValidationError{\n\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *GitHub_BasicAuth:\n\t\tif v == nil {\n\t\t\terr := GitHubValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetBasicAuth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GitHubValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GitHubValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetBasicAuth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GitHubValidationError{\n\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn GitHubMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// GitHubMultiError is an error wrapping multiple validation errors returned by\n// GitHub.ValidateAll() if the designated constraints aren't met.\ntype GitHubMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m GitHubMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m GitHubMultiError) AllErrors() []error { return m }\n\n// GitHubValidationError is the validation error returned by GitHub.Validate if\n// the designated constraints aren't met.\ntype GitHubValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e GitHubValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e GitHubValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e GitHubValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e GitHubValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e GitHubValidationError) ErrorName() string { return \"GitHubValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e GitHubValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sGitHub.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = GitHubValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = GitHubValidationError{}\n\n// Validate checks the field values on GitHubExperimental with the rules\n// defined in the proto definition for this message. If any rules are\n// violated, the first error encountered is returned, or nil if there are no violations.\nfunc (m *GitHubExperimental) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on GitHubExperimental with the rules\n// defined in the proto definition for this message. If any rules are\n// violated, the result is a list of violation errors wrapped in\n// GitHubExperimentalMultiError, or nil if none found.\nfunc (m *GitHubExperimental) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *GitHubExperimental) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Repository\n\n\t// no validation rules for ObjectDiscovery\n\n\t// no validation rules for CollisionThreshold\n\n\t// no validation rules for DeleteCachedData\n\n\tswitch v := m.Credential.(type) {\n\tcase *GitHubExperimental_Token:\n\t\tif v == nil {\n\t\t\terr := GitHubExperimentalValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for Token\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn GitHubExperimentalMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// GitHubExperimentalMultiError is an error wrapping multiple validation errors\n// returned by GitHubExperimental.ValidateAll() if the designated constraints\n// aren't met.\ntype GitHubExperimentalMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m GitHubExperimentalMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m GitHubExperimentalMultiError) AllErrors() []error { return m }\n\n// GitHubExperimentalValidationError is the validation error returned by\n// GitHubExperimental.Validate if the designated constraints aren't met.\ntype GitHubExperimentalValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e GitHubExperimentalValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e GitHubExperimentalValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e GitHubExperimentalValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e GitHubExperimentalValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e GitHubExperimentalValidationError) ErrorName() string {\n\treturn \"GitHubExperimentalValidationError\"\n}\n\n// Error satisfies the builtin error interface\nfunc (e GitHubExperimentalValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sGitHubExperimental.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = GitHubExperimentalValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = GitHubExperimentalValidationError{}\n\n// Validate checks the field values on GitHubRealtime with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *GitHubRealtime) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on GitHubRealtime with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in GitHubRealtimeMultiError,\n// or nil if none found.\nfunc (m *GitHubRealtime) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *GitHubRealtime) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for ListenerPort\n\n\t// no validation rules for WebhookSecret\n\n\tif _, err := url.Parse(m.GetEndpoint()); err != nil {\n\t\terr = GitHubRealtimeValidationError{\n\t\t\tfield:  \"Endpoint\",\n\t\t\treason: \"value must be a valid URI\",\n\t\t\tcause:  err,\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\tswitch v := m.Credential.(type) {\n\tcase *GitHubRealtime_GithubApp:\n\t\tif v == nil {\n\t\t\terr := GitHubRealtimeValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetGithubApp()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GitHubRealtimeValidationError{\n\t\t\t\t\t\tfield:  \"GithubApp\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GitHubRealtimeValidationError{\n\t\t\t\t\t\tfield:  \"GithubApp\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetGithubApp()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GitHubRealtimeValidationError{\n\t\t\t\t\tfield:  \"GithubApp\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *GitHubRealtime_Token:\n\t\tif v == nil {\n\t\t\terr := GitHubRealtimeValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for Token\n\tcase *GitHubRealtime_Unauthenticated:\n\t\tif v == nil {\n\t\t\terr := GitHubRealtimeValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetUnauthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GitHubRealtimeValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GitHubRealtimeValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetUnauthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GitHubRealtimeValidationError{\n\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *GitHubRealtime_BasicAuth:\n\t\tif v == nil {\n\t\t\terr := GitHubRealtimeValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetBasicAuth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GitHubRealtimeValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GitHubRealtimeValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetBasicAuth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GitHubRealtimeValidationError{\n\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn GitHubRealtimeMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// GitHubRealtimeMultiError is an error wrapping multiple validation errors\n// returned by GitHubRealtime.ValidateAll() if the designated constraints\n// aren't met.\ntype GitHubRealtimeMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m GitHubRealtimeMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m GitHubRealtimeMultiError) AllErrors() []error { return m }\n\n// GitHubRealtimeValidationError is the validation error returned by\n// GitHubRealtime.Validate if the designated constraints aren't met.\ntype GitHubRealtimeValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e GitHubRealtimeValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e GitHubRealtimeValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e GitHubRealtimeValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e GitHubRealtimeValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e GitHubRealtimeValidationError) ErrorName() string { return \"GitHubRealtimeValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e GitHubRealtimeValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sGitHubRealtime.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = GitHubRealtimeValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = GitHubRealtimeValidationError{}\n\n// Validate checks the field values on GoogleDrive with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *GoogleDrive) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on GoogleDrive with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in GoogleDriveMultiError, or\n// nil if none found.\nfunc (m *GoogleDrive) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *GoogleDrive) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tswitch v := m.Credential.(type) {\n\tcase *GoogleDrive_RefreshToken:\n\t\tif v == nil {\n\t\t\terr := GoogleDriveValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for RefreshToken\n\tcase *GoogleDrive_UseTokenService:\n\t\tif v == nil {\n\t\t\terr := GoogleDriveValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for UseTokenService\n\tcase *GoogleDrive_Oauth:\n\t\tif v == nil {\n\t\t\terr := GoogleDriveValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetOauth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GoogleDriveValidationError{\n\t\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GoogleDriveValidationError{\n\t\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetOauth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GoogleDriveValidationError{\n\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *GoogleDrive_Dwd:\n\t\tif v == nil {\n\t\t\terr := GoogleDriveValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetDwd()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GoogleDriveValidationError{\n\t\t\t\t\t\tfield:  \"Dwd\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GoogleDriveValidationError{\n\t\t\t\t\t\tfield:  \"Dwd\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetDwd()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GoogleDriveValidationError{\n\t\t\t\t\tfield:  \"Dwd\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn GoogleDriveMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// GoogleDriveMultiError is an error wrapping multiple validation errors\n// returned by GoogleDrive.ValidateAll() if the designated constraints aren't met.\ntype GoogleDriveMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m GoogleDriveMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m GoogleDriveMultiError) AllErrors() []error { return m }\n\n// GoogleDriveValidationError is the validation error returned by\n// GoogleDrive.Validate if the designated constraints aren't met.\ntype GoogleDriveValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e GoogleDriveValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e GoogleDriveValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e GoogleDriveValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e GoogleDriveValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e GoogleDriveValidationError) ErrorName() string { return \"GoogleDriveValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e GoogleDriveValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sGoogleDrive.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = GoogleDriveValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = GoogleDriveValidationError{}\n\n// Validate checks the field values on Huggingface with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Huggingface) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Huggingface with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in HuggingfaceMultiError, or\n// nil if none found.\nfunc (m *Huggingface) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Huggingface) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif _, err := url.Parse(m.GetEndpoint()); err != nil {\n\t\terr = HuggingfaceValidationError{\n\t\t\tfield:  \"Endpoint\",\n\t\t\treason: \"value must be a valid URI\",\n\t\t\tcause:  err,\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\t// no validation rules for SkipAllModels\n\n\t// no validation rules for SkipAllSpaces\n\n\t// no validation rules for SkipAllDatasets\n\n\t// no validation rules for IncludeDiscussions\n\n\t// no validation rules for IncludePrs\n\n\tswitch v := m.Credential.(type) {\n\tcase *Huggingface_Token:\n\t\tif v == nil {\n\t\t\terr := HuggingfaceValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for Token\n\tcase *Huggingface_Unauthenticated:\n\t\tif v == nil {\n\t\t\terr := HuggingfaceValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetUnauthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, HuggingfaceValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, HuggingfaceValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetUnauthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn HuggingfaceValidationError{\n\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn HuggingfaceMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// HuggingfaceMultiError is an error wrapping multiple validation errors\n// returned by Huggingface.ValidateAll() if the designated constraints aren't met.\ntype HuggingfaceMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m HuggingfaceMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m HuggingfaceMultiError) AllErrors() []error { return m }\n\n// HuggingfaceValidationError is the validation error returned by\n// Huggingface.Validate if the designated constraints aren't met.\ntype HuggingfaceValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e HuggingfaceValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e HuggingfaceValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e HuggingfaceValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e HuggingfaceValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e HuggingfaceValidationError) ErrorName() string { return \"HuggingfaceValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e HuggingfaceValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sHuggingface.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = HuggingfaceValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = HuggingfaceValidationError{}\n\n// Validate checks the field values on JIRA with the rules defined in the proto\n// definition for this message. If any rules are violated, the first error\n// encountered is returned, or nil if there are no violations.\nfunc (m *JIRA) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on JIRA with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in JIRAMultiError, or nil if none found.\nfunc (m *JIRA) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *JIRA) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif _, err := url.Parse(m.GetEndpoint()); err != nil {\n\t\terr = JIRAValidationError{\n\t\t\tfield:  \"Endpoint\",\n\t\t\treason: \"value must be a valid URI\",\n\t\t\tcause:  err,\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\t// no validation rules for InsecureSkipVerifyTls\n\n\t// no validation rules for InstallationType\n\n\tswitch v := m.Credential.(type) {\n\tcase *JIRA_BasicAuth:\n\t\tif v == nil {\n\t\t\terr := JIRAValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetBasicAuth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, JIRAValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, JIRAValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetBasicAuth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn JIRAValidationError{\n\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *JIRA_Unauthenticated:\n\t\tif v == nil {\n\t\t\terr := JIRAValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetUnauthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, JIRAValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, JIRAValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetUnauthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn JIRAValidationError{\n\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *JIRA_Oauth:\n\t\tif v == nil {\n\t\t\terr := JIRAValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetOauth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, JIRAValidationError{\n\t\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, JIRAValidationError{\n\t\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetOauth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn JIRAValidationError{\n\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *JIRA_Token:\n\t\tif v == nil {\n\t\t\terr := JIRAValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for Token\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn JIRAMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// JIRAMultiError is an error wrapping multiple validation errors returned by\n// JIRA.ValidateAll() if the designated constraints aren't met.\ntype JIRAMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m JIRAMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m JIRAMultiError) AllErrors() []error { return m }\n\n// JIRAValidationError is the validation error returned by JIRA.Validate if the\n// designated constraints aren't met.\ntype JIRAValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e JIRAValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e JIRAValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e JIRAValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e JIRAValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e JIRAValidationError) ErrorName() string { return \"JIRAValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e JIRAValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sJIRA.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = JIRAValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = JIRAValidationError{}\n\n// Validate checks the field values on NPMUnauthenticatedPackage with the rules\n// defined in the proto definition for this message. If any rules are\n// violated, the first error encountered is returned, or nil if there are no violations.\nfunc (m *NPMUnauthenticatedPackage) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on NPMUnauthenticatedPackage with the\n// rules defined in the proto definition for this message. If any rules are\n// violated, the result is a list of violation errors wrapped in\n// NPMUnauthenticatedPackageMultiError, or nil if none found.\nfunc (m *NPMUnauthenticatedPackage) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *NPMUnauthenticatedPackage) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tswitch v := m.Credential.(type) {\n\tcase *NPMUnauthenticatedPackage_Unauthenticated:\n\t\tif v == nil {\n\t\t\terr := NPMUnauthenticatedPackageValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetUnauthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, NPMUnauthenticatedPackageValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, NPMUnauthenticatedPackageValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetUnauthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn NPMUnauthenticatedPackageValidationError{\n\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn NPMUnauthenticatedPackageMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// NPMUnauthenticatedPackageMultiError is an error wrapping multiple validation\n// errors returned by NPMUnauthenticatedPackage.ValidateAll() if the\n// designated constraints aren't met.\ntype NPMUnauthenticatedPackageMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m NPMUnauthenticatedPackageMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m NPMUnauthenticatedPackageMultiError) AllErrors() []error { return m }\n\n// NPMUnauthenticatedPackageValidationError is the validation error returned by\n// NPMUnauthenticatedPackage.Validate if the designated constraints aren't met.\ntype NPMUnauthenticatedPackageValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e NPMUnauthenticatedPackageValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e NPMUnauthenticatedPackageValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e NPMUnauthenticatedPackageValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e NPMUnauthenticatedPackageValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e NPMUnauthenticatedPackageValidationError) ErrorName() string {\n\treturn \"NPMUnauthenticatedPackageValidationError\"\n}\n\n// Error satisfies the builtin error interface\nfunc (e NPMUnauthenticatedPackageValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sNPMUnauthenticatedPackage.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = NPMUnauthenticatedPackageValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = NPMUnauthenticatedPackageValidationError{}\n\n// Validate checks the field values on PyPIUnauthenticatedPackage with the\n// rules defined in the proto definition for this message. If any rules are\n// violated, the first error encountered is returned, or nil if there are no violations.\nfunc (m *PyPIUnauthenticatedPackage) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on PyPIUnauthenticatedPackage with the\n// rules defined in the proto definition for this message. If any rules are\n// violated, the result is a list of violation errors wrapped in\n// PyPIUnauthenticatedPackageMultiError, or nil if none found.\nfunc (m *PyPIUnauthenticatedPackage) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *PyPIUnauthenticatedPackage) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tswitch v := m.Credential.(type) {\n\tcase *PyPIUnauthenticatedPackage_Unauthenticated:\n\t\tif v == nil {\n\t\t\terr := PyPIUnauthenticatedPackageValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetUnauthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, PyPIUnauthenticatedPackageValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, PyPIUnauthenticatedPackageValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetUnauthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn PyPIUnauthenticatedPackageValidationError{\n\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn PyPIUnauthenticatedPackageMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// PyPIUnauthenticatedPackageMultiError is an error wrapping multiple\n// validation errors returned by PyPIUnauthenticatedPackage.ValidateAll() if\n// the designated constraints aren't met.\ntype PyPIUnauthenticatedPackageMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m PyPIUnauthenticatedPackageMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m PyPIUnauthenticatedPackageMultiError) AllErrors() []error { return m }\n\n// PyPIUnauthenticatedPackageValidationError is the validation error returned\n// by PyPIUnauthenticatedPackage.Validate if the designated constraints aren't met.\ntype PyPIUnauthenticatedPackageValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e PyPIUnauthenticatedPackageValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e PyPIUnauthenticatedPackageValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e PyPIUnauthenticatedPackageValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e PyPIUnauthenticatedPackageValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e PyPIUnauthenticatedPackageValidationError) ErrorName() string {\n\treturn \"PyPIUnauthenticatedPackageValidationError\"\n}\n\n// Error satisfies the builtin error interface\nfunc (e PyPIUnauthenticatedPackageValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sPyPIUnauthenticatedPackage.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = PyPIUnauthenticatedPackageValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = PyPIUnauthenticatedPackageValidationError{}\n\n// Validate checks the field values on S3 with the rules defined in the proto\n// definition for this message. If any rules are violated, the first error\n// encountered is returned, or nil if there are no violations.\nfunc (m *S3) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on S3 with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in S3MultiError, or nil if none found.\nfunc (m *S3) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *S3) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for MaxObjectSize\n\n\t// no validation rules for EnableResumption\n\n\tswitch v := m.Credential.(type) {\n\tcase *S3_AccessKey:\n\t\tif v == nil {\n\t\t\terr := S3ValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetAccessKey()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, S3ValidationError{\n\t\t\t\t\t\tfield:  \"AccessKey\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, S3ValidationError{\n\t\t\t\t\t\tfield:  \"AccessKey\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetAccessKey()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn S3ValidationError{\n\t\t\t\t\tfield:  \"AccessKey\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *S3_Unauthenticated:\n\t\tif v == nil {\n\t\t\terr := S3ValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetUnauthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, S3ValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, S3ValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetUnauthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn S3ValidationError{\n\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *S3_CloudEnvironment:\n\t\tif v == nil {\n\t\t\terr := S3ValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetCloudEnvironment()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, S3ValidationError{\n\t\t\t\t\t\tfield:  \"CloudEnvironment\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, S3ValidationError{\n\t\t\t\t\t\tfield:  \"CloudEnvironment\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetCloudEnvironment()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn S3ValidationError{\n\t\t\t\t\tfield:  \"CloudEnvironment\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *S3_SessionToken:\n\t\tif v == nil {\n\t\t\terr := S3ValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetSessionToken()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, S3ValidationError{\n\t\t\t\t\t\tfield:  \"SessionToken\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, S3ValidationError{\n\t\t\t\t\t\tfield:  \"SessionToken\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetSessionToken()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn S3ValidationError{\n\t\t\t\t\tfield:  \"SessionToken\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn S3MultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// S3MultiError is an error wrapping multiple validation errors returned by\n// S3.ValidateAll() if the designated constraints aren't met.\ntype S3MultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m S3MultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m S3MultiError) AllErrors() []error { return m }\n\n// S3ValidationError is the validation error returned by S3.Validate if the\n// designated constraints aren't met.\ntype S3ValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e S3ValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e S3ValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e S3ValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e S3ValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e S3ValidationError) ErrorName() string { return \"S3ValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e S3ValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sS3.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = S3ValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = S3ValidationError{}\n\n// Validate checks the field values on Slack with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Slack) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Slack with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in SlackMultiError, or nil if none found.\nfunc (m *Slack) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Slack) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif _, err := url.Parse(m.GetEndpoint()); err != nil {\n\t\terr = SlackValidationError{\n\t\t\tfield:  \"Endpoint\",\n\t\t\treason: \"value must be a valid URI\",\n\t\t\tcause:  err,\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\tswitch v := m.Credential.(type) {\n\tcase *Slack_Token:\n\t\tif v == nil {\n\t\t\terr := SlackValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for Token\n\tcase *Slack_Tokens:\n\t\tif v == nil {\n\t\t\terr := SlackValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetTokens()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, SlackValidationError{\n\t\t\t\t\t\tfield:  \"Tokens\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, SlackValidationError{\n\t\t\t\t\t\tfield:  \"Tokens\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetTokens()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn SlackValidationError{\n\t\t\t\t\tfield:  \"Tokens\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn SlackMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// SlackMultiError is an error wrapping multiple validation errors returned by\n// Slack.ValidateAll() if the designated constraints aren't met.\ntype SlackMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m SlackMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m SlackMultiError) AllErrors() []error { return m }\n\n// SlackValidationError is the validation error returned by Slack.Validate if\n// the designated constraints aren't met.\ntype SlackValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e SlackValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e SlackValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e SlackValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e SlackValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e SlackValidationError) ErrorName() string { return \"SlackValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e SlackValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sSlack.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = SlackValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = SlackValidationError{}\n\n// Validate checks the field values on Test with the rules defined in the proto\n// definition for this message. If any rules are violated, the first error\n// encountered is returned, or nil if there are no violations.\nfunc (m *Test) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Test with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in TestMultiError, or nil if none found.\nfunc (m *Test) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Test) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif len(errors) > 0 {\n\t\treturn TestMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// TestMultiError is an error wrapping multiple validation errors returned by\n// Test.ValidateAll() if the designated constraints aren't met.\ntype TestMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m TestMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m TestMultiError) AllErrors() []error { return m }\n\n// TestValidationError is the validation error returned by Test.Validate if the\n// designated constraints aren't met.\ntype TestValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e TestValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e TestValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e TestValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e TestValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e TestValidationError) ErrorName() string { return \"TestValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e TestValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sTest.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = TestValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = TestValidationError{}\n\n// Validate checks the field values on Buildkite with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Buildkite) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Buildkite with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in BuildkiteMultiError, or nil\n// if none found.\nfunc (m *Buildkite) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Buildkite) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tswitch v := m.Credential.(type) {\n\tcase *Buildkite_Token:\n\t\tif v == nil {\n\t\t\terr := BuildkiteValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for Token\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn BuildkiteMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// BuildkiteMultiError is an error wrapping multiple validation errors returned\n// by Buildkite.ValidateAll() if the designated constraints aren't met.\ntype BuildkiteMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m BuildkiteMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m BuildkiteMultiError) AllErrors() []error { return m }\n\n// BuildkiteValidationError is the validation error returned by\n// Buildkite.Validate if the designated constraints aren't met.\ntype BuildkiteValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e BuildkiteValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e BuildkiteValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e BuildkiteValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e BuildkiteValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e BuildkiteValidationError) ErrorName() string { return \"BuildkiteValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e BuildkiteValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sBuildkite.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = BuildkiteValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = BuildkiteValidationError{}\n\n// Validate checks the field values on Gerrit with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Gerrit) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Gerrit with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in GerritMultiError, or nil if none found.\nfunc (m *Gerrit) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Gerrit) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif _, err := url.Parse(m.GetEndpoint()); err != nil {\n\t\terr = GerritValidationError{\n\t\t\tfield:  \"Endpoint\",\n\t\t\treason: \"value must be a valid URI\",\n\t\t\tcause:  err,\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\t// no validation rules for SkipBinaries\n\n\t// no validation rules for SkipArchives\n\n\tswitch v := m.Credential.(type) {\n\tcase *Gerrit_BasicAuth:\n\t\tif v == nil {\n\t\t\terr := GerritValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetBasicAuth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GerritValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GerritValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetBasicAuth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GerritValidationError{\n\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *Gerrit_Unauthenticated:\n\t\tif v == nil {\n\t\t\terr := GerritValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetUnauthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, GerritValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, GerritValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetUnauthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn GerritValidationError{\n\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn GerritMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// GerritMultiError is an error wrapping multiple validation errors returned by\n// Gerrit.ValidateAll() if the designated constraints aren't met.\ntype GerritMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m GerritMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m GerritMultiError) AllErrors() []error { return m }\n\n// GerritValidationError is the validation error returned by Gerrit.Validate if\n// the designated constraints aren't met.\ntype GerritValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e GerritValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e GerritValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e GerritValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e GerritValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e GerritValidationError) ErrorName() string { return \"GerritValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e GerritValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sGerrit.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = GerritValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = GerritValidationError{}\n\n// Validate checks the field values on Jenkins with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Jenkins) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Jenkins with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in JenkinsMultiError, or nil if none found.\nfunc (m *Jenkins) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Jenkins) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif _, err := url.Parse(m.GetEndpoint()); err != nil {\n\t\terr = JenkinsValidationError{\n\t\t\tfield:  \"Endpoint\",\n\t\t\treason: \"value must be a valid URI\",\n\t\t\tcause:  err,\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\t// no validation rules for InsecureSkipVerifyTls\n\n\tswitch v := m.Credential.(type) {\n\tcase *Jenkins_BasicAuth:\n\t\tif v == nil {\n\t\t\terr := JenkinsValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetBasicAuth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, JenkinsValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, JenkinsValidationError{\n\t\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetBasicAuth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn JenkinsValidationError{\n\t\t\t\t\tfield:  \"BasicAuth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *Jenkins_Header:\n\t\tif v == nil {\n\t\t\terr := JenkinsValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetHeader()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, JenkinsValidationError{\n\t\t\t\t\t\tfield:  \"Header\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, JenkinsValidationError{\n\t\t\t\t\t\tfield:  \"Header\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetHeader()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn JenkinsValidationError{\n\t\t\t\t\tfield:  \"Header\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *Jenkins_Unauthenticated:\n\t\tif v == nil {\n\t\t\terr := JenkinsValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetUnauthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, JenkinsValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, JenkinsValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetUnauthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn JenkinsValidationError{\n\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn JenkinsMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// JenkinsMultiError is an error wrapping multiple validation errors returned\n// by Jenkins.ValidateAll() if the designated constraints aren't met.\ntype JenkinsMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m JenkinsMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m JenkinsMultiError) AllErrors() []error { return m }\n\n// JenkinsValidationError is the validation error returned by Jenkins.Validate\n// if the designated constraints aren't met.\ntype JenkinsValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e JenkinsValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e JenkinsValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e JenkinsValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e JenkinsValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e JenkinsValidationError) ErrorName() string { return \"JenkinsValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e JenkinsValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sJenkins.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = JenkinsValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = JenkinsValidationError{}\n\n// Validate checks the field values on Teams with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Teams) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Teams with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in TeamsMultiError, or nil if none found.\nfunc (m *Teams) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Teams) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif _, err := url.Parse(m.GetEndpoint()); err != nil {\n\t\terr = TeamsValidationError{\n\t\t\tfield:  \"Endpoint\",\n\t\t\treason: \"value must be a valid URI\",\n\t\t\tcause:  err,\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\t// no validation rules for TenantId\n\n\tswitch v := m.Credential.(type) {\n\tcase *Teams_Token:\n\t\tif v == nil {\n\t\t\terr := TeamsValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for Token\n\tcase *Teams_Authenticated:\n\t\tif v == nil {\n\t\t\terr := TeamsValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetAuthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, TeamsValidationError{\n\t\t\t\t\t\tfield:  \"Authenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, TeamsValidationError{\n\t\t\t\t\t\tfield:  \"Authenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetAuthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn TeamsValidationError{\n\t\t\t\t\tfield:  \"Authenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *Teams_Oauth:\n\t\tif v == nil {\n\t\t\terr := TeamsValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetOauth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, TeamsValidationError{\n\t\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, TeamsValidationError{\n\t\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetOauth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn TeamsValidationError{\n\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn TeamsMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// TeamsMultiError is an error wrapping multiple validation errors returned by\n// Teams.ValidateAll() if the designated constraints aren't met.\ntype TeamsMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m TeamsMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m TeamsMultiError) AllErrors() []error { return m }\n\n// TeamsValidationError is the validation error returned by Teams.Validate if\n// the designated constraints aren't met.\ntype TeamsValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e TeamsValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e TeamsValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e TeamsValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e TeamsValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e TeamsValidationError) ErrorName() string { return \"TeamsValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e TeamsValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sTeams.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = TeamsValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = TeamsValidationError{}\n\n// Validate checks the field values on Syslog with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Syslog) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Syslog with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in SyslogMultiError, or nil if none found.\nfunc (m *Syslog) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Syslog) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Protocol\n\n\t// no validation rules for ListenAddress\n\n\t// no validation rules for TlsCert\n\n\t// no validation rules for TlsKey\n\n\t// no validation rules for Format\n\n\tif len(errors) > 0 {\n\t\treturn SyslogMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// SyslogMultiError is an error wrapping multiple validation errors returned by\n// Syslog.ValidateAll() if the designated constraints aren't met.\ntype SyslogMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m SyslogMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m SyslogMultiError) AllErrors() []error { return m }\n\n// SyslogValidationError is the validation error returned by Syslog.Validate if\n// the designated constraints aren't met.\ntype SyslogValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e SyslogValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e SyslogValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e SyslogValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e SyslogValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e SyslogValidationError) ErrorName() string { return \"SyslogValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e SyslogValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sSyslog.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = SyslogValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = SyslogValidationError{}\n\n// Validate checks the field values on Forager with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Forager) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Forager with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in ForagerMultiError, or nil if none found.\nfunc (m *Forager) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Forager) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for MaxDepth\n\n\tif all {\n\t\tswitch v := interface{}(m.GetSince()).(type) {\n\t\tcase interface{ ValidateAll() error }:\n\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\terrors = append(errors, ForagerValidationError{\n\t\t\t\t\tfield:  \"Since\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t})\n\t\t\t}\n\t\tcase interface{ Validate() error }:\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\terrors = append(errors, ForagerValidationError{\n\t\t\t\t\tfield:  \"Since\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t} else if v, ok := interface{}(m.GetSince()).(interface{ Validate() error }); ok {\n\t\tif err := v.Validate(); err != nil {\n\t\t\treturn ForagerValidationError{\n\t\t\t\tfield:  \"Since\",\n\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\tcause:  err,\n\t\t\t}\n\t\t}\n\t}\n\n\tswitch v := m.Credential.(type) {\n\tcase *Forager_Unauthenticated:\n\t\tif v == nil {\n\t\t\terr := ForagerValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetUnauthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, ForagerValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, ForagerValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetUnauthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn ForagerValidationError{\n\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn ForagerMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// ForagerMultiError is an error wrapping multiple validation errors returned\n// by Forager.ValidateAll() if the designated constraints aren't met.\ntype ForagerMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m ForagerMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m ForagerMultiError) AllErrors() []error { return m }\n\n// ForagerValidationError is the validation error returned by Forager.Validate\n// if the designated constraints aren't met.\ntype ForagerValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e ForagerValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e ForagerValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e ForagerValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e ForagerValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e ForagerValidationError) ErrorName() string { return \"ForagerValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e ForagerValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sForager.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = ForagerValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = ForagerValidationError{}\n\n// Validate checks the field values on SlackRealtime with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *SlackRealtime) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on SlackRealtime with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in SlackRealtimeMultiError, or\n// nil if none found.\nfunc (m *SlackRealtime) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *SlackRealtime) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tswitch v := m.Credential.(type) {\n\tcase *SlackRealtime_Tokens:\n\t\tif v == nil {\n\t\t\terr := SlackRealtimeValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetTokens()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, SlackRealtimeValidationError{\n\t\t\t\t\t\tfield:  \"Tokens\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, SlackRealtimeValidationError{\n\t\t\t\t\t\tfield:  \"Tokens\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetTokens()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn SlackRealtimeValidationError{\n\t\t\t\t\tfield:  \"Tokens\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn SlackRealtimeMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// SlackRealtimeMultiError is an error wrapping multiple validation errors\n// returned by SlackRealtime.ValidateAll() if the designated constraints\n// aren't met.\ntype SlackRealtimeMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m SlackRealtimeMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m SlackRealtimeMultiError) AllErrors() []error { return m }\n\n// SlackRealtimeValidationError is the validation error returned by\n// SlackRealtime.Validate if the designated constraints aren't met.\ntype SlackRealtimeValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e SlackRealtimeValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e SlackRealtimeValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e SlackRealtimeValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e SlackRealtimeValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e SlackRealtimeValidationError) ErrorName() string { return \"SlackRealtimeValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e SlackRealtimeValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sSlackRealtime.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = SlackRealtimeValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = SlackRealtimeValidationError{}\n\n// Validate checks the field values on Sharepoint with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Sharepoint) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Sharepoint with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in SharepointMultiError, or\n// nil if none found.\nfunc (m *Sharepoint) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Sharepoint) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for SiteUrl\n\n\tswitch v := m.Credential.(type) {\n\tcase *Sharepoint_Oauth:\n\t\tif v == nil {\n\t\t\terr := SharepointValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetOauth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, SharepointValidationError{\n\t\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, SharepointValidationError{\n\t\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetOauth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn SharepointValidationError{\n\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn SharepointMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// SharepointMultiError is an error wrapping multiple validation errors\n// returned by Sharepoint.ValidateAll() if the designated constraints aren't met.\ntype SharepointMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m SharepointMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m SharepointMultiError) AllErrors() []error { return m }\n\n// SharepointValidationError is the validation error returned by\n// Sharepoint.Validate if the designated constraints aren't met.\ntype SharepointValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e SharepointValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e SharepointValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e SharepointValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e SharepointValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e SharepointValidationError) ErrorName() string { return \"SharepointValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e SharepointValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sSharepoint.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = SharepointValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = SharepointValidationError{}\n\n// Validate checks the field values on AzureRepos with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *AzureRepos) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on AzureRepos with the rules defined in\n// the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in AzureReposMultiError, or\n// nil if none found.\nfunc (m *AzureRepos) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *AzureRepos) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif _, err := url.Parse(m.GetEndpoint()); err != nil {\n\t\terr = AzureReposValidationError{\n\t\t\tfield:  \"Endpoint\",\n\t\t\treason: \"value must be a valid URI\",\n\t\t\tcause:  err,\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\t// no validation rules for IncludeForks\n\n\t// no validation rules for SkipBinaries\n\n\t// no validation rules for SkipArchives\n\n\tswitch v := m.Credential.(type) {\n\tcase *AzureRepos_Token:\n\t\tif v == nil {\n\t\t\terr := AzureReposValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for Token\n\tcase *AzureRepos_Oauth:\n\t\tif v == nil {\n\t\t\terr := AzureReposValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetOauth()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, AzureReposValidationError{\n\t\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, AzureReposValidationError{\n\t\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetOauth()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn AzureReposValidationError{\n\t\t\t\t\tfield:  \"Oauth\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn AzureReposMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// AzureReposMultiError is an error wrapping multiple validation errors\n// returned by AzureRepos.ValidateAll() if the designated constraints aren't met.\ntype AzureReposMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m AzureReposMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m AzureReposMultiError) AllErrors() []error { return m }\n\n// AzureReposValidationError is the validation error returned by\n// AzureRepos.Validate if the designated constraints aren't met.\ntype AzureReposValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e AzureReposValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e AzureReposValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e AzureReposValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e AzureReposValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e AzureReposValidationError) ErrorName() string { return \"AzureReposValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e AzureReposValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sAzureRepos.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = AzureReposValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = AzureReposValidationError{}\n\n// Validate checks the field values on Postman with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Postman) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Postman with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in PostmanMultiError, or nil if none found.\nfunc (m *Postman) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Postman) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tswitch v := m.Credential.(type) {\n\tcase *Postman_Unauthenticated:\n\t\tif v == nil {\n\t\t\terr := PostmanValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetUnauthenticated()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, PostmanValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, PostmanValidationError{\n\t\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetUnauthenticated()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn PostmanValidationError{\n\t\t\t\t\tfield:  \"Unauthenticated\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tcase *Postman_Token:\n\t\tif v == nil {\n\t\t\terr := PostmanValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for Token\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn PostmanMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// PostmanMultiError is an error wrapping multiple validation errors returned\n// by Postman.ValidateAll() if the designated constraints aren't met.\ntype PostmanMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m PostmanMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m PostmanMultiError) AllErrors() []error { return m }\n\n// PostmanValidationError is the validation error returned by Postman.Validate\n// if the designated constraints aren't met.\ntype PostmanValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e PostmanValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e PostmanValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e PostmanValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e PostmanValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e PostmanValidationError) ErrorName() string { return \"PostmanValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e PostmanValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sPostman.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = PostmanValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = PostmanValidationError{}\n\n// Validate checks the field values on Webhook with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Webhook) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Webhook with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in WebhookMultiError, or nil if none found.\nfunc (m *Webhook) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Webhook) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif err := m._validateHostname(m.GetListenAddress()); err != nil {\n\t\terr = WebhookValidationError{\n\t\t\tfield:  \"ListenAddress\",\n\t\t\treason: \"value must be a valid hostname\",\n\t\t\tcause:  err,\n\t\t}\n\t\tif !all {\n\t\t\treturn err\n\t\t}\n\t\terrors = append(errors, err)\n\t}\n\n\tswitch v := m.Credential.(type) {\n\tcase *Webhook_Header:\n\t\tif v == nil {\n\t\t\terr := WebhookValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetHeader()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, WebhookValidationError{\n\t\t\t\t\t\tfield:  \"Header\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, WebhookValidationError{\n\t\t\t\t\t\tfield:  \"Header\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetHeader()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn WebhookValidationError{\n\t\t\t\t\tfield:  \"Header\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\tswitch v := m.Variant.(type) {\n\tcase *Webhook_Vector:\n\t\tif v == nil {\n\t\t\terr := WebhookValidationError{\n\t\t\t\tfield:  \"Variant\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\n\t\tif all {\n\t\t\tswitch v := interface{}(m.GetVector()).(type) {\n\t\t\tcase interface{ ValidateAll() error }:\n\t\t\t\tif err := v.ValidateAll(); err != nil {\n\t\t\t\t\terrors = append(errors, WebhookValidationError{\n\t\t\t\t\t\tfield:  \"Vector\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\tcase interface{ Validate() error }:\n\t\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\t\terrors = append(errors, WebhookValidationError{\n\t\t\t\t\t\tfield:  \"Vector\",\n\t\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\t\tcause:  err,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t} else if v, ok := interface{}(m.GetVector()).(interface{ Validate() error }); ok {\n\t\t\tif err := v.Validate(); err != nil {\n\t\t\t\treturn WebhookValidationError{\n\t\t\t\t\tfield:  \"Vector\",\n\t\t\t\t\treason: \"embedded message failed validation\",\n\t\t\t\t\tcause:  err,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn WebhookMultiError(errors)\n\t}\n\n\treturn nil\n}\n\nfunc (m *Webhook) _validateHostname(host string) error {\n\ts := strings.ToLower(strings.TrimSuffix(host, \".\"))\n\n\tif len(host) > 253 {\n\t\treturn errors.New(\"hostname cannot exceed 253 characters\")\n\t}\n\n\tfor _, part := range strings.Split(s, \".\") {\n\t\tif l := len(part); l == 0 || l > 63 {\n\t\t\treturn errors.New(\"hostname part must be non-empty and cannot exceed 63 characters\")\n\t\t}\n\n\t\tif part[0] == '-' {\n\t\t\treturn errors.New(\"hostname parts cannot begin with hyphens\")\n\t\t}\n\n\t\tif part[len(part)-1] == '-' {\n\t\t\treturn errors.New(\"hostname parts cannot end with hyphens\")\n\t\t}\n\n\t\tfor _, r := range part {\n\t\t\tif (r < 'a' || r > 'z') && (r < '0' || r > '9') && r != '-' {\n\t\t\t\treturn fmt.Errorf(\"hostname parts can only contain alphanumeric characters or hyphens, got %q\", string(r))\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// WebhookMultiError is an error wrapping multiple validation errors returned\n// by Webhook.ValidateAll() if the designated constraints aren't met.\ntype WebhookMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m WebhookMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m WebhookMultiError) AllErrors() []error { return m }\n\n// WebhookValidationError is the validation error returned by Webhook.Validate\n// if the designated constraints aren't met.\ntype WebhookValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e WebhookValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e WebhookValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e WebhookValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e WebhookValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e WebhookValidationError) ErrorName() string { return \"WebhookValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e WebhookValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sWebhook.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = WebhookValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = WebhookValidationError{}\n\n// Validate checks the field values on Vector with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Vector) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Vector with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in VectorMultiError, or nil if none found.\nfunc (m *Vector) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Vector) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for LocatorField\n\n\t// no validation rules for LinkFormat\n\n\tif len(errors) > 0 {\n\t\treturn VectorMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// VectorMultiError is an error wrapping multiple validation errors returned by\n// Vector.ValidateAll() if the designated constraints aren't met.\ntype VectorMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m VectorMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m VectorMultiError) AllErrors() []error { return m }\n\n// VectorValidationError is the validation error returned by Vector.Validate if\n// the designated constraints aren't met.\ntype VectorValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e VectorValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e VectorValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e VectorValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e VectorValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e VectorValidationError) ErrorName() string { return \"VectorValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e VectorValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sVector.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = VectorValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = VectorValidationError{}\n\n// Validate checks the field values on Elasticsearch with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Elasticsearch) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Elasticsearch with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in ElasticsearchMultiError, or\n// nil if none found.\nfunc (m *Elasticsearch) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Elasticsearch) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Username\n\n\t// no validation rules for Password\n\n\t// no validation rules for CloudId\n\n\t// no validation rules for ApiKey\n\n\t// no validation rules for ServiceToken\n\n\t// no validation rules for IndexPattern\n\n\t// no validation rules for QueryJson\n\n\t// no validation rules for SinceTimestamp\n\n\t// no validation rules for BestEffortScan\n\n\tif len(errors) > 0 {\n\t\treturn ElasticsearchMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// ElasticsearchMultiError is an error wrapping multiple validation errors\n// returned by Elasticsearch.ValidateAll() if the designated constraints\n// aren't met.\ntype ElasticsearchMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m ElasticsearchMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m ElasticsearchMultiError) AllErrors() []error { return m }\n\n// ElasticsearchValidationError is the validation error returned by\n// Elasticsearch.Validate if the designated constraints aren't met.\ntype ElasticsearchValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e ElasticsearchValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e ElasticsearchValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e ElasticsearchValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e ElasticsearchValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e ElasticsearchValidationError) ErrorName() string { return \"ElasticsearchValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e ElasticsearchValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sElasticsearch.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = ElasticsearchValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = ElasticsearchValidationError{}\n\n// Validate checks the field values on Sentry with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Sentry) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Sentry with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in SentryMultiError, or nil if none found.\nfunc (m *Sentry) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Sentry) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Endpoint\n\n\t// no validation rules for InsecureSkipVerifyTls\n\n\t// no validation rules for Projects\n\n\tswitch v := m.Credential.(type) {\n\tcase *Sentry_AuthToken:\n\t\tif v == nil {\n\t\t\terr := SentryValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for AuthToken\n\tcase *Sentry_DsnKey:\n\t\tif v == nil {\n\t\t\terr := SentryValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for DsnKey\n\tcase *Sentry_ApiKey:\n\t\tif v == nil {\n\t\t\terr := SentryValidationError{\n\t\t\t\tfield:  \"Credential\",\n\t\t\t\treason: \"oneof value cannot be a typed-nil\",\n\t\t\t}\n\t\t\tif !all {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\terrors = append(errors, err)\n\t\t}\n\t\t// no validation rules for ApiKey\n\tdefault:\n\t\t_ = v // ensures v is used\n\t}\n\n\tif len(errors) > 0 {\n\t\treturn SentryMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// SentryMultiError is an error wrapping multiple validation errors returned by\n// Sentry.ValidateAll() if the designated constraints aren't met.\ntype SentryMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m SentryMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m SentryMultiError) AllErrors() []error { return m }\n\n// SentryValidationError is the validation error returned by Sentry.Validate if\n// the designated constraints aren't met.\ntype SentryValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e SentryValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e SentryValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e SentryValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e SentryValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e SentryValidationError) ErrorName() string { return \"SentryValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e SentryValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sSentry.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = SentryValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = SentryValidationError{}\n\n// Validate checks the field values on Stdin with the rules defined in the\n// proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *Stdin) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on Stdin with the rules defined in the\n// proto definition for this message. If any rules are violated, the result is\n// a list of violation errors wrapped in StdinMultiError, or nil if none found.\nfunc (m *Stdin) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *Stdin) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif len(errors) > 0 {\n\t\treturn StdinMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// StdinMultiError is an error wrapping multiple validation errors returned by\n// Stdin.ValidateAll() if the designated constraints aren't met.\ntype StdinMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m StdinMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m StdinMultiError) AllErrors() []error { return m }\n\n// StdinValidationError is the validation error returned by Stdin.Validate if\n// the designated constraints aren't met.\ntype StdinValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e StdinValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e StdinValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e StdinValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e StdinValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e StdinValidationError) ErrorName() string { return \"StdinValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e StdinValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sStdin.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = StdinValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = StdinValidationError{}\n\n// Validate checks the field values on SlackContinuous with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// first error encountered is returned, or nil if there are no violations.\nfunc (m *SlackContinuous) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on SlackContinuous with the rules\n// defined in the proto definition for this message. If any rules are\n// violated, the result is a list of violation errors wrapped in\n// SlackContinuousMultiError, or nil if none found.\nfunc (m *SlackContinuous) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *SlackContinuous) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\t// no validation rules for Namespace\n\n\t// no validation rules for ProjectId\n\n\tif len(errors) > 0 {\n\t\treturn SlackContinuousMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// SlackContinuousMultiError is an error wrapping multiple validation errors\n// returned by SlackContinuous.ValidateAll() if the designated constraints\n// aren't met.\ntype SlackContinuousMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m SlackContinuousMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m SlackContinuousMultiError) AllErrors() []error { return m }\n\n// SlackContinuousValidationError is the validation error returned by\n// SlackContinuous.Validate if the designated constraints aren't met.\ntype SlackContinuousValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e SlackContinuousValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e SlackContinuousValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e SlackContinuousValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e SlackContinuousValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e SlackContinuousValidationError) ErrorName() string { return \"SlackContinuousValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e SlackContinuousValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sSlackContinuous.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = SlackContinuousValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = SlackContinuousValidationError{}\n\n// Validate checks the field values on JSONEnumerator with the rules defined in\n// the proto definition for this message. If any rules are violated, the first\n// error encountered is returned, or nil if there are no violations.\nfunc (m *JSONEnumerator) Validate() error {\n\treturn m.validate(false)\n}\n\n// ValidateAll checks the field values on JSONEnumerator with the rules defined\n// in the proto definition for this message. If any rules are violated, the\n// result is a list of violation errors wrapped in JSONEnumeratorMultiError,\n// or nil if none found.\nfunc (m *JSONEnumerator) ValidateAll() error {\n\treturn m.validate(true)\n}\n\nfunc (m *JSONEnumerator) validate(all bool) error {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tvar errors []error\n\n\tif len(errors) > 0 {\n\t\treturn JSONEnumeratorMultiError(errors)\n\t}\n\n\treturn nil\n}\n\n// JSONEnumeratorMultiError is an error wrapping multiple validation errors\n// returned by JSONEnumerator.ValidateAll() if the designated constraints\n// aren't met.\ntype JSONEnumeratorMultiError []error\n\n// Error returns a concatenation of all the error messages it wraps.\nfunc (m JSONEnumeratorMultiError) Error() string {\n\tvar msgs []string\n\tfor _, err := range m {\n\t\tmsgs = append(msgs, err.Error())\n\t}\n\treturn strings.Join(msgs, \"; \")\n}\n\n// AllErrors returns a list of validation violation errors.\nfunc (m JSONEnumeratorMultiError) AllErrors() []error { return m }\n\n// JSONEnumeratorValidationError is the validation error returned by\n// JSONEnumerator.Validate if the designated constraints aren't met.\ntype JSONEnumeratorValidationError struct {\n\tfield  string\n\treason string\n\tcause  error\n\tkey    bool\n}\n\n// Field function returns field value.\nfunc (e JSONEnumeratorValidationError) Field() string { return e.field }\n\n// Reason function returns reason value.\nfunc (e JSONEnumeratorValidationError) Reason() string { return e.reason }\n\n// Cause function returns cause value.\nfunc (e JSONEnumeratorValidationError) Cause() error { return e.cause }\n\n// Key function returns key value.\nfunc (e JSONEnumeratorValidationError) Key() bool { return e.key }\n\n// ErrorName returns error name.\nfunc (e JSONEnumeratorValidationError) ErrorName() string { return \"JSONEnumeratorValidationError\" }\n\n// Error satisfies the builtin error interface\nfunc (e JSONEnumeratorValidationError) Error() string {\n\tcause := \"\"\n\tif e.cause != nil {\n\t\tcause = fmt.Sprintf(\" | caused by: %v\", e.cause)\n\t}\n\n\tkey := \"\"\n\tif e.key {\n\t\tkey = \"key for \"\n\t}\n\n\treturn fmt.Sprintf(\n\t\t\"invalid %sJSONEnumerator.%s: %s%s\",\n\t\tkey,\n\t\te.field,\n\t\te.reason,\n\t\tcause)\n}\n\nvar _ error = JSONEnumeratorValidationError{}\n\nvar _ interface {\n\tField() string\n\tReason() string\n\tKey() bool\n\tCause() error\n\tErrorName() string\n} = JSONEnumeratorValidationError{}\n"
  },
  {
    "path": "pkg/process/zombies.go",
    "content": "package process\n\nimport (\n\t\"os/exec\"\n\t\"runtime\"\n\t\"strings\"\n)\n\nfunc GetGitProcessList() []string {\n\tvar cmd *exec.Cmd\n\tif runtime.GOOS == \"darwin\" {\n\t\tcmd = exec.Command(\"ps\", \"-eo\", \"pid,state,command\")\n\t} else {\n\t\tcmd = exec.Command(\"ps\", \"-eo\", \"pid,stat,cmd\")\n\t}\n\n\toutput, err := cmd.Output()\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tlines := strings.Split(string(output), \"\\n\")\n\tvar gitProcesses []string\n\tfor _, line := range lines {\n\t\tif strings.Contains(line, \"git\") {\n\t\t\tgitProcesses = append(gitProcesses, line)\n\t\t}\n\t}\n\treturn gitProcesses\n}\n\nfunc DetectGitZombies(before, after []string) []string {\n\tbeforeMap := make(map[string]bool)\n\tfor _, process := range before {\n\t\tbeforeMap[process] = true\n\t}\n\n\tvar zombies []string\n\tfor _, process := range after {\n\t\tif !beforeMap[process] {\n\t\t\tfields := strings.Fields(process)\n\t\t\tif len(fields) >= 2 && (fields[1] == \"Z\" || strings.HasPrefix(fields[1], \"Z\")) {\n\t\t\t\tzombies = append(zombies, process)\n\t\t\t}\n\t\t}\n\t}\n\treturn zombies\n}\n"
  },
  {
    "path": "pkg/protoyaml/protoyaml.go",
    "content": "package protoyaml\n\nimport (\n\t\"google.golang.org/protobuf/encoding/protojson\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nvar nonStrict = protojson.UnmarshalOptions{DiscardUnknown: true}\nvar strict = protojson.UnmarshalOptions{DiscardUnknown: false}\n\n// Marshal writes the given proto.Message in YAML format.\nfunc Marshal(m proto.Message) ([]byte, error) {\n\tjson, err := protojson.Marshal(m)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn yaml.JSONToYAML(json)\n}\n\n// Unmarshal reads the given []byte into the given proto.Message, discarding\n// any unknown fields in the input.\nfunc Unmarshal(b []byte, m proto.Message) error {\n\tjson, err := yaml.YAMLToJSON(b)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nonStrict.Unmarshal(json, m)\n}\n\n// UnmarshalStrict reads the given []byte into the given proto.Message. If there\n// are any unknown fields in the input, an error is returned.\nfunc UnmarshalStrict(b []byte, m proto.Message) error {\n\tjson, err := yaml.YAMLToJSON(b)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn strict.Unmarshal(json, m)\n}\n"
  },
  {
    "path": "pkg/roundtripper/roundtripper.go",
    "content": "package roundtripper\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\ntype RoundTripper struct {\n\tlogger   logr.Logger\n\toriginal http.RoundTripper\n\t*BasicAuthRoundTripper\n\t*RetryableRoundtripper\n\t*LoggingRoundtripper\n}\n\ntype BasicAuthRoundTripper struct {\n\tusername, password string\n}\n\ntype RetryableRoundtripper struct {\n\tenabled                  bool\n\tmaxRetries               uint\n\tshouldRetryError         bool\n\tshouldRetryErrorDuration time.Duration\n\tshouldRetry5XX           bool\n\tshouldRetry5XXDuration   time.Duration\n\tshouldRetry401           bool\n\tshouldRetry401Duration   time.Duration\n\tdefault429RetryDuration  time.Duration\n}\n\ntype LoggingRoundtripper struct{}\n\n// NewRoundTripper creates a new RoundTripper instance tailored for the application's specific needs.\n// This custom RoundTripper provides a centralized place to manage outbound HTTP requests.\n// By allowing configuration through functional options, it provides a clear, extensible, and maintainable\n// way to adjust the behavior of HTTP calls. This ensures consistent logging, error handling,\n// and other behaviors across all HTTP requests made in the application, reducing potential points of\n// failure and simplifying debugging.\nfunc NewRoundTripper(original http.RoundTripper, opts ...func(*RoundTripper)) *RoundTripper {\n\tr := &RoundTripper{\n\t\tlogger:                context.Background().Logger().WithValues(\"component\", \"basic_auth_roundtripper\"),\n\t\toriginal:              original,\n\t\tRetryableRoundtripper: &RetryableRoundtripper{enabled: false},\n\t}\n\n\tif original == nil {\n\t\tr.original = common.NewCustomTransport(nil)\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(r)\n\t}\n\n\treturn r\n}\n\nfunc (r *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {\n\tretries := 0\n\tfor {\n\t\tlogger := r.logger.WithValues(\n\t\t\t\"method\", req.Method,\n\t\t\t\"url\", req.URL,\n\t\t)\n\n\t\tif r.BasicAuthRoundTripper != nil {\n\t\t\treq.SetBasicAuth(r.BasicAuthRoundTripper.username, r.BasicAuthRoundTripper.password)\n\t\t}\n\n\t\tauthMethod := determineAuth(req)\n\t\tif r.LoggingRoundtripper != nil {\n\t\t\tlogger.V(5).Info(fmt.Sprintf(\"sending request with %s\", authMethod))\n\t\t}\n\n\t\tresponse, err := r.original.RoundTrip(req)\n\t\tif err != nil {\n\t\t\tlogger.V(5).Info(\"got error while sending request\",\n\t\t\t\t\"error\", err,\n\t\t\t)\n\t\t}\n\n\t\tif r.LoggingRoundtripper != nil && response != nil {\n\t\t\tlogger.V(5).Info(\"got response\",\n\t\t\t\t\"status_code\", response.StatusCode,\n\t\t\t)\n\t\t}\n\n\t\treason, shouldRetry, duration := r.RetryableRoundtripper.shouldRetryRequest(response, err)\n\t\tif shouldRetry {\n\t\t\tif retries >= int(r.RetryableRoundtripper.maxRetries) {\n\t\t\t\tr.logger.V(2).Info(\"max retries reached\",\n\t\t\t\t\t\"host\", req.Host,\n\t\t\t\t\t\"reason\", reason,\n\t\t\t\t\t\"retries\", retries,\n\t\t\t\t)\n\t\t\t\treturn response, err\n\t\t\t}\n\t\t\tr.logger.V(2).Info(\"retrying request\",\n\t\t\t\t\"host\", req.Host,\n\t\t\t\t\"reason\", reason,\n\t\t\t\t\"retries\", retries,\n\t\t\t)\n\n\t\t\tretries++\n\t\t\ttime.Sleep(duration)\n\t\t\tcontinue\n\t\t}\n\n\t\treturn response, err\n\t}\n}\n\n// WithInsecureTLS will disable TLS verification.\nfunc WithInsecureTLS() func(*RoundTripper) {\n\treturn func(r *RoundTripper) {\n\t\tr.original = common.NewCustomTransport(&http.Transport{\n\t\t\tTLSClientConfig: &tls.Config{InsecureSkipVerify: true},\n\t\t})\n\t}\n}\n\nfunc WithLogger(log logr.Logger) func(*RoundTripper) {\n\treturn func(r *RoundTripper) {\n\t\tr.logger = log\n\t}\n}\n\n// Logging\n\nfunc WithLogging() func(*RoundTripper) {\n\treturn func(r *RoundTripper) {\n\t\tr.LoggingRoundtripper = &LoggingRoundtripper{}\n\t}\n}\n\n// Basic Auth\n\nfunc WithBasicAuth(username, password string) func(*RoundTripper) {\n\treturn func(r *RoundTripper) {\n\t\tr.BasicAuthRoundTripper = &BasicAuthRoundTripper{\n\t\t\tusername: username,\n\t\t\tpassword: password,\n\t\t}\n\t}\n}\n\n// Retryable\n\nfunc (r *RetryableRoundtripper) shouldRetryRequest(response *http.Response, err error) (reason string, shouldRetry bool, after time.Duration) {\n\tif !r.enabled {\n\t\treturn \"\", false, 0\n\t}\n\n\tif err != nil {\n\t\treturn err.Error(), r.shouldRetryError, r.shouldRetryErrorDuration\n\t}\n\n\tif response.StatusCode == http.StatusTooManyRequests || response.StatusCode == http.StatusServiceUnavailable {\n\t\tretryAfter := getRetryAfter(response)\n\t\tif retryAfter == 0 {\n\t\t\tretryAfter = r.default429RetryDuration\n\t\t}\n\t\treturn strconv.Itoa(response.StatusCode), true, retryAfter\n\t}\n\n\tcode := response.StatusCode\n\tswitch {\n\tcase code == 401:\n\t\treturn strconv.Itoa(response.StatusCode), r.shouldRetry401, r.shouldRetry401Duration\n\tcase code >= 500:\n\t\treturn strconv.Itoa(response.StatusCode), r.shouldRetry5XX, r.shouldRetry5XXDuration\n\tdefault:\n\t\treturn \"\", false, 0\n\t}\n}\n\nfunc getRetryAfter(response *http.Response) time.Duration {\n\tif s, ok := response.Header[\"Retry-After\"]; ok {\n\t\tif sleep, err := strconv.ParseInt(s[0], 10, 64); err == nil {\n\t\t\treturn time.Second * time.Duration(sleep)\n\t\t}\n\t}\n\treturn 0\n}\n\nfunc WithRetryable(opts ...func(*RetryableRoundtripper)) func(*RoundTripper) {\n\treturn func(r *RoundTripper) {\n\t\trt := &RetryableRoundtripper{\n\t\t\tenabled:                  true,\n\t\t\tmaxRetries:               3,\n\t\t\tshouldRetryError:         true,\n\t\t\tshouldRetryErrorDuration: time.Second * 5,\n\t\t\tshouldRetry5XX:           true,\n\t\t\tshouldRetry5XXDuration:   time.Second * 5,\n\t\t\tshouldRetry401:           false,\n\t\t\tshouldRetry401Duration:   time.Second * 5,\n\t\t\tdefault429RetryDuration:  time.Second * 30,\n\t\t}\n\n\t\tfor _, opt := range opts {\n\t\t\topt(rt)\n\t\t}\n\n\t\tr.RetryableRoundtripper = rt\n\t}\n}\n\nfunc WithShouldRetryError(should bool) func(*RetryableRoundtripper) {\n\treturn func(r *RetryableRoundtripper) {\n\t\tr.shouldRetryError = should\n\t}\n}\n\nfunc WithShouldRetryErrorDuration(duration time.Duration) func(*RetryableRoundtripper) {\n\treturn func(r *RetryableRoundtripper) {\n\t\tr.shouldRetryErrorDuration = duration\n\t}\n}\n\nfunc WithShouldRetry5XX(should bool) func(*RetryableRoundtripper) {\n\treturn func(r *RetryableRoundtripper) {\n\t\tr.shouldRetry5XX = should\n\t}\n}\n\nfunc WithShouldRetry5XXDuration(duration time.Duration) func(*RetryableRoundtripper) {\n\treturn func(r *RetryableRoundtripper) {\n\t\tr.shouldRetry5XXDuration = duration\n\t}\n}\n\nfunc WithShouldRetry401Duration(duration time.Duration) func(*RetryableRoundtripper) {\n\treturn func(r *RetryableRoundtripper) {\n\t\tr.shouldRetry401 = true\n\t\tr.shouldRetry401Duration = duration\n\t}\n}\n\nfunc WithDefault429RetryDuration(duration time.Duration) func(*RetryableRoundtripper) {\n\treturn func(r *RetryableRoundtripper) {\n\t\tr.default429RetryDuration = duration\n\t}\n}\n\nfunc WithMaxRetries(max uint) func(*RetryableRoundtripper) {\n\treturn func(r *RetryableRoundtripper) {\n\t\tr.maxRetries = max\n\t}\n}\n\n// determineAuth will let us know if the Authorization header is set,\n// and if it is, if it contains the prefix \"Basic\" or \"Bearer\"\nfunc determineAuth(req *http.Request) string {\n\tswitch authHeader := req.Header.Get(\"Authorization\"); true {\n\tcase authHeader == \"\":\n\t\treturn \"no auth\"\n\tcase strings.HasPrefix(authHeader, \"Basic\"):\n\t\treturn \"basic auth\"\n\tcase strings.HasPrefix(authHeader, \"Bearer\"):\n\t\treturn \"bearer token\"\n\tdefault:\n\t\treturn \"unknown auth\"\n\t}\n}\n"
  },
  {
    "path": "pkg/sanitizer/utf8.go",
    "content": "package sanitizer\n\nimport (\n\t\"strings\"\n)\n\nfunc UTF8(in string) string {\n\treturn strings.Replace(strings.ToValidUTF8(in, \"❗\"), \"\\x00\", \"\", -1)\n}\n"
  },
  {
    "path": "pkg/sanitizer/utf8_test.go",
    "content": "package sanitizer\n\nimport \"testing\"\n\nfunc TestUTF8(t *testing.T) {\n\ttype args struct {\n\t\tin string\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"valid\",\n\t\t\targs: args{\n\t\t\t\tin: \"hello123\",\n\t\t\t},\n\t\t\twant: \"hello123\",\n\t\t},\n\t\t{\n\t\t\tname: \"sanitized\",\n\t\t\targs: args{\n\t\t\t\tin: \"Gr\\351gory Smith\",\n\t\t\t},\n\t\t\twant: \"Gr❗gory Smith\",\n\t\t},\n\t\t{\n\t\t\tname: \"sanitized\",\n\t\t\targs: args{\n\t\t\t\tin: \"no \\x00 nulls because postgres does not support it in text fields\",\n\t\t\t},\n\t\t\twant: \"no  nulls because postgres does not support it in text fields\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := UTF8(tt.args.in); got != tt.want {\n\t\t\t\tt.Errorf(\"UTF8() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/sources/chunker.go",
    "content": "package sources\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nconst (\n\t// DefaultChunkSize used by the chunker.\n\tDefaultChunkSize = 10 * 1024\n\t// DefaultPeekSize is the size of the peek into the previous chunk.\n\tDefaultPeekSize = 3 * 1024\n\t// TotalChunkSize is the total size of a chunk with peek data.\n\tTotalChunkSize = DefaultChunkSize + DefaultPeekSize\n)\n\ntype chunkReaderConfig struct {\n\tchunkSize int\n\ttotalSize int\n\tpeekSize  int\n\tfileSize  int\n}\n\n// ConfigOption is a function that configures a chunker.\ntype ConfigOption func(*chunkReaderConfig)\n\n// WithChunkSize sets the chunk size.\nfunc WithChunkSize(size int) ConfigOption {\n\treturn func(c *chunkReaderConfig) { c.chunkSize = size }\n}\n\n// WithPeekSize sets the peek size.\nfunc WithPeekSize(size int) ConfigOption {\n\treturn func(c *chunkReaderConfig) { c.peekSize = size }\n}\n\n// WithFileSize sets the file size.\n// Note: If WithChunkSize is also provided, WithChunkSize takes precedence.\nfunc WithFileSize(size int) ConfigOption {\n\treturn func(c *chunkReaderConfig) { c.fileSize = size }\n}\n\n// ChunkResult is the output unit of a ChunkReader,\n// it contains the data and error of a chunk.\ntype ChunkResult struct {\n\tdata []byte\n\t// contentSize is the size of actual content, excluding peek data\n\tcontentSize int\n\terr         error\n}\n\n// Bytes for a ChunkResult.\nfunc (cr ChunkResult) Bytes() []byte {\n\treturn cr.data\n}\n\n// ContentSize returns the size of actual content, excluding peek data.\n// Use this when you need to process only the unique content of each chunk\n// without the overlapping peek portion.\nfunc (cr ChunkResult) ContentSize() int {\n\treturn cr.contentSize\n}\n\n// Error for a ChunkResult.\nfunc (cr ChunkResult) Error() error {\n\treturn cr.err\n}\n\n// NewChunkResult creates a ChunkResult with the given data and content size.\nfunc NewChunkResult(data []byte, contentSize int) ChunkResult {\n\treturn ChunkResult{data: data, contentSize: contentSize}\n}\n\n// NewChunkResultError creates a ChunkResult containing an error.\nfunc NewChunkResultError(err error) ChunkResult {\n\treturn ChunkResult{err: err}\n}\n\nconst (\n\t// Size thresholds.\n\txsmallFileSizeThreshold = 4 * 1024        // 4KB\n\tsmallFileSizeThreshold  = 10 * 1024       // 10KB\n\tmediumFileSizeThreshold = 100 * 1024      // 100KB\n\tlargeFileSizeThreshold  = 1 * 1024 * 1024 // 1MB\n\n\t// Chunk sizes.\n\txsmallFileChunkSize = 1 << 12 // 4KB\n\tsmallFileChunkSize  = 1 << 13 // 8KB\n\tmediumFileChunkSize = 1 << 14 // 16KB\n\tlargeFileChunkSize  = 1 << 15 // 32KB\n\txlargeFileChunkSize = 1 << 16 // 64KB\n)\n\n// ChunkReader reads chunks from a reader and returns a channel of chunks and a channel of errors.\n// The channel of chunks is closed when the reader is closed.\n// This should be used whenever a large amount of data is read from a reader.\n// Ex: reading attachments, archives, etc.\ntype ChunkReader func(ctx context.Context, reader io.Reader) <-chan ChunkResult\n\n// NewChunkReader returns a ChunkReader with the given options.\nfunc NewChunkReader(opts ...ConfigOption) ChunkReader {\n\tconfig := applyOptions(opts)\n\treturn createReaderFn(config)\n}\n\nfunc applyOptions(opts []ConfigOption) *chunkReaderConfig {\n\t// Set defaults.\n\tconfig := &chunkReaderConfig{\n\t\tchunkSize: DefaultChunkSize, // default\n\t\tpeekSize:  DefaultPeekSize,  // default\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(config)\n\t}\n\n\t// Prioritize chunkSize over fileSize if both are provided.\n\tif config.fileSize != 0 && config.chunkSize == DefaultChunkSize {\n\t\tconfig.chunkSize = calculateOptimalChunkSize(config.fileSize)\n\t}\n\n\tconfig.totalSize = config.chunkSize + config.peekSize\n\n\treturn config\n}\n\nfunc calculateOptimalChunkSize(fileSize int) int {\n\tswitch {\n\tcase fileSize < xsmallFileSizeThreshold:\n\t\treturn xsmallFileChunkSize\n\tcase fileSize < smallFileSizeThreshold:\n\t\treturn smallFileChunkSize\n\tcase fileSize < mediumFileSizeThreshold:\n\t\treturn mediumFileChunkSize\n\tcase fileSize < largeFileSizeThreshold:\n\t\treturn largeFileChunkSize\n\tdefault:\n\t\treturn xlargeFileChunkSize\n\t}\n}\n\nfunc createReaderFn(config *chunkReaderConfig) ChunkReader {\n\treturn func(ctx context.Context, reader io.Reader) <-chan ChunkResult {\n\t\treturn readInChunks(ctx, reader, config)\n\t}\n}\n\nfunc readInChunks(ctx context.Context, reader io.Reader, config *chunkReaderConfig) <-chan ChunkResult {\n\tconst channelSize = 64\n\tchunkReader := bufio.NewReaderSize(reader, config.chunkSize)\n\tchunkResultChan := make(chan ChunkResult, channelSize)\n\n\tgo func() {\n\t\tdefer close(chunkResultChan)\n\n\t\t// Defer a panic recovery to handle any panics that occur while reading, which can sometimes unavoidably happen\n\t\t// due to third-party library bugs.\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tvar panicErr error\n\t\t\t\tif e, ok := r.(error); ok {\n\t\t\t\t\tpanicErr = e\n\t\t\t\t} else {\n\t\t\t\t\tpanicErr = fmt.Errorf(\"panic occurred: %v\", r)\n\t\t\t\t}\n\t\t\t\tchunkResultChan <- NewChunkResultError(fmt.Errorf(\"panic error: %w\", panicErr))\n\t\t\t}\n\t\t}()\n\n\t\tfor {\n\t\t\tchunkBytes := make([]byte, config.totalSize)\n\t\t\tchunkBytes = chunkBytes[:config.chunkSize]\n\t\t\tn, err := io.ReadFull(chunkReader, chunkBytes)\n\n\t\t\tif n > 0 {\n\t\t\t\tpeekData, _ := chunkReader.Peek(config.totalSize - n)\n\t\t\t\tchunkBytes = append(chunkBytes[:n], peekData...)\n\t\t\t}\n\n\t\t\t// If there is an error other than EOF, or if we have read some bytes, send the chunk.\n\t\t\t// io.ReadFull will only return io.EOF when n == 0.\n\t\t\tvar chunkRes ChunkResult\n\t\t\tswitch {\n\t\t\tcase isErrAndNotEOF(err):\n\t\t\t\tctx.Logger().Error(err, \"error reading chunk\")\n\t\t\t\tchunkRes = NewChunkResultError(err)\n\t\t\tcase n > 0:\n\t\t\t\tchunkRes = NewChunkResult(chunkBytes, n)\n\t\t\tdefault:\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase chunkResultChan <- chunkRes:\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\treturn chunkResultChan\n}\n\n// reportableErr checks whether the error is one we are interested in flagging.\nfunc isErrAndNotEOF(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\tif errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "pkg/sources/chunker_test.go",
    "content": "package sources\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"math/rand\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\t\"testing/iotest\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestNewChunkedReader(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\tinput      string\n\t\tchunkSize  int\n\t\tpeekSize   int\n\t\twantChunks []string\n\t\twantErr    bool\n\t}{\n\t\t{\n\t\t\tname:       \"Smaller data than default chunkSize and peekSize\",\n\t\t\tinput:      \"example input\",\n\t\t\tchunkSize:  DefaultChunkSize,\n\t\t\tpeekSize:   DefaultPeekSize,\n\t\t\twantChunks: []string{\"example input\"},\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname:       \"Reader with no data\",\n\t\t\tinput:      \"\",\n\t\t\tchunkSize:  1024,\n\t\t\tpeekSize:   512,\n\t\t\twantChunks: []string{},\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname:       \"Smaller data than chunkSize and peekSize\",\n\t\t\tinput:      \"small data\",\n\t\t\tchunkSize:  1024,\n\t\t\tpeekSize:   512,\n\t\t\twantChunks: []string{\"small data\"},\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname:       \"Equal to chunkSize\",\n\t\t\tinput:      strings.Repeat(\"a\", 1024),\n\t\t\tchunkSize:  1024,\n\t\t\tpeekSize:   512,\n\t\t\twantChunks: []string{strings.Repeat(\"a\", 1024)},\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname:       \"Equal to chunkSize + peekSize\",\n\t\t\tinput:      strings.Repeat(\"a\", 1536),\n\t\t\tchunkSize:  1024,\n\t\t\tpeekSize:   512,\n\t\t\twantChunks: []string{strings.Repeat(\"a\", 1536), strings.Repeat(\"a\", 512)},\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname:       \"EOF during peeking\",\n\t\t\tinput:      strings.Repeat(\"a\", 1300),\n\t\t\tchunkSize:  1024,\n\t\t\tpeekSize:   512,\n\t\t\twantChunks: []string{strings.Repeat(\"a\", 1300), strings.Repeat(\"a\", 276)},\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname:       \"EOF during reading\",\n\t\t\tinput:      strings.Repeat(\"a\", 512),\n\t\t\tchunkSize:  1024,\n\t\t\tpeekSize:   512,\n\t\t\twantChunks: []string{strings.Repeat(\"a\", 512)},\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname:       \"Equal to totalSize\",\n\t\t\tinput:      strings.Repeat(\"a\", 2048),\n\t\t\tchunkSize:  1024,\n\t\t\tpeekSize:   1024,\n\t\t\twantChunks: []string{strings.Repeat(\"a\", 2048), strings.Repeat(\"a\", 1024)},\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname:       \"Larger than totalSize\",\n\t\t\tinput:      strings.Repeat(\"a\", 4096),\n\t\t\tchunkSize:  1024,\n\t\t\tpeekSize:   1024,\n\t\t\twantChunks: []string{strings.Repeat(\"a\", 2048), strings.Repeat(\"a\", 2048), strings.Repeat(\"a\", 2048), strings.Repeat(\"a\", 1024)},\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname:       \"binary data - bin\",\n\t\t\tinput:      string(generateBinaryContent(\"bin\")),\n\t\t\tchunkSize:  DefaultChunkSize,\n\t\t\tpeekSize:   DefaultPeekSize,\n\t\t\twantChunks: []string{\"TuffleHog\"},\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname:       \"binary data - exe\",\n\t\t\tinput:      string(generateBinaryContent(\"exe\")),\n\t\t\tchunkSize:  DefaultChunkSize,\n\t\t\tpeekSize:   DefaultPeekSize,\n\t\t\twantChunks: []string{\"MZ\\x90\\x03\\x00\\x04\\x00\\xff\\x00\\xb8:\\xf2~\\x11]\\x9b\\xc8O\\xa1g0\\xeb\\x94,\\rzV\\x88\\xfa\\x19+\\xc3\\xd0nTuffleHog\\xab\\xcd8\\x04W\\xf1j\\x9e\\x03\\xd8A\\xb6/u\\xcc\\v\\x94\\xe7P8\\xad\\x1fc{\\x0e\\xf5)\\xc4m\\x82\\x10\"},\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname:       \"binary data - dmg\",\n\t\t\tinput:      string(generateBinaryContent(\"dmg\")),\n\t\t\tchunkSize:  DefaultChunkSize,\n\t\t\tpeekSize:   DefaultPeekSize,\n\t\t\twantChunks: []string{\"\\x00\\x00\\x00\\x00TruffleHog\\x00\\x00\\x00\\x00koly\"},\n\t\t\twantErr:    false,\n\t\t},\n\t\t{\n\t\t\tname:       \"binary data - tag.gz\",\n\t\t\tinput:      string(generateBinaryContent(\"tar.gz\")),\n\t\t\tchunkSize:  DefaultChunkSize,\n\t\t\tpeekSize:   DefaultPeekSize,\n\t\t\twantChunks: []string{\"\\x1f\\x8b\\bthis is binary content - trufflehog\\x00\\x00\\x00\\x00\"},\n\t\t\twantErr:    false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\treaderFunc := NewChunkReader(WithChunkSize(tt.chunkSize), WithPeekSize(tt.peekSize))\n\t\t\treader := strings.NewReader(tt.input)\n\t\t\tctx := context.Background()\n\t\t\tchunkResChan := readerFunc(ctx, reader)\n\n\t\t\tvar err error\n\t\t\tchunks := make([]string, 0)\n\t\t\tfor data := range chunkResChan {\n\t\t\t\tchunks = append(chunks, string(data.Bytes()))\n\t\t\t\terr = data.Error()\n\t\t\t}\n\n\t\t\tassert.Equal(t, tt.wantChunks, chunks, \"Chunks do not match\")\n\t\t\tif tt.wantErr {\n\t\t\t\tassert.Error(t, err, \"Expected an error\")\n\t\t\t} else {\n\t\t\t\tassert.NoError(t, err, \"Unexpected error\")\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype panicReader struct{}\n\nvar _ io.Reader = (*panicReader)(nil)\n\nfunc (_ panicReader) Read([]byte) (int, error) {\n\tpanic(\"panic for testing\")\n}\n\nfunc TestChunkReader_UnderlyingReaderPanics_DoesNotPanic(t *testing.T) {\n\trequire.NotPanics(t, func() {\n\t\tfor range NewChunkReader()(context.Background(), &panicReader{}) {\n\t\t}\n\t})\n}\n\nfunc BenchmarkChunkReader(b *testing.B) {\n\tvar bigChunk = make([]byte, 1<<24) // 16MB\n\n\treader := bytes.NewReader(bigChunk)\n\tchunkReader := NewChunkReader(WithChunkSize(DefaultChunkSize), WithPeekSize(DefaultPeekSize))\n\n\tb.ReportAllocs()\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tb.StartTimer()\n\t\tchunkResChan := chunkReader(context.Background(), reader)\n\n\t\t// Drain the channel.\n\t\tfor range chunkResChan {\n\t\t}\n\n\t\tb.StopTimer()\n\t\t_, err := reader.Seek(0, 0)\n\t\tassert.Nil(b, err)\n\t}\n}\n\nfunc TestFlakyChunkReader(t *testing.T) {\n\ta := \"aaaa\"\n\tb := \"bbbb\"\n\n\treader := iotest.OneByteReader(strings.NewReader(a + b))\n\n\tchunkReader := NewChunkReader()\n\tchunkResChan := chunkReader(context.TODO(), reader)\n\n\tvar chunks []ChunkResult\n\tfor chunk := range chunkResChan {\n\t\tchunks = append(chunks, chunk)\n\t}\n\n\tassert.Equal(t, 1, len(chunks))\n\tchunk := chunks[0]\n\tassert.NoError(t, chunk.Error())\n\tassert.Equal(t, a+b, string(chunk.Bytes()))\n}\n\nfunc TestReadInChunksWithCancellation(t *testing.T) {\n\tlargeData := strings.Repeat(\"large test data \", 1024*1024) // Large data string.\n\n\tfor i := 0; i < 10; i++ {\n\t\tinitialGoroutines := runtime.NumGoroutine()\n\n\t\tfor j := 0; j < 5; j++ { // Call readInChunks multiple times\n\t\t\tctx, cancel := context.WithCancel(context.Background())\n\n\t\t\treader := strings.NewReader(largeData)\n\t\t\tchunkReader := NewChunkReader()\n\n\t\t\tchunkChan := chunkReader(ctx, reader)\n\n\t\t\tif rand.Intn(2) == 0 { // Randomly decide to cancel the context\n\t\t\t\tcancel()\n\t\t\t} else {\n\t\t\t\tfor range chunkChan {\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Allow for goroutine finalization.\n\t\ttime.Sleep(time.Millisecond * 100)\n\n\t\t// Check for goroutine leaks.\n\t\tif runtime.NumGoroutine() > initialGoroutines {\n\t\t\tt.Error(\"Potential goroutine leak detected\")\n\t\t}\n\t}\n}\n\n// https://en.wikipedia.org/wiki/List_of_file_signatures\nfunc generateBinaryContent(contentType string) []byte {\n\tswitch contentType {\n\tcase \"tar.gz\":\n\t\treturn []byte{\n\t\t\t0x1F, 0x8B, 0x08, // GZIP magic + compression method\n\t\t\t0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20,\n\t\t\t0x62, 0x69, 0x6E, 0x61, 0x72, 0x79, 0x20, 0x63,\n\t\t\t0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x74, 0x20, 0x2D,\n\t\t\t0x20, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6C, 0x65,\n\t\t\t0x68, 0x6F, 0x67, 0x00, 0x00, 0x00, 0x00,\n\t\t}\n\tcase \"exe\":\n\t\treturn []byte{\n\t\t\t// https://superuser.com/questions/1334140/how-to-check-if-a-binary-is-16-bit-on-windows\n\t\t\t0x4D, 0x5A, // 'MZ' magic number for EXE\n\t\t\t0x90, 0x03, 0x00, 0x04, 0x00, 0xff, 0x00, 0xb8,\n\t\t\t0x3a, 0xf2, 0x7e, 0x11, 0x5d, 0x9b, 0xc8, 0x4f,\n\t\t\t0xa1, 0x67, 0x30, 0xeb, 0x94, 0x2c, 0x0d, 0x7a,\n\t\t\t0x56, 0x88, 0xfa, 0x19, 0x2b, 0xc3, 0xd0, 0x6e,\n\t\t\t0x54, 0x75, 0x66, 0x66, 0x6C, 0x65, 0x48, 0x6F,\n\t\t\t0x67, 0xab, 0xcd, 0x38, 0x04, 0x57, 0xf1, 0x6a,\n\t\t\t0x9e, 0x03, 0xd8, 0x41, 0xb6, 0x2f, 0x75, 0xcc,\n\t\t\t0x0b, 0x94, 0xe7, 0x50, 0x38, 0xad, 0x1f, 0x63,\n\t\t\t0x7b, 0x0e, 0xf5, 0x29, 0xc4, 0x6d, 0x82, 0x10,\n\t\t}\n\tcase \"bin\":\n\t\treturn []byte{0x54, 0x75, 0x66, 0x66, 0x6C, 0x65, 0x48, 0x6F, 0x67}\n\tcase \"dmg\":\n\t\treturn []byte{\n\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t0x54, 0x72, 0x75, 0x66, 0x66, 0x6C, 0x65, 0x48, 0x6F, 0x67,\n\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t0x6B, 0x6F, 0x6C, 0x79, // \"koly\" magic number for dmg\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/sources/circleci/circleci.go",
    "content": "package circleci\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/go-errors/errors\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/log\"\n\t\"golang.org/x/sync/errgroup\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nconst (\n\tSourceType = sourcespb.SourceType_SOURCE_TYPE_CIRCLECI\n\n\t// CircleCI has released API v2, but we're continuing to use v1.1\n\t// Because v1.1 still supports listing endpoints and remains officially supported.\n\tbaseURL = \"https://circleci.com/api/v1.1\"\n)\n\ntype Source struct {\n\tname     string\n\ttoken    string\n\tsourceId sources.SourceID\n\tjobId    sources.JobID\n\tverify   bool\n\tjobPool  *errgroup.Group\n\tsources.Progress\n\tclient *http.Client\n\tsources.CommonSourceUnitUnmarshaller\n}\n\n// CircleCI API Response types\ntype project struct {\n\tVCS      string `json:\"vcs_type\"`\n\tUsername string `json:\"username\"`\n\tReponame string `json:\"reponame\"`\n\tVcsUrl   string `json:\"vcs_url\"`\n}\n\ntype BuildNum int\n\ntype buildJobs struct {\n\tCircleYaml struct {\n\t\tYamlString string `json:\"string\"`\n\t} `json:\"circle_yml\"`\n\tSteps []buildStep `json:\"steps\"`\n}\n\ntype buildStep struct {\n\tName    string   `json:\"name\"`\n\tActions []action `json:\"actions\"`\n}\n\ntype action struct {\n\tOutputUrl string `json:\"output_url\"`\n}\n\n// Ensure the Source satisfies the interfaces at compile time.\nvar _ sources.Source = (*Source)(nil)\nvar _ sources.SourceUnitUnmarshaller = (*Source)(nil)\n\n// Type returns the type of source.\n// It is used for matching source types in configuration and job input.\nfunc (s *Source) Type() sourcespb.SourceType {\n\treturn SourceType\n}\n\nfunc (s *Source) SourceID() sources.SourceID {\n\treturn s.sourceId\n}\n\nfunc (s *Source) JobID() sources.JobID {\n\treturn s.jobId\n}\n\n// Init returns an initialized CircleCI source.\nfunc (s *Source) Init(_ context.Context, name string, jobId sources.JobID, sourceId sources.SourceID, verify bool, connection *anypb.Any, concurrency int) error {\n\ts.name = name\n\ts.sourceId = sourceId\n\ts.jobId = jobId\n\ts.verify = verify\n\ts.jobPool = &errgroup.Group{}\n\ts.jobPool.SetLimit(concurrency)\n\ts.client = common.RetryableHTTPClientTimeout(3)\n\n\tvar conn sourcespb.CircleCI\n\tif err := anypb.UnmarshalTo(connection, &conn, proto.UnmarshalOptions{}); err != nil {\n\t\treturn errors.WrapPrefix(err, \"error unmarshalling connection\", 0)\n\t}\n\n\tswitch conn.Credential.(type) {\n\tcase *sourcespb.CircleCI_Token:\n\t\ts.token = conn.GetToken()\n\t\tlog.RedactGlobally(s.token)\n\t}\n\n\treturn nil\n}\n\n// Chunks emits chunks of bytes over a channel.\nfunc (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, _ ...sources.ChunkingTarget) error {\n\t// TODO: list Artifacts, list checkout-keys, list env-variables\n\n\tprojects, err := s.listAllProjects(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting projects: %w\", err)\n\t}\n\n\tvar countOfProjectsScanned atomic.Uint64\n\tvar errors []error\n\n\tfor _, project := range projects {\n\t\tctx = context.WithValues(ctx,\n\t\t\t\"repository_name\", project.Reponame,\n\t\t)\n\n\t\ts.jobPool.Go(func() error {\n\t\t\tprojectBuilds, err := s.listProjectBuilds(ctx, &project)\n\t\t\tif err != nil {\n\t\t\t\tctx.Logger().Error(err, \"error getting builds for project\")\n\t\t\t\terrors = append(errors, err)\n\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tfor _, buildNum := range projectBuilds {\n\t\t\t\tctx = context.WithValues(ctx,\n\t\t\t\t\t\"build_number\", buildNum,\n\t\t\t\t)\n\n\t\t\t\tprojBuildJobs, err := s.listProjBuildJobs(ctx, &project, buildNum)\n\t\t\t\tif err != nil {\n\t\t\t\t\tctx.Logger().Error(err, \"error getting steps for build\")\n\t\t\t\t\terrors = append(errors, err)\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tfor _, step := range projBuildJobs.Steps {\n\t\t\t\t\tfor _, action := range step.Actions {\n\t\t\t\t\t\tdata, err := s.getOutputUrlResponse(action.OutputUrl)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tctx.Logger().Error(err, \"error getting action output url response\")\n\t\t\t\t\t\t\terrors = append(errors, err)\n\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif err = s.chunk(ctx, project, buildNum, step.Name, data, chunksChan); err != nil {\n\t\t\t\t\t\t\tctx.Logger().Error(err, \"error chunking action\")\n\t\t\t\t\t\t\terrors = append(errors, err)\n\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif err = s.chunk(ctx, project, buildNum, \"\", projBuildJobs.CircleYaml.YamlString, chunksChan); err != nil {\n\t\t\t\t\tctx.Logger().Error(err, \"error chunking build yaml\")\n\t\t\t\t\terrors = append(errors, err)\n\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tscanCount := countOfProjectsScanned.Add(1)\n\t\t\ts.SetProgressComplete(\n\t\t\t\tint(scanCount),\n\t\t\t\tlen(projects),\n\t\t\t\tfmt.Sprintf(\"Scanned %d/%d projects\", scanCount, len(projects)),\n\t\t\t\t\"\", // this would be for resumption, but we're not currently using it\n\t\t\t)\n\n\t\t\tctx.Logger().V(2).Info(fmt.Sprintf(\"scanned %d/%d projects\", countOfProjectsScanned.Load(), len(projects)))\n\n\t\t\treturn nil\n\t\t})\n\t}\n\n\t_ = s.jobPool.Wait()\n\n\tif len(errors) > 0 {\n\t\tctx.Logger().Info(\"encountered errors during scanning\", \"count\", len(errors), \"errors\", errors)\n\t}\n\n\treturn nil\n}\n\n// listAllProjects lists all the projects the token can access\nfunc (s *Source) listAllProjects(ctx context.Context) ([]project, error) {\n\tctx.Logger().V(5).Info(\"listing projects\")\n\treq, err := http.NewRequest(http.MethodGet, fmt.Sprintf(\"%s/projects\", baseURL), http.NoBody)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq.Header.Set(\"Circle-Token\", s.token)\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\tresp, err := s.client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tvar projects []project\n\t\tif err := json.NewDecoder(resp.Body).Decode(&projects); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"cannot decode CircleCI projects JSON: %w\", err)\n\t\t}\n\n\t\tctx.Logger().V(2).Info(\"successfully listed projects\", \"project_count\", len(projects))\n\n\t\treturn projects, nil\n\tcase http.StatusUnauthorized:\n\t\treturn nil, fmt.Errorf(\"invalid credentials, status %d\", resp.StatusCode)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected status code: %d while listing projects\", resp.StatusCode)\n\t}\n}\n\nfunc (s *Source) listProjectBuilds(ctx context.Context, proj *project) ([]BuildNum, error) {\n\t// the vcs url is in format //circleci.com/org-id/proj-id, so we need to remove the // and .com to use it in the API\n\tparsedURL, err := url.Parse(proj.VcsUrl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvcsURL := strings.ReplaceAll(parsedURL.Host, \".com\", \"\") + parsedURL.Path // circleci/org-id/proj-id\n\n\t// update clean vcs url in the project for later use\n\tproj.VcsUrl = vcsURL\n\n\tctx.Logger().V(5).Info(\"listing project builds with URL\", \"repo_name\", proj.Reponame, \"vcs_url\", proj.VcsUrl)\n\n\treq, err := http.NewRequest(http.MethodGet, fmt.Sprintf(\"%s/project/%s\", baseURL, vcsURL), http.NoBody)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq.Header.Set(\"Circle-Token\", s.token)\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\tresp, err := s.client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tvar rawBuilds []map[string]interface{}\n\t\tif err := json.NewDecoder(resp.Body).Decode(&rawBuilds); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"cannot decode CircleCI project builds JSON: %w\", err)\n\t\t}\n\n\t\tbuildNums := make([]BuildNum, 0)\n\t\tfor _, item := range rawBuilds {\n\t\t\tif bn, ok := item[\"build_num\"].(float64); ok {\n\t\t\t\tbuildNums = append(buildNums, BuildNum(bn))\n\t\t\t}\n\t\t}\n\n\t\tctx.Logger().V(2).Info(\"successfully listed builds for project\", \"repo_name\", proj.Reponame, \"build_count\", len(buildNums))\n\n\t\treturn buildNums, nil\n\tcase http.StatusNotFound:\n\t\treturn nil, fmt.Errorf(\"no builds found for project: %s\", proj.Reponame)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected status code while fetching builds for project: %s\", proj.Reponame)\n\t}\n}\n\nfunc (s *Source) listProjBuildJobs(ctx context.Context, proj *project, buildNum BuildNum) (*buildJobs, error) {\n\tctx.Logger().V(5).Info(fmt.Sprintf(\"listing project: %s build: %d jobs\", proj.Reponame, buildNum))\n\t// /project/circleci/org-id/proj-id/1\n\treq, err := http.NewRequest(http.MethodGet, fmt.Sprintf(\"%s/project/%s/%d\", baseURL, proj.VcsUrl, buildNum), http.NoBody)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq.Header.Set(\"Circle-Token\", s.token)\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\tresp, err := s.client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\tvar buildResp buildJobs\n\t\tif err := json.NewDecoder(resp.Body).Decode(&buildResp); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"cannot decode CircleCI project build jobs JSON: %w\", err)\n\t\t}\n\n\t\tctx.Logger().V(2).Info(\"successfully listed project build jobs\", \"repo_name\", proj.Reponame, \"build_num\", buildNum)\n\n\t\treturn &buildResp, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected status code: %d while fetching project: %s build: %d jobs\", resp.StatusCode, proj.Reponame, buildNum)\n\t}\n}\n\nfunc (s *Source) getOutputUrlResponse(outputUrl string) (string, error) {\n\treq, err := http.NewRequest(http.MethodGet, outputUrl, nil)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tresp, err := s.client.Do(req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer func() {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tbodyBytes, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to read response body: %w\", err)\n\t}\n\n\treturn string(bodyBytes), nil\n}\n\nfunc (s *Source) chunk(ctx context.Context, proj project, buildNum BuildNum, stepName, chunkData string, chunksChan chan *sources.Chunk) error {\n\tlinkURL := fmt.Sprintf(\"https://app.circleci.com/pipelines/%s/%s/%s/%d\", proj.VCS, proj.Username, proj.Reponame, buildNum)\n\n\tchunkReader := sources.NewChunkReader()\n\tchunkResChan := chunkReader(ctx, strings.NewReader(chunkData))\n\tfor data := range chunkResChan {\n\t\tchunk := &sources.Chunk{\n\t\t\tSourceType: s.Type(),\n\t\t\tSourceName: s.name,\n\t\t\tSourceID:   s.SourceID(),\n\t\t\tJobID:      s.JobID(),\n\t\t\tData:       removeCircleSha1Line(data.Bytes()),\n\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Circleci{\n\t\t\t\t\tCircleci: &source_metadatapb.CircleCI{\n\t\t\t\t\t\tVcsType:     proj.VCS,\n\t\t\t\t\t\tUsername:    proj.Username,\n\t\t\t\t\t\tRepository:  proj.Reponame,\n\t\t\t\t\t\tBuildNumber: int64(buildNum),\n\t\t\t\t\t\tBuildStep:   stepName,\n\t\t\t\t\t\tLink:        linkURL,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tSourceVerify: s.verify,\n\t\t}\n\t\tif err := data.Error(); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := common.CancellableWrite(ctx, chunksChan, chunk); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc removeCircleSha1Line(input []byte) []byte {\n\t// Split the input slice into a slice of lines.\n\tlines := bytes.Split(input, []byte(\"\\n\"))\n\n\t// Iterate over the lines and add the ones that don't contain \"CIRCLE_SHA1=\" to the result slice.\n\tresult := make([][]byte, 0, len(lines))\n\tfor _, line := range lines {\n\t\tif !bytes.Contains(line, []byte(\"CIRCLE_SHA1=\")) {\n\t\t\tresult = append(result, line)\n\t\t}\n\t}\n\n\t// Join the lines in the result slice and return the resulting slice.\n\treturn bytes.Join(result, []byte(\"\\n\"))\n}\n"
  },
  {
    "path": "pkg/sources/circleci/circleci_test.go",
    "content": "package circleci\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nfunc TestSource_Scan(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*10)\n\tdefer cancel()\n\n\tsecret, err := common.GetSecret(ctx, \"trufflehog-testing\", \"test\")\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\t// Fix the chunks test, which might involve updating this token.\n\ttoken := secret.MustGetField(\"CIRCLECI_TOKEN\")\n\n\ttype init struct {\n\t\tname       string\n\t\tverify     bool\n\t\tconnection *sourcespb.CircleCI\n\t}\n\ttests := []struct {\n\t\tname                 string\n\t\tinit                 init\n\t\twantErr              bool\n\t\ttotalScannedProjects int32\n\t}{\n\t\t{\n\t\t\tname: \"scan all projects\",\n\t\t\tinit: init{\n\t\t\t\tname: \"trufflehog-test\",\n\t\t\t\tconnection: &sourcespb.CircleCI{\n\t\t\t\t\tCredential: &sourcespb.CircleCI_Token{\n\t\t\t\t\t\tToken: token,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantErr:              false,\n\t\t\ttotalScannedProjects: 2,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid token\",\n\t\t\tinit: init{\n\t\t\t\tname: \"invalid token test\",\n\t\t\t\tconnection: &sourcespb.CircleCI{\n\t\t\t\t\tCredential: &sourcespb.CircleCI_Token{\n\t\t\t\t\t\tToken: \"invalid-token\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantErr:              true,\n\t\t\ttotalScannedProjects: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"empty token\",\n\t\t\tinit: init{\n\t\t\t\tname: \"empty token test\",\n\t\t\t\tconnection: &sourcespb.CircleCI{\n\t\t\t\t\tCredential: &sourcespb.CircleCI_Token{\n\t\t\t\t\t\tToken: \"\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantErr:              true,\n\t\t\ttotalScannedProjects: 0,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Source{}\n\n\t\t\tconn, err := anypb.New(tt.init.connection)\n\t\t\tassert.NoError(t, err)\n\n\t\t\terr = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 5)\n\t\t\tassert.NoError(t, err)\n\n\t\t\tchunksCh := make(chan *sources.Chunk, 1000)\n\t\t\tchunkErr := s.Chunks(ctx, chunksCh)\n\t\t\tclose(chunksCh)\n\n\t\t\t// check error\n\t\t\tif tt.wantErr {\n\t\t\t\tassert.Error(t, chunkErr)\n\t\t\t} else {\n\t\t\t\tassert.NoError(t, chunkErr)\n\t\t\t}\n\n\t\t\t// check total count of projects scanned\n\t\t\tprogress := s.GetProgress()\n\t\t\tassert.Equal(t, tt.totalScannedProjects, progress.SectionsCompleted)\n\t\t})\n\t}\n}\n\n// additional test for edge cases\nfunc TestSource_EdgeCases(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\ttest func(t *testing.T)\n\t}{\n\t\t{\n\t\t\tname: \"nil connection\",\n\t\t\ttest: func(t *testing.T) {\n\t\t\t\ts := Source{}\n\t\t\t\terr := s.Init(context.Background(), \"test\", 0, 0, false, nil, 5)\n\t\t\t\tassert.Error(t, err)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"cancelled context\",\n\t\t\ttest: func(t *testing.T) {\n\t\t\t\tctx, cancel := context.WithCancel(context.Background())\n\t\t\t\tcancel() // cancel immediately\n\n\t\t\t\ts := Source{}\n\t\t\t\tconn, _ := anypb.New(&sourcespb.CircleCI{\n\t\t\t\t\tCredential: &sourcespb.CircleCI_Token{\n\t\t\t\t\t\tToken: \"test-token\",\n\t\t\t\t\t},\n\t\t\t\t})\n\n\t\t\t\terr := s.Init(ctx, \"test\", 0, 0, false, conn, 5)\n\t\t\t\tassert.NoError(t, err)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, tt.test)\n\t}\n}\n"
  },
  {
    "path": "pkg/sources/docker/README.md",
    "content": "# Docker Source\n\n## Overview\n\nThe Docker source enables TruffleHog to scan Docker images for secrets, credentials, and sensitive data. It supports scanning images from multiple sources including Docker registries, local Docker daemon, and tarball files.\n\n## Docker Fundamentals\n\n### What is Docker?\n\nDocker is a containerization platform that packages applications and their dependencies into isolated containers. A Docker image is a read-only template used to create containers, consisting of multiple layers stacked on top of each other.\n\n### Key Docker Terminology\n\n| Term | Description |\n|------|-------------|\n| **Image** | A read-only template containing application code, runtime, libraries, and dependencies |\n| **Layer** | Each modification to an image creates a new layer. Layers are stacked and merged to form the final image |\n| **Tag** | A label applied to an image (e.g., `latest`, `v1.0.0`) for version identification |\n| **Digest** | A SHA256 hash that uniquely identifies an image or layer |\n| **Registry** | A repository for storing and distributing Docker images (e.g., Docker Hub, Quay, GHCR) |\n| **Daemon** | The Docker service running on the host that manages containers and images |\n| **Tarball** | A compressed archive file containing an exported Docker image |\n| **History** | Metadata about how an image was built, including commands executed |\n\n## Features\n\n- **Multiple Image Sources**: Scan images from remote registries, local Docker daemon, or tarball files\n- **Layer-by-Layer Scanning**: Examines each layer independently for comprehensive coverage\n- **History Metadata Scanning**: Analyzes image build history for exposed secrets in commands\n- **Concurrent Processing**: Parallel layer scanning for improved performance\n- **Authentication Support**: Multiple authentication methods for private registries\n- **File Exclusion**: Configure patterns to skip specific files or directories\n- **Size Limits**: Automatically skips files exceeding 50MB to optimize performance\n- **Scan All Images Under a Namespace**: Enables automatic discovery and scanning of all container images under a specified namespace (organization or user) in supported registries such as Docker Hub, Quay, and GHCR. Users no longer need to manually list or specify individual image names. The system retrieves all public images within the namespace, and if a valid registry token is provided includes private images as well. This allows for large-scale, automated scanning across all repositories within an organization.\n\n## Configuration\n\n### Connection Types\n\nThe Docker source supports several image reference formats:\n\n```text\n// Remote registry (default)\n\"nginx:latest\"\n\"myregistry.com/myapp:v1.0.0\"\n\"gcr.io/project/image@sha256:abcd1234...\"\n\n// Local Docker daemon\n\"docker://nginx:latest\"\n\n// Tarball file\n\"file:///path/to/image.tar\"\n```\n\n### Authentication Methods\n\n#### 1. Unauthenticated (Public Images)\n\nFor public images that don't require authentication:\n\n**YAML Configuration:**\n```yaml\nsources:\n  - type: docker\n    name: public-images\n    docker:\n      unauthenticated: {}\n      images:\n        - nginx:latest\n        - alpine:3.18\n```\n\n**CLI Usage:**\n```bash\ntrufflehog docker --image nginx:latest\n```\n\n---\n\n#### 2. Basic Authentication\n\nFor private registries requiring username and password:\n\n**YAML Configuration:**\n```yaml\nsources:\n  - type: docker\n    name: private-registry\n    docker:\n      basic_auth:\n        username: myuser\n        password: mypassword\n      images:\n        - myregistry.com/private-image:latest\n        - myregistry.com/another-image:v1.0.0\n```\n\n**CLI Usage:**\n\nTrufflehog does not provide basic authentication using username and password through CLI at the moment.\n\n---\n\n#### 3. Bearer Token\n\nFor registries using token-based authentication (e.g., Dockerhub registry):\n\n**YAML Configuration:**\n```yaml\nsources:\n  - type: docker\n    name: truffle-packages\n    docker:\n      bearer_token: \"ghp_xxxxxxxxxxxxxxxxxxxx\"\n      images:\n        - myorg/myapp:latest\n        - myorg/frontend:v2.1.0\n```\n\n**CLI Usage:**\n```bash\ntrufflehog docker --image myorg/myapp:latest --bearer-token eyJ_xxxxxxxxxxxxxxxxxxxx\n```\n\n---\n\n#### 4. Docker Keychain\n\nUses credentials from your local Docker configuration (`~/.docker/config.json`):\n\n**YAML Configuration:**\n```yaml\nsources:\n  - type: docker\n    name: local-docker-creds\n    docker:\n      docker_keychain: true\n      images:\n        - myregistry.com/private-image:latest\n        - docker.io/myorg/app:latest\n```\n\n**CLI Usage:**\n```bash\n# First, authenticate with Docker\ndocker login myregistry.com\n\n# Then scan using stored credentials\ntrufflehog docker --image myregistry.com/private-image:latest\n```\n\n**Prerequisites:**\n```bash\n# Authenticate with your registry first\ndocker login\ndocker login ghcr.io\ndocker login quay.io\n\n# Verify credentials are stored\ncat ~/.docker/config.json\n```\n\n---\n\n### Namespace Scanning (This feature is currently in beta version and under testing)\n\nTo scan **all images** under a namespace (organization or user):\n\n**CLI Usage:**\n```bash\n# If no registry prefix is provided, Docker Hub is used by default\ntrufflehog docker --namespace myorg\n\n# For other registries, include the registry prefix (e.g., quay.io, ghcr.io)\ntrufflehog docker --namespace quay.io/my_namespace\n```\n\nTo include private images within that namespace:\n```bash\ntrufflehog docker --namespace myorg --registry-token <access_token>\n```\n\n**YAML Configuration:**\n```yaml\nsources:\n  - type: docker\n    name: org-scan\n    docker:\n      namespace: myorg\n      registry_token: \"ghp_xxxxxxxxxxxxxxxxxxxx\"\n```\n\nSupported registries:\n- Docker Hub (`docker.io`)\n- Quay (`quay.io`)\n- GitHub Container Registry (`ghcr.io`)\n\nThis mode automatically enumerates all repositories within the specified namespace before scanning.\n\nNote: According to the GHCR documentation, only GitHub Classic Personal Access Tokens (PATs) are currently supported for accessing container packages - including public ones.\nSource: [GitHub Roadmap Issue #558](https://github.com/github/roadmap/issues/558)\n\n---\n\n### File Exclusion\n\nExclude specific files or directories from scanning using glob patterns:\n\n```bash\ntrufflehog docker --image myregistry.com/private-image:latest --exclude-paths **/*.log\n```\n\n## How Image Scanning Works\n\n### Scanning Process\n\n1. **Image Retrieval**: Fetches the image from the specified source (registry, daemon, or file)\n2. **History Scanning**: Extracts and scans image configuration history for secrets in build commands\n3. **Layer Processing**: Iterates through each layer in parallel\n4. **File Extraction**: Decompresses and extracts files from each layer\n5. **Content Scanning**: Analyzes file contents for secrets and credentials\n6. **Chunk Generation**: Emits chunks of data to the detection engine\n\n### What Gets Scanned\n\n- **Layer Contents**: All files within each image layer\n- **Build History**: Commands used to build the image (FROM, RUN, ENV, etc.)\n- **Configuration**: Environment variables and labels\n- **Metadata**: Image annotations and custom metadata\n\n### What Doesn't Get Scanned\n\n- Files larger than 50MB (configurable limit)\n- Files matching exclude patterns\n- Empty layers (no content changes)\n\n## Usage Examples\n\n### Scanning a Public Image\n\n```bash\ntrufflehog docker --image nginx:latest\n```\n\n### Scanning All Images Under a Namespace (Beta Version)\n\n```bash\ntrufflehog docker --namespace trufflesecurity\n```\n\nIncluding private images:\n\n```bash\ntrufflehog docker --namespace trufflesecurity --registry-token ghp_xxxxxxxxxxxxxxxxxxxx\n```\n\n### Scanning Multiple Images\n\n```bash\ntrufflehog docker --image nginx:latest --image postgres:13 --image redis:alpine\n```\n\n### Scanning from Local Docker Daemon\n\n```bash\ntrufflehog docker --image docker://myapp:local\n```\n\n### Scanning a Tarball\n\n```bash\ndocker save myapp:latest -o myapp.tar\ntrufflehog docker --image file:///path/to/myapp.tar\n```\n\n### Scanning Private Registry with Authentication\n\n```bash\ndocker login my-registry.io\ntrufflehog docker --image my-registry.io/private-app:v1.0.0\n```\n\n## Testing Results\n\n| Test Case | Status | Command/Configuration | Registry URL | Notes |\n|-----------|--------|----------------------|--------------|-------|\n| Scan remote image on DockerHub | ✅ Success | `--image <image_name>` | https://hub.docker.com/ | Public images work without authentication |\n| Scan specific tag of image on DockerHub | ✅ Success | `--image <image_name>:<tag_name>` | https://hub.docker.com/ | Tag specification working correctly |\n| Scan all images under namespace | In Progress | `--namespace <namespace>` | DockerHub, Quay, GHCR | Automatically discovers all public images |\n| Scan remote image on Quay.io | ✅ Success | `--image quay.io/prometheus/prometheus` | https://quay.io/search | Public Quay.io registry supported |\n| Scan multiple images | ✅ Success | `--image <image_name> --image <image_name>` | Multiple registries | Sequential scanning of multiple images |\n| Scan remote image on DockerHub with token | ✅ Success | `--token <token>`(Generate token using username and password) | https://hub.docker.com/ | Authenticated scanning for private repos |\n| Scan private image on Quay | ⏸️ Halted | N/A | https://quay.io/ | RedHat requires paid account for private repos |\n| Scan private image on GHCR | ✅ Success | `--image ghcr.io/<image_name>` | https://github.com/packages | GitHub Container Registry |\n\n## Troubleshooting\n\n### Common Issues\n\n**Issue**: Authentication failures with private registries\n**Solution**: Ensure credentials are correct and have pull permissions. Use `docker login` first when using Docker Keychain.\n\n---\n\n**Issue**: Out of memory errors with large images\n**Solution**: Reduce concurrency or scan smaller images. Consider increasing available memory.\n\n---\n\n**Issue**: Slow scanning performance\n**Solution**: Enable concurrent processing, use local daemon instead of remote registry, or exclude unnecessary directories.\n\n---\n\n**Issue**: Files not being scanned\n**Solution**: Check exclude patterns and file size limits. Verify files are under 50MB.\n"
  },
  {
    "path": "pkg/sources/docker/docker.go",
    "content": "package docker\n\nimport (\n\t\"archive/tar\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/google/go-containerregistry/pkg/authn\"\n\t\"github.com/google/go-containerregistry/pkg/name\"\n\tv1 \"github.com/google/go-containerregistry/pkg/v1\"\n\t\"github.com/google/go-containerregistry/pkg/v1/daemon\"\n\t\"github.com/google/go-containerregistry/pkg/v1/remote\"\n\t\"github.com/google/go-containerregistry/pkg/v1/tarball\"\n\tgzip \"github.com/klauspost/pgzip\"\n\t\"golang.org/x/sync/errgroup\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common/glob\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nconst SourceType = sourcespb.SourceType_SOURCE_TYPE_DOCKER\n\ntype Source struct {\n\tname        string\n\tsourceId    sources.SourceID\n\tjobId       sources.JobID\n\tverify      bool\n\tconcurrency int\n\tconn        sourcespb.Docker\n\tglobFilter  *glob.Filter // Filter for excluding files based on glob patterns\n\tsources.Progress\n\tsources.CommonSourceUnitUnmarshaller\n}\n\n// Ensure the Source satisfies the interfaces at compile time.\nvar _ sources.Source = (*Source)(nil)\nvar _ sources.SourceUnitUnmarshaller = (*Source)(nil)\n\n// Type returns the type of source.\n// It is used for matching source types in configuration and job input.\nfunc (s *Source) Type() sourcespb.SourceType {\n\treturn SourceType\n}\n\nfunc (s *Source) SourceID() sources.SourceID {\n\treturn s.sourceId\n}\n\nfunc (s *Source) JobID() sources.JobID {\n\treturn s.jobId\n}\n\n// Init initializes the source.\nfunc (s *Source) Init(ctx context.Context, name string, jobId sources.JobID, sourceId sources.SourceID, verify bool, connection *anypb.Any, concurrency int) error {\n\ts.name = name\n\ts.sourceId = sourceId\n\ts.jobId = jobId\n\ts.verify = verify\n\ts.concurrency = concurrency\n\n\tjobIDStr := fmt.Sprint(s.jobId)\n\n\t// Reset metrics for this source at initialization time.\n\tdockerImagesScanned.WithLabelValues(s.name, jobIDStr).Set(0)\n\tdockerLayersScanned.WithLabelValues(s.name, jobIDStr).Set(0)\n\tdockerLayersEnumerated.WithLabelValues(s.name, jobIDStr).Set(0)\n\tdockerHistoryEntriesEnumerated.WithLabelValues(s.name, jobIDStr).Set(0)\n\tdockerImagesEnumerated.WithLabelValues(s.name, jobIDStr).Set(0)\n\tdockerHistoryEntriesScanned.WithLabelValues(s.name, jobIDStr).Set(0)\n\n\tif err := anypb.UnmarshalTo(connection, &s.conn, proto.UnmarshalOptions{}); err != nil {\n\t\treturn fmt.Errorf(\"error unmarshalling connection: %w\", err)\n\t}\n\n\t// Extract exclude paths from connection and compile glob patterns\n\tif paths := s.conn.GetExcludePaths(); len(paths) > 0 {\n\t\tvar err error\n\t\ts.globFilter, err = glob.NewGlobFilter(glob.WithExcludeGlobs(paths...))\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error creating glob filter for exclude paths: %w\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// imageInfo holds information about a Docker image being processed\ntype imageInfo struct {\n\timage v1.Image // The image object from go-containerregistry\n\tbase  string   // Base name of the image (without tag/digest)\n\ttag   string   // Tag or digest of the image\n}\n\n// historyEntryInfo represents a single entry from the image's build history\ntype historyEntryInfo struct {\n\tindex       int        // Position in the history array\n\tentry       v1.History // The history entry containing build commands\n\tlayerDigest string     // SHA256 digest of the associated layer (empty for empty layers)\n\tbase        string     // Base name of the image\n\ttag         string     // Tag or digest of the image\n}\n\n// layerInfo contains identifying information for a Docker image layer\ntype layerInfo struct {\n\tdigest v1.Hash // SHA256 digest uniquely identifying the layer\n\tbase   string  // Base name of the image\n\ttag    string  // Tag or digest of the image\n}\n\n// Chunks emits data over a channel that is decoded and scanned for secrets.\nfunc (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, _ ...sources.ChunkingTarget) error {\n\tctx = context.WithValues(ctx, \"source_type\", s.Type(), \"source_name\", s.name)\n\tjobIDStr := fmt.Sprint(s.jobId)\n\n\tworkers := new(errgroup.Group)\n\tworkers.SetLimit(s.concurrency)\n\n\t// if namespace is set and no images are specified, fetch all images in that namespace.\n\tregistryNamespace := s.conn.GetNamespace()\n\tif registryNamespace != \"\" && len(s.conn.Images) == 0 {\n\t\tstart := time.Now()\n\t\tnamespaceImages, err := GetNamespaceImages(ctx, registryNamespace, s.conn.GetRegistryToken())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to list namespace: %s images: %w\", registryNamespace, err)\n\t\t}\n\n\t\tdockerListImagesAPIDuration.WithLabelValues(s.name).Observe(time.Since(start).Seconds())\n\n\t\ts.conn.Images = append(s.conn.Images, namespaceImages...)\n\t}\n\n\tfor _, image := range s.conn.GetImages() {\n\t\tif common.IsDone(ctx) {\n\t\t\treturn nil\n\t\t}\n\n\t\timgInfo, err := s.processImage(ctx, image)\n\t\tif err != nil {\n\t\t\tctx.Logger().Error(err, \"error processing image\", \"image\", image)\n\t\t\tcontinue\n\t\t}\n\n\t\timageCtx := context.WithValues(ctx, \"image\", imgInfo.base, \"tag\", imgInfo.tag)\n\n\t\timageCtx.Logger().V(2).Info(\"scanning image history\")\n\n\t\tlayers, err := imgInfo.image.Layers()\n\t\tif err != nil {\n\t\t\timageCtx.Logger().Error(err, \"error getting image layers\")\n\t\t\tcontinue\n\t\t}\n\t\tdockerLayersEnumerated.WithLabelValues(s.name, jobIDStr).Add(float64(len(layers)))\n\n\t\t// Get history entries and associate them with layers\n\t\thistoryEntries, err := getHistoryEntries(imageCtx, imgInfo, layers)\n\t\tif err != nil {\n\t\t\timageCtx.Logger().Error(err, \"error getting image history entries\")\n\t\t\tcontinue\n\t\t}\n\t\tdockerHistoryEntriesEnumerated.WithLabelValues(s.name, jobIDStr).Add(float64(len(historyEntries)))\n\n\t\t// Scan each history entry for secrets in build commands\n\t\tfor _, historyEntry := range historyEntries {\n\t\t\tif err := s.processHistoryEntry(imageCtx, historyEntry, chunksChan); err != nil {\n\t\t\t\timageCtx.Logger().Error(err, \"error processing history entry\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdockerHistoryEntriesScanned.WithLabelValues(s.name, jobIDStr).Inc()\n\t\t}\n\n\t\timageCtx.Logger().V(2).Info(\"scanning image layers\")\n\n\t\t// Process each layer concurrently\n\t\tfor _, layer := range layers {\n\t\t\tworkers.Go(func() error {\n\t\t\t\tif err := s.processLayer(imageCtx, layer, imgInfo, chunksChan); err != nil {\n\t\t\t\t\timageCtx.Logger().Error(err, \"error processing layer\")\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tdockerLayersScanned.WithLabelValues(s.name, jobIDStr).Inc()\n\n\t\t\t\treturn nil\n\t\t\t})\n\t\t}\n\n\t\tif err := workers.Wait(); err != nil {\n\t\t\timageCtx.Logger().Error(err, \"error processing layers\")\n\t\t\tcontinue\n\t\t}\n\n\t\tdockerImagesScanned.WithLabelValues(s.name, jobIDStr).Inc()\n\t}\n\n\treturn nil\n}\n\n// processImage processes an individual image and prepares it for further processing.\n// It handles three image source types: remote registry, local daemon, and tarball file.\nfunc (s *Source) processImage(ctx context.Context, image string) (imageInfo, error) {\n\tctx.Logger().V(5).Info(\"Processing individual Image\")\n\tvar (\n\t\timgInfo   imageInfo\n\t\timageName name.Reference\n\t\terr       error\n\t)\n\n\tremoteOpts, err := s.remoteOpts()\n\tif err != nil {\n\t\treturn imgInfo, err\n\t}\n\n\tconst filePrefix = \"file://\"\n\tconst dockerPrefix = \"docker://\"\n\n\t// Handle tarball file images\n\tif image, ok := strings.CutPrefix(image, filePrefix); ok {\n\t\timgInfo.base = image\n\t\timgInfo.image, err = tarball.ImageFromPath(image, nil)\n\t\tif err != nil {\n\t\t\treturn imgInfo, err\n\t\t}\n\t\t// Handle local Docker daemon images\n\t} else if image, ok := strings.CutPrefix(image, dockerPrefix); ok {\n\t\timgInfo, imageName, err = s.extractImageNameTagDigest(image)\n\t\tif err != nil {\n\t\t\treturn imgInfo, err\n\t\t}\n\t\timgInfo.image, err = daemon.Image(imageName)\n\t\tif err != nil {\n\t\t\treturn imgInfo, err\n\t\t}\n\t\t// Handle remote registry images (default)\n\t} else {\n\t\timgInfo, imageName, err = s.extractImageNameTagDigest(image)\n\t\tif err != nil {\n\t\t\treturn imgInfo, err\n\t\t}\n\t\timgInfo.image, err = remote.Image(imageName, remoteOpts...)\n\t\tif err != nil {\n\t\t\treturn imgInfo, err\n\t\t}\n\t}\n\n\tctx.Logger().WithValues(\"image\", imgInfo.base, \"tag\", imgInfo.tag).V(2).Info(\"scanning image\")\n\n\treturn imgInfo, nil\n}\n\n// extractImageNameTagDigest parses the provided Docker image string and returns a name.Reference\n// representing either the image's tag or digest, and any error encountered during parsing.\nfunc (*Source) extractImageNameTagDigest(image string) (imageInfo, name.Reference, error) {\n\tvar (\n\t\thasDigest bool\n\t\timgInfo   imageInfo\n\t\timgName   name.Reference\n\t\terr       error\n\t)\n\timgInfo.base, imgInfo.tag, hasDigest = baseAndTagFromImage(image)\n\n\t// Parse as digest reference (e.g., nginx@sha256:abc123...)\n\tif hasDigest {\n\t\timgName, err = name.NewDigest(image)\n\t\t// Parse as tag reference (e.g., nginx:latest)\n\t} else {\n\t\timgName, err = name.NewTag(image)\n\t}\n\tif err != nil {\n\t\treturn imgInfo, imgName, err\n\t}\n\n\treturn imgInfo, imgName, nil\n}\n\n// getHistoryEntries collates an image's configuration history together with the\n// corresponding layer digests for any non-empty layers.\nfunc getHistoryEntries(ctx context.Context, imgInfo imageInfo, layers []v1.Layer) ([]historyEntryInfo, error) {\n\tctx.Logger().V(5).Info(\"Getting history entries\")\n\tconfig, err := imgInfo.image.ConfigFile()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\thistory := config.History\n\tentries := make([]historyEntryInfo, len(history))\n\n\tlayerIndex := 0\n\tfor historyIndex, entry := range history {\n\t\te := historyEntryInfo{\n\t\t\tbase:  imgInfo.base,\n\t\t\ttag:   imgInfo.tag,\n\t\t\tentry: entry,\n\t\t\tindex: historyIndex,\n\t\t}\n\n\t\t// Associate with a layer if possible. Some history entries don't create layers (e.g., ENV, LABEL)\n\t\t// Failing to associate won't affect scanning, just reduces traceability\n\t\tif !entry.EmptyLayer {\n\t\t\tif layerIndex < len(layers) {\n\t\t\t\tdigest, err := layers[layerIndex].Digest()\n\n\t\t\t\tif err == nil {\n\t\t\t\t\te.layerDigest = digest.String()\n\t\t\t\t} else {\n\t\t\t\t\tctx.Logger().Error(err, \"cannot associate layer with history entry: layer digest failed\",\n\t\t\t\t\t\t\"layerIndex\", layerIndex, \"historyIndex\", historyIndex)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tctx.Logger().V(2).Info(\"cannot associate layer with history entry: no correlated layer exists at this index\",\n\t\t\t\t\t\"layerIndex\", layerIndex, \"historyIndex\", historyIndex)\n\t\t\t}\n\n\t\t\tlayerIndex++\n\t\t}\n\n\t\tentries[historyIndex] = e\n\t}\n\n\treturn entries, nil\n}\n\n// processHistoryEntry processes a history entry from the image configuration metadata.\n// It scans the CreatedBy field which contains the command used to create that layer.\nfunc (s *Source) processHistoryEntry(ctx context.Context, historyInfo historyEntryInfo, chunksChan chan *sources.Chunk) error {\n\tctx.Logger().V(5).Info(\"Processing history entries\")\n\t// Create a descriptive identifier for this history entry\n\t// There's no file name here, so we use a synthetic path\n\tentryPath := fmt.Sprintf(\"image-metadata:history:%d:created-by\", historyInfo.index)\n\n\tchunk := &sources.Chunk{\n\t\tSourceType: s.Type(),\n\t\tSourceName: s.name,\n\t\tSourceID:   s.SourceID(),\n\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\tData: &source_metadatapb.MetaData_Docker{\n\t\t\t\tDocker: &source_metadatapb.Docker{\n\t\t\t\t\tFile:  entryPath,\n\t\t\t\t\tImage: historyInfo.base,\n\t\t\t\t\tTag:   historyInfo.tag,\n\t\t\t\t\tLayer: historyInfo.layerDigest,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tSourceVerify: s.verify,\n\t\tData:         []byte(historyInfo.entry.CreatedBy),\n\t}\n\n\tctx.Logger().V(2).Info(\"scanning image history entry\", \"index\", historyInfo.index, \"layer\", historyInfo.layerDigest)\n\n\treturn common.CancellableWrite(ctx, chunksChan, chunk)\n}\n\n// processLayer processes an individual layer of an image.\n// It decompresses the layer and extracts all files for scanning.\nfunc (s *Source) processLayer(ctx context.Context, layer v1.Layer, imgInfo imageInfo, chunksChan chan *sources.Chunk) error {\n\tctx.Logger().V(5).Info(\"Processing layer\")\n\n\tlayerInfo := layerInfo{\n\t\tbase: imgInfo.base,\n\t\ttag:  imgInfo.tag,\n\t}\n\n\tvar err error\n\tlayerInfo.digest, err = layer.Digest()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tctx.Logger().WithValues(\"layer\", layerInfo.digest.String()).V(2).Info(\"scanning layer\")\n\n\trc, err := layer.Compressed()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer rc.Close()\n\n\t// Configure parallel gzip decompression for better performance\n\tconst (\n\t\tdefaultBlockSize = 1 << 24 // 16MB blocks\n\t\tdefaultBlocks    = 8       // Process 8 blocks in parallel\n\t)\n\n\tgzipReader, err := gzip.NewReaderN(rc, defaultBlockSize, defaultBlocks)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer gzipReader.Close()\n\n\t// Layers are tar archives, so we read them as tar files\n\ttarReader := tar.NewReader(gzipReader)\n\tfor {\n\t\theader, err := tarReader.Next()\n\t\tif errors.Is(err, io.EOF) {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tinfo := chunkProcessingInfo{size: header.Size, name: header.Name, reader: tarReader, layer: layerInfo}\n\t\tif err := s.processChunk(ctx, info, chunksChan); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// chunkProcessingInfo holds information needed to process a single file from a layer\ntype chunkProcessingInfo struct {\n\tsize   int64     // Size of the file in bytes\n\tname   string    // Full path of the file within the layer\n\treader io.Reader // Reader for the file contents\n\tlayer  layerInfo // Information about the layer this file belongs to\n}\n\n// processChunk processes an individual chunk of a layer.\n// It applies file size limits and exclusion patterns before scanning.\nfunc (s *Source) processChunk(ctx context.Context, info chunkProcessingInfo, chunksChan chan *sources.Chunk) error {\n\t// Skip files that are too large to avoid memory issues\n\tconst filesizeLimitBytes int64 = 50 * 1024 * 1024 // 50MB\n\tif info.size > filesizeLimitBytes {\n\t\tctx.Logger().V(2).Info(\"skipping file: size exceeds max allowed\", \"file\", info.name, \"size\", info.size, \"limit\", filesizeLimitBytes)\n\t\treturn nil\n\t}\n\n\t// Check if the file matches any exclude patterns\n\tfilePath := \"/\" + info.name\n\tif s.isExcluded(ctx, filePath) {\n\t\treturn nil\n\t}\n\n\t// Read the file in chunks to handle large files efficiently\n\tchunkReader := sources.NewChunkReader(sources.WithFileSize(int(info.size)))\n\tchunkResChan := chunkReader(ctx, info.reader)\n\n\tfor data := range chunkResChan {\n\t\tif err := data.Error(); err != nil {\n\t\t\tctx.Logger().Error(err, \"error reading chunk.\")\n\t\t\tcontinue\n\t\t}\n\n\t\tchunk := &sources.Chunk{\n\t\t\tSourceType: s.Type(),\n\t\t\tSourceName: s.name,\n\t\t\tSourceID:   s.SourceID(),\n\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Docker{\n\t\t\t\t\tDocker: &source_metadatapb.Docker{\n\t\t\t\t\t\tFile:  \"/\" + info.name,\n\t\t\t\t\t\tImage: info.layer.base,\n\t\t\t\t\t\tTag:   info.layer.tag,\n\t\t\t\t\t\tLayer: info.layer.digest.String(),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tSourceVerify: s.verify,\n\t\t}\n\t\tchunk.Data = data.Bytes()\n\n\t\tif err := common.CancellableWrite(ctx, chunksChan, chunk); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// isExcluded checks if a given filePath should be excluded based on the configured glob patterns.\nfunc (s *Source) isExcluded(ctx context.Context, filePath string) bool {\n\tif s.globFilter == nil {\n\t\treturn false // No filter configured, so nothing is excluded\n\t}\n\n\t// ShouldInclude returns true if the file should be scanned (not excluded)\n\t// If it returns false, the file matches an exclude pattern\n\tisIncluded := s.globFilter.ShouldInclude(filePath)\n\n\tif !isIncluded {\n\t\tctx.Logger().V(2).Info(\"skipping file: matches an exclude pattern\", \"file\", filePath, \"configured_exclude_paths\", s.conn.GetExcludePaths())\n\t}\n\treturn !isIncluded\n}\n\n// remoteOpts configures the options for fetching images from remote registries.\n// It sets up HTTP transport and authentication based on the connection configuration.\nfunc (s *Source) remoteOpts() ([]remote.Option, error) {\n\t// Configure HTTP transport with reasonable timeouts and connection pooling\n\tdefaultTransport := &http.Transport{\n\t\tProxy: http.ProxyFromEnvironment,\n\t\tDialContext: (&net.Dialer{\n\t\t\tTimeout:   30 * time.Second,\n\t\t\tKeepAlive: 30 * time.Second,\n\t\t}).DialContext,\n\t\tForceAttemptHTTP2:     true,\n\t\tMaxIdleConns:          s.concurrency * 4,\n\t\tIdleConnTimeout:       90 * time.Second,\n\t\tTLSHandshakeTimeout:   10 * time.Second,\n\t\tExpectContinueTimeout: 1 * time.Second,\n\t\tMaxIdleConnsPerHost:   s.concurrency * 2,\n\t}\n\n\tvar opts []remote.Option\n\topts = append(opts, remote.WithTransport(common.NewInstrumentedTransport(common.NewCustomTransport(defaultTransport))))\n\n\t// Configure authentication based on credential type\n\tswitch s.conn.GetCredential().(type) {\n\tcase *sourcespb.Docker_Unauthenticated:\n\t\treturn nil, nil\n\tcase *sourcespb.Docker_BasicAuth:\n\t\topts = append(opts, remote.WithAuth(&authn.Basic{\n\t\t\tUsername: s.conn.GetBasicAuth().GetUsername(),\n\t\t\tPassword: s.conn.GetBasicAuth().GetPassword(),\n\t\t}))\n\tcase *sourcespb.Docker_BearerToken:\n\t\topts = append(opts, remote.WithAuth(&authn.Bearer{\n\t\t\tToken: s.conn.GetBearerToken(),\n\t\t}))\n\tcase *sourcespb.Docker_DockerKeychain:\n\t\t// Use credentials from ~/.docker/config.json\n\t\topts = append(opts, remote.WithAuthFromKeychain(authn.DefaultKeychain))\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown credential type: %T\", s.conn.Credential)\n\t}\n\n\treturn opts, nil\n}\n\nfunc GetNamespaceImages(ctx context.Context, namespace, registryToken string) ([]string, error) {\n\tctx.Logger().V(5).Info(\"Getting namespace images\")\n\n\tregistry := MakeRegistryFromNamespace(namespace)\n\n\t// attach the registry authentication token, if one is available.\n\tif registryToken != \"\" {\n\t\tregistry.WithRegistryToken(registryToken)\n\t}\n\n\tctx.Logger().Info(fmt.Sprintf(\"using registry: %s\", registry.Name()))\n\n\tnamespaceImages, err := registry.ListImages(ctx, namespace)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to list namespace images: %w\", err)\n\t}\n\n\tctx.Logger().Info(fmt.Sprintf(\"namespace: %s has %d images\", namespace, len(namespaceImages)))\n\n\treturn namespaceImages, nil\n}\n\n// baseAndTagFromImage extracts the base name and tag/digest from an image reference string.\n// It handles both digest-based references (image@sha256:...) and tag-based references (image:tag).\nfunc baseAndTagFromImage(image string) (base, tag string, hasDigest bool) {\n\tif base, tag, hasDigest = extractDigest(image); hasDigest {\n\t\treturn base, tag, true\n\t}\n\n\tbase, tag = extractTagOrUseDefault(image)\n\treturn base, tag, false\n}\n\n// extractDigest tries to split the image string on the digest delimiter (@).\n// If successful, it means the image has a digest reference.\nfunc extractDigest(image string) (base, tag string, hasDigest bool) {\n\tconst digestDelim = \"@\"\n\n\tif parts := strings.SplitN(image, digestDelim, 2); len(parts) > 1 {\n\t\treturn parts[0], parts[1], true\n\t}\n\treturn \"\", \"\", false\n}\n\n// extractTagOrUseDefault extracts the tag from the image string.\n// If no tag is found, it defaults to \"latest\".\nfunc extractTagOrUseDefault(image string) (base, tag string) {\n\tconst (\n\t\ttagDelim     = \":\"\n\t\tregRepoDelim = \"/\"\n\t)\n\n\tparts := strings.Split(image, tagDelim)\n\n\t// Check if the last part is a tag (not a hostname with port)\n\t// We use a weak validation: if it contains \"/\" it's likely part of a registry hostname\n\tif len(parts) > 1 && !strings.Contains(parts[len(parts)-1], regRepoDelim) {\n\t\treturn strings.Join(parts[:len(parts)-1], tagDelim), parts[len(parts)-1]\n\t}\n\treturn image, \"latest\"\n}\n"
  },
  {
    "path": "pkg/sources/docker/docker_test.go",
    "content": "package docker\n\nimport (\n\t\"io\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\n\timage \"github.com/docker/docker/api/types/image\"\n\tdockerClient \"github.com/docker/docker/client\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/credentialspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nfunc TestDockerImageScan(t *testing.T) {\n\tdockerConn := &sourcespb.Docker{\n\t\tCredential: &sourcespb.Docker_Unauthenticated{\n\t\t\tUnauthenticated: &credentialspb.Unauthenticated{},\n\t\t},\n\t\tImages: []string{\"trufflesecurity/secrets\"},\n\t}\n\n\tconn := &anypb.Any{}\n\terr := conn.MarshalFrom(dockerConn)\n\tassert.NoError(t, err)\n\n\ts := &Source{}\n\terr = s.Init(context.TODO(), \"test source\", 0, 0, false, conn, 1)\n\tassert.NoError(t, err)\n\n\tvar wg sync.WaitGroup\n\tchunksChan := make(chan *sources.Chunk, 1)\n\tchunkCounter := 0\n\tlayerCounter := 0\n\thistoryCounter := 0\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor chunk := range chunksChan {\n\t\t\tassert.NotEmpty(t, chunk)\n\t\t\tchunkCounter++\n\n\t\t\tif isHistoryChunk(t, chunk) {\n\t\t\t\thistoryCounter++\n\t\t\t} else {\n\t\t\t\tlayerCounter++\n\t\t\t}\n\t\t}\n\t}()\n\n\terr = s.Chunks(context.TODO(), chunksChan)\n\tassert.NoError(t, err)\n\n\tclose(chunksChan)\n\twg.Wait()\n\n\tassert.Equal(t, 2, chunkCounter)\n\tassert.Equal(t, 1, layerCounter)\n\tassert.Equal(t, 1, historyCounter)\n}\n\nfunc TestQuayRegistry(t *testing.T) {\n\tdockerConn := &sourcespb.Docker{\n\t\tCredential: &sourcespb.Docker_Unauthenticated{\n\t\t\tUnauthenticated: &credentialspb.Unauthenticated{},\n\t\t},\n\t\tImages: []string{\"quay.io/prometheus/node-exporter@sha256:337ff1d356b68d39cef853e8c6345de11ce7556bb34cda8bd205bcf2ed30b565\"},\n\t}\n\n\tconn := &anypb.Any{}\n\terr := conn.MarshalFrom(dockerConn)\n\tassert.NoError(t, err)\n\n\ts := &Source{}\n\terr = s.Init(context.TODO(), \"test source\", 0, 0, false, conn, 1)\n\tassert.NoError(t, err)\n\n\tvar wg sync.WaitGroup\n\tchunksChan := make(chan *sources.Chunk, 1)\n\tchunkCounter := 0\n\tlayerCounter := 0\n\thistoryCounter := 0\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor chunk := range chunksChan {\n\t\t\tassert.NotEmpty(t, chunk)\n\t\t\tchunkCounter++\n\n\t\t\tif isHistoryChunk(t, chunk) {\n\t\t\t\thistoryCounter++\n\t\t\t} else {\n\t\t\t\tlayerCounter++\n\t\t\t}\n\t\t}\n\t}()\n\n\terr = s.Chunks(context.TODO(), chunksChan)\n\tassert.NoError(t, err)\n\n\tclose(chunksChan)\n\twg.Wait()\n\n\tassert.Equal(t, 1302, chunkCounter)\n\tassert.Equal(t, 1291, layerCounter)\n\tassert.Equal(t, 11, historyCounter)\n}\n\nfunc TestGHCRRegistry(t *testing.T) {\n\tdockerConn := &sourcespb.Docker{\n\t\tCredential: &sourcespb.Docker_Unauthenticated{\n\t\t\tUnauthenticated: &credentialspb.Unauthenticated{},\n\t\t},\n\t\tImages: []string{\"ghcr.io/trufflesecurity/trufflehog:3.0.0-rc0-amd64\"}, // https://github.com/trufflesecurity/trufflehog/pkgs/container/trufflehog\n\t}\n\n\tconn := &anypb.Any{}\n\terr := conn.MarshalFrom(dockerConn)\n\tassert.NoError(t, err)\n\n\ts := &Source{}\n\terr = s.Init(context.TODO(), \"test source\", 0, 0, false, conn, 1)\n\tassert.NoError(t, err)\n\n\tvar wg sync.WaitGroup\n\tchunksChan := make(chan *sources.Chunk, 1)\n\tchunkCounter := 0\n\tlayerCounter := 0\n\thistoryCounter := 0\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor chunk := range chunksChan {\n\t\t\tassert.NotEmpty(t, chunk)\n\t\t\tchunkCounter++\n\n\t\t\tif isHistoryChunk(t, chunk) {\n\t\t\t\thistoryCounter++\n\t\t\t} else {\n\t\t\t\tlayerCounter++\n\t\t\t}\n\t\t}\n\t}()\n\n\terr = s.Chunks(context.TODO(), chunksChan)\n\tassert.NoError(t, err)\n\n\tclose(chunksChan)\n\twg.Wait()\n\n\tassert.Equal(t, 714, chunkCounter)\n\tassert.Equal(t, 709, layerCounter)\n\tassert.Equal(t, 5, historyCounter)\n}\n\nfunc TestDockerImageScanWithDigest(t *testing.T) {\n\tdockerConn := &sourcespb.Docker{\n\t\tCredential: &sourcespb.Docker_Unauthenticated{\n\t\t\tUnauthenticated: &credentialspb.Unauthenticated{},\n\t\t},\n\t\tImages: []string{\"trufflesecurity/secrets@sha256:864f6d41209462d8e37fc302ba1532656e265f7c361f11e29fed6ca1f4208e11\"},\n\t}\n\n\tconn := &anypb.Any{}\n\terr := conn.MarshalFrom(dockerConn)\n\tassert.NoError(t, err)\n\n\ts := &Source{}\n\terr = s.Init(context.TODO(), \"test source\", 0, 0, false, conn, 1)\n\tassert.NoError(t, err)\n\n\tvar wg sync.WaitGroup\n\tchunksChan := make(chan *sources.Chunk, 1)\n\tchunkCounter := 0\n\tlayerCounter := 0\n\thistoryCounter := 0\n\n\tvar historyChunk *source_metadatapb.Docker\n\tvar layerChunk *source_metadatapb.Docker\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor chunk := range chunksChan {\n\t\t\tassert.NotEmpty(t, chunk)\n\t\t\tchunkCounter++\n\n\t\t\tif isHistoryChunk(t, chunk) {\n\t\t\t\t// save last for later comparison\n\t\t\t\thistoryChunk = chunk.SourceMetadata.GetDocker()\n\t\t\t\thistoryCounter++\n\t\t\t} else {\n\t\t\t\tlayerChunk = chunk.SourceMetadata.GetDocker()\n\t\t\t\tlayerCounter++\n\t\t\t}\n\t\t}\n\t}()\n\n\terr = s.Chunks(context.TODO(), chunksChan)\n\tassert.NoError(t, err)\n\n\tclose(chunksChan)\n\twg.Wait()\n\n\t// Since this test pins the layer by digest, layers will have consistent\n\t// hashes. This allows layer digest comparison as they will be stable for\n\t// given image digest.\n\tassert.Equal(t, &source_metadatapb.Docker{\n\t\tImage: \"trufflesecurity/secrets\",\n\t\tTag:   \"sha256:864f6d41209462d8e37fc302ba1532656e265f7c361f11e29fed6ca1f4208e11\",\n\t\tFile:  \"image-metadata:history:0:created-by\",\n\t\tLayer: \"sha256:a794864de8c4ff087813fd66cff74601b84cbef8fe1a1f17f9923b40cf051b59\",\n\t}, historyChunk)\n\n\tassert.Equal(t, &source_metadatapb.Docker{\n\t\tImage: \"trufflesecurity/secrets\",\n\t\tTag:   \"sha256:864f6d41209462d8e37fc302ba1532656e265f7c361f11e29fed6ca1f4208e11\",\n\t\tFile:  \"/aws\",\n\t\tLayer: \"sha256:a794864de8c4ff087813fd66cff74601b84cbef8fe1a1f17f9923b40cf051b59\",\n\t}, layerChunk)\n\n\tassert.Equal(t, 2, chunkCounter)\n\tassert.Equal(t, 1, layerCounter)\n\tassert.Equal(t, 1, historyCounter)\n}\n\nfunc TestDockerImageScanFromLocalDaemon(t *testing.T) {\n\tdockerDaemonTestCases := []struct {\n\t\tname  string\n\t\timage string\n\t}{\n\t\t{\n\t\t\tname:  \"TestDockerImageScanFromLocalDaemon\",\n\t\t\timage: \"docker://trufflesecurity/secrets\",\n\t\t},\n\t\t{\n\t\t\tname:  \"TestDockerImageScanFromLocalDaemonWithDigest\",\n\t\t\timage: \"docker://trufflesecurity/secrets@sha256:864f6d41209462d8e37fc302ba1532656e265f7c361f11e29fed6ca1f4208e11\",\n\t\t},\n\t\t{\n\t\t\tname:  \"TestDockerImageScanFromLocalDaemonWithTag\",\n\t\t\timage: \"docker://trufflesecurity/secrets:latest\",\n\t\t},\n\t}\n\n\t// pull the image here to ensure it exists locally\n\timg := \"docker.io/trufflesecurity/secrets:latest\"\n\n\tclient, err := dockerClient.NewClientWithOpts(dockerClient.FromEnv, dockerClient.WithAPIVersionNegotiation())\n\tif err != nil {\n\t\tt.Errorf(\"Failed to create Docker client: %v\", err)\n\t\treturn\n\t}\n\n\tresp, err := client.ImagePull(context.TODO(), img, image.PullOptions{})\n\tif err != nil {\n\t\tt.Errorf(\"Failed to load image %s: %v\", img, err)\n\t\treturn\n\t}\n\n\tdefer resp.Close()\n\n\t// if we don't read the response, the image will not be available in the local Docker daemon\n\t_, err = io.ReadAll(resp)\n\tif err != nil {\n\t\tt.Errorf(\"Failed to read response body: %v\", err)\n\t}\n\n\tfor _, tt := range dockerDaemonTestCases {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// This test assumes the local Docker daemon is running\n\t\t\tdockerConn := &sourcespb.Docker{\n\t\t\t\tCredential: &sourcespb.Docker_Unauthenticated{\n\t\t\t\t\tUnauthenticated: &credentialspb.Unauthenticated{},\n\t\t\t\t},\n\t\t\t\tImages: []string{tt.image},\n\t\t\t}\n\n\t\t\tconn := &anypb.Any{}\n\t\t\terr = conn.MarshalFrom(dockerConn)\n\t\t\tassert.NoError(t, err)\n\n\t\t\ts := &Source{}\n\t\t\terr = s.Init(context.TODO(), \"test source\", 0, 0, false, conn, 1)\n\t\t\tassert.NoError(t, err)\n\n\t\t\tvar wg sync.WaitGroup\n\t\t\tchunksChan := make(chan *sources.Chunk, 1)\n\t\t\tchunkCounter := 0\n\t\t\tlayerCounter := 0\n\t\t\thistoryCounter := 0\n\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tfor chunk := range chunksChan {\n\t\t\t\t\tassert.NotEmpty(t, chunk)\n\t\t\t\t\tchunkCounter++\n\n\t\t\t\t\tif isHistoryChunk(t, chunk) {\n\t\t\t\t\t\thistoryCounter++\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlayerCounter++\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\terr = s.Chunks(context.TODO(), chunksChan)\n\t\t\tassert.NoError(t, err)\n\n\t\t\tclose(chunksChan)\n\t\t\twg.Wait()\n\n\t\t\tassert.Equal(t, 2, chunkCounter)\n\t\t\tassert.Equal(t, 1, layerCounter)\n\t\t\tassert.Equal(t, 1, historyCounter)\n\t\t})\n\t}\n}\n\nfunc TestBaseAndTagFromImage(t *testing.T) {\n\ttests := []struct {\n\t\timage      string\n\t\twantBase   string\n\t\twantTag    string\n\t\twantDigest bool\n\t}{\n\t\t{\"golang:1.16\", \"golang\", \"1.16\", false},\n\t\t{\"golang@sha256:abcdef\", \"golang\", \"sha256:abcdef\", true},\n\t\t{\"ghcr.io/golang:1.16\", \"ghcr.io/golang\", \"1.16\", false},\n\t\t{\"ghcr.io/golang:nightly\", \"ghcr.io/golang\", \"nightly\", false},\n\t\t{\"ghcr.io/golang\", \"ghcr.io/golang\", \"latest\", false},\n\t\t{\"ghcr.io/trufflesecurity/secrets\", \"ghcr.io/trufflesecurity/secrets\", \"latest\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tgotBase, gotTag, gotDigest := baseAndTagFromImage(tt.image)\n\t\tif gotBase != tt.wantBase || gotTag != tt.wantTag || gotDigest != tt.wantDigest {\n\t\t\tt.Errorf(\"baseAndTagFromImage(%q) = (%q, %q, %v), want (%q, %q, %v)\",\n\t\t\t\ttt.image, gotBase, gotTag, gotDigest, tt.wantBase, tt.wantTag, tt.wantDigest)\n\t\t}\n\t}\n}\n\nfunc isHistoryChunk(t *testing.T, chunk *sources.Chunk) bool {\n\tt.Helper()\n\n\tmetadata := chunk.SourceMetadata.GetDocker()\n\n\treturn metadata != nil &&\n\t\tstrings.HasPrefix(metadata.File, \"image-metadata:history:\")\n}\n\nfunc TestDockerScanWithExclusions(t *testing.T) {\n\tdockerConn := &sourcespb.Docker{\n\t\tCredential: &sourcespb.Docker_Unauthenticated{\n\t\t\tUnauthenticated: &credentialspb.Unauthenticated{},\n\t\t},\n\t\tImages:       []string{\"trufflesecurity/secrets@sha256:864f6d41209462d8e37fc302ba1532656e265f7c361f11e29fed6ca1f4208e11\"},\n\t\tExcludePaths: []string{\"/aws\", \"/gcp*\", \"/exactmatch\"},\n\t}\n\n\tconn := &anypb.Any{}\n\terr := conn.MarshalFrom(dockerConn)\n\tassert.NoError(t, err)\n\n\ts := &Source{}\n\terr = s.Init(context.TODO(), \"test source\", 0, 0, false, conn, 1)\n\tassert.NoError(t, err)\n\n\t// Test cases for exclusion logic\n\ttestCases := []struct {\n\t\tname     string\n\t\tpath     string\n\t\texpected bool\n\t}{\n\t\t{\"excluded_exact\", \"/aws\", true},\n\t\t{\"excluded_wildcard\", \"/gcp/something\", true},\n\t\t{\"excluded_exact_match_file\", \"/exactmatch\", true},\n\t\t{\"not_excluded\", \"/azure\", false},\n\t\t{\"gcp_root_should_be_excluded_by_gcp_star\", \"/gcp\", true},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tassert.Equal(t, tc.expected, s.isExcluded(context.TODO(), tc.path))\n\t\t})\n\t}\n\n\t// Keep the original test structure to ensure Chunks processing respects exclusions\n\tvar wg sync.WaitGroup\n\tchunksChan := make(chan *sources.Chunk, 1)\n\tfoundExcludedPath := false\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor chunk := range chunksChan {\n\t\t\t// Skip history chunks\n\t\t\tif isHistoryChunk(t, chunk) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmetadata := chunk.SourceMetadata.GetDocker()\n\t\t\tassert.NotNil(t, metadata)\n\n\t\t\t// Check if we found a chunk with the excluded path\n\t\t\tif metadata.File == \"/aws\" {\n\t\t\t\tfoundExcludedPath = true\n\t\t\t}\n\t\t}\n\t}()\n\n\terr = s.Chunks(context.TODO(), chunksChan)\n\tassert.NoError(t, err)\n\n\tclose(chunksChan)\n\twg.Wait()\n\n\tassert.False(t, foundExcludedPath, \"Found a chunk that should have been excluded\")\n}\n"
  },
  {
    "path": "pkg/sources/docker/metrics.go",
    "content": "package docker\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\nvar (\n\tdockerLayersScanned = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"docker_layers_scanned\",\n\t\tHelp:      \"Total number of Docker layers scanned.\",\n\t},\n\t\t[]string{\"source_name\", \"job_id\"})\n\n\tdockerLayersEnumerated = promauto.NewGaugeVec(\n\t\tprometheus.GaugeOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"docker_layers_enumerated\",\n\t\t\tHelp:      \"Total number of Docker layers enumerated.\",\n\t\t},\n\t\t[]string{\"source_name\", \"job_id\"})\n\n\tdockerHistoryEntriesScanned = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"docker_history_entries_scanned\",\n\t\tHelp:      \"Total number of Docker image history entries scanned.\",\n\t},\n\t\t[]string{\"source_name\", \"job_id\"})\n\n\tdockerHistoryEntriesEnumerated = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"docker_history_entries_enumerated\",\n\t\tHelp:      \"Total number of Docker history entries enumerated.\",\n\t},\n\t\t[]string{\"source_name\", \"job_id\"})\n\n\tdockerImagesScanned = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"docker_images_scanned\",\n\t\tHelp:      \"Total number of Docker images scanned.\",\n\t},\n\t\t[]string{\"source_name\", \"job_id\"})\n\n\tdockerImagesEnumerated = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"docker_images_enumerated\",\n\t\tHelp:      \"Total number of Docker images enumerated.\",\n\t},\n\t\t[]string{\"source_name\", \"job_id\"})\n\n\tdockerListImagesAPIDuration = promauto.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"docker_list_images_api_duration_seconds\",\n\t\t\tHelp:      \"Duration of Docker list images API calls.\",\n\t\t\tBuckets:   prometheus.DefBuckets,\n\t\t},\n\t\t[]string{\"source_name\"})\n)\n"
  },
  {
    "path": "pkg/sources/docker/registries.go",
    "content": "package docker\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\n\t\"golang.org/x/time/rate\"\n)\n\n// defaultHTTPClient defines a shared HTTP client with timeout for all registry requests.\nvar defaultHTTPClient = common.RetryableHTTPClientTimeout(10)\n\n// registryRateLimiter limits how quickly we make registry API calls across all registries.\n// We allow roughly 1 event every 1.5s, with a burst of 2 as a simple safeguard against overloading upstream APIs.\nvar registryRateLimiter = rate.NewLimiter(rate.Limit(2.0/3.0), 2)\n\n// maxRegistryPageSize defines the maximum number of images to request per page from a registry API.\nconst maxRegistryPageSize = 100\n\n// Image represents a container image or repository entry in a registry API response.\ntype Image struct {\n\tName string `json:\"name\"`\n}\n\n// Registry is an interface for any Docker/OCI registry implementation that can list all images under a given namespace.\ntype Registry interface {\n\tName() string                                                       // return name of the registry\n\tWithRegistryToken(registryToken string)                             // set token for registry\n\tListImages(ctx context.Context, namespace string) ([]string, error) // list all images\n\tWithClient(client *http.Client)                                     // return the HTTP client to use\n}\n\n// MakeRegistryFromNamespace returns a Registry implementation\n// based on the namespace prefix (e.g. \"ghcr.io/\", \"quay.io/\").\n// If no known prefix is found, DockerHub is used by default.\nfunc MakeRegistryFromNamespace(namespace string) Registry {\n\tvar registry Registry\n\tswitch {\n\tcase strings.HasPrefix(namespace, \"quay.io/\"): // quay.io/abc123\n\t\tregistry = &Quay{}\n\tcase strings.HasPrefix(namespace, \"ghcr.io/\"): // ghcr.io/abc123\n\t\tregistry = &GHCR{}\n\tdefault: // default is dockerhub\n\t\tregistry = &DockerHub{}\n\t}\n\n\treturn registry\n}\n\n// === Docker Hub Registry ===\n\n// DockerHub implements the Registry interface for hub.docker.com.\ntype DockerHub struct {\n\tToken  string\n\tClient *http.Client\n}\n\n// dockerhubResp models Docker Hub's /v2/namespaces/<ns>/repositories API response.\ntype dockerhubResp struct {\n\tNext    string  `json:\"next\"`\n\tResults []Image `json:\"results\"`\n}\n\nfunc (d *DockerHub) Name() string {\n\treturn \"Dockerhub\"\n}\n\nfunc (d *DockerHub) WithRegistryToken(registryToken string) {\n\td.Token = registryToken\n}\n\nfunc (d *DockerHub) WithClient(client *http.Client) {\n\td.Client = client\n}\n\n// ListImages lists all images under a Docker Hub namespace using Docker Hub's API.\n//\n// We fetch images in fixed-size pages and keep following the \"next\" link until there\n// are no more pages. A shared rate limiter is used so we don't accidentally hammer\n// the Docker Hub API.\nfunc (d *DockerHub) ListImages(ctx context.Context, namespace string) ([]string, error) {\n\tbaseURL := &url.URL{\n\t\tScheme: \"https\",\n\t\tHost:   \"hub.docker.com\",\n\t\tPath:   path.Join(\"v2\", \"namespaces\", namespace, \"repositories\"),\n\t}\n\n\tquery := baseURL.Query()\n\tquery.Set(\"page_size\", fmt.Sprint(maxRegistryPageSize))\n\tbaseURL.RawQuery = query.Encode()\n\n\tallImages := []string{}\n\tnextURL := baseURL.String()\n\n\tfor {\n\t\tif err := registryRateLimiter.Wait(ctx); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, nextURL, http.NoBody)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif d.Token != \"\" {\n\t\t\treq.Header.Set(\"Authorization\", \"Bearer \"+d.Token)\n\t\t}\n\n\t\tclient := d.Client\n\t\tif client == nil {\n\t\t\tclient = defaultHTTPClient\n\t\t}\n\t\tresp, err := client.Do(req)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tdiscardBody(resp)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\treturn nil, fmt.Errorf(\"failed to list dockerhub images: unexpected status code: %d\", resp.StatusCode)\n\t\t}\n\n\t\tvar page dockerhubResp\n\t\tif err := json.Unmarshal(body, &page); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor _, image := range page.Results {\n\t\t\tallImages = append(allImages, fmt.Sprintf(\"%s/%s\", namespace, image.Name)) // <namespace>/<image_name>\n\t\t}\n\n\t\tif page.Next == \"\" {\n\t\t\tbreak\n\t\t}\n\n\t\t// Docker Hub sometimes returns an absolute \"next\" URL and sometimes a\n\t\t// relative one. ResolveReference cleans that up for us and turns whatever\n\t\t// they send into a proper URL we can call.\n\t\tnext, err := url.Parse(page.Next)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tnextURL = baseURL.ResolveReference(next).String()\n\t}\n\n\treturn allImages, nil\n}\n\n// === Red Hat Quay Registry ===\n\n// Quay implements the Registry interface for Quay.io.\ntype Quay struct {\n\tToken  string\n\tClient *http.Client\n}\n\n// quayResp models the JSON structure returned by Quay's /api/v1/repository endpoint.\ntype quayResp struct {\n\tRepositories  []Image `json:\"repositories\"`\n\tHasAdditional bool    `json:\"has_additional\"`\n\tNextPage      string  `json:\"next_page\"`\n}\n\nfunc (q *Quay) Name() string {\n\treturn \"Quay.io\"\n}\n\nfunc (q *Quay) WithRegistryToken(registryToken string) {\n\tq.Token = registryToken\n}\n\nfunc (q *Quay) WithClient(client *http.Client) {\n\tq.Client = client\n}\n\n// ListImages lists all images under a Quay namespace.\n// API reference:\n//\n//\tGET https://quay.io/api/v1/repository?namespace=<namespace>&public=true&private=true&next_page=<token>\n//\n// We keep following next_page while has_additional is true.\nfunc (q *Quay) ListImages(ctx context.Context, namespace string) ([]string, error) {\n\tquayNamespace := path.Base(namespace) // quay.io/<namespace> -> namespace\n\tbaseURL := &url.URL{\n\t\tScheme: \"https\",\n\t\tHost:   \"quay.io\",\n\t\tPath:   path.Join(\"api\", \"v1\", \"repository\"),\n\t}\n\n\tallImages := []string{}\n\tnextPageToken := \"\"\n\n\tfor {\n\t\tif err := registryRateLimiter.Wait(ctx); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tu := *baseURL\n\t\tquery := u.Query()\n\t\tquery.Set(\"namespace\", quayNamespace)\n\t\tquery.Set(\"public\", \"true\")\n\t\tquery.Set(\"private\", \"true\")\n\n\t\t// Quay's API controls page size internally; we just fetch page by page.\n\t\tif nextPageToken != \"\" {\n\t\t\tquery.Set(\"next_page\", nextPageToken)\n\t\t}\n\t\tu.RawQuery = query.Encode()\n\n\t\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), http.NoBody)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif q.Token != \"\" {\n\t\t\treq.Header.Set(\"Authorization\", \"Bearer \"+q.Token)\n\t\t}\n\n\t\tclient := q.Client\n\t\tif client == nil {\n\t\t\tclient = defaultHTTPClient\n\t\t}\n\t\tresp, err := client.Do(req)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tdiscardBody(resp)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\treturn nil, fmt.Errorf(\"failed to list quay images: unexpected status code: %d\", resp.StatusCode)\n\t\t}\n\n\t\tvar page quayResp\n\t\tif err := json.Unmarshal(body, &page); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor _, image := range page.Repositories {\n\t\t\tallImages = append(allImages, fmt.Sprintf(\"%s/%s\", namespace, image.Name)) // quay.io/<namespace>/<image_name>\n\t\t}\n\n\t\tif !page.HasAdditional || page.NextPage == \"\" {\n\t\t\tbreak\n\t\t}\n\t\tnextPageToken = page.NextPage\n\t}\n\n\treturn allImages, nil\n}\n\n// === GHCR Registry ===\n\n// GHCR implements the Registry interface for GHCR.io.\ntype GHCR struct {\n\tToken  string // https://github.com/github/roadmap/issues/558\n\tClient *http.Client\n}\n\nfunc (g *GHCR) Name() string {\n\treturn \"ghcr.io\"\n}\n\nfunc (g *GHCR) WithRegistryToken(registryToken string) {\n\tg.Token = registryToken\n}\n\nfunc (g *GHCR) WithClient(client *http.Client) {\n\tg.Client = client\n}\n\n// GHCR paginates results and includes pagination links in the HTTP Link header.\n// The Link header contains URLs for \"next\", \"prev\", \"first\", and \"last\" pages.\n// Example Link header:\n//\n// <https://api.github.com/user/abc/packages?package_type=container&per_page=100&page=2>; rel=\"next\",\n// <https://api.github.com/user/abc/packages?package_type=container&per_page=100&page=5>; rel=\"last\"\nfunc parseNextLinkURL(linkHeader string) string {\n\tif linkHeader == \"\" {\n\t\treturn \"\"\n\t}\n\n\tparts := strings.Split(linkHeader, \",\")\n\tfor _, part := range parts {\n\t\tsection := strings.Split(strings.TrimSpace(part), \";\")\n\t\tif len(section) < 2 {\n\t\t\tcontinue\n\t\t}\n\n\t\tlinkPart := strings.TrimSpace(section[0])\n\t\tif !strings.HasPrefix(linkPart, \"<\") || !strings.HasSuffix(linkPart, \">\") {\n\t\t\tcontinue\n\t\t}\n\t\turlStr := strings.Trim(linkPart, \"<>\")\n\n\t\trel := \"\"\n\t\tfor _, attr := range section[1:] {\n\t\t\tattr = strings.TrimSpace(attr)\n\t\t\tif strings.HasPrefix(attr, \"rel=\") {\n\t\t\t\trel = strings.Trim(strings.TrimPrefix(attr, \"rel=\"), \"\\\"\")\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif rel == \"next\" {\n\t\t\treturn urlStr\n\t\t}\n\t}\n\n\treturn \"\"\n}\n\n// ListImages lists all images under a GHCR namespace.\n// For GitHub Container Registry the package listing endpoint is:\n//\n//\tGET https://api.github.com/users/{namespace}/packages?package_type=container&per_page=100\n//\n// The GitHub API is paginated via the Link response header.\nfunc (g *GHCR) ListImages(ctx context.Context, namespace string) ([]string, error) {\n\tghcrNamespace := path.Base(namespace) // ghcr.io/<namespace> -> namespace\n\n\tbaseURL := &url.URL{\n\t\tScheme: \"https\",\n\t\tHost:   \"api.github.com\",\n\t\tPath:   path.Join(\"users\", ghcrNamespace, \"packages\"),\n\t}\n\n\tallImages := []string{}\n\tnextURL := func() string {\n\t\tu := *baseURL\n\t\tq := u.Query()\n\t\tq.Set(\"package_type\", \"container\")\n\t\tq.Set(\"per_page\", fmt.Sprint(maxRegistryPageSize)) // fetch images in batches of 100 per page\n\t\tu.RawQuery = q.Encode()\n\t\treturn u.String()\n\t}()\n\n\tfor nextURL != \"\" {\n\t\tif err := registryRateLimiter.Wait(ctx); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, nextURL, http.NoBody)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// https://stackoverflow.com/questions/72732582/using-github-packages-without-personal-access-token\n\t\tif g.Token != \"\" {\n\t\t\treq.Header.Set(\"Authorization\", \"Bearer \"+g.Token)\n\t\t}\n\t\t// GitHub recommends explicitly sending the v3 media type.\n\t\treq.Header.Set(\"Accept\", \"application/vnd.github+json\")\n\n\t\tclient := g.Client\n\t\tif client == nil {\n\t\t\tclient = defaultHTTPClient\n\t\t}\n\t\tresp, err := client.Do(req)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tdiscardBody(resp)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\treturn nil, fmt.Errorf(\"failed to list ghcr images: unexpected status code: %d\", resp.StatusCode)\n\t\t}\n\n\t\t// The GHCR packages list returns an array of package objects. We only\n\t\t// care about the \"name\" field at this layer, so reuse the Image struct.\n\t\tvar page []Image\n\t\tif err := json.Unmarshal(body, &page); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor _, image := range page {\n\t\t\tallImages = append(allImages, fmt.Sprintf(\"%s/%s\", namespace, image.Name)) // ghcr.io/<namespace>/<image_name>\n\t\t}\n\n\t\t// Determine if there's another page via the Link header.\n\t\tnextURL = parseNextLinkURL(resp.Header.Get(\"Link\"))\n\t}\n\n\treturn allImages, nil\n}\n\n// Function to discard response body\nfunc discardBody(resp *http.Response) {\n\tif resp != nil && resp.Body != nil {\n\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t_ = resp.Body.Close()\n\t}\n}\n"
  },
  {
    "path": "pkg/sources/docker/registries_test.go",
    "content": "package docker\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"slices\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nfunc TestDockerhubListImages(t *testing.T) {\n\t// Dockerhub registry\n\tdockerhub := MakeRegistryFromNamespace(\"trufflesecurity\") // no authentication\n\n\tdockerImages, err := dockerhub.ListImages(context.Background(), \"trufflesecurity\") // namespace without any prefix defaults to dockerhub registry\n\tassert.NoError(t, err)\n\tassert.Equal(t, len(dockerImages), 7)\n\n\tdockerExpectedImages := []string{\n\t\t\"trufflesecurity/trufflehog\", \"trufflesecurity/lint-robot\", \"trufflesecurity/protos\",\n\t\t\"trufflesecurity/driftwood\", \"trufflesecurity/secrets\", \"trufflesecurity/of-cors\", \"trufflesecurity/email-graffiti\",\n\t}\n\tslices.Sort(dockerImages)\n\tslices.Sort(dockerExpectedImages)\n\tassert.Equal(t, dockerImages, dockerExpectedImages)\n}\n\nfunc TestQuayListImages(t *testing.T) {\n\t// Quay.io registry\n\tquay := MakeRegistryFromNamespace(\"quay.io/truffledockerman\") // no authentication\n\n\tquayImages, err := quay.ListImages(context.Background(), \"quay.io/truffledockerman\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, len(quayImages), 2)\n\n\texpectedQuayImages := []string{\"quay.io/truffledockerman/test\", \"quay.io/truffledockerman/test2\"}\n\tslices.Sort(quayImages)\n\tslices.Sort(expectedQuayImages)\n\tassert.Equal(t, quayImages, expectedQuayImages)\n}\n\nfunc TestGHCRListImages(t *testing.T) {\n\tsecret, err := common.GetTestSecret(context.Background())\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\n\t// For the personal access token test\n\tgithubToken := secret.MustGetField(\"GITHUB_PACKAGES_TEST\")\n\n\tghcr := MakeRegistryFromNamespace(\"ghcr.io/mongodb\")\n\tghcr.WithRegistryToken(githubToken) // authentication is required for GHCR\n\n\tghcrImages, err := ghcr.ListImages(context.Background(), \"ghcr.io/mongodb\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, len(ghcrImages), 1)\n\n\tassert.Equal(t, ghcrImages, []string{\"ghcr.io/mongodb/kingfisher\"})\n}\n\nfunc TestDockerHubListImages_RateLimitError(t *testing.T) {\n\tt.Parallel()\n\n\t// Dockerhub registry\n\tdockerhub := MakeRegistryFromNamespace(\"trufflesecurity\") // no authentication\n\n\t// Cast dockerhub to *DockerHub registry to override the HTTP client\n\tdockerhub.WithClient(common.ConstantResponseHttpClient(http.StatusTooManyRequests, \"{}\"))\n\tdockerImages, err := dockerhub.ListImages(context.Background(), \"trufflesecurity\") // namespace without any prefix defaults to dockerhub registry\n\tassert.Error(t, err)\n\tassert.Nil(t, dockerImages)\n}\n\nfunc TestQuayListImages_RateLimitError(t *testing.T) {\n\tt.Parallel()\n\n\t// Quay.io registry\n\tquay := MakeRegistryFromNamespace(\"quay.io/truffledockerman\") // no authentication\n\t// Cast quay to *Quay registry to override the HTTP client\n\tquay.WithClient(common.ConstantResponseHttpClient(http.StatusTooManyRequests, \"{}\"))\n\n\tquayImages, err := quay.ListImages(context.Background(), \"quay.io/truffledockerman\")\n\tassert.Error(t, err)\n\tassert.Nil(t, quayImages)\n}\n\nfunc TestGHCRListImages_RateLimitError(t *testing.T) {\n\tt.Parallel()\n\n\t// GHCR registry\n\tghcr := MakeRegistryFromNamespace(\"ghcr.io/mongodb\") // no authentication\n\t// Cast ghcr to *GHCR registry to override the HTTP client\n\tghcr.WithClient(common.ConstantResponseHttpClient(http.StatusTooManyRequests, \"{}\"))\n\n\tghcrImages, err := ghcr.ListImages(context.Background(), \"ghcr.io/mongodb\")\n\tassert.Error(t, err)\n\tassert.Nil(t, ghcrImages)\n}\n"
  },
  {
    "path": "pkg/sources/elasticsearch/api.go",
    "content": "package elasticsearch\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tes \"github.com/elastic/go-elasticsearch/v8\"\n\t\"github.com/elastic/go-elasticsearch/v8/esapi\"\n)\n\nconst PAGE_SIZE = 10\n\ntype IndexStatus int\n\ntype FilterParams struct {\n\tindexPattern   string\n\tqueryJSON      string\n\tsinceTimestamp string\n}\n\ntype PointInTime struct {\n\tID        string `json:\"id\"`\n\tKeepAlive string `json:\"keep_alive\"`\n}\n\ntype SearchRequestBody struct {\n\tPIT         PointInTime    `json:\"pit\"`\n\tSort        []string       `json:\"sort\"`\n\tSearchAfter []int          `json:\"search_after,omitempty\"`\n\tQuery       map[string]any `json:\"query,omitempty\"`\n}\n\ntype Document struct {\n\tid        string\n\ttimestamp string\n\tmessage   string\n}\n\ntype Index struct {\n\tname                   string\n\tdocumentCount          int\n\tlatestTimestamp        time.Time\n\tlatestTimestampLastRun time.Time\n\tlatestDocumentIDs      []string\n\tlock                   sync.RWMutex\n}\n\ntype Indices struct {\n\tindices                 []*Index\n\tdocumentCount           int\n\tprocessedDocumentsCount int\n\tfilterParams            *FilterParams\n\tlock                    sync.RWMutex\n}\n\ntype elasticSearchRequest interface {\n\tDo(providedCtx context.Context, transport esapi.Transport) (*esapi.Response, error)\n}\n\nfunc (fp *FilterParams) Query(latestTimestamp time.Time) (map[string]any, error) {\n\ttimestampRangeQueryClause := make(map[string]any)\n\n\tif fp.queryJSON != \"\" {\n\t\terr := json.Unmarshal([]byte(fp.queryJSON), &timestampRangeQueryClause)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif !latestTimestamp.IsZero() {\n\t\tgte := make(map[string]string)\n\t\tgte[\"gte\"] = latestTimestamp.Format(time.RFC3339)\n\n\t\ttimestamp := make(map[string]map[string]string)\n\t\ttimestamp[\"@timestamp\"] = gte\n\n\t\ttimestampRangeQueryClause[\"range\"] = timestamp\n\t} else if fp.sinceTimestamp != \"\" {\n\t\tgte := make(map[string]string)\n\t\tgte[\"gte\"] = fp.sinceTimestamp\n\n\t\ttimestamp := make(map[string]map[string]string)\n\t\ttimestamp[\"@timestamp\"] = gte\n\n\t\ttimestampRangeQueryClause[\"range\"] = timestamp\n\t}\n\n\tquery := make(map[string]any)\n\tquery[\"query\"] = timestampRangeQueryClause\n\n\treturn query, nil\n}\n\nfunc NewIndex() *Index {\n\treturn &Index{}\n}\n\nfunc (i *Index) DocumentAlreadySeen(document *Document) bool {\n\tparsedTimestamp, err := time.Parse(time.RFC3339, document.timestamp)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\t// We mutate the index in different ways depending on whether the timestamp\n\t// is newer, equal, or older than the its current latest timestamp, so\n\t// everything at this point must be write synchronized.\n\ti.lock.Lock()\n\tdefer i.lock.Unlock()\n\n\tif parsedTimestamp.After(i.latestTimestamp) {\n\t\ti.latestTimestamp = parsedTimestamp\n\t\ti.latestDocumentIDs = i.latestDocumentIDs[:0]\n\t\treturn false\n\t}\n\n\tif i.latestTimestamp.Equal(i.latestTimestampLastRun) &&\n\t\tslices.Contains(i.latestDocumentIDs, document.id) {\n\t\treturn true\n\t}\n\n\ti.latestDocumentIDs = append(i.latestDocumentIDs, document.id)\n\treturn false\n}\n\nfunc (i *Index) UpdateLatestTimestampLastRun() {\n\ti.lock.Lock()\n\ti.latestTimestampLastRun = i.latestTimestamp\n\ti.lock.Unlock()\n}\n\nfunc makeElasticSearchRequest(\n\tctx context.Context,\n\ttransport esapi.Transport,\n\treq elasticSearchRequest,\n) (map[string]any, error) {\n\tres, err := req.Do(ctx, transport)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdefer res.Body.Close()\n\n\trawData, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdata := make(map[string]any)\n\n\terr = json.Unmarshal(rawData, &data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn data, nil\n}\n\nfunc fetchIndexNames(\n\tctx context.Context,\n\tclient *es.TypedClient,\n\tindexPattern string,\n) ([]string, error) {\n\treq := esapi.IndicesGetRequest{\n\t\tIndex: []string{indexPattern},\n\t}\n\n\tdata, err := makeElasticSearchRequest(ctx, client, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnames := make([]string, len(data))\n\tcount := 0\n\n\tfor indexName := range data {\n\t\tnames[count] = indexName\n\t\tcount++\n\t}\n\n\treturn names, nil\n}\n\nfunc fetchIndexDocumentCount(\n\tctx context.Context,\n\tclient *es.TypedClient,\n\tindexName string,\n\tquery map[string]any,\n) (int, error) {\n\tsize := 0\n\n\treq := esapi.SearchRequest{\n\t\tIndex:      []string{indexName},\n\t\tSearchType: \"query_then_fetch\",\n\t\tSize:       &size,\n\t}\n\n\tif len(query[\"query\"].(map[string]any)) > 0 {\n\t\tbody, err := json.MarshalIndent(query, \"\", \"  \")\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\treq.Body = strings.NewReader(string(body))\n\t}\n\n\tdata, err := makeElasticSearchRequest(ctx, client, req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\thits, ok := data[\"hits\"].(map[string]any)\n\tif !ok {\n\t\treturn 0, errors.New(\"No hits in response\")\n\t}\n\n\ttotal, ok := hits[\"total\"].(map[string]any)\n\tif !ok {\n\t\treturn 0, errors.New(\"No total in hits\")\n\t}\n\n\tcount, ok := total[\"value\"].(float64)\n\tif !ok {\n\t\treturn 0, errors.New(\"No value in total\")\n\t}\n\n\treturn int(count), nil\n}\n\nfunc createPITForSearch(\n\tctx context.Context,\n\tclient *es.TypedClient,\n\tdocSearch *DocumentSearch,\n) (string, error) {\n\treq := esapi.OpenPointInTimeRequest{\n\t\tIndex:     []string{docSearch.index.name},\n\t\tKeepAlive: \"1m\",\n\t}\n\n\tdata, err := makeElasticSearchRequest(ctx, client, req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tpitID, ok := data[\"id\"].(string)\n\tif !ok {\n\t\treturn \"\", errors.New(\"No id in response\")\n\t}\n\n\treturn pitID, nil\n}\n\n// Processes documents fetched by a search, returns the number of documents\n// fetched.\nfunc processSearchedDocuments(\n\tctx context.Context,\n\tclient *es.TypedClient,\n\tdocSearch *DocumentSearch,\n\tprocessDocument func(document *Document) error,\n) (int, error) {\n\tpitID, err := createPITForSearch(ctx, client, docSearch)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tdocumentsFetched := 0\n\tdocumentsProcessed := 0\n\tsort := []int{}\n\n\tfor documentsProcessed < docSearch.documentCount {\n\t\tsearchReqBody := SearchRequestBody{\n\t\t\tPIT: PointInTime{\n\t\t\t\tID:        pitID,\n\t\t\t\tKeepAlive: \"1m\",\n\t\t\t},\n\t\t\tSort: []string{\"_shard_doc\"},\n\t\t}\n\n\t\tquery, err := docSearch.filterParams.Query(docSearch.index.latestTimestamp)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\tsearchReqBody.Query = query[\"query\"].(map[string]any)\n\n\t\tif len(sort) > 0 {\n\t\t\tsearchReqBody.SearchAfter = sort\n\t\t}\n\n\t\tbody, err := json.MarshalIndent(searchReqBody, \"\", \"  \")\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\treq := esapi.SearchRequest{\n\t\t\tBody: strings.NewReader(string(body)),\n\t\t}\n\n\t\t// If we've yet to reach our offset, or if we're still in the \"skip\" phase\n\t\t// of scanning, don't actually fetch any document bodies.\n\t\tskipCount := docSearch.offset + docSearch.skipCount\n\t\tprocessingDocuments := documentsFetched+PAGE_SIZE > skipCount\n\t\tif processingDocuments {\n\t\t\treq.SourceIncludes = []string{\"@timestamp\", \"message\"}\n\t\t} else {\n\t\t\treq.SourceExcludes = []string{\"*\"}\n\t\t\treq.SearchType = \"query_then_fetch\"\n\t\t}\n\n\t\tsearchResults, err := makeElasticSearchRequest(ctx, client, req)\n\t\tif err != nil {\n\t\t\treturn 0, err\n\t\t}\n\n\t\ttopLevelHits, ok := searchResults[\"hits\"].(map[string]any)\n\t\tif !ok {\n\t\t\tapiErr, ok := searchResults[\"error\"].(map[string]any)\n\t\t\tif ok {\n\t\t\t\treturn 0, fmt.Errorf(\"Error fetching search results: %v\\n\", apiErr)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\thits, ok := topLevelHits[\"hits\"].([]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(hits) == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, jsonHit := range hits {\n\t\t\tdocumentsFetched++\n\n\t\t\thit, ok := jsonHit.(map[string]any)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tjsonSort, ok := hit[\"sort\"].([]any)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsort = sort[:0]\n\t\t\tfor _, elem := range jsonSort {\n\t\t\t\tsort = append(sort, int(elem.(float64)))\n\t\t\t}\n\n\t\t\tif documentsFetched <= skipCount {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tid, ok := hit[\"_id\"].(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsource, ok := hit[\"_source\"].(map[string]any)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttimestamp, ok := source[\"@timestamp\"].(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmessage, ok := source[\"message\"].(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdocument := Document{\n\t\t\t\tid:        id,\n\t\t\t\ttimestamp: timestamp,\n\t\t\t\tmessage:   message,\n\t\t\t}\n\n\t\t\tif err = processDocument(&document); err != nil {\n\t\t\t\treturn 0, nil\n\t\t\t}\n\n\t\t\tdocumentsProcessed++\n\t\t}\n\t}\n\n\treturn documentsProcessed, nil\n}\n\n// Returns the number of documents processed within these indices\nfunc (indices *Indices) GetProcessedDocumentCount() int {\n\tindices.lock.RLock()\n\tprocessedDocumentsCount := indices.processedDocumentsCount\n\tindices.lock.RUnlock()\n\n\treturn processedDocumentsCount\n}\n\n// Adds documents processed to the count, used for progress\nfunc (indices *Indices) UpdateProcessedDocumentCount(additionalDocumentsProcessed int) {\n\tindices.lock.Lock()\n\tindices.processedDocumentsCount += additionalDocumentsProcessed\n\tindices.lock.Unlock()\n}\n\n// Updates a set of indices from an Elasticsearch cluster. If an index has been\n// deleted it will be removed; if it's been added it'll be added; if its\n// document count has changed (based on filterParams and latestTimestamp) it'll\n// be updated.\nfunc (indices *Indices) Update(\n\tctx context.Context,\n\tclient *es.TypedClient,\n) error {\n\tindexNames, err := fetchIndexNames(ctx, client, indices.filterParams.indexPattern)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tindicesByName := make(map[string]*Index)\n\tif indices.indices != nil {\n\t\tfor _, index := range indices.indices {\n\t\t\tindicesByName[index.name] = index\n\t\t}\n\t}\n\n\tnewIndicesByName := make(map[string]*Index)\n\tfor _, name := range indexNames {\n\t\tindex, ok := indicesByName[name]\n\t\tif ok {\n\t\t\tnewIndicesByName[name] = index\n\t\t} else {\n\t\t\tindex = NewIndex()\n\t\t\tindex.name = name\n\t\t\tnewIndicesByName[name] = index\n\t\t}\n\t}\n\n\tfor _, indexName := range indexNames {\n\t\t// This can't be an index we don't know about because we passed indexNames\n\t\tindex := newIndicesByName[indexName]\n\n\t\tquery, err := indices.filterParams.Query(index.latestTimestamp)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tdocumentCount, err := fetchIndexDocumentCount(ctx, client, indexName, query)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tindex.documentCount = documentCount\n\t}\n\n\tindices.indices = make([]*Index, 0, len(newIndicesByName))\n\tindices.documentCount = 0\n\tindices.processedDocumentsCount = 0\n\n\tfor _, index := range newIndicesByName {\n\t\tindices.indices = append(indices.indices, index)\n\t\tindices.documentCount += index.documentCount\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/sources/elasticsearch/elasticsearch.go",
    "content": "package elasticsearch\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\tes \"github.com/elastic/go-elasticsearch/v8\"\n\t\"github.com/go-errors/errors\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/log\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sanitizer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"golang.org/x/sync/errgroup\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n)\n\nconst SourceType = sourcespb.SourceType_SOURCE_TYPE_ELASTICSEARCH\n\ntype Source struct {\n\tname           string\n\tsourceId       sources.SourceID\n\tjobId          sources.JobID\n\tconcurrency    int\n\tverify         bool\n\tesConfig       es.Config\n\tfilterParams   FilterParams\n\tbestEffortScan bool\n\tctx            context.Context\n\tclient         *es.TypedClient\n\tlog            logr.Logger\n\tsources.Progress\n}\n\n// Init returns an initialized Elasticsearch source\nfunc (s *Source) Init(\n\taCtx context.Context,\n\tname string,\n\tjobId sources.JobID,\n\tsourceId sources.SourceID,\n\tverify bool,\n\tconnection *anypb.Any,\n\tconcurrency int,\n) error {\n\tvar conn sourcespb.Elasticsearch\n\tif err := anypb.UnmarshalTo(connection, &conn, proto.UnmarshalOptions{}); err != nil {\n\t\treturn errors.WrapPrefix(err, \"error unmarshalling connection\", 0)\n\t}\n\n\ts.name = name\n\ts.sourceId = sourceId\n\ts.jobId = jobId\n\ts.concurrency = concurrency\n\ts.verify = verify\n\n\ts.ctx = aCtx\n\ts.log = aCtx.Logger()\n\n\tesConfig := es.Config{}\n\n\tif len(conn.Nodes) > 0 {\n\t\tesConfig.Addresses = conn.Nodes\n\t}\n\n\tif conn.Username != \"\" {\n\t\tesConfig.Username = conn.Username\n\t}\n\n\tif conn.Password != \"\" {\n\t\tesConfig.Password = conn.Password\n\t\tlog.RedactGlobally(conn.Password)\n\t}\n\n\tif conn.CloudId != \"\" {\n\t\tesConfig.CloudID = conn.CloudId\n\t}\n\n\tif conn.ApiKey != \"\" {\n\t\tesConfig.APIKey = conn.ApiKey\n\t\tlog.RedactGlobally(conn.ApiKey)\n\t}\n\n\tif conn.ServiceToken != \"\" {\n\t\tesConfig.ServiceToken = conn.ServiceToken\n\t\tlog.RedactGlobally(conn.ServiceToken)\n\t}\n\n\ts.esConfig = esConfig\n\n\tif conn.IndexPattern == \"\" {\n\t\ts.filterParams.indexPattern = \"*\"\n\t} else {\n\t\ts.filterParams.indexPattern = conn.IndexPattern\n\t}\n\n\ts.filterParams.queryJSON = conn.QueryJson\n\ts.filterParams.sinceTimestamp = conn.SinceTimestamp\n\n\ts.bestEffortScan = conn.BestEffortScan\n\n\tclient, err := es.NewTypedClient(s.esConfig)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.client = client\n\n\treturn nil\n}\n\nfunc (s *Source) Type() sourcespb.SourceType {\n\treturn SourceType\n}\n\nfunc (s *Source) SourceID() sources.SourceID {\n\treturn s.sourceId\n}\n\nfunc (s *Source) JobID() sources.JobID {\n\treturn s.jobId\n}\n\n// Chunks emits chunks of bytes over a channel.\nfunc (s *Source) Chunks(\n\tctx context.Context,\n\tchunksChan chan *sources.Chunk,\n\ttargets ...sources.ChunkingTarget,\n) error {\n\tindices := Indices{filterParams: &s.filterParams}\n\n\tfor {\n\t\tworkerPool := new(errgroup.Group)\n\t\tworkerPool.SetLimit(s.concurrency)\n\n\t\tpreviousDocumentCount := indices.documentCount\n\t\terr := indices.Update(s.ctx, s.client)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Don't burn up the ES API with rapid requests if there's no work to do\n\t\tif previousDocumentCount > 0 && indices.documentCount == 0 {\n\t\t\tduration, _ := time.ParseDuration(\"5s\")\n\t\t\ttime.Sleep(duration)\n\t\t\tcontinue\n\t\t}\n\n\t\t// The scanCoverageRate is documentsScanned / documentsAdded. If it's < 1 we\n\t\t// need each DocumentSearch to skip some records.\n\t\tscanCoverageRate := 1.0\n\t\tif previousDocumentCount > 0 && indices.documentCount > 0 && previousDocumentCount < indices.documentCount {\n\t\t\tscanCoverageRate =\n\t\t\t\tfloat64(previousDocumentCount) / float64(indices.documentCount)\n\t\t\ts.log.V(1).Info(fmt.Sprintf(\n\t\t\t\t\"Scan coverage rate is %f%% (%d/%d); skipping documents to catch up\",\n\t\t\t\tscanCoverageRate,\n\t\t\t\tpreviousDocumentCount,\n\t\t\t\tindices.documentCount,\n\t\t\t))\n\t\t}\n\n\t\tunitsOfWork := distributeDocumentScans(&indices, s.concurrency, scanCoverageRate)\n\n\t\tfor outerUOWIndex, outerUOW := range unitsOfWork {\n\t\t\tuowIndex := outerUOWIndex\n\t\t\tuow := outerUOW\n\n\t\t\tworkerPool.Go(func() error {\n\t\t\t\t// Give each worker its own client\n\t\t\t\tclient, err := es.NewTypedClient(s.esConfig)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tuowDocumentsProcessed := 0\n\n\t\t\t\tfor _, docSearch := range uow.documentSearches {\n\t\t\t\t\tdocumentsProcessed, err := processSearchedDocuments(\n\t\t\t\t\t\ts.ctx,\n\t\t\t\t\t\tclient,\n\t\t\t\t\t\t&docSearch,\n\t\t\t\t\t\tfunc(document *Document) error {\n\t\t\t\t\t\t\tif docSearch.index.DocumentAlreadySeen(document) {\n\t\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tchunk := sources.Chunk{\n\t\t\t\t\t\t\t\tSourceType: s.Type(),\n\t\t\t\t\t\t\t\tSourceName: s.name,\n\t\t\t\t\t\t\t\tSourceID:   s.SourceID(),\n\t\t\t\t\t\t\t\tJobID:      s.JobID(),\n\t\t\t\t\t\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\t\t\t\t\t\tData: &source_metadatapb.MetaData_Elasticsearch{\n\t\t\t\t\t\t\t\t\t\tElasticsearch: &source_metadatapb.Elasticsearch{\n\t\t\t\t\t\t\t\t\t\t\tIndex:      sanitizer.UTF8(docSearch.index.name),\n\t\t\t\t\t\t\t\t\t\t\tDocumentId: sanitizer.UTF8(document.id),\n\t\t\t\t\t\t\t\t\t\t\tTimestamp:  sanitizer.UTF8(document.timestamp),\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tSourceVerify: s.verify,\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tchunk.Data = []byte(document.message)\n\n\t\t\t\t\t\t\treturn common.CancellableWrite(ctx, chunksChan, &chunk)\n\t\t\t\t\t\t},\n\t\t\t\t\t)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\ts.log.V(2).Info(fmt.Sprintf(\n\t\t\t\t\t\t\"[Worker %d] Scanned %d documents from index %s\",\n\t\t\t\t\t\tuowIndex,\n\t\t\t\t\t\tdocumentsProcessed,\n\t\t\t\t\t\tdocSearch.index.name,\n\t\t\t\t\t))\n\n\t\t\t\t\tif documentsProcessed != docSearch.documentCount-docSearch.skipCount {\n\t\t\t\t\t\ts.log.V(1).Info(fmt.Sprintf(\n\t\t\t\t\t\t\t\"documentsProcessed != docSearch.documentCount = docSearch.skipCount (%d != %d)\",\n\t\t\t\t\t\t\tdocumentsProcessed,\n\t\t\t\t\t\t\tdocSearch.documentCount-docSearch.skipCount,\n\t\t\t\t\t\t))\n\t\t\t\t\t}\n\n\t\t\t\t\tuowDocumentsProcessed += documentsProcessed\n\t\t\t\t\tindices.UpdateProcessedDocumentCount(documentsProcessed)\n\t\t\t\t\ts.SetProgressComplete(\n\t\t\t\t\t\tindices.GetProcessedDocumentCount(),\n\t\t\t\t\t\tindices.documentCount,\n\t\t\t\t\t\tfmt.Sprintf(\n\t\t\t\t\t\t\t\"[Worker %d] Scanned %d documents from index %s\",\n\t\t\t\t\t\t\tuowIndex,\n\t\t\t\t\t\t\tdocumentsProcessed,\n\t\t\t\t\t\t\tdocSearch.index.name,\n\t\t\t\t\t\t),\n\t\t\t\t\t\t\"\",\n\t\t\t\t\t)\n\n\t\t\t\t\t// When we use the Elastic API in this way, we can't tell\n\t\t\t\t\t// it to only return a specific number of documents. We can\n\t\t\t\t\t// only say \"return a page of documents after this offset\".\n\t\t\t\t\t// So we might have reached the limit of how many documents\n\t\t\t\t\t// we're supposed to process with this worker in the middle\n\t\t\t\t\t// of a page, so check for that here.\n\t\t\t\t\t//\n\t\t\t\t\t// (We could use the API in a different way to get a\n\t\t\t\t\t// precise number of documents back, but that use is\n\t\t\t\t\t// limited to 10000 documents which we could well exceed)\n\t\t\t\t\tif uowDocumentsProcessed >= uow.documentCount {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\n\t\t\t\t\tdocSearch.index.UpdateLatestTimestampLastRun()\n\t\t\t\t}\n\n\t\t\t\treturn nil\n\t\t\t})\n\t\t}\n\n\t\terr = workerPool.Wait()\n\t\tif err != nil {\n\t\t\ts.log.V(2).Info(fmt.Sprintf(\"Error waiting on worker pool: %s\\n\", err))\n\t\t}\n\n\t\tif !s.bestEffortScan {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/sources/elasticsearch/elasticsearch_integration_test.go",
    "content": "//go:build integration\n// +build integration\n\npackage elasticsearch\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"log\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/brianvoe/gofakeit/v7\"\n\tes \"github.com/elastic/go-elasticsearch/v8\"\n\t\"github.com/elastic/go-elasticsearch/v8/esapi\"\n\t\"github.com/testcontainers/testcontainers-go\"\n\telasticcontainer \"github.com/testcontainers/testcontainers-go/modules/elasticsearch\"\n)\n\nconst USER string = \"elastic\" // This is hardcoded in the container\n\nfunc buildTestClient(\n\tec *elasticcontainer.ElasticsearchContainer,\n) (*es.TypedClient, error) {\n\treturn es.NewTypedClient(es.Config{\n\t\tAddresses: []string{ec.Settings.Address},\n\t\tUsername:  USER,\n\t\tPassword:  ec.Settings.Password,\n\t\tCACert:    ec.Settings.CACert,\n\t})\n}\n\nfunc TestSource_ElasticAPI(t *testing.T) {\n\tctx := context.Background()\n\tec, err := elasticcontainer.RunContainer(\n\t\tctx,\n\t\ttestcontainers.WithImage(\"docker.elastic.co/elasticsearch/elasticsearch:8.9.0\"),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"Could not start elasticsearch: %s\", err)\n\t}\n\tdefer func() {\n\t\tif err := ec.Terminate(ctx); err != nil {\n\t\t\tlog.Fatalf(\"Could not stop elasticsearch: %s\", err)\n\t\t}\n\t}()\n\n\tes, err := buildTestClient(ec)\n\n\tif err != nil {\n\t\tlog.Fatalf(\"error creating the elasticsearch client: %s\", err)\n\t}\n\n\tt.Run(\"New server contains no indexes\", func(t *testing.T) {\n\t\tindexNames, err := fetchIndexNames(ctx, es, \"*\")\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\n\t\tif len(indexNames) != 0 {\n\t\t\tt.Errorf(\"wanted 0 indexNames, got %d\\n\", len(indexNames))\n\t\t}\n\t})\n\n\tindexName := gofakeit.Word()\n\tindexName2 := gofakeit.Word()\n\tnow := time.Now()\n\n\tpayload := make(map[string]string)\n\tpayload[\"message\"] = gofakeit.SentenceSimple()\n\tpayload[\"@timestamp\"] = now.Format(time.RFC3339)\n\n\tjsonMessage, err := json.Marshal(payload)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treq := esapi.IndexRequest{\n\t\tIndex:   indexName,\n\t\tBody:    bytes.NewReader(jsonMessage),\n\t\tRefresh: \"true\",\n\t}\n\n\tres, err := req.Do(ctx, es)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer res.Body.Close()\n\n\tt.Run(\n\t\t\"Adding a document to a new index creates a single index\",\n\t\tfunc(t *testing.T) {\n\t\t\tindexNames, err := fetchIndexNames(ctx, es, \"*\")\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\n\t\t\tif len(indexNames) != 1 {\n\t\t\t\tt.Fatalf(\"wanted 1 indexNames, got %d\\n\", len(indexNames))\n\t\t\t}\n\n\t\t\tif indexNames[0] != indexName {\n\t\t\t\tt.Errorf(\"wanted index name \\\"%s\\\", got %s\", indexName, indexNames[0])\n\t\t\t}\n\t\t},\n\t)\n\n\tnowAgain := time.Now()\n\tpayload2 := make(map[string]string)\n\tpayload2[\"message\"] = gofakeit.SentenceSimple()\n\tpayload2[\"@timestamp\"] = nowAgain.Format(time.RFC3339)\n\n\tjsonMessage, err = json.Marshal(payload)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treq = esapi.IndexRequest{\n\t\tIndex:   indexName2,\n\t\tBody:    bytes.NewReader(jsonMessage),\n\t\tRefresh: \"true\",\n\t}\n\n\tres, err = req.Do(ctx, es)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer res.Body.Close()\n\n\tt.Run(\n\t\t\"Indices have the correct document count\",\n\t\tfunc(t *testing.T) {\n\t\t\tindices := Indices{filterParams: &FilterParams{indexPattern: \"*\"}}\n\t\t\terr := indices.Update(ctx, es)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tif len(indices.indices) != 2 {\n\t\t\t\tt.Errorf(\"wanted 2 indices, got %d\\n\", len(indices.indices))\n\t\t\t}\n\n\t\t\tif indices.indices[0].documentCount != 1 {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"wanted documentCount of 1 in 1st index, got %d\\n\",\n\t\t\t\t\tindices.indices[0].documentCount,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tif indices.indices[1].documentCount != 1 {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"wanted documentCount of 1 in 2nd index, got %d\\n\",\n\t\t\t\t\tindices.indices[1].documentCount,\n\t\t\t\t)\n\t\t\t}\n\t\t},\n\t)\n\n\tt.Run(\n\t\t\"A single unit of work has the correct max document count\",\n\t\tfunc(t *testing.T) {\n\t\t\tindices := Indices{filterParams: &FilterParams{indexPattern: \"*\"}}\n\t\t\terr := indices.Update(ctx, es)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tunitsOfWork := distributeDocumentScans(&indices, 1, 1.0)\n\n\t\t\tif len(unitsOfWork) != 1 {\n\t\t\t\tt.Fatalf(\"wanted 1 unit of work, got %d\\n\", len(unitsOfWork))\n\t\t\t}\n\n\t\t\tif len(unitsOfWork[0].documentSearches) != 2 {\n\t\t\t\tt.Fatalf(\n\t\t\t\t\t\"wanted 1 doc search in 1st unit of work, got %d\\n\",\n\t\t\t\t\tlen(unitsOfWork[0].documentSearches),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tif unitsOfWork[0].documentSearches[0].documentCount != 1 {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"wanted max document count of 1 in unit of work's 1st doc search, got %d\\n\",\n\t\t\t\t\tunitsOfWork[0].documentSearches[0].documentCount,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tif unitsOfWork[0].documentSearches[1].documentCount != 1 {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"wanted max document count of 1 in unit of work's 2nd doc search, got %d\\n\",\n\t\t\t\t\tunitsOfWork[0].documentSearches[1].documentCount,\n\t\t\t\t)\n\t\t\t}\n\t\t},\n\t)\n\n\tt.Run(\n\t\t\"Multiple units of work have the correct max document count\",\n\t\tfunc(t *testing.T) {\n\t\t\tindices := Indices{filterParams: &FilterParams{indexPattern: \"*\"}}\n\t\t\terr := indices.Update(ctx, es)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tunitsOfWork := distributeDocumentScans(&indices, 2, 1.0)\n\n\t\t\tif len(unitsOfWork) != 2 {\n\t\t\t\tt.Fatalf(\"wanted 2 units of work, got %d\\n\", len(unitsOfWork))\n\t\t\t}\n\n\t\t\tif len(unitsOfWork[0].documentSearches) != 1 {\n\t\t\t\tt.Fatalf(\n\t\t\t\t\t\"wanted 1 doc search in 1st unit of work, got %d\\n\",\n\t\t\t\t\tlen(unitsOfWork[0].documentSearches),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tif len(unitsOfWork[1].documentSearches) != 1 {\n\t\t\t\tt.Fatalf(\n\t\t\t\t\t\"wanted 1 doc search in 2nd unit of work, got %d\\n\",\n\t\t\t\t\tlen(unitsOfWork[0].documentSearches),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tif unitsOfWork[0].documentSearches[0].documentCount != 1 {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"wanted max document count of 1 in 1st unit of work's doc search, got %d\\n\",\n\t\t\t\t\tunitsOfWork[0].documentSearches[0].documentCount,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tif unitsOfWork[1].documentSearches[0].documentCount != 1 {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"wanted max document count of 1 in 2nd unit of work's doc search, got %d\\n\",\n\t\t\t\t\tunitsOfWork[1].documentSearches[0].documentCount,\n\t\t\t\t)\n\t\t\t}\n\t\t},\n\t)\n\n\tt.Run(\n\t\t\"Adding a document to a new index creates a document count of 1\",\n\t\tfunc(t *testing.T) {\n\t\t\tquery := make(map[string]any)\n\t\t\tquery[\"query\"] = make(map[string]any)\n\t\t\tindexDocumentCount, err := fetchIndexDocumentCount(\n\t\t\t\tctx,\n\t\t\t\tes,\n\t\t\t\tindexName,\n\t\t\t\tquery,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\n\t\t\tif indexDocumentCount != 1 {\n\t\t\t\tt.Errorf(\"wanted 1 document count, got %d\\n\", indexDocumentCount)\n\t\t\t}\n\t\t},\n\t)\n\n\tt.Run(\n\t\t\"Stored document matches passed values\",\n\t\tfunc(t *testing.T) {\n\t\t\tdocSearch := DocumentSearch{\n\t\t\t\tindex: &Index{\n\t\t\t\t\tname:          indexName,\n\t\t\t\t\tdocumentCount: 1,\n\t\t\t\t},\n\t\t\t\tdocumentCount: 1,\n\t\t\t\toffset:        0,\n\t\t\t\tfilterParams:  &FilterParams{},\n\t\t\t}\n\n\t\t\tdocs := []Document{}\n\n\t\t\tdocsProcessed, err := processSearchedDocuments(\n\t\t\t\tctx,\n\t\t\t\tes,\n\t\t\t\t&docSearch,\n\t\t\t\tfunc(document *Document) error {\n\t\t\t\t\tdocs = append(docs, *document)\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\n\t\t\tif docsProcessed != 1 {\n\t\t\t\tt.Fatalf(\"wanted 1 document processed, got %d\\n\", docsProcessed)\n\t\t\t}\n\n\t\t\tif len(docs) != 1 {\n\t\t\t\tt.Fatalf(\"wanted 1 document, got %d\\n\", len(docs))\n\t\t\t}\n\n\t\t\t// if docSearch.index.latestDocumentID != 0 {\n\t\t\t// \tt.Errorf(\"Wanted latestDocumentID 0, got %d\\n\", docSearch.index.latestDocumentID)\n\t\t\t// }\n\n\t\t\tdoc := docs[0]\n\t\t\tif doc.timestamp != now.Format(time.RFC3339) {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"wanted timestamp %s, got %s\\n\",\n\t\t\t\t\tnow.Format(time.RFC3339),\n\t\t\t\t\tdoc.timestamp,\n\t\t\t\t)\n\t\t\t}\n\t\t\tif doc.message != payload[\"message\"] {\n\t\t\t\tt.Errorf(\n\t\t\t\t\t\"wanted message %s, got %s\\n\",\n\t\t\t\t\tpayload[\"message\"],\n\t\t\t\t\tdoc.message,\n\t\t\t\t)\n\t\t\t}\n\t\t},\n\t)\n\n\tt.Run(\n\t\t\"Correct number of documents is skipped given a skipPercent\",\n\t\tfunc(t *testing.T) {\n\t\t\tmessagesProcessed := 0\n\n\t\t\tfor i := 0; i < 40; i++ {\n\t\t\t\tpl := make(map[string]string)\n\t\t\t\tpl[\"message\"] = gofakeit.Word()\n\t\t\t\tpl[\"@timestamp\"] = time.Now().Format(time.RFC3339)\n\n\t\t\t\tindex := indexName\n\t\t\t\tif i > 19 {\n\t\t\t\t\tindex = indexName2\n\t\t\t\t}\n\n\t\t\t\tjsonMsg, err := json.Marshal(pl)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\treq = esapi.IndexRequest{\n\t\t\t\t\tIndex:   index,\n\t\t\t\t\tBody:    bytes.NewReader(jsonMsg),\n\t\t\t\t\tRefresh: \"true\",\n\t\t\t\t}\n\n\t\t\t\tres, err = req.Do(ctx, es)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t\tdefer res.Body.Close()\n\t\t\t}\n\n\t\t\tdocSearch := DocumentSearch{\n\t\t\t\tindex: &Index{\n\t\t\t\t\tname:          indexName,\n\t\t\t\t\tdocumentCount: 21,\n\t\t\t\t},\n\t\t\t\tdocumentCount: 21,\n\t\t\t\toffset:        0,\n\t\t\t\tfilterParams:  &FilterParams{},\n\t\t\t\tskipCount:     10,\n\t\t\t}\n\n\t\t\tdocumentsProcessed, err := processSearchedDocuments(\n\t\t\t\tctx,\n\t\t\t\tes,\n\t\t\t\t&docSearch,\n\t\t\t\tfunc(document *Document) error {\n\t\t\t\t\tmessagesProcessed += 1\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\n\t\t\tif documentsProcessed != 11 {\n\t\t\t\tt.Errorf(\"wanted 11 documents processed, got %d\\n\", documentsProcessed)\n\t\t\t}\n\n\t\t\tif messagesProcessed != 11 {\n\t\t\t\tt.Errorf(\"wanted 11 messages processed, got %d\\n\", messagesProcessed)\n\t\t\t}\n\n\t\t\tdocSearch = DocumentSearch{\n\t\t\t\tindex: &Index{\n\t\t\t\t\tname:          indexName2,\n\t\t\t\t\tdocumentCount: 21,\n\t\t\t\t},\n\t\t\t\tdocumentCount: 21,\n\t\t\t\toffset:        0,\n\t\t\t\tfilterParams:  &FilterParams{},\n\t\t\t\tskipCount:     10,\n\t\t\t}\n\n\t\t\tdocumentsProcessed, err = processSearchedDocuments(\n\t\t\t\tctx,\n\t\t\t\tes,\n\t\t\t\t&docSearch,\n\t\t\t\tfunc(document *Document) error {\n\t\t\t\t\tmessagesProcessed += 1\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\n\t\t\tif documentsProcessed != 11 {\n\t\t\t\tt.Errorf(\"wanted 11 documents processed, got %d\\n\", documentsProcessed)\n\t\t\t}\n\n\t\t\tif messagesProcessed != 22 {\n\t\t\t\tt.Errorf(\"wanted 22 messages processed, got %d\\n\", messagesProcessed)\n\t\t\t}\n\t\t},\n\t)\n}\n"
  },
  {
    "path": "pkg/sources/elasticsearch/unit_of_work.go",
    "content": "package elasticsearch\n\nimport \"fmt\"\n\ntype DocumentSearch struct {\n\tindex         *Index\n\toffset        int\n\tdocumentCount int\n\tskipCount     int\n\tfilterParams  *FilterParams\n}\n\ntype UnitOfWork struct {\n\tmaxDocumentCount int\n\tdocumentCount    int\n\tdocumentSearches []DocumentSearch\n}\n\nfunc NewUnitOfWork(maxDocumentCount int) UnitOfWork {\n\tuow := UnitOfWork{maxDocumentCount: maxDocumentCount}\n\tuow.documentSearches = []DocumentSearch{}\n\n\treturn uow\n}\n\nfunc (ds *DocumentSearch) String() string {\n\tif ds.offset > 0 {\n\t\treturn fmt.Sprintf(\"%s [%d:]\", ds.index.name, ds.offset)\n\t} else {\n\t\treturn ds.index.name\n\t}\n}\n\nfunc (uow *UnitOfWork) addSearch(\n\tindex *Index,\n\tfilterParams *FilterParams,\n\toffset int,\n\tscanCoverageRate float64,\n) int {\n\tindexDocCount := index.documentCount - offset\n\taddedDocumentCount := min(uow.maxDocumentCount-uow.documentCount, indexDocCount)\n\n\tif addedDocumentCount > 0 {\n\t\tuow.documentSearches = append(uow.documentSearches, DocumentSearch{\n\t\t\tindex:         index,\n\t\t\toffset:        offset,\n\t\t\tdocumentCount: addedDocumentCount,\n\t\t\tskipCount:     int(float64(addedDocumentCount) * (1.0 - scanCoverageRate)),\n\t\t\tfilterParams:  filterParams,\n\t\t})\n\n\t\tuow.documentCount += addedDocumentCount\n\t}\n\n\treturn addedDocumentCount\n}\n\nfunc distributeDocumentScans(\n\tindices *Indices,\n\tmaxUnits int,\n\tscanCoverageRate float64,\n) []UnitOfWork {\n\ttotalDocumentCount := 0\n\n\tfor _, i := range indices.indices {\n\t\ttotalDocumentCount += i.documentCount\n\t}\n\n\tunitsOfWork := make([]UnitOfWork, maxUnits)\n\tdocumentsAssigned := 0\n\tfor i := 0; i < maxUnits; i++ {\n\t\tdocumentCount := totalDocumentCount / maxUnits\n\n\t\t// The total number of documents to process might not be perfectly\n\t\t// divisible by the number of workers, so make sure any remaining documents\n\t\t// get processed by assigning them to the last worker\n\t\tif i == maxUnits-1 {\n\t\t\tdocumentCount = totalDocumentCount - documentsAssigned\n\t\t}\n\n\t\tunitsOfWork[i] = NewUnitOfWork(documentCount)\n\n\t\tdocumentsAssigned += documentCount\n\t}\n\n\tunitOfWorkIndex := 0\n\tfor _, i := range indices.indices {\n\t\tuow := &unitsOfWork[unitOfWorkIndex]\n\t\toffset := uow.addSearch(i, indices.filterParams, 0, scanCoverageRate)\n\n\t\t// If we've yet to distribute all the documents in the index, go into the\n\t\t// next unit of work, and the next, and the next....\n\t\tfor offset < i.documentCount {\n\t\t\tunitOfWorkIndex++\n\t\t\tuow := &unitsOfWork[unitOfWorkIndex]\n\t\t\toffset += uow.addSearch(i, indices.filterParams, offset, scanCoverageRate)\n\t\t}\n\t}\n\n\treturn unitsOfWork\n}\n"
  },
  {
    "path": "pkg/sources/elasticsearch/unit_of_work_test.go",
    "content": "package elasticsearch\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSource_distributeDocumentScans(t *testing.T) {\n\tindices := Indices{\n\t\tindices: []*Index{\n\t\t\t&Index{name: \"index\", documentCount: 20},\n\t\t\t&Index{name: \"index2\", documentCount: 9},\n\t\t\t&Index{name: \"index3\", documentCount: 0},\n\t\t},\n\t\tfilterParams: &FilterParams{},\n\t}\n\n\tt.Run(\n\t\t\"Distributing 30 documents from 3 indices (1 empty) with 2 workers works\",\n\t\tfunc(t *testing.T) {\n\t\t\tuows := distributeDocumentScans(&indices, 2, .9)\n\n\t\t\tassert.Equal(t, 2, len(uows))\n\n\t\t\tassert.Equal(t, 14, uows[0].maxDocumentCount)\n\t\t\tassert.Equal(t, 14, uows[0].documentCount)\n\t\t\tassert.Equal(t, 1, len(uows[0].documentSearches))\n\n\t\t\tassert.Equal(t, \"index\", uows[0].documentSearches[0].index.name)\n\t\t\tassert.Equal(t, 0, uows[0].documentSearches[0].offset)\n\t\t\tassert.Equal(t, 14, uows[0].documentSearches[0].documentCount)\n\t\t\tassert.Equal(t, 1, uows[0].documentSearches[0].skipCount)\n\n\t\t\tassert.Equal(t, 15, uows[1].maxDocumentCount)\n\t\t\tassert.Equal(t, 15, uows[1].documentCount)\n\t\t\tassert.Equal(t, 2, len(uows[1].documentSearches))\n\n\t\t\tassert.Equal(t, \"index\", uows[1].documentSearches[0].index.name)\n\t\t\tassert.Equal(t, 14, uows[1].documentSearches[0].offset)\n\t\t\tassert.Equal(t, 6, uows[1].documentSearches[0].documentCount)\n\t\t\tassert.Equal(t, 0, uows[1].documentSearches[0].skipCount)\n\n\t\t\tassert.Equal(t, \"index2\", uows[1].documentSearches[1].index.name)\n\t\t\tassert.Equal(t, 0, uows[1].documentSearches[1].offset)\n\t\t\tassert.Equal(t, 9, uows[1].documentSearches[1].documentCount)\n\t\t\tassert.Equal(t, 0, uows[1].documentSearches[1].skipCount)\n\t\t},\n\t)\n}\n\nfunc TestSource_addSearch(t *testing.T) {\n\tindex := Index{name: \"index1\", documentCount: 20}\n\tindex2 := Index{name: \"index2\", documentCount: 10}\n\tindex3 := Index{name: \"index3\", documentCount: 0}\n\n\tuow := NewUnitOfWork(10)\n\n\t// Does filling up a UOW with a larger index work?\n\toffset := uow.addSearch(&index, &FilterParams{}, 0, 1.0)\n\tassert.Equal(t, 10, offset)\n\tassert.Equal(t, 10, uow.maxDocumentCount)\n\tassert.Equal(t, uow.maxDocumentCount, uow.documentCount)\n\tassert.Equal(t, 1, len(uow.documentSearches))\n\tassert.Equal(t, index.name, uow.documentSearches[0].index.name)\n\tassert.Equal(t, 0, uow.documentSearches[0].offset)\n\tassert.Equal(t, uow.maxDocumentCount, uow.documentSearches[0].documentCount)\n\n\t// Does trying to add another range into a full UOW leave it unchanged?\n\toffset2 := uow.addSearch(&index2, &FilterParams{}, 0, 1.0)\n\tassert.Equal(t, 0, offset2)\n\tassert.Equal(t, 10, uow.maxDocumentCount)\n\tassert.Equal(t, uow.maxDocumentCount, uow.documentCount)\n\tassert.Equal(t, 1, len(uow.documentSearches))\n\tassert.Equal(t, index.name, uow.documentSearches[0].index.name)\n\tassert.Equal(t, 0, uow.documentSearches[0].offset)\n\tassert.Equal(t, uow.maxDocumentCount, uow.documentSearches[0].documentCount)\n\n\t// Does trying to add an index with no documents leave it unchanged?\n\toffset += uow.addSearch(&index3, &FilterParams{}, 0, 1.0)\n\tassert.Equal(t, 10, offset)\n\tassert.Equal(t, 10, uow.maxDocumentCount)\n\tassert.Equal(t, uow.maxDocumentCount, uow.documentCount)\n\tassert.Equal(t, 1, len(uow.documentSearches))\n\tassert.Equal(t, index.name, uow.documentSearches[0].index.name)\n\tassert.Equal(t, 0, uow.documentSearches[0].offset)\n\tassert.Equal(t, uow.maxDocumentCount, uow.documentSearches[0].documentCount)\n\n\t// Does filling up another UOW with a larger index work?\n\tuow2 := NewUnitOfWork(9)\n\n\toffset += uow2.addSearch(&index, &FilterParams{}, offset, 1.0)\n\tassert.Equal(t, 19, offset)\n\tassert.Equal(t, 9, uow2.maxDocumentCount)\n\tassert.Equal(t, uow2.maxDocumentCount, uow2.documentCount)\n\tassert.Equal(t, 1, len(uow2.documentSearches))\n\tassert.Equal(t, index.name, uow2.documentSearches[0].index.name)\n\tassert.Equal(t, 10, uow2.documentSearches[0].offset)\n\tassert.Equal(t, uow2.maxDocumentCount, uow2.documentSearches[0].documentCount)\n\n\t// Does finishing off an index into a UOW with room to spare work?\n\tuow3 := NewUnitOfWork(9)\n\n\toffset += uow3.addSearch(&index, &FilterParams{}, offset, 1.0)\n\tassert.Equal(t, 20, offset)\n\tassert.Equal(t, 9, uow3.maxDocumentCount)\n\tassert.Equal(t, 1, uow3.documentCount)\n\tassert.Equal(t, 1, len(uow3.documentSearches))\n\tassert.Equal(t, index.name, uow3.documentSearches[0].index.name)\n\tassert.Equal(t, 19, uow3.documentSearches[0].offset)\n\tassert.Equal(t, 1, uow3.documentSearches[0].documentCount)\n\n\tuow = NewUnitOfWork(21)\n\n\t// Does adding an empty range into a new UOW leave it unchanged?\n\toffset = uow.addSearch(&index3, &FilterParams{}, 0, 1.0)\n\tassert.Equal(t, 0, offset)\n\tassert.Equal(t, 21, uow.maxDocumentCount)\n\tassert.Equal(t, 0, uow.documentCount)\n\tassert.Equal(t, 0, len(uow.documentSearches))\n\n\t// Does adding a range into a larger UOW work?\n\toffset = uow.addSearch(&index, &FilterParams{}, 0, 1.0)\n\tassert.Equal(t, 20, offset)\n\tassert.Equal(t, 1, len(uow.documentSearches))\n\tassert.Equal(t, index.name, uow.documentSearches[0].index.name)\n\tassert.Equal(t, 0, uow.documentSearches[0].offset)\n\tassert.Equal(t, 20, uow.documentSearches[0].documentCount)\n\n\t// Does filling up a UOW that already has a range in it work?\n\toffset = uow.addSearch(&index2, &FilterParams{}, 0, 1.0)\n\tassert.Equal(t, 1, offset)\n\tassert.Equal(t, 2, len(uow.documentSearches))\n\tassert.Equal(t, index.name, uow.documentSearches[0].index.name)\n\tassert.Equal(t, 0, uow.documentSearches[0].offset)\n\tassert.Equal(t, 20, uow.documentSearches[0].documentCount)\n\tassert.Equal(t, index2.name, uow.documentSearches[1].index.name)\n\tassert.Equal(t, 0, uow.documentSearches[1].offset)\n\tassert.Equal(t, 1, uow.documentSearches[1].documentCount)\n}\n"
  },
  {
    "path": "pkg/sources/errors.go",
    "content": "package sources\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\t\"sync\"\n)\n\n// ScanErrors is used to collect errors encountered while scanning.\n// It ensures that errors are collected in a thread-safe manner.\ntype ScanErrors struct {\n\tmu     sync.RWMutex\n\terrors []error\n}\n\n// NewScanErrors creates a new thread safe error collector.\nfunc NewScanErrors() *ScanErrors {\n\treturn &ScanErrors{errors: make([]error, 0)}\n}\n\n// Add an error to the collection in a thread-safe manner.\nfunc (s *ScanErrors) Add(err error) {\n\tif err == nil {\n\t\treturn\n\t}\n\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\ts.errors = append(s.errors, err)\n}\n\n// Count returns the number of errors collected.\nfunc (s *ScanErrors) Count() uint64 {\n\ts.mu.RLock()\n\tdefer s.mu.RUnlock()\n\treturn uint64(len(s.errors))\n}\n\nfunc (s *ScanErrors) String() string {\n\ts.mu.RLock()\n\tdefer s.mu.RUnlock()\n\n\tvar sb strings.Builder\n\tsb.WriteString(\"[\")\n\tfor i, err := range s.errors {\n\t\tsb.WriteString(`\"` + err.Error() + `\"`)\n\t\tif i < len(s.errors)-1 {\n\t\t\tsb.WriteString(\", \")\n\t\t}\n\t}\n\tsb.WriteString(\"]\")\n\treturn sb.String()\n}\n\nfunc (s *ScanErrors) Errors() error {\n\ts.mu.RLock()\n\tdefer s.mu.RUnlock()\n\treturn errors.Join(s.errors...)\n}\n\n// TargetedScanError is an error with a secret ID attached. Collections of them can be returned by targeted scans that\n// scan multiple targets in order to associate individual errors with individual scan targets.\ntype TargetedScanError struct {\n\tErr      error\n\tSecretID int64\n}\n\nvar _ error = (*TargetedScanError)(nil)\n\nfunc (t TargetedScanError) Error() string {\n\treturn t.Err.Error()\n}\n\nfunc (t TargetedScanError) Unwrap() error {\n\treturn t.Err\n}\n"
  },
  {
    "path": "pkg/sources/errors_test.go",
    "content": "package sources\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\t\"testing\"\n)\n\nvar testError = fmt.Errorf(\"simulated failure\")\n\nfunc TestNewScanErrors(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\tprojects int\n\t\twant     *ScanErrors\n\t}{\n\t\t{\n\t\t\tname:     \"no projects\",\n\t\t\tprojects: 0,\n\t\t\twant: &ScanErrors{\n\t\t\t\terrors: make([]error, 0),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"one project\",\n\t\t\tprojects: 1,\n\t\t\twant: &ScanErrors{\n\t\t\t\terrors: make([]error, 0, 1),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"fifty projects\",\n\t\t\tprojects: 50,\n\t\t\twant: &ScanErrors{\n\t\t\t\terrors: make([]error, 0, 50),\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := NewScanErrors()\n\n\t\t\tif !errors.Is(got.Errors(), tc.want.Errors()) {\n\t\t\t\tt.Errorf(\"got %+v, want %+v\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestScanErrorsAdd(t *testing.T) {\n\ttestCases := []struct {\n\t\tname        string\n\t\tconcurrency int\n\t\twantErr     int\n\t}{\n\t\t{\n\t\t\tname:        \"no concurrency, no errors\",\n\t\t\tconcurrency: 1,\n\t\t\twantErr:     0,\n\t\t},\n\t\t{\n\t\t\tname:        \"no concurrency, one error\",\n\t\t\tconcurrency: 1,\n\t\t\twantErr:     1,\n\t\t},\n\t\t{\n\t\t\tname:        \"concurrency, 100 errors\",\n\t\t\tconcurrency: 10,\n\t\t\twantErr:     100,\n\t\t},\n\t\t{\n\t\t\tname:        \"concurrency, 1000 errors\",\n\t\t\tconcurrency: 10,\n\t\t\twantErr:     1000,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tse := NewScanErrors()\n\n\t\t\tvar wg sync.WaitGroup\n\t\t\tfor i := 0; i < tc.concurrency; i++ {\n\t\t\t\twg.Add(1)\n\t\t\t\tgo func() {\n\t\t\t\t\tfor j := 0; j < tc.wantErr/tc.concurrency; j++ {\n\t\t\t\t\t\tse.Add(testError)\n\t\t\t\t\t}\n\t\t\t\t\twg.Done()\n\t\t\t\t}()\n\t\t\t}\n\t\t\twg.Wait()\n\n\t\t\tif se.Count() != uint64(tc.wantErr) {\n\t\t\t\tt.Errorf(\"got %d, want %d\", se.Count(), tc.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestScanErrorsCount(t *testing.T) {\n\ttestCases := []struct {\n\t\tname        string\n\t\tconcurrency int\n\t\twantErrCnt  int\n\t}{\n\t\t{\n\t\t\tname:        \"no concurrency, no errors\",\n\t\t\tconcurrency: 1,\n\t\t\twantErrCnt:  0,\n\t\t},\n\t\t{\n\t\t\tname:        \"no concurrency, one error\",\n\t\t\tconcurrency: 1,\n\t\t\twantErrCnt:  1,\n\t\t},\n\t\t{\n\t\t\tname:        \"concurrency, 100 errors\",\n\t\t\tconcurrency: 10,\n\t\t\twantErrCnt:  100,\n\t\t},\n\t\t{\n\t\t\tname:        \"concurrency, 2048 errors\",\n\t\t\tconcurrency: 8,\n\t\t\twantErrCnt:  2048,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tse := NewScanErrors()\n\n\t\t\tvar wg sync.WaitGroup\n\t\t\tfor i := 0; i < tc.concurrency; i++ {\n\t\t\t\twg.Add(1)\n\t\t\t\tgo func() {\n\t\t\t\t\tfor j := 0; j < tc.wantErrCnt/tc.concurrency; j++ {\n\t\t\t\t\t\tse.Add(testError)\n\t\t\t\t\t}\n\t\t\t\t\twg.Done()\n\t\t\t\t}()\n\t\t\t}\n\t\t\twg.Wait()\n\n\t\t\tif se.Count() != uint64(tc.wantErrCnt) {\n\t\t\t\tt.Errorf(\"got %d, want %d\", se.Count(), tc.wantErrCnt)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestScanErrorsString(t *testing.T) {\n\tse := NewScanErrors()\n\tse.Add(testError)\n\twant := `[\"` + testError.Error() + `\"]`\n\tif got := fmt.Sprintf(\"%v\", se); got != want {\n\t\tt.Errorf(\"got %q, want %q\", got, want)\n\t}\n}\n"
  },
  {
    "path": "pkg/sources/filesystem/filesystem.go",
    "content": "package filesystem\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/go-errors/errors\"\n\t\"github.com/go-logr/logr\"\n\t\"golang.org/x/sync/errgroup\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\ttrContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/feature\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/handlers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sanitizer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nconst SourceType = sourcespb.SourceType_SOURCE_TYPE_FILESYSTEM\n\ntype Source struct {\n\tname         string\n\tsourceId     sources.SourceID\n\tjobId        sources.JobID\n\tconcurrency  int\n\tverify       bool\n\tpaths        []string\n\tlog          logr.Logger\n\tfilter       *common.Filter\n\tskipBinaries bool\n\tsources.Progress\n\tsources.CommonSourceUnitUnmarshaller\n\tmaxSymlinkDepth int\n}\n\n// Ensure the Source satisfies the interfaces at compile time\nvar _ sources.Source = (*Source)(nil)\nvar _ sources.SourceUnitUnmarshaller = (*Source)(nil)\nvar _ sources.SourceUnitEnumChunker = (*Source)(nil)\n\n// max symlink depth allowed\nconst defaultMaxSymlinkDepth = 40\n\n// Type returns the type of source.\n// It is used for matching source types in configuration and job input.\nfunc (s *Source) Type() sourcespb.SourceType {\n\treturn SourceType\n}\n\nfunc (s *Source) SourceID() sources.SourceID {\n\treturn s.sourceId\n}\n\nfunc (s *Source) JobID() sources.JobID {\n\treturn s.jobId\n}\n\n// Init returns an initialized Filesystem source.\nfunc (s *Source) Init(aCtx trContext.Context, name string, jobId sources.JobID, sourceId sources.SourceID, verify bool, connection *anypb.Any, concurrency int) error {\n\ts.log = aCtx.Logger()\n\n\ts.concurrency = concurrency\n\ts.name = name\n\ts.sourceId = sourceId\n\ts.jobId = jobId\n\ts.verify = verify\n\n\tvar conn sourcespb.Filesystem\n\tif err := anypb.UnmarshalTo(connection, &conn, proto.UnmarshalOptions{}); err != nil {\n\t\treturn errors.WrapPrefix(err, \"error unmarshalling connection\", 0)\n\t}\n\ts.paths = append(conn.Paths, conn.Directories...)\n\ts.skipBinaries = conn.GetSkipBinaries()\n\n\tfilter, err := common.FilterFromFiles(conn.IncludePathsFile, conn.ExcludePathsFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to create filter: %w\", err)\n\t}\n\ts.filter = filter\n\terr = s.setMaxSymlinkDepth(&conn)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (s *Source) setMaxSymlinkDepth(conn *sourcespb.Filesystem) error {\n\tdepth := int(conn.GetMaxSymlinkDepth())\n\tif depth > defaultMaxSymlinkDepth {\n\t\treturn fmt.Errorf(\n\t\t\t\"specified symlink depth %d exceeds the allowed max of %d\",\n\t\t\tdepth,\n\t\t\tdefaultMaxSymlinkDepth,\n\t\t)\n\t}\n\ts.maxSymlinkDepth = depth\n\treturn nil\n}\n\nfunc (s *Source) canFollowSymlinks() bool {\n\treturn s.maxSymlinkDepth > 0\n}\n\n// Chunks emits chunks of bytes over a channel.\nfunc (s *Source) Chunks(ctx trContext.Context, chunksChan chan *sources.Chunk, _ ...sources.ChunkingTarget) error {\n\tfor i, rootPath := range s.paths {\n\t\tlogger := ctx.Logger().WithValues(\"path\", rootPath)\n\t\tif common.IsDone(ctx) {\n\t\t\treturn nil\n\t\t}\n\t\ts.SetProgressComplete(i, len(s.paths), fmt.Sprintf(\"Path: %s\", rootPath), \"\")\n\n\t\tcleanPath := filepath.Clean(rootPath)\n\t\tfileInfo, err := os.Lstat(cleanPath)\n\t\tif err != nil {\n\t\t\tlogger.Error(err, \"unable to get file info\")\n\t\t\tcontinue\n\t\t}\n\n\t\tif fileInfo.Mode()&os.ModeSymlink != 0 {\n\t\t\t// if the root path is a symlink we scan the symlink\n\t\t\tctx.Logger().V(5).Info(\"Root path is a symlink\", \"path\", cleanPath)\n\t\t\tinitialDepth := 0\n\t\t\terr = s.scanSymlink(ctx, chunksChan, rootPath, initialDepth, cleanPath)\n\t\t\ts.ClearEncodedResumeInfoFor(rootPath)\n\t\t} else if fileInfo.IsDir() {\n\t\t\tctx.Logger().V(5).Info(\"Root path is a dir\", \"path\", cleanPath)\n\t\t\tinitialDepth := 0\n\t\t\terr = s.scanDir(ctx, chunksChan, rootPath, initialDepth, cleanPath)\n\t\t\ts.ClearEncodedResumeInfoFor(rootPath)\n\t\t} else {\n\t\t\tif !fileInfo.Mode().IsRegular() {\n\t\t\t\tlogger.Info(\"skipping non-regular file\", \"path\", cleanPath)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tctx.Logger().V(5).Info(\"Root path is a file\", \"path\", cleanPath)\n\t\t\terr = s.scanFile(ctx, chunksChan, cleanPath)\n\t\t}\n\n\t\tif err != nil && !errors.Is(err, io.EOF) {\n\t\t\tlogger.Error(err, \"error scanning filesystem\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *Source) scanSymlink(\n\tctx trContext.Context,\n\tchunksChan chan *sources.Chunk,\n\trootPath string,\n\tdepth int,\n\tpath string,\n) error {\n\tif !s.canFollowSymlinks() {\n\t\t// If the file or directory is a symlink but the followSymlinks is disable ignore the path\n\t\tctx.Logger().V(2).Info(\"skipping, following symlinks is not allowed\", \"path\", path)\n\t\treturn nil\n\t}\n\n\tdepth++\n\n\tif depth > s.maxSymlinkDepth {\n\t\treturn errors.New(\"max symlink depth reached\")\n\t}\n\n\tcleanPath := filepath.Clean(path)\n\n\tresolvedPath, err := os.Readlink(cleanPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"readlink error: %w\", err)\n\t}\n\tif !filepath.IsAbs(resolvedPath) {\n\t\tresolvedPath = filepath.Join(filepath.Dir(cleanPath), resolvedPath)\n\t}\n\tfileInfo, err := os.Lstat(resolvedPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"lstat error: %w\", err)\n\t}\n\tif fileInfo.Mode()&os.ModeSymlink != 0 {\n\t\tctx.Logger().V(5).Info(\n\t\t\t\"found symlink to symlink\",\n\t\t\t\"symlinkPath\", cleanPath,\n\t\t\t\"resolvedPath\", resolvedPath,\n\t\t\t\"depth\", depth,\n\t\t)\n\t\treturn s.scanSymlink(ctx, chunksChan, rootPath, depth, resolvedPath)\n\t}\n\n\tif fileInfo.IsDir() {\n\t\tctx.Logger().V(5).Info(\n\t\t\t\"found symlink to dir\",\n\t\t\t\"symlinkPath\", cleanPath,\n\t\t\t\"resolvedPath\", resolvedPath,\n\t\t\t\"depth\", depth,\n\t\t)\n\n\t\treturn s.scanDir(ctx, chunksChan, rootPath, depth, resolvedPath)\n\t}\n\tctx.Logger().V(5).Info(\n\t\t\"found symlink to file\",\n\t\t\"symlinkPath\", cleanPath,\n\t\t\"resolvedPath\", resolvedPath,\n\t\t\"depth\", depth,\n\t)\n\tif s.filter != nil && !s.filter.Pass(resolvedPath) {\n\t\treturn nil\n\t}\n\n\t// Use a single resumption key for the entire scan rooted at rootPath.\n\t// Resume checks are handled by the calling scanDir function.\n\tresumptionKey := rootPath\n\n\tif !fileInfo.Mode().Type().IsRegular() {\n\t\tctx.Logger().V(5).Info(\"skipping non-regular file\", \"path\", resolvedPath)\n\t\treturn nil\n\t}\n\tif err := s.scanFile(ctx, chunksChan, resolvedPath); err != nil {\n\t\tctx.Logger().Error(err, \"error scanning file\", \"path\", resolvedPath)\n\t}\n\ts.SetEncodedResumeInfoFor(resumptionKey, cleanPath)\n\treturn nil\n}\n\nfunc (s *Source) scanDir(\n\tctx trContext.Context,\n\tchunksChan chan *sources.Chunk,\n\trootPath string,\n\tdepth int,\n\tpath string,\n) error {\n\t// check if the full path is not matching any pattern in include\n\t// FilterRuleSet and matching any exclude FilterRuleSet.\n\tif s.filter != nil && s.filter.ShouldExclude(path) {\n\t\treturn nil\n\t}\n\n\t// Use a single resumption key for the entire scan rooted at rootPath.\n\t// The value stored is the full path of the last successfully scanned file.\n\t// This avoids accumulating separate entries for each subdirectory visited.\n\tresumptionKey := rootPath\n\tresumeAfter := s.GetEncodedResumeInfoFor(resumptionKey)\n\n\t// Only consider resumption if the resume point is within this directory's subtree.\n\t// Since os.ReadDir returns entries sorted by filename:\n\t// - If we're scanning /root/ccc and the resume point is /root/bbb/file.txt,\n\t//   we've already passed it (bbb < ccc) and should process ccc normally.\n\t// - If we're scanning /root/aaa and the resume point is /root/bbb/file.txt,\n\t//   we haven't reached it yet (aaa < bbb), so aaa was already fully scanned\n\t//   and should be skipped entirely.\n\tif resumeAfter != \"\" && !strings.HasPrefix(resumeAfter, path+string(filepath.Separator)) && resumeAfter != path {\n\t\t// Resume point is not in this subtree. Compare paths to determine if we\n\t\t// should skip this directory (already scanned) or process it (already passed).\n\t\tif path < resumeAfter {\n\t\t\t// This directory comes before the resume point lexicographically,\n\t\t\t// meaning it was already fully scanned. Skip it entirely.\n\t\t\treturn nil\n\t\t}\n\t\t// This directory comes after the resume point, so we've already passed\n\t\t// the resume point. Process this directory normally.\n\t\tresumeAfter = \"\"\n\t}\n\n\tctx.Logger().V(5).Info(\"Full path found is\", \"fullPath\", path)\n\n\tentries, err := os.ReadDir(path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"readdir error: %w\", err)\n\t}\n\n\tworkerPool := new(errgroup.Group)\n\tworkerPool.SetLimit(s.concurrency)\n\n\tfor _, entry := range entries {\n\t\tentryPath := filepath.Join(path, entry.Name())\n\t\tif s.filter != nil && !s.filter.Pass(entryPath) {\n\t\t\tif !entry.IsDir() && entry.Type()&os.ModeSymlink == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// Skip entries until we pass the resume point.\n\t\t// We don't clear the resume info when we find the resume point - instead we\n\t\t// keep it set until a new file is scanned. This ensures we don't lose progress\n\t\t// if the scan is interrupted between finding the resume point and scanning\n\t\t// the next file.\n\t\tif resumeAfter != \"\" {\n\t\t\t// If this entry is the resume point, stop skipping.\n\t\t\tif entryPath == resumeAfter {\n\t\t\t\tresumeAfter = \"\"\n\t\t\t\tcontinue // Skip the resume point itself since it was already processed.\n\t\t\t}\n\t\t\t// If the resume point is within this entry (a descendant), we need to\n\t\t\t// traverse into it to find where to resume.\n\t\t\tif entry.IsDir() && strings.HasPrefix(resumeAfter, entryPath+string(filepath.Separator)) {\n\t\t\t\t// Recurse into this directory to find the resume point.\n\t\t\t\tif err := s.scanDir(ctx, chunksChan, rootPath, depth, entryPath); err != nil {\n\t\t\t\t\tctx.Logger().Error(err, \"error scanning directory\", \"path\", entryPath)\n\t\t\t\t}\n\t\t\t\t// After recursing, clear local resumeAfter. The child scanDir will have\n\t\t\t\t// handled resumption within its subtree, and subsequent entries in this\n\t\t\t\t// directory should be processed normally.\n\t\t\t\tresumeAfter = \"\"\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Skip this entry - it comes before the resume point in traversal order.\n\t\t\tcontinue\n\t\t}\n\n\t\tif entry.Type()&os.ModeSymlink != 0 {\n\t\t\tctx.Logger().V(5).Info(\"Entry found is a symlink\", \"path\", entryPath)\n\t\t\tif err := s.scanSymlink(ctx, chunksChan, rootPath, depth, entryPath); err != nil {\n\t\t\t\tctx.Logger().Error(err, \"error scanning symlink\", \"path\", entryPath)\n\t\t\t}\n\t\t} else if entry.IsDir() {\n\t\t\tctx.Logger().V(5).Info(\"Entry found is a directory\", \"path\", entryPath)\n\t\t\tif err := s.scanDir(ctx, chunksChan, rootPath, depth, entryPath); err != nil {\n\t\t\t\tctx.Logger().Error(err, \"error scanning directory\", \"path\", entryPath)\n\t\t\t}\n\t\t} else {\n\t\t\tif !entry.Type().IsRegular() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tctx.Logger().V(5).Info(\"Entry found is a file\", \"path\", entryPath)\n\t\t\tworkerPool.Go(func() error {\n\t\t\t\tif err := s.scanFile(ctx, chunksChan, entryPath); err != nil {\n\t\t\t\t\tctx.Logger().Error(err, \"error scanning file\", \"path\", entryPath)\n\t\t\t\t}\n\t\t\t\ts.SetEncodedResumeInfoFor(resumptionKey, entryPath)\n\t\t\t\treturn nil\n\t\t\t})\n\t\t}\n\t}\n\n\t_ = workerPool.Wait() // [TODO] Handle errors\n\n\treturn nil\n}\n\nfunc (s *Source) scanFile(ctx trContext.Context, chunksChan chan *sources.Chunk, path string) error {\n\tfileCtx := trContext.WithValues(ctx, \"path\", path)\n\n\t_, err := os.Lstat(path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to stat file: %w\", err)\n\t}\n\n\t// Check if file is binary and should be skipped\n\tif (s.skipBinaries || feature.ForceSkipBinaries.Load()) && common.IsBinary(path) {\n\t\tfileCtx.Logger().V(5).Info(\"skipping binary file\", \"path\", path)\n\t\treturn nil\n\t}\n\n\tinputFile, err := os.Open(path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to open file: %w\", err)\n\t}\n\tdefer inputFile.Close()\n\n\tfileCtx.Logger().V(3).Info(\"scanning file\")\n\n\tchunkSkel := &sources.Chunk{\n\t\tSourceType: s.Type(),\n\t\tSourceName: s.name,\n\t\tSourceID:   s.SourceID(),\n\t\tJobID:      s.JobID(),\n\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\tData: &source_metadatapb.MetaData_Filesystem{\n\t\t\t\tFilesystem: &source_metadatapb.Filesystem{\n\t\t\t\t\tFile: sanitizer.UTF8(path),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tSourceVerify: s.verify,\n\t}\n\n\treturn handlers.HandleFile(fileCtx, inputFile, chunkSkel, sources.ChanReporter{Ch: chunksChan})\n}\n\n// Enumerate implements SourceUnitEnumerator interface. This implementation simply\n// passes the configured paths as the source unit, whether it be a single\n// filepath or a directory.\nfunc (s *Source) Enumerate(ctx trContext.Context, reporter sources.UnitReporter) error {\n\tfor _, rootPath := range s.paths {\n\t\t_, err := os.Lstat(filepath.Clean(rootPath))\n\t\tif err != nil {\n\t\t\tif err := reporter.UnitErr(ctx, err); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\titem := sources.CommonSourceUnit{ID: rootPath}\n\t\tif err := reporter.UnitOk(ctx, item); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// ChunkUnit implements SourceUnitChunker interface.\nfunc (s *Source) ChunkUnit(ctx trContext.Context, unit sources.SourceUnit, reporter sources.ChunkReporter) error {\n\trootPath, _ := unit.SourceUnitID()\n\tlogger := ctx.Logger().WithValues(\"path\", rootPath)\n\n\tcleanPath := filepath.Clean(rootPath)\n\tfileInfo, err := os.Lstat(cleanPath)\n\tif err != nil {\n\t\treturn reporter.ChunkErr(ctx, fmt.Errorf(\"unable to get file info: %w\", err))\n\t}\n\t// This will always be the FileInfo we use to decide dir vs file\n\n\tch := make(chan *sources.Chunk)\n\tvar scanErr error\n\tgo func() {\n\t\tdefer close(ch)\n\t\tif fileInfo.Mode()&os.ModeSymlink != 0 {\n\t\t\t// if the root path is a symlink we scan the symlink\n\t\t\tctx.Logger().V(5).Info(\"Root path is a symlink\", \"path\", cleanPath)\n\t\t\tinitialDepth := 0\n\t\t\tscanErr = s.scanSymlink(ctx, ch, rootPath, initialDepth, cleanPath)\n\t\t\ts.ClearEncodedResumeInfoFor(rootPath)\n\n\t\t} else if fileInfo.IsDir() {\n\t\t\tctx.Logger().V(5).Info(\"Root path is a dir\", \"path\", cleanPath)\n\t\t\tinitialDepth := 0\n\t\t\t// TODO: Finer grain error tracking of individual chunks.\n\t\t\tscanErr = s.scanDir(ctx, ch, rootPath, initialDepth, cleanPath)\n\t\t\ts.ClearEncodedResumeInfoFor(rootPath)\n\t\t} else {\n\t\t\tctx.Logger().V(5).Info(\"Root path is a file\", \"path\", cleanPath)\n\t\t\t// TODO: Finer grain error tracking of individual\n\t\t\t// chunks (in the case of archives).\n\t\t\tif !fileInfo.Mode().IsRegular() {\n\t\t\t\tlogger.Info(\"skipping non-regular file\", \"path\", cleanPath)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tscanErr = s.scanFile(ctx, ch, cleanPath)\n\t\t}\n\t}()\n\n\tfor chunk := range ch {\n\t\tif chunk == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif err := reporter.ChunkOk(ctx, *chunk); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif scanErr != nil && !errors.Is(scanErr, io.EOF) {\n\t\tlogger.Error(scanErr, \"error scanning filesystem\")\n\t\treturn reporter.ChunkErr(ctx, scanErr)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/sources/filesystem/filesystem_symlink_test.go",
    "content": "package filesystem\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\ttrContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sourcestest\"\n)\n\nfunc probeSymlinkSupport(t *testing.T, baseDir string) {\n\tprobe := filepath.Join(baseDir, \"symlink-probe\")\n\tif err := os.Symlink(\"x\", probe); err != nil {\n\t\tt.Skip(\"symlinks not supported\")\n\t}\n\t_ = os.Remove(probe)\n}\n\nfunc TestScanDir_VisitedPath_PreventInfiniteRecursion(t *testing.T) {\n\tctx, cancel := trContext.WithTimeout(trContext.Background(), time.Second*3)\n\tdefer cancel()\n\tbaseDir, cleanup, err := createTempDir(\"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer cleanup()\n\t// Skip if symlinks unsupported\n\tprobeSymlinkSupport(t, baseDir)\n\n\tdirA := filepath.Join(baseDir, \"A\")\n\tdirB := filepath.Join(baseDir, \"B\")\n\terr = os.Mkdir(dirA, 0755)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create directory A %v\", err)\n\t}\n\terr = os.Mkdir(dirB, 0755)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create directory B %v\", err)\n\t}\n\n\t// We create\n\t// A/linkToB -> /B\n\t// B/linkToA -> /A\n\terr = os.Symlink(dirB, filepath.Join(dirA, \"linkToB\"))\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create symlink %v\", err)\n\t}\n\terr = os.Symlink(dirA, filepath.Join(dirB, \"linkToA\"))\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create symlink %v\", err)\n\t}\n\n\tsrc := &Source{\n\t\tconcurrency:     1,\n\t\tmaxSymlinkDepth: 20,\n\t}\n\tchunks := make(chan *sources.Chunk, 10)\n\tgo func() {\n\t\terr := src.Chunks(ctx, chunks)\n\t\trequire.NoError(t, err)\n\t\tclose(chunks)\n\t}()\n\tvar chunkCount int\n\tfor range chunks {\n\t\tchunkCount++\n\t}\n\t// Assert no chunks were emitted due to the infinite symlink loop\n\trequire.Equal(t, 0, chunkCount, \"No chunks should be processed due to infinite symlink loop\")\n}\n\nfunc TestChunks_DirectorySymlinkLoop(t *testing.T) {\n\tctx, cancel := trContext.WithTimeout(trContext.Background(), time.Second*3)\n\tdefer cancel()\n\tbaseDir, cleanup, err := createTempDir(\"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer cleanup()\n\n\tprobeSymlinkSupport(t, baseDir)\n\n\t// We Create\n\t// /A->/B\n\t// /B->/A\n\terr = os.Symlink(filepath.Join(baseDir, \"B\"), filepath.Join(baseDir, \"A\"))\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create symlink %v\", err)\n\t}\n\terr = os.Symlink(filepath.Join(baseDir, \"A\"), filepath.Join(baseDir, \"B\"))\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create symlink %v\", err)\n\t}\n\n\tsrc := &Source{\n\t\tmaxSymlinkDepth: 20,\n\t\tconcurrency:     1,\n\t\tpaths:           []string{filepath.Join(baseDir, \"B\")},\n\t}\n\n\tchunks := make(chan *sources.Chunk, 10)\n\t// Run the scan\n\tgo func() {\n\t\terr := src.Chunks(ctx, chunks)\n\t\trequire.NoError(t, err)\n\t\tclose(chunks)\n\t}()\n\tvar chunkCount int\n\tfor range chunks {\n\t\tchunkCount++\n\t}\n\t// Assert no chunks were emitted due to the infinite symlink loop\n\trequire.Equal(t, 0, chunkCount, \"No chunks should be processed due to infinite symlink loop\")\n}\n\nfunc TestChunkUnit_DirectorySymlinkLoop(t *testing.T) {\n\tctx, cancel := trContext.WithTimeout(trContext.Background(), time.Second*3)\n\tdefer cancel()\n\tbaseDir, cleanup, err := createTempDir(\"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer cleanup()\n\n\tprobeSymlinkSupport(t, baseDir)\n\n\t// We Create\n\t// /A->/B\n\t// /B->/A\n\terr = os.Symlink(filepath.Join(baseDir, \"B\"), filepath.Join(baseDir, \"A\"))\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create symlink %v\", err)\n\t}\n\terr = os.Symlink(filepath.Join(baseDir, \"A\"), filepath.Join(baseDir, \"B\"))\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create symlink %v\", err)\n\t}\n\n\tconn, err := anypb.New(&sourcespb.Filesystem{\n\t\tMaxSymlinkDepth: 20,\n\t})\n\tassert.NoError(t, err)\n\n\t// Initialize the source.\n\ts := Source{}\n\terr = s.Init(ctx, \"test chunk unit\", 0, 0, true, conn, 1)\n\tassert.NoError(t, err)\n\n\treporter := sourcestest.TestReporter{}\n\terr = s.ChunkUnit(ctx, sources.CommonSourceUnit{\n\t\tID: filepath.Join(baseDir, \"B\"),\n\t}, &reporter)\n\tassert.NoError(t, err)\n\t// Assert no chunks were emitted due to the infinite symlink loop\n\tassert.Equal(t, 0, len(reporter.Chunks))\n}\n\nfunc TestChunks_FileSymlinkLoop(t *testing.T) {\n\tctx, cancel := trContext.WithTimeout(trContext.Background(), time.Second*3)\n\tdefer cancel()\n\tbaseDir, cleanup, err := createTempDir(\"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer cleanup()\n\n\tprobeSymlinkSupport(t, baseDir)\n\n\t// We Create\n\t// /fileA->/fileB\n\t// /fileB->/fileA\n\tfileA := filepath.Join(baseDir, \"fileA.txt\")\n\tfileB := filepath.Join(baseDir, \"fileB.txt\")\n\terr = os.Symlink(fileA, fileB)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create symlink %v\", err)\n\t}\n\terr = os.Symlink(fileB, fileA)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create symlink %v\", err)\n\t}\n\n\tsrc := &Source{\n\t\tmaxSymlinkDepth: 20,\n\t\tconcurrency:     1,\n\t\tpaths:           []string{fileA},\n\t}\n\n\tchunks := make(chan *sources.Chunk, 10)\n\t// Run the scan\n\tgo func() {\n\t\terr := src.Chunks(ctx, chunks)\n\t\trequire.NoError(t, err)\n\t\tclose(chunks)\n\t}()\n\tvar chunkCount int\n\tfor range chunks {\n\t\tchunkCount++\n\t}\n\t// Assert no chunks were emitted due to the infinite symlink loop\n\trequire.Equal(t, 0, chunkCount, \"No chunks should be processed due to infinite symlink loop\")\n}\n\nfunc TestChunkUnit_FileSymlinkLoop(t *testing.T) {\n\tctx, cancel := trContext.WithTimeout(trContext.Background(), 3*time.Second)\n\tdefer cancel()\n\n\tbaseDir, cleanup, err := createTempDir(\"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer cleanup()\n\n\t// Probe symlink support\n\tprobeSymlinkSupport(t, baseDir)\n\n\t// Create two files that are symlinks to each other\n\tfileA := filepath.Join(baseDir, \"fileA.txt\")\n\tfileB := filepath.Join(baseDir, \"fileB.txt\")\n\n\tif err := os.Symlink(fileB, fileA); err != nil {\n\t\tt.Fatalf(\"Unable to create symlink %v\", err)\n\t}\n\tif err := os.Symlink(fileA, fileB); err != nil {\n\t\tt.Fatalf(\"Unable to create symlink %v\", err)\n\t}\n\n\tconn, err := anypb.New(&sourcespb.Filesystem{\n\t\tMaxSymlinkDepth: 20,\n\t})\n\trequire.NoError(t, err)\n\n\ts := Source{}\n\terr = s.Init(ctx, \"test chunk unit\", 0, 0, true, conn, 1)\n\trequire.NoError(t, err)\n\n\treporter := sourcestest.TestReporter{}\n\terr = s.ChunkUnit(ctx, sources.CommonSourceUnit{\n\t\tID: fileA,\n\t}, &reporter)\n\trequire.NoError(t, err)\n\n\t// Assert no chunks were emitted due to the infinite symlink loop\n\tassert.Equal(t, 0, len(reporter.Chunks), \"No chunks should be processed due to infinite symlink loop\")\n}\n\nfunc TestChunks_ValidDirectorySymlink(t *testing.T) {\n\tctx, cancel := trContext.WithTimeout(trContext.Background(), 3*time.Second)\n\tdefer cancel()\n\n\t// Create a temporary base directory\n\tbaseDir, cleanup, err := createTempDir(\"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer cleanup()\n\n\t// Probe symlink support\n\tprobeSymlinkSupport(t, baseDir)\n\n\t// Create a real file\n\tdirA := filepath.Join(baseDir, \"A\")\n\terr = os.Mkdir(dirA, 0755)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create directory A %v\", err)\n\t}\n\tdirB := filepath.Join(baseDir, \"B\")\n\terr = os.Mkdir(dirB, 0755)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create directory B %v\", err)\n\t}\n\n\tdata := \"Hello world!\"\n\tfile, cleanupFile, err := createTempFile(dirA, data)\n\tassert.NoError(t, err)\n\tdefer cleanupFile()\n\n\t// we create\n\t// /B/link.txt->/A/trufflehogtest*\n\tlinkFile := filepath.Join(dirB, \"link.txt\")\n\tif err := os.Symlink(file.Name(), linkFile); err != nil {\n\t\tt.Fatalf(\"failed to create symlink: %v\", err)\n\t}\n\n\tsrc := &Source{\n\t\tconcurrency:     1,\n\t\tpaths:           []string{dirB},\n\t\tmaxSymlinkDepth: 20,\n\t}\n\n\tchunksCh := make(chan *sources.Chunk, 1)\n\tgo func() {\n\t\tdefer close(chunksCh)\n\t\terr = src.Chunks(ctx, chunksCh)\n\t\trequire.NoError(t, err)\n\t}()\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error scanning symlink: %v\", err)\n\t}\n\n\tfor chunk := range chunksCh {\n\t\tif string(chunk.Data) != data {\n\t\t\tt.Fatalf(\"expected chunk.Data: %v to be equal to %v\", string(chunk.Data), data)\n\t\t}\n\t}\n}\n\nfunc TestChunkUnit_ValidDirectorySymlink(t *testing.T) {\n\tctx, cancel := trContext.WithTimeout(trContext.Background(), 3*time.Second)\n\tdefer cancel()\n\n\t// Create a temporary base directory\n\tbaseDir, cleanup, err := createTempDir(\"\")\n\trequire.NoError(t, err)\n\tdefer cleanup()\n\n\t// Probe symlink support\n\tprobeSymlinkSupport(t, baseDir)\n\n\t// Create directories\n\tdirA := filepath.Join(baseDir, \"A\")\n\tdirB := filepath.Join(baseDir, \"B\")\n\trequire.NoError(t, os.Mkdir(dirA, 0755))\n\trequire.NoError(t, os.Mkdir(dirB, 0755))\n\n\t// Create a file in dirA\n\tdata := \"Hello world!\"\n\tfile, cleanupFile, err := createTempFile(dirA, data)\n\trequire.NoError(t, err)\n\tdefer cleanupFile()\n\n\t// Create symlink: /B/link.txt -> /A/trufflehogtest*\n\tlinkFile := filepath.Join(dirB, \"link.txt\")\n\trequire.NoError(t, os.Symlink(file.Name(), linkFile))\n\n\t// Prepare Source\n\tconn, err := anypb.New(&sourcespb.Filesystem{\n\t\tMaxSymlinkDepth: 20,\n\t})\n\trequire.NoError(t, err)\n\n\tsrc := Source{}\n\trequire.NoError(t, src.Init(ctx, \"test chunk unit\", 0, 0, true, conn, 1))\n\n\treporter := sourcestest.TestReporter{}\n\terr = src.ChunkUnit(ctx, sources.CommonSourceUnit{\n\t\tID: dirB,\n\t}, &reporter)\n\trequire.NoError(t, err)\n\n\t// Assert exactly 1 chunk is scanned and data matches\n\trequire.Len(t, reporter.Chunks, 1, \"Expected exactly 1 chunk from symlinked file\")\n\trequire.Equal(t, data, string(reporter.Chunks[0].Data))\n}\n\nfunc TestChunks_ValidFileSymlink(t *testing.T) {\n\tctx, cancel := trContext.WithTimeout(trContext.Background(), 3*time.Second)\n\tdefer cancel()\n\n\t// Create a temporary base directory\n\tbaseDir, cleanup, err := createTempDir(\"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer cleanup()\n\n\t// Probe symlink support\n\tprobeSymlinkSupport(t, baseDir)\n\n\t// Create a real file\n\tdirA := filepath.Join(baseDir, \"A\")\n\terr = os.Mkdir(dirA, 0755)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create directory A %v\", err)\n\t}\n\tdirB := filepath.Join(baseDir, \"B\")\n\terr = os.Mkdir(dirB, 0755)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create directory B %v\", err)\n\t}\n\n\tdata := \"Hello world!\"\n\tfile, cleanupFile, err := createTempFile(dirA, data)\n\tassert.NoError(t, err)\n\tdefer cleanupFile()\n\n\t// we create\n\t// /B/link.txt->/A/trufflehogtest*\n\tlinkFile := filepath.Join(dirB, \"link.txt\")\n\tif err := os.Symlink(file.Name(), linkFile); err != nil {\n\t\tt.Fatalf(\"failed to create symlink: %v\", err)\n\t}\n\n\tsrc := &Source{\n\t\tconcurrency:     1,\n\t\tmaxSymlinkDepth: 20,\n\t\tpaths:           []string{linkFile},\n\t}\n\n\tchunksCh := make(chan *sources.Chunk, 1)\n\n\tgo func() {\n\t\tdefer close(chunksCh)\n\t\terr = src.Chunks(ctx, chunksCh)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"src.scanFile() error=%v\", err)\n\t\t}\n\t}()\n\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error scanning symlink: %v\", err)\n\t}\n\n\tfor chunk := range chunksCh {\n\t\tif string(chunk.Data) != data {\n\t\t\tt.Fatalf(\"expected chunk.Data: %v to be equal to %v\", string(chunk.Data), data)\n\t\t}\n\t}\n}\n\nfunc TestChunkUnit_ValidFileSymlink(t *testing.T) {\n\tctx, cancel := trContext.WithTimeout(trContext.Background(), 3*time.Second)\n\tdefer cancel()\n\n\t// Create a temporary base directory\n\tbaseDir, cleanup, err := createTempDir(\"\")\n\trequire.NoError(t, err)\n\tdefer cleanup()\n\n\tprobeSymlinkSupport(t, baseDir)\n\n\tdirA := filepath.Join(baseDir, \"A\")\n\tdirB := filepath.Join(baseDir, \"B\")\n\trequire.NoError(t, os.Mkdir(dirA, 0755))\n\trequire.NoError(t, os.Mkdir(dirB, 0755))\n\n\tdata := \"Hello world!\"\n\tfile, cleanupFile, err := createTempFile(dirA, data)\n\trequire.NoError(t, err)\n\tdefer cleanupFile()\n\n\t// Create symlink: /B/link.txt -> /A/trufflehogtest*\n\tlinkFile := filepath.Join(dirB, \"link.txt\")\n\trequire.NoError(t, os.Symlink(file.Name(), linkFile))\n\n\tconn, err := anypb.New(&sourcespb.Filesystem{\n\t\tMaxSymlinkDepth: 20,\n\t})\n\trequire.NoError(t, err)\n\n\tsrc := Source{}\n\trequire.NoError(t, src.Init(ctx, \"test chunk unit\", 0, 0, true, conn, 1))\n\n\treporter := sourcestest.TestReporter{}\n\terr = src.ChunkUnit(ctx, sources.CommonSourceUnit{\n\t\tID: linkFile,\n\t}, &reporter)\n\trequire.NoError(t, err)\n\n\t// Assert exactly 1 chunk scanned and data matches\n\trequire.Len(t, reporter.Chunks, 1, \"Expected exactly 1 chunk from symlinked file\")\n\trequire.Equal(t, data, string(reporter.Chunks[0].Data))\n}\n\nfunc TestScanSymlink_NoError(t *testing.T) {\n\tctx, cancel := trContext.WithTimeout(trContext.Background(), time.Second*3)\n\tdefer cancel()\n\tbaseDir, cleanup, err := createTempDir(\"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer cleanup()\n\n\tprobeSymlinkSupport(t, baseDir)\n\n\tdirD := filepath.Join(baseDir, \"D\")\n\terr = os.Mkdir(dirD, 0755)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create directory D %v\", err)\n\t}\n\n\terr = os.Symlink(filepath.Join(baseDir, \"B\"), filepath.Join(baseDir, \"A\"))\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create symlink %v\", err)\n\t}\n\terr = os.Symlink(filepath.Join(baseDir, \"C\"), filepath.Join(baseDir, \"B\"))\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create symlink %v\", err)\n\t}\n\terr = os.Symlink(filepath.Join(baseDir, \"D\"), filepath.Join(baseDir, \"C\"))\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create symlink %v\", err)\n\t}\n\n\tsrc := &Source{\n\t\tconcurrency:     1,\n\t\tmaxSymlinkDepth: 20,\n\t}\n\tchunks := make(chan *sources.Chunk, 10)\n\tgo func() {\n\t\tpath := filepath.Join(baseDir, \"A\")\n\t\terr := src.scanSymlink(ctx, chunks, path, 0, path)\n\t\trequire.NoError(t, err)\n\t\tclose(chunks)\n\t}()\n\tvar chunkCount int\n\tfor range chunks {\n\t\tchunkCount++\n\t}\n\trequire.Equal(t, 0, chunkCount, \"No chunks should be because dir D has no file\")\n}\n\nfunc TestScanSymlink_MaxDepthExceeded(t *testing.T) {\n\tctx, cancel := trContext.WithTimeout(trContext.Background(), time.Second*3)\n\tdefer cancel()\n\tbaseDir, cleanup, err := createTempDir(\"\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer cleanup()\n\n\tprobeSymlinkSupport(t, baseDir)\n\n\tdirD := filepath.Join(baseDir, \"D\")\n\terr = os.Mkdir(dirD, 0755)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create directory D %v\", err)\n\t}\n\n\terr = os.Symlink(filepath.Join(baseDir, \"B\"), filepath.Join(baseDir, \"A\"))\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create symlink %v\", err)\n\t}\n\terr = os.Symlink(filepath.Join(baseDir, \"C\"), filepath.Join(baseDir, \"B\"))\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create symlink %v\", err)\n\t}\n\terr = os.Symlink(filepath.Join(baseDir, \"D\"), filepath.Join(baseDir, \"C\"))\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create symlink %v\", err)\n\t}\n\n\tsrc := &Source{\n\t\tconcurrency:     1,\n\t\tmaxSymlinkDepth: 2,\n\t}\n\tchunks := make(chan *sources.Chunk, 10)\n\n\tpath := filepath.Join(baseDir, \"A\")\n\terr = src.scanSymlink(ctx, chunks, path, 0, path)\n\tclose(chunks)\n\n\trequire.Error(t, err)\n\trequire.EqualError(t, err, \"max symlink depth reached\")\n}\n\nfunc TestScanSymlink_FileTarget(t *testing.T) {\n\tctx, cancel := trContext.WithTimeout(trContext.Background(), 3*time.Second)\n\tdefer cancel()\n\n\tbaseDir, cleanup, err := createTempDir(\"\")\n\trequire.NoError(t, err)\n\tdefer cleanup()\n\n\tprobeSymlinkSupport(t, baseDir)\n\t// Create a file\n\tfilePath := filepath.Join(baseDir, \"file.txt\")\n\terr = os.WriteFile(filePath, []byte(\"data\"), 0644)\n\trequire.NoError(t, err)\n\n\t// Create a symlink pointing to the file\n\tsymlinkPath := filepath.Join(baseDir, \"link.txt\")\n\terr = os.Symlink(filePath, symlinkPath)\n\trequire.NoError(t, err)\n\n\tsrc := &Source{\n\t\tmaxSymlinkDepth: 5,\n\t\tconcurrency:     1,\n\t}\n\n\tchunks := make(chan *sources.Chunk, 10)\n\n\terr = src.scanSymlink(ctx, chunks, symlinkPath, 0, symlinkPath)\n\trequire.NoError(t, err)\n\tclose(chunks)\n\tvar chunkCount int\n\tfor chunk := range chunks {\n\t\trequire.Equal(t, \"data\", string(chunk.Data))\n\t\tchunkCount++\n\t}\n\trequire.Equal(t, 1, chunkCount, \"Expected 1 chunk\")\n\n}\n\nfunc TestScanSymlink_SelfLoop(t *testing.T) {\n\tctx, cancel := trContext.WithTimeout(trContext.Background(), 3*time.Second)\n\tdefer cancel()\n\n\tbaseDir, cleanup, err := createTempDir(\"\")\n\trequire.NoError(t, err)\n\tdefer cleanup()\n\n\tprobeSymlinkSupport(t, baseDir)\n\n\tsymlinkPath := filepath.Join(baseDir, \"loop.txt\")\n\terr = os.Symlink(symlinkPath, symlinkPath)\n\trequire.NoError(t, err)\n\n\tsrc := &Source{\n\t\tmaxSymlinkDepth: 5,\n\t}\n\n\tchunks := make(chan *sources.Chunk, 10)\n\n\terr = src.scanSymlink(ctx, chunks, symlinkPath, 0, symlinkPath)\n\tclose(chunks)\n\trequire.Error(t, err)\n\trequire.EqualError(t, err, \"max symlink depth reached\")\n}\n\nfunc TestScanSymlink_BrokenSymlink(t *testing.T) {\n\tctx, cancel := trContext.WithTimeout(trContext.Background(), 3*time.Second)\n\tdefer cancel()\n\n\tbaseDir, cleanup, err := createTempDir(\"\")\n\trequire.NoError(t, err)\n\tdefer cleanup()\n\n\tprobeSymlinkSupport(t, baseDir)\n\n\tsymlinkPath := filepath.Join(baseDir, \"broken\")\n\terr = os.Symlink(filepath.Join(baseDir, \"nonexistent.txt\"), symlinkPath)\n\trequire.NoError(t, err)\n\n\tsrc := &Source{\n\t\tmaxSymlinkDepth: 5,\n\t}\n\n\tchunks := make(chan *sources.Chunk, 10)\n\n\terr = src.scanSymlink(ctx, chunks, symlinkPath, 0, symlinkPath)\n\tclose(chunks)\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"lstat error\")\n}\n\nfunc TestScanSymlink_TwoFileLoop(t *testing.T) {\n\tctx, cancel := trContext.WithTimeout(trContext.Background(), 3*time.Second)\n\tdefer cancel()\n\n\tbaseDir, cleanup, err := createTempDir(\"\")\n\trequire.NoError(t, err)\n\tdefer cleanup()\n\n\tprobeSymlinkSupport(t, baseDir)\n\n\tfileA := filepath.Join(baseDir, \"fileA.txt\")\n\tfileB := filepath.Join(baseDir, \"fileB.txt\")\n\n\t// A -> B, B -> A\n\trequire.NoError(t, os.Symlink(fileB, fileA))\n\trequire.NoError(t, os.Symlink(fileA, fileB))\n\n\tsrc := &Source{\n\t\tmaxSymlinkDepth: 5,\n\t}\n\n\tchunks := make(chan *sources.Chunk, 10)\n\n\terr = src.scanSymlink(ctx, chunks, fileA, 0, fileA)\n\tclose(chunks)\n\trequire.Error(t, err)\n\trequire.EqualError(t, err, \"max symlink depth reached\")\n}\n"
  },
  {
    "path": "pkg/sources/filesystem/filesystem_test.go",
    "content": "package filesystem\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/protobuf/testing/protocmp\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\ttrContext \"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sourcestest\"\n)\n\nfunc TestSource_Scan(t *testing.T) {\n\tctx, cancel := trContext.WithTimeout(trContext.Background(), time.Second*3)\n\tdefer cancel()\n\n\ttype init struct {\n\t\tname       string\n\t\tverify     bool\n\t\tconnection *sourcespb.Filesystem\n\t}\n\ttests := []struct {\n\t\tname               string\n\t\tinit               init\n\t\twantSourceMetadata *source_metadatapb.MetaData\n\t\twantErr            bool\n\t}{\n\t\t{\n\t\t\tname: \"get a chunk\",\n\t\t\tinit: init{\n\t\t\t\tname: \"this repo\",\n\t\t\t\tconnection: &sourcespb.Filesystem{\n\t\t\t\t\tPaths: []string{\".\"},\n\t\t\t\t},\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Filesystem{\n\t\t\t\t\tFilesystem: &source_metadatapb.Filesystem{\n\t\t\t\t\t\tFile: \"filesystem.go\",\n\t\t\t\t\t\tLine: 1, // First chunk starts at line 1\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Source{}\n\n\t\t\tconn, err := anypb.New(tt.init.connection)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\terr = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 5)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Source.Init() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tchunksCh := make(chan *sources.Chunk, 1)\n\t\t\t// TODO: this is kind of bad, if it errors right away we don't see it as a test failure.\n\t\t\t// Debugging this usually requires setting a breakpoint on L78 and running test w/ debug.\n\t\t\tgo func() {\n\t\t\t\tdefer close(chunksCh)\n\t\t\t\terr = s.Chunks(ctx, chunksCh)\n\t\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\t\tt.Errorf(\"Source.Chunks() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}()\n\t\t\tvar counter int\n\t\t\tfor chunk := range chunksCh {\n\t\t\t\tif chunk.SourceMetadata.GetFilesystem().GetFile() == \"filesystem.go\" {\n\t\t\t\t\tcounter++\n\t\t\t\t\tif diff := cmp.Diff(tt.wantSourceMetadata, chunk.SourceMetadata, protocmp.Transform()); diff != \"\" && counter == 1 { // First chunk should start at line 1\n\t\t\t\t\t\tt.Errorf(\"Source.Chunks() %s metadata mismatch (-want +got):\\n%s\", tt.name, diff)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tassert.Equal(t, 2, counter)\n\t\t})\n\t}\n}\n\nfunc TestScanFile(t *testing.T) {\n\tchunkSize := sources.DefaultChunkSize\n\tsecretPart1 := \"SECRET\"\n\tsecretPart2 := \"SPLIT\"\n\t// Split the secret into two parts and pad the rest of the chunk with A's.\n\tdata := strings.Repeat(\"A\", chunkSize-len(secretPart1)) + secretPart1 + secretPart2 + strings.Repeat(\"A\", chunkSize-len(secretPart2))\n\n\ttmpfile, cleanup, err := createTempFile(\"\", data)\n\tassert.Nil(t, err)\n\tdefer cleanup()\n\n\tsource := &Source{}\n\tchunksChan := make(chan *sources.Chunk, 2)\n\n\tctx := trContext.WithLogger(trContext.Background(), logr.Discard())\n\tgo func() {\n\t\tdefer close(chunksChan)\n\t\terr = source.scanFile(ctx, chunksChan, tmpfile.Name())\n\t\tassert.Nil(t, err)\n\t}()\n\n\t// Read from the channel and validate the secrets.\n\tfoundSecret := \"\"\n\tfor chunkCh := range chunksChan {\n\t\tfoundSecret += string(chunkCh.Data)\n\t}\n\n\tassert.Contains(t, foundSecret, secretPart1+secretPart2)\n}\n\nfunc TestScanBinaryFile(t *testing.T) {\n\ttmpfile, err := os.CreateTemp(\"\", \"example.bin\")\n\trequire.NoError(t, err)\n\tdefer os.Remove(tmpfile.Name())\n\n\t// binary data that decodes to \"TuffleHog\"\n\tfileContents := []byte{0x54, 0x75, 0x66, 0x66, 0x6C, 0x65, 0x48, 0x6F, 0x67}\n\t_, err = tmpfile.Write(fileContents)\n\trequire.NoError(t, err)\n\trequire.NoError(t, tmpfile.Close())\n\n\tsource := &Source{}\n\tchunksChan := make(chan *sources.Chunk, 2)\n\terrChan := make(chan error, 1)\n\n\tctx := trContext.WithLogger(trContext.Background(), logr.Discard())\n\n\tgo func() {\n\t\tdefer close(chunksChan)\n\t\terrChan <- source.scanFile(ctx, chunksChan, tmpfile.Name())\n\t}()\n\n\terr = <-errChan\n\trequire.NoError(t, err)\n\n\tvar data string\n\tfor chunk := range chunksChan {\n\t\trequire.NotNil(t, chunk)\n\t\tdata += string(chunk.Data)\n\t}\n\n\tassert.Contains(t, data, \"TuffleHog\")\n}\n\nfunc TestEnumerate(t *testing.T) {\n\t// TODO: refactor to allow a virtual filesystem.\n\tt.Parallel()\n\tctx := trContext.Background()\n\n\t// Setup the connection to test enumeration.\n\tdir, err := os.MkdirTemp(\"\", \"trufflehog-test-enumerate\")\n\tassert.NoError(t, err)\n\tdefer os.RemoveAll(dir)\n\n\tunits := []string{\n\t\t\"/one\", \"/two\", \"/three\",\n\t\t\"/path/to/dir/\", \"/path/to/another/dir/\",\n\t}\n\t// Prefix the units with the tempdir and create files on disk.\n\tfor i, unit := range units {\n\t\tfullPath := filepath.Join(dir, unit)\n\t\tunits[i] = fullPath\n\t\tif i < 3 {\n\t\t\tf, err := os.Create(fullPath)\n\t\t\tassert.NoError(t, err)\n\t\t\tf.Close()\n\t\t} else {\n\t\t\tassert.NoError(t, os.MkdirAll(fullPath, 0755))\n\t\t\t// Create a file in the directory for enumeration to find.\n\t\t\tf, err := os.CreateTemp(fullPath, \"file\")\n\t\t\tassert.NoError(t, err)\n\t\t\tunits[i] = f.Name()\n\t\t\tf.Close()\n\t\t}\n\t}\n\tconn, err := anypb.New(&sourcespb.Filesystem{\n\t\tPaths:       units[0:3],\n\t\tDirectories: units[3:],\n\t})\n\tassert.NoError(t, err)\n\n\t// Initialize the source.\n\ts := Source{}\n\terr = s.Init(ctx, \"test enumerate\", 0, 0, true, conn, 1)\n\tassert.NoError(t, err)\n\n\treporter := sourcestest.TestReporter{}\n\terr = s.Enumerate(ctx, &reporter)\n\tassert.NoError(t, err)\n\n\tassert.Equal(t, len(units), len(reporter.Units))\n\tassert.Equal(t, 0, len(reporter.UnitErrs))\n\tfor _, unit := range reporter.Units {\n\t\tpath, _ := unit.SourceUnitID()\n\t\tassert.Contains(t, units, path)\n\t}\n\tfor _, unit := range units {\n\t\tassert.Contains(t, reporter.Units, sources.CommonSourceUnit{ID: unit})\n\t}\n}\n\nfunc TestChunkUnit(t *testing.T) {\n\tt.Parallel()\n\tctx := trContext.Background()\n\n\t// Setup test file to chunk.\n\tfileContents := \"TestChunkUnit\"\n\ttmpfile, cleanup, err := createTempFile(\"\", fileContents)\n\tassert.NoError(t, err)\n\tdefer cleanup()\n\n\ttmpdir, cleanup, err := createTempDir(\"\", \"foo\", \"bar\", \"baz\")\n\tassert.NoError(t, err)\n\tdefer cleanup()\n\n\tconn, err := anypb.New(&sourcespb.Filesystem{})\n\tassert.NoError(t, err)\n\n\t// Initialize the source.\n\ts := Source{}\n\terr = s.Init(ctx, \"test chunk unit\", 0, 0, true, conn, 1)\n\tassert.NoError(t, err)\n\n\t// Happy path single file.\n\treporter := sourcestest.TestReporter{}\n\terr = s.ChunkUnit(ctx, sources.CommonSourceUnit{\n\t\tID: tmpfile.Name(),\n\t}, &reporter)\n\tassert.NoError(t, err)\n\n\t// Happy path directory.\n\terr = s.ChunkUnit(ctx, sources.CommonSourceUnit{\n\t\tID: tmpdir,\n\t}, &reporter)\n\tassert.NoError(t, err)\n\n\t// Error path.\n\terr = s.ChunkUnit(ctx, sources.CommonSourceUnit{\n\t\tID: \"/file/not/found\",\n\t}, &reporter)\n\tassert.NoError(t, err)\n\n\tassert.Equal(t, 4, len(reporter.Chunks))\n\tassert.Equal(t, 1, len(reporter.ChunkErrs))\n\tdataFound := make(map[string]struct{}, 4)\n\tfor _, chunk := range reporter.Chunks {\n\t\tdataFound[string(chunk.Data)] = struct{}{}\n\t}\n\tassert.Contains(t, dataFound, fileContents)\n\tassert.Contains(t, dataFound, \"foo\")\n\tassert.Contains(t, dataFound, \"bar\")\n\tassert.Contains(t, dataFound, \"baz\")\n}\n\nfunc TestEnumerateReporterErr(t *testing.T) {\n\tt.Parallel()\n\tctx := trContext.Background()\n\n\t// Setup the connection to test enumeration.\n\tunits := []string{\n\t\t\"/one\", \"/two\", \"/three\",\n\t\t\"/path/to/dir/\", \"/path/to/another/dir/\",\n\t}\n\tconn, err := anypb.New(&sourcespb.Filesystem{\n\t\tPaths:       units[0:3],\n\t\tDirectories: units[3:],\n\t})\n\tassert.NoError(t, err)\n\n\t// Initialize the source.\n\ts := Source{}\n\terr = s.Init(ctx, \"test enumerate\", 0, 0, true, conn, 1)\n\tassert.NoError(t, err)\n\n\t// Enumerate should always return an error if the reporter returns an\n\t// error.\n\treporter := sourcestest.ErrReporter{}\n\terr = s.Enumerate(ctx, &reporter)\n\tassert.Error(t, err)\n}\n\nfunc TestChunkUnitReporterErr(t *testing.T) {\n\tt.Parallel()\n\tctx := trContext.Background()\n\n\t// Setup test file to chunk.\n\ttmpfile, err := os.CreateTemp(\"\", \"example.txt\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer os.Remove(tmpfile.Name())\n\n\tfileContents := []byte(\"TestChunkUnit\")\n\t_, err = tmpfile.Write(fileContents)\n\tassert.NoError(t, err)\n\tassert.NoError(t, tmpfile.Close())\n\n\tconn, err := anypb.New(&sourcespb.Filesystem{})\n\tassert.NoError(t, err)\n\n\t// Initialize the source.\n\ts := Source{}\n\terr = s.Init(ctx, \"test chunk unit\", 0, 0, true, conn, 1)\n\tassert.NoError(t, err)\n\n\t// Happy path. ChunkUnit should always return an error if the reporter\n\t// returns an error.\n\treporter := sourcestest.ErrReporter{}\n\terr = s.ChunkUnit(ctx, sources.CommonSourceUnit{\n\t\tID: tmpfile.Name(),\n\t}, &reporter)\n\tassert.Error(t, err)\n\n\t// Error path. ChunkUnit should always return an error if the reporter\n\t// returns an error.\n\terr = s.ChunkUnit(ctx, sources.CommonSourceUnit{\n\t\tID: \"/file/not/found\",\n\t}, &reporter)\n\tassert.Error(t, err)\n}\n\nfunc TestSkipDir(t *testing.T) {\n\tt.Parallel()\n\tctx := trContext.Background()\n\n\t// create a temp directory with files\n\tignoreDir, cleanupDir, err := createTempDir(\"\", \"ignore1\", \"ignore2\", \"ignore3\")\n\trequire.NoError(t, err)\n\tdefer cleanupDir()\n\n\t// create an ExcludePathsFile that contains the ignoreDir path\n\t// In windows path contains \\ so we escape it by replacing it with \\\\ in ignoreDir\n\texcludeFile, cleanupFile, err := createTempFile(\"\", strings.ReplaceAll(ignoreDir, `\\`, `\\\\`)+\"\\n\")\n\trequire.NoError(t, err)\n\tdefer cleanupFile()\n\n\tconn, err := anypb.New(&sourcespb.Filesystem{\n\t\tExcludePathsFile: excludeFile.Name(),\n\t})\n\trequire.NoError(t, err)\n\n\t// initialize the source.\n\ts := Source{}\n\terr = s.Init(ctx, \"exclude directory\", 0, 0, true, conn, 1)\n\trequire.NoError(t, err)\n\n\treporter := sourcestest.TestReporter{}\n\terr = s.ChunkUnit(ctx, sources.CommonSourceUnit{\n\t\tID: ignoreDir,\n\t}, &reporter)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, 0, len(reporter.Chunks), \"Expected no chunks from excluded directory\")\n\trequire.Equal(t, 0, len(reporter.ChunkErrs), \"Expected no errors for excluded directory\")\n}\n\nfunc TestScanSubDirFile(t *testing.T) {\n\tt.Parallel()\n\tctx := trContext.Background()\n\n\t// Use a fixed directory for the test\n\ttestDir := filepath.Join(os.TempDir(), \"trufflehog-test\")\n\terr := os.MkdirAll(testDir, 0755)\n\trequire.NoError(t, err)\n\tdefer os.RemoveAll(testDir)\n\n\t// Create a subdirectory and file\n\tchildDir := filepath.Join(testDir, \"child\")\n\terr = os.MkdirAll(childDir, 0755)\n\trequire.NoError(t, err)\n\n\tfilePath := filepath.Join(childDir, \"testfile.txt\")\n\terr = os.WriteFile(filePath, []byte(\"should scan this file\"), 0644)\n\trequire.NoError(t, err)\n\n\t// Create an IncludePathsFile with the absolute path of the file\n\tincludeFilePath := filepath.Join(testDir, \"include.txt\")\n\terr = os.WriteFile(includeFilePath, []byte(strings.ReplaceAll(filePath, `\\`, `\\\\`)+\"\\n\"), 0644)\n\trequire.NoError(t, err)\n\n\tconn, err := anypb.New(&sourcespb.Filesystem{\n\t\tIncludePathsFile: includeFilePath,\n\t})\n\trequire.NoError(t, err)\n\n\t// Initialize the source\n\ts := Source{}\n\terr = s.Init(ctx, \"include sub directory file\", 0, 0, true, conn, 1)\n\trequire.NoError(t, err)\n\n\treporter := sourcestest.TestReporter{}\n\terr = s.ChunkUnit(ctx, sources.CommonSourceUnit{\n\t\tID: testDir,\n\t}, &reporter)\n\trequire.NoError(t, err)\n\trequire.Equal(t, 1, len(reporter.Chunks), \"Expected chunks from included file\")\n\trequire.Equal(t, 0, len(reporter.ChunkErrs), \"Expected no errors\")\n}\n\nfunc TestSkipBinaries(t *testing.T) {\n\t// Create a temporary directory for testing\n\ttempDir, err := os.MkdirTemp(\"\", \"trufflehog_test\")\n\trequire.NoError(t, err)\n\tdefer os.RemoveAll(tempDir)\n\n\t// Create a binary file (executable)\n\tbinaryFile := filepath.Join(tempDir, \"test.exe\")\n\terr = os.WriteFile(binaryFile, []byte{0x4D, 0x5A, 0x90, 0x00}, 0644)\n\trequire.NoError(t, err)\n\n\t// Create a text file\n\ttextFile := filepath.Join(tempDir, \"test.txt\")\n\terr = os.WriteFile(textFile, []byte(\"This is a text file\"), 0644)\n\trequire.NoError(t, err)\n\n\t// Test with skipBinaries = true\n\tsource := &Source{\n\t\tpaths:        []string{textFile, binaryFile}, // Test individual files\n\t\tskipBinaries: true,\n\t\tlog:          logr.Discard(),\n\t}\n\n\tchunks := make(chan *sources.Chunk, 10)\n\tctx := trContext.Background()\n\n\t// Run the scan\n\tgo func() {\n\t\terr := source.Chunks(ctx, chunks)\n\t\trequire.NoError(t, err)\n\t\tclose(chunks)\n\t}()\n\n\t// Collect chunks\n\tvar chunkCount int\n\tvar processedFiles []string\n\tfor chunk := range chunks {\n\t\tchunkCount++\n\t\tmetadata := chunk.SourceMetadata.GetFilesystem()\n\t\trequire.NotNil(t, metadata)\n\t\tprocessedFiles = append(processedFiles, metadata.File)\n\t}\n\n\t// Should have exactly one chunk from the text file\n\trequire.Equal(t, 1, chunkCount, \"Should have processed exactly one text file\")\n\trequire.Contains(t, processedFiles, textFile, \"Should have processed the text file\")\n\trequire.NotContains(t, processedFiles, binaryFile, \"Binary file should be skipped\")\n}\n\nfunc TestResumptionInfoDoesNotGrowWithSubdirectories(t *testing.T) {\n\tctx := trContext.AddLogger(t.Context())\n\n\t// Create a deeply nested directory structure with files at each level.\n\t// Structure: root/dir0/dir1/dir2/.../dir9, each containing a file.\n\trootDir, err := os.MkdirTemp(\"\", \"trufflehog-resumption-test\")\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { _ = os.RemoveAll(rootDir) })\n\n\tconst numSubdirs = 10\n\tcurrentDir := rootDir\n\tfor i := 0; i < numSubdirs; i++ {\n\t\t// Create a file in the current directory\n\t\tfilePath := filepath.Join(currentDir, fmt.Sprintf(\"file%d.txt\", i))\n\t\terr := os.WriteFile(filePath, []byte(fmt.Sprintf(\"content %d\", i)), 0644)\n\t\trequire.NoError(t, err)\n\n\t\t// Create the next subdirectory\n\t\tsubDir := filepath.Join(currentDir, fmt.Sprintf(\"subdir%d\", i))\n\t\terr = os.Mkdir(subDir, 0755)\n\t\trequire.NoError(t, err)\n\t\tcurrentDir = subDir\n\t}\n\t// Create a file in the deepest directory\n\terr = os.WriteFile(filepath.Join(currentDir, \"deepest.txt\"), []byte(\"deepest\"), 0644)\n\trequire.NoError(t, err)\n\n\tconn, err := anypb.New(&sourcespb.Filesystem{MaxSymlinkDepth: 0})\n\trequire.NoError(t, err)\n\n\t// Initialize the source.\n\ts := Source{}\n\terr = s.Init(ctx, \"test resumption growth\", 0, 0, true, conn, 1)\n\trequire.NoError(t, err)\n\n\t// Track the maximum size of EncodedResumeInfo during the scan.\n\tvar maxResumeInfoSize int\n\tvar mu sync.Mutex\n\n\t// We need to periodically check the resume info size during scanning.\n\t// Run ChunkUnit in a goroutine and poll the progress.\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(done)\n\t\treporter := sourcestest.TestReporter{}\n\t\terr := s.ChunkUnit(ctx, sources.CommonSourceUnit{\n\t\t\tID: rootDir,\n\t\t}, &reporter)\n\t\trequire.NoError(t, err)\n\t}()\n\n\t// Poll the resume info size while scanning is in progress.\n\tticker := time.NewTicker(1 * time.Millisecond)\n\tdefer ticker.Stop()\n\npolling:\n\tfor {\n\t\tselect {\n\t\tcase <-done:\n\t\t\tbreak polling\n\t\tcase <-ticker.C:\n\t\t\tprogress := s.GetProgress()\n\t\t\tmu.Lock()\n\t\t\tif len(progress.EncodedResumeInfo) > maxResumeInfoSize {\n\t\t\t\tmaxResumeInfoSize = len(progress.EncodedResumeInfo)\n\t\t\t}\n\t\t\tmu.Unlock()\n\t\t}\n\t}\n\n\t// After scan completes, check the final state.\n\tfinalProgress := s.GetProgress()\n\tt.Logf(\"Final EncodedResumeInfo length: %d\", len(finalProgress.EncodedResumeInfo))\n\tt.Logf(\"Max EncodedResumeInfo length during scan: %d\", maxResumeInfoSize)\n\n\t// Parse the resume info to count entries if it's not empty.\n\tif maxResumeInfoSize > 0 {\n\t\tvar resumeMap map[string]string\n\t\terr := json.Unmarshal([]byte(finalProgress.EncodedResumeInfo), &resumeMap)\n\t\tif err == nil {\n\t\t\tt.Logf(\"Final resume info entries: %d\", len(resumeMap))\n\t\t}\n\t}\n\n\t// The key assertion: resumption info should NOT grow proportionally with\n\t// the number of subdirectories. During the scan, it should only track the\n\t// current position, not accumulate entries for every directory visited.\n\t//\n\t// With proper implementation, resume info should have at most a few entries\n\t// (e.g., one per directory being actively scanned), not one entry per\n\t// directory that has ever been visited.\n\t//\n\t// A reasonable upper bound for resume info size: each entry is roughly\n\t// \"rootPath#subPath\": \"filePath\". With temp paths ~50 chars, one entry is\n\t// ~150 bytes with JSON overhead. For 10 directories, accumulation would\n\t// mean ~1500+ bytes. A non-accumulating implementation should stay well\n\t// under that.\n\tconst maxAcceptableResumeInfoSize = 300 // bytes - allows for ~2 entries max\n\tassert.LessOrEqual(t, maxResumeInfoSize, maxAcceptableResumeInfoSize,\n\t\t\"Resume info grew to %d bytes during scan, suggesting accumulation across %d subdirectories. \"+\n\t\t\t\"Resume info should not accumulate entries for each subdirectory visited.\",\n\t\tmaxResumeInfoSize, numSubdirs)\n}\n\nfunc TestResumptionSkipsAlreadyScannedFiles(t *testing.T) {\n\tctx := trContext.Background()\n\n\t// Create a directory with files that have predictable alphabetical order.\n\trootDir, err := os.MkdirTemp(\"\", \"trufflehog-resumption-test\")\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { _ = os.RemoveAll(rootDir) })\n\n\t// Create files with predictable names for sorting.\n\tfiles := []string{\"aaa.txt\", \"bbb.txt\", \"ccc.txt\", \"ddd.txt\"}\n\tfor _, name := range files {\n\t\tfilePath := filepath.Join(rootDir, name)\n\t\terr := os.WriteFile(filePath, []byte(\"content of \"+name), 0644)\n\t\trequire.NoError(t, err)\n\t}\n\n\tconn, err := anypb.New(&sourcespb.Filesystem{})\n\trequire.NoError(t, err)\n\n\t// Initialize the source.\n\ts := Source{}\n\terr = s.Init(ctx, \"test resumption\", 0, 0, true, conn, 1)\n\trequire.NoError(t, err)\n\n\t// Pre-set the resume point to simulate a previous interrupted scan.\n\t// Setting it to bbb.txt means we should skip aaa.txt and bbb.txt,\n\t// and only scan ccc.txt and ddd.txt.\n\tresumePoint := filepath.Join(rootDir, \"bbb.txt\")\n\ts.SetEncodedResumeInfoFor(rootDir, resumePoint)\n\n\t// Run the scan.\n\treporter := sourcestest.TestReporter{}\n\terr = s.ChunkUnit(ctx, sources.CommonSourceUnit{ID: rootDir}, &reporter)\n\trequire.NoError(t, err)\n\n\t// Collect scanned file names.\n\tscannedFiles := make(map[string]bool)\n\tfor _, chunk := range reporter.Chunks {\n\t\tfile := chunk.SourceMetadata.GetFilesystem().GetFile()\n\t\tscannedFiles[filepath.Base(file)] = true\n\t}\n\n\t// Assert only files after the resume point were scanned.\n\tassert.False(t, scannedFiles[\"aaa.txt\"], \"aaa.txt should have been skipped (before resume point)\")\n\tassert.False(t, scannedFiles[\"bbb.txt\"], \"bbb.txt should have been skipped (the resume point itself)\")\n\tassert.True(t, scannedFiles[\"ccc.txt\"], \"ccc.txt should have been scanned (after resume point)\")\n\tassert.True(t, scannedFiles[\"ddd.txt\"], \"ddd.txt should have been scanned (after resume point)\")\n\tassert.Equal(t, 2, len(reporter.Chunks), \"expected exactly 2 files to be scanned\")\n}\n\nfunc TestResumptionWithNestedDirectories(t *testing.T) {\n\tctx := trContext.Background()\n\n\t// Create a nested directory structure:\n\t// root/\n\t//   aaa/\n\t//     file1.txt\n\t//   bbb/\n\t//     file2.txt\n\t//   ccc/\n\t//     file3.txt\n\trootDir, err := os.MkdirTemp(\"\", \"trufflehog-resumption-nested-test\")\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { _ = os.RemoveAll(rootDir) })\n\n\tdirs := []string{\"aaa\", \"bbb\", \"ccc\"}\n\tfor i, dir := range dirs {\n\t\tdirPath := filepath.Join(rootDir, dir)\n\t\terr := os.Mkdir(dirPath, 0755)\n\t\trequire.NoError(t, err)\n\n\t\tfilePath := filepath.Join(dirPath, fmt.Sprintf(\"file%d.txt\", i+1))\n\t\terr = os.WriteFile(filePath, []byte(fmt.Sprintf(\"content of file%d\", i+1)), 0644)\n\t\trequire.NoError(t, err)\n\t}\n\n\tconn, err := anypb.New(&sourcespb.Filesystem{})\n\trequire.NoError(t, err)\n\n\t// Initialize the source.\n\ts := Source{}\n\terr = s.Init(ctx, \"test resumption nested\", 0, 0, true, conn, 1)\n\trequire.NoError(t, err)\n\n\t// Pre-set the resume point to bbb/file2.txt.\n\t// This should skip aaa/file1.txt and bbb/file2.txt, only scanning ccc/file3.txt.\n\tresumePoint := filepath.Join(rootDir, \"bbb\", \"file2.txt\")\n\ts.SetEncodedResumeInfoFor(rootDir, resumePoint)\n\n\t// Run the scan.\n\treporter := sourcestest.TestReporter{}\n\terr = s.ChunkUnit(ctx, sources.CommonSourceUnit{ID: rootDir}, &reporter)\n\trequire.NoError(t, err)\n\n\t// Collect scanned file names.\n\tscannedFiles := make(map[string]bool)\n\tfor _, chunk := range reporter.Chunks {\n\t\tfile := chunk.SourceMetadata.GetFilesystem().GetFile()\n\t\tscannedFiles[filepath.Base(file)] = true\n\t}\n\n\t// Assert only file3.txt was scanned.\n\tassert.False(t, scannedFiles[\"file1.txt\"], \"file1.txt should have been skipped (in aaa/, before resume point)\")\n\tassert.False(t, scannedFiles[\"file2.txt\"], \"file2.txt should have been skipped (the resume point itself)\")\n\tassert.True(t, scannedFiles[\"file3.txt\"], \"file3.txt should have been scanned (in ccc/, after resume point)\")\n\tassert.Equal(t, 1, len(reporter.Chunks), \"expected exactly 1 file to be scanned\")\n}\n\nfunc TestResumptionWithOutOfSubtreeResumePoint(t *testing.T) {\n\tctx := trContext.Background()\n\n\t// Create a directory structure:\n\t// root/\n\t//   aaa/\n\t//     file1.txt\n\t//   bbb/\n\t//     file2.txt\n\t//   ccc/\n\t//     file3.txt\n\t//\n\t// This test verifies correct behavior when scanDir is called for a directory\n\t// with a resume point OUTSIDE that directory's subtree. Since os.ReadDir\n\t// returns entries sorted by filename, directories that lexicographically\n\t// precede the resume point were already fully scanned and should be skipped.\n\trootDir, err := os.MkdirTemp(\"\", \"trufflehog-resumption-subtree-test\")\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { _ = os.RemoveAll(rootDir) })\n\n\tdirs := []string{\"aaa\", \"bbb\", \"ccc\"}\n\tfor i, dir := range dirs {\n\t\tdirPath := filepath.Join(rootDir, dir)\n\t\terr := os.Mkdir(dirPath, 0755)\n\t\trequire.NoError(t, err)\n\n\t\tfilePath := filepath.Join(dirPath, fmt.Sprintf(\"file%d.txt\", i+1))\n\t\terr = os.WriteFile(filePath, []byte(fmt.Sprintf(\"content of file%d\", i+1)), 0644)\n\t\trequire.NoError(t, err)\n\t}\n\n\tconn, err := anypb.New(&sourcespb.Filesystem{})\n\trequire.NoError(t, err)\n\n\t// Initialize the source.\n\ts := Source{}\n\terr = s.Init(ctx, \"test resumption subtree\", 0, 0, true, conn, 1)\n\trequire.NoError(t, err)\n\n\t// Pre-set the resume point to bbb/file2.txt using aaaDir as the key.\n\t// This simulates an edge case where scanDir is called directly for a\n\t// directory with a resume point outside its subtree.\n\taaaDir := filepath.Join(rootDir, \"aaa\")\n\tresumePoint := filepath.Join(rootDir, \"bbb\", \"file2.txt\")\n\ts.SetEncodedResumeInfoFor(aaaDir, resumePoint)\n\n\t// Scan the aaa directory with a resume point outside its subtree.\n\treporter := sourcestest.TestReporter{}\n\terr = s.ChunkUnit(ctx, sources.CommonSourceUnit{ID: aaaDir}, &reporter)\n\trequire.NoError(t, err)\n\n\t// Collect scanned file names.\n\tscannedFiles := make(map[string]bool)\n\tfor _, chunk := range reporter.Chunks {\n\t\tfile := chunk.SourceMetadata.GetFilesystem().GetFile()\n\t\tscannedFiles[filepath.Base(file)] = true\n\t}\n\n\t// file1.txt should NOT be scanned because aaa/ comes before bbb/\n\t// lexicographically, meaning aaa/ would have been fully processed\n\t// before reaching the resume point.\n\tassert.False(t, scannedFiles[\"file1.txt\"],\n\t\t\"file1.txt should NOT be scanned because aaa/ comes before resume point bbb/file2.txt lexicographically\")\n\tassert.Equal(t, 0, len(reporter.Chunks),\n\t\t\"expected 0 files to be scanned since aaa/ was already fully processed before the resume point\")\n}\n\n// createTempFile is a helper function to create a temporary file in the given\n// directory with the provided contents. If dir is \"\", the operating system's\n// temp directory is used.\nfunc createTempFile(dir string, contents string) (*os.File, func(), error) {\n\ttmpfile, err := os.CreateTemp(dir, \"trufflehogtest\")\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tif _, err := tmpfile.Write([]byte(contents)); err != nil {\n\t\t_ = os.Remove(tmpfile.Name())\n\t\treturn nil, nil, err\n\t}\n\tif err := tmpfile.Close(); err != nil {\n\t\t_ = os.Remove(tmpfile.Name())\n\t\treturn nil, nil, err\n\t}\n\treturn tmpfile, func() { _ = os.Remove(tmpfile.Name()) }, nil\n}\n\n// createTempDir is a helper function to create a temporary directory in the\n// given directory with files containing the provided contents. If dir is \"\",\n// the operating system's temp directory is used.\nfunc createTempDir(dir string, contents ...string) (string, func(), error) {\n\ttmpdir, err := os.MkdirTemp(dir, \"trufflehogtest\")\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\tfor _, content := range contents {\n\t\tif _, _, err := createTempFile(tmpdir, content); err != nil {\n\t\t\t_ = os.RemoveAll(tmpdir)\n\t\t\treturn \"\", nil, err\n\t\t}\n\t}\n\treturn tmpdir, func() { _ = os.RemoveAll(tmpdir) }, nil\n}\n"
  },
  {
    "path": "pkg/sources/gcs/gcs.go",
    "content": "package gcs\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"cloud.google.com/go/storage\"\n\t\"github.com/go-errors/errors\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/log\"\n\t\"golang.org/x/oauth2\"\n\t\"golang.org/x/oauth2/endpoints\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/handlers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/credentialspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nconst (\n\tSourceType = sourcespb.SourceType_SOURCE_TYPE_GCS\n\n\tdefaultCachePersistIncrement = 2500\n)\n\n// Ensure the Source satisfies the interfaces at compile time.\nvar _ sources.Source = (*Source)(nil)\nvar _ sources.SourceUnitUnmarshaller = (*Source)(nil)\n\n// Type returns the type of source.\n// It is used for matching source types in configuration and job input.\nfunc (s *Source) Type() sourcespb.SourceType {\n\treturn SourceType\n}\n\n// SourceID number for GCS Source.\nfunc (s *Source) SourceID() sources.SourceID {\n\treturn s.sourceId\n}\n\n// JobID number for GCS Source.\nfunc (s *Source) JobID() sources.JobID {\n\treturn s.jobId\n}\n\ntype objectManager interface {\n\tListObjects(context.Context) (chan io.Reader, error)\n\tAttributes(ctx context.Context) (*attributes, error)\n}\n\n// Source represents a GCS source.\ntype Source struct {\n\tname        string\n\tjobId       sources.JobID\n\tsourceId    sources.SourceID\n\tconcurrency int\n\tverify      bool\n\n\tgcsManager objectManager\n\tstats      *attributes\n\tlog        logr.Logger\n\tchunksCh   chan *sources.Chunk\n\n\tmu               sync.Mutex\n\tsources.Progress // progress is not thread safe\n\tsources.CommonSourceUnitUnmarshaller\n}\n\n// persistableCache is a wrapper around cache.Cache that allows\n// for the persistence of the cache contents in the Progress of the source\n// at given increments.\ntype persistableCache struct {\n\tpersistIncrement int\n\tcache.Cache[string]\n\t*sources.Progress\n}\n\nfunc newPersistableCache(increment int, cache cache.Cache[string], p *sources.Progress) *persistableCache {\n\treturn &persistableCache{\n\t\tpersistIncrement: increment,\n\t\tCache:            cache,\n\t\tProgress:         p,\n\t}\n}\n\n// Set overrides the cache Set method of the cache to enable the persistence\n// of the cache contents the Progress of the source at given increments.\nfunc (c *persistableCache) Set(key string, val string) {\n\tc.Cache.Set(key, val)\n\tif ok, contents := c.shouldPersist(); ok {\n\t\tc.Progress.EncodedResumeInfo = contents\n\t}\n}\n\nfunc (c *persistableCache) shouldPersist() (bool, string) {\n\tif c.Count()%c.persistIncrement != 0 {\n\t\treturn false, \"\"\n\t}\n\treturn true, c.Contents()\n}\n\n// Init returns an initialized GCS source.\nfunc (s *Source) Init(aCtx context.Context, name string, id sources.JobID, sourceID sources.SourceID, verify bool, connection *anypb.Any, concurrency int) error {\n\ts.log = aCtx.Logger()\n\n\ts.name = name\n\ts.verify = verify\n\ts.sourceId = sourceID\n\ts.jobId = id\n\ts.concurrency = concurrency\n\n\tvar conn sourcespb.GCS\n\terr := anypb.UnmarshalTo(connection, &conn, proto.UnmarshalOptions{})\n\tif err != nil {\n\t\treturn errors.WrapPrefix(err, \"error unmarshalling connection\", 0)\n\t}\n\n\tgcsManager, err := configureGCSManager(aCtx, &conn, concurrency)\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.gcsManager = gcsManager\n\n\ts.log.V(2).Info(\"enumerating buckets and objects\")\n\tif err := s.enumerate(aCtx); err != nil {\n\t\treturn fmt.Errorf(\"error enumerating buckets and objects: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc configureGCSManager(aCtx context.Context, conn *sourcespb.GCS, concurrency int) (*gcsManager, error) {\n\tif conn == nil {\n\t\treturn nil, fmt.Errorf(\"GCS connection is nil, cannot configure GCS manager\")\n\t}\n\n\tvar gcsManagerAuthOption gcsManagerOption\n\n\tswitch conn.Credential.(type) {\n\tcase *sourcespb.GCS_ApiKey:\n\t\tgcsManagerAuthOption = withAPIKey(aCtx, conn.GetApiKey())\n\t\tlog.RedactGlobally(conn.GetApiKey())\n\tcase *sourcespb.GCS_ServiceAccountFile:\n\t\tb, err := os.ReadFile(conn.GetServiceAccountFile())\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error reading GCS JSON Service Account file: %w\", err)\n\t\t}\n\t\tgcsManagerAuthOption = withJSONServiceAccount(aCtx, b)\n\tcase *sourcespb.GCS_JsonServiceAccount:\n\t\tgcsManagerAuthOption = withJSONServiceAccount(aCtx, []byte(conn.GetJsonServiceAccount()))\n\tcase *sourcespb.GCS_Adc:\n\t\tgcsManagerAuthOption = withDefaultADC(aCtx)\n\tcase *sourcespb.GCS_Unauthenticated:\n\t\tgcsManagerAuthOption = withoutAuthentication()\n\tcase *sourcespb.GCS_Oauth:\n\t\tclient, err := oauth2Client(aCtx, conn.GetOauth())\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error creating oauth2 client: %w\", err)\n\t\t}\n\t\tgcsManagerAuthOption = withHTTPClient(aCtx, client)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown GCS authentication type: %T\", conn.Credential)\n\n\t}\n\n\tgcsManagerOpts := []gcsManagerOption{\n\t\twithConcurrency(concurrency),\n\t\twithMaxObjectSize(conn.MaxObjectSize),\n\t\tgcsManagerAuthOption,\n\t}\n\tif setGCSManagerBucketOptions(conn) != nil {\n\t\tgcsManagerOpts = append(gcsManagerOpts, setGCSManagerBucketOptions(conn))\n\t}\n\tif setGCSManagerObjectOptions(conn) != nil {\n\t\tgcsManagerOpts = append(gcsManagerOpts, setGCSManagerObjectOptions(conn))\n\t}\n\n\tgcsManager, err := newGCSManager(conn.ProjectId, gcsManagerOpts...)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error creating GCS manager: %w\", err)\n\t}\n\n\treturn gcsManager, nil\n}\n\nfunc oauth2Client(ctx context.Context, creds *credentialspb.Oauth2) (*http.Client, error) {\n\tif creds == nil {\n\t\treturn nil, fmt.Errorf(\"oauth2 credentials are required\")\n\t}\n\tif creds.GetClientId() == \"\" || creds.GetRefreshToken() == \"\" || creds.GetAccessToken() == \"\" {\n\t\treturn nil, fmt.Errorf(\"oauth2 credentials are incomplete, client_id, refresh_token, and access_token are required\")\n\t}\n\n\tconf := &oauth2.Config{\n\t\tClientID: creds.GetClientId(),\n\t\tScopes:   []string{storage.ScopeReadOnly},\n\t\tEndpoint: oauth2.Endpoint{\n\t\t\tAuthURL:  endpoints.Google.AuthURL,\n\t\t\tTokenURL: endpoints.Google.TokenURL,\n\t\t},\n\t}\n\n\ttok := &oauth2.Token{\n\t\tAccessToken:  creds.GetAccessToken(),\n\t\tRefreshToken: creds.GetRefreshToken(),\n\t}\n\n\treturn conf.Client(ctx, tok), nil\n}\n\nfunc setGCSManagerBucketOptions(conn *sourcespb.GCS) gcsManagerOption {\n\treturn setGCSManagerOptions(conn.GetIncludeBuckets(), conn.GetExcludeBuckets(), withIncludeBuckets, withExcludeBuckets)\n}\n\nfunc setGCSManagerObjectOptions(conn *sourcespb.GCS) gcsManagerOption {\n\treturn setGCSManagerOptions(conn.GetIncludeObjects(), conn.GetExcludeObjects(), withIncludeObjects, withExcludeObjects)\n}\n\nfunc setGCSManagerOptions(include, exclude []string, includeFn, excludeFn func([]string) gcsManagerOption) gcsManagerOption {\n\t// Only allow one of include/exclude to be set.\n\t// If both are set, include takes precedence.\n\tif len(include) > 0 {\n\t\treturn includeFn(include)\n\t}\n\tif len(exclude) > 0 {\n\t\treturn excludeFn(exclude)\n\t}\n\n\treturn nil\n}\n\n// enumerate all the objects and buckets in the source.\n// This will be used to calculate progress.\nfunc (s *Source) enumerate(ctx context.Context) error {\n\tstats, err := s.gcsManager.Attributes(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting attributes during enumeration: %w\", err)\n\t}\n\ts.stats = stats\n\n\treturn nil\n}\n\n// Chunks emits chunks of bytes over a channel.\nfunc (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, _ ...sources.ChunkingTarget) error {\n\tpersistableCache := s.setupCache(ctx)\n\n\tobjectCh, err := s.gcsManager.ListObjects(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error listing objects: %w\", err)\n\t}\n\ts.chunksCh = chunksChan\n\ts.Progress.Message = \"starting to process objects...\"\n\n\tvar wg sync.WaitGroup\n\tfor obj := range objectCh {\n\t\to, ok := obj.(object)\n\t\tif !ok {\n\t\t\tctx.Logger().Error(fmt.Errorf(\"unexpected object type: %T\", obj), \"GCS source unexpected object type\", \"name\", s.name)\n\t\t\tcontinue\n\t\t}\n\n\t\tif persistableCache.Exists(o.md5) {\n\t\t\tctx.Logger().V(5).Info(\"skipping object, object already processed\", \"name\", o.name)\n\t\t\tcontinue\n\t\t}\n\n\t\twg.Add(1)\n\t\tgo func(obj object) {\n\t\t\tdefer wg.Done()\n\n\t\t\tif err := s.processObject(ctx, o); err != nil {\n\t\t\t\tctx.Logger().V(1).Info(\"error setting start progress progress\", \"name\", o.name, \"error\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\ts.setProgress(ctx, o.md5, o.name, persistableCache)\n\t\t}(o)\n\t}\n\twg.Wait()\n\n\ts.completeProgress(ctx)\n\treturn nil\n}\n\nfunc (s *Source) setupCache(ctx context.Context) *persistableCache {\n\tvar c cache.Cache[string]\n\tif s.Progress.EncodedResumeInfo != \"\" {\n\t\tkeys := strings.Split(s.Progress.EncodedResumeInfo, \",\")\n\t\tentries := make([]simple.CacheEntry[string], len(keys))\n\t\tfor i, val := range keys {\n\t\t\tentries[i] = simple.CacheEntry[string]{Key: val, Value: val}\n\t\t}\n\n\t\tc = simple.NewCacheWithData[string](entries)\n\t\tctx.Logger().V(3).Info(\"Loaded cache\", \"num_entries\", len(entries))\n\t} else {\n\t\tc = simple.NewCache[string]()\n\t}\n\n\t// TODO (ahrav): Make this configurable via conn.\n\tpersistCache := newPersistableCache(defaultCachePersistIncrement, c, &s.Progress)\n\treturn persistCache\n}\n\nfunc (s *Source) setProgress(ctx context.Context, md5, objName string, cache cache.Cache[string]) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\tctx.Logger().V(5).Info(\"setting progress for object\", \"object-name\", objName)\n\ts.SectionsCompleted++\n\n\tcache.Set(md5, md5)\n\ts.Progress.SectionsRemaining = int32(s.stats.numObjects)\n\ts.Progress.PercentComplete = int64(float64(s.SectionsCompleted) / float64(s.stats.numObjects) * 100)\n}\n\nfunc (s *Source) completeProgress(ctx context.Context) {\n\tmsg := fmt.Sprintf(\"GCS source finished processing %d objects\", s.stats.numObjects)\n\tctx.Logger().Info(msg)\n\ts.Progress.Message = msg\n}\n\nfunc (s *Source) processObject(ctx context.Context, o object) error {\n\tchunkSkel := &sources.Chunk{\n\t\tSourceName:   s.name,\n\t\tSourceType:   s.Type(),\n\t\tJobID:        s.JobID(),\n\t\tSourceID:     s.sourceId,\n\t\tSourceVerify: s.verify,\n\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\tData: &source_metadatapb.MetaData_Gcs{\n\t\t\t\tGcs: &source_metadatapb.GCS{\n\t\t\t\t\tBucket:      o.bucket,\n\t\t\t\t\tFilename:    o.name,\n\t\t\t\t\tLink:        o.link,\n\t\t\t\t\tEmail:       o.owner,\n\t\t\t\t\tContentType: o.contentType,\n\t\t\t\t\tAcls:        o.acl,\n\t\t\t\t\tCreatedAt:   strconv.FormatInt(o.createdAt.Unix(), 10), // Unix time as string\n\t\t\t\t\tUpdatedAt:   o.updatedAt.String(),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\treturn handlers.HandleFile(ctx, io.NopCloser(o), chunkSkel, sources.ChanReporter{Ch: s.chunksCh})\n}\n"
  },
  {
    "path": "pkg/sources/gcs/gcs_integration_test.go",
    "content": "//go:build integration\n// +build integration\n\npackage gcs\n\nimport (\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nfunc TestChunks(t *testing.T) {\n\tctx := context.Background()\n\n\tsource, conn := createTestSource(&sourcespb.GCS{\n\t\tProjectId:      testProjectID,\n\t\tCredential:     &sourcespb.GCS_Adc{},\n\t\tExcludeBuckets: []string{perfTestBucketGlob, publicBucket},\n\t})\n\n\terr := source.Init(ctx, \"test\", 1, 1, true, conn, 8)\n\tassert.Nil(t, err)\n\n\tchunksCh := make(chan *sources.Chunk, 1)\n\n\tgo func() {\n\t\tdefer close(chunksCh)\n\t\terr := source.Chunks(ctx, chunksCh)\n\t\tassert.Nil(t, err)\n\t}()\n\n\twant := createTestChunks()\n\n\tgot := make([]*sources.Chunk, 0, len(want))\n\tfor chunk := range chunksCh {\n\t\tgot = append(got, chunk)\n\t}\n\tsort.Slice(got, func(i, j int) bool {\n\t\treturn got[i].SourceMetadata.GetGcs().Filename < got[j].SourceMetadata.GetGcs().Filename\n\t})\n\n\tassert.Equal(t, len(want), len(got))\n\n\tfor i, chunk := range got {\n\t\tif diff := cmp.Diff(want[i].SourceMetadata.GetGcs(), chunk.SourceMetadata.GetGcs(),\n\t\t\tcmpopts.IgnoreFields(source_metadatapb.GCS{}, \"state\", \"sizeCache\", \"unknownFields\", \"CreatedAt\", \"UpdatedAt\"),\n\t\t); diff != \"\" {\n\t\t\tt.Errorf(\"chunk mismatch (-want +got):\\n%s\", diff)\n\t\t}\n\t}\n}\n\nfunc TestChunks_PublicBucket(t *testing.T) {\n\tctx := context.Background()\n\n\tsource, conn := createTestSource(&sourcespb.GCS{\n\t\tCredential:     &sourcespb.GCS_Unauthenticated{},\n\t\tIncludeBuckets: []string{publicBucket},\n\t})\n\n\terr := source.Init(ctx, \"test\", 1, 1, true, conn, 8)\n\tassert.Nil(t, err)\n\n\tchunksCh := make(chan *sources.Chunk, 1)\n\n\tgo func() {\n\t\tdefer close(chunksCh)\n\t\terr := source.Chunks(ctx, chunksCh)\n\t\tassert.Nil(t, err)\n\t}()\n\n\twant := []*sources.Chunk{\n\t\t{\n\t\t\tSourceName:   \"test\",\n\t\t\tSourceType:   sourcespb.SourceType_SOURCE_TYPE_GCS,\n\t\t\tSourceID:     0,\n\t\t\tSourceVerify: true,\n\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Gcs{\n\t\t\t\t\tGcs: &source_metadatapb.GCS{\n\t\t\t\t\t\tFilename:    \"aws1.txt\",\n\t\t\t\t\t\tBucket:      publicBucket,\n\t\t\t\t\t\tContentType: \"text/plain\",\n\t\t\t\t\t\tEmail:       \"\",\n\t\t\t\t\t\tLink:        \"https://storage.googleapis.com/download/storage/v1/b/public-trufflehog-test-bucket/o/aws1.txt?generation=1678334408999764&alt=media\",\n\t\t\t\t\t\tAcls:        []string{},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tgot := make([]*sources.Chunk, 0, len(want))\n\tfor chunk := range chunksCh {\n\t\tgot = append(got, chunk)\n\t}\n\tsort.Slice(got, func(i, j int) bool {\n\t\treturn got[i].SourceMetadata.GetGcs().Filename < got[j].SourceMetadata.GetGcs().Filename\n\t})\n\n\tassert.Equal(t, len(want), len(got))\n\n\tfor i, chunk := range got {\n\t\tif diff := cmp.Diff(want[i].SourceMetadata.GetGcs(), chunk.SourceMetadata.GetGcs(),\n\t\t\tcmpopts.IgnoreFields(source_metadatapb.GCS{}, \"state\", \"sizeCache\", \"unknownFields\", \"CreatedAt\", \"UpdatedAt\"),\n\t\t); diff != \"\" {\n\t\t\tt.Errorf(\"chunk mismatch (-want +got):\\n%s\", diff)\n\t\t}\n\t}\n}\n\nfunc createTestChunks() []*sources.Chunk {\n\tobjects := []object{\n\t\t{\n\t\t\tname:        \"aws1.txt\",\n\t\t\tbucket:      testBucket,\n\t\t\tcontentType: \"text/plain\",\n\t\t\tsize:        150,\n\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/aws1.txt?generation=1677870994890594&alt=media\",\n\t\t\tacl:         []string{},\n\t\t},\n\t\t{\n\t\t\tname:        \"moar2.txt\",\n\t\t\tbucket:      testBucket,\n\t\t\tcontentType: \"text/plain\",\n\t\t\tsize:        12,\n\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/moar2.txt?generation=1677871000378542&alt=media\",\n\t\t\tacl:         []string{},\n\t\t},\n\t\t{\n\t\t\tname:        \"aws3.txt\",\n\t\t\tbucket:      testBucket2,\n\t\t\tcontentType: \"text/plain\",\n\t\t\tsize:        150,\n\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th2/o/aws3.txt?generation=1677871022489611&alt=media\",\n\t\t\tacl:         []string{},\n\t\t},\n\t\t{\n\t\t\tname:        \"moar.txt\",\n\t\t\tbucket:      testBucket3,\n\t\t\tcontentType: \"text/plain\",\n\t\t\tsize:        6,\n\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th3/o/moar.txt?generation=1677871042896804&alt=media\",\n\t\t\tacl:         []string{},\n\t\t},\n\t\t{\n\t\t\tname:        \"AMAZON_FASHION_5.json\",\n\t\t\tbucket:      testBucket4,\n\t\t\tcontentType: \"application/json\",\n\t\t\tsize:        1413469,\n\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th4/o/AMAZON_FASHION_5.json?generation=1677871063457469&alt=media\",\n\t\t\tacl:         []string{},\n\t\t},\n\t}\n\n\tchunks := make([]*sources.Chunk, 0, len(objects))\n\tfor _, o := range objects {\n\t\tchunks = append(chunks, &sources.Chunk{\n\t\t\tSourceName:   \"test\",\n\t\t\tSourceType:   sourcespb.SourceType_SOURCE_TYPE_GCS,\n\t\t\tSourceID:     0,\n\t\t\tSourceVerify: true,\n\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Gcs{\n\t\t\t\t\tGcs: &source_metadatapb.GCS{\n\t\t\t\t\t\tFilename:    o.name,\n\t\t\t\t\t\tBucket:      o.bucket,\n\t\t\t\t\t\tContentType: o.contentType,\n\t\t\t\t\t\tEmail:       o.owner,\n\t\t\t\t\t\tLink:        o.link,\n\t\t\t\t\t\tAcls:        o.acl,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t}\n\tsort.Slice(chunks, func(i, j int) bool {\n\t\treturn chunks[i].SourceMetadata.GetGcs().Filename < chunks[j].SourceMetadata.GetGcs().Filename\n\t})\n\n\treturn chunks\n}\n"
  },
  {
    "path": "pkg/sources/gcs/gcs_manager.go",
    "content": "package gcs\n\nimport (\n\taCtx \"context\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"runtime\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"cloud.google.com/go/storage\"\n\t\"github.com/gobwas/glob\"\n\t\"github.com/googleapis/gax-go/v2\"\n\t\"github.com/pkg/errors\"\n\t\"golang.org/x/sync/errgroup\"\n\t\"google.golang.org/api/iterator\"\n\t\"google.golang.org/api/option\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"golang.org/x/oauth2/google\"\n)\n\nconst (\n\tdefaultMaxObjectSize = 10 * 1024 * 1024 // 10MB\n\tmaxObjectSizeLimit   = 50 * 1024 * 1024 // 50MB\n)\n\nvar defaultConcurrency = runtime.NumCPU()\n\n// bucketManager is a simplified *storage.Client wrapper.\n// It provides only a subset of methods that are needed by the gcsManager.\ntype bucketManager interface {\n\t// Bucket returns a BucketHandle for the given bucket name.\n\tBucket(name string) *storage.BucketHandle\n\t// Buckets returns an iterator over the buckets in the project.\n\tBuckets(ctx aCtx.Context, projectID string) *storage.BucketIterator\n}\n\n// gcsManager serves as simple facade for interacting with GCS.\n// It's main purpose is to retrieve objects from GCS.\ntype gcsManager struct {\n\tprojectID string\n\twithoutAuth,\n\thasEnumerated bool\n\n\tconcurrency int\n\tworkerPool  *errgroup.Group\n\n\tmaxObjectSize int64\n\tnumBuckets    uint32\n\tnumObjects    uint64\n\n\tincludeBuckets,\n\texcludeBuckets,\n\tincludeObjects,\n\texcludeObjects map[string]struct{}\n\n\tbuckets map[string]bucket\n\tattr    *attributes\n\n\tclient bucketManager\n}\n\n// bucket is a simplified *storage.BucketHandle wrapper.\n// It also provides a mechanism for resuming an interrupted iteration.\n// This reduces the number of objects that need to be re-processed.\ntype bucket struct {\n\tshouldInclude bool\n\tname          string\n\t// startOffset is the name of the object to resume from. (inclusive)\n\t// This works because GCS objects are sorted lexicographically.\n\t// If this is empty, the iteration will start from the beginning.\n\tstartOffset string\n\t*storage.BucketHandle\n}\n\n// offsetInfo is used to resume an interrupted iteration.\ntype offsetInfo struct {\n\tisBucketProcessed   bool\n\tlastProcessedObject string\n}\n\n// attributes contains metadata about the GCS source.\n// This will be collected during the initial scan.\ntype attributes struct {\n\tnumBuckets    uint32\n\tnumObjects    uint64\n\tmu            sync.RWMutex\n\tbucketObjects map[string]uint64\n}\n\nfunc newStats(numBkts int) *attributes {\n\treturn &attributes{\n\t\tnumBuckets:    uint32(numBkts),\n\t\tbucketObjects: make(map[string]uint64, numBkts),\n\t}\n}\n\nfunc (s *attributes) incObjects() {\n\ts.mu.Lock()\n\ts.numObjects++\n\ts.mu.Unlock()\n}\n\nfunc (s *attributes) setBucketCnt(bkt string, cnt uint64) {\n\ts.mu.Lock()\n\ts.bucketObjects[bkt] = cnt\n\ts.mu.Unlock()\n}\n\ntype gcsManagerOption func(*gcsManager) error\n\n// withHTTPClient uses the provided HTTP client when creating a new GCS client.\nfunc withHTTPClient(ctx context.Context, httpClient *http.Client) gcsManagerOption {\n\tclient, err := storage.NewClient(ctx, option.WithHTTPClient(httpClient), option.WithScopes(storage.ScopeReadOnly))\n\treturn func(m *gcsManager) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tm.client = client\n\t\treturn nil\n\t}\n}\n\n// withAPIKey uses the provided API key when creating a new GCS client.\n// This can ONLY be used for public buckets.\nfunc withAPIKey(ctx context.Context, apiKey string) gcsManagerOption {\n\tclient, err := storage.NewClient(ctx, option.WithAPIKey(apiKey), option.WithScopes(storage.ScopeReadOnly))\n\treturn func(m *gcsManager) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tm.client = client\n\t\treturn nil\n\t}\n}\n\n// withJSONServiceAccount uses the provided JSON service account when creating a new GCS client.\nfunc withJSONServiceAccount(ctx context.Context, jsonServiceAccount []byte) gcsManagerOption {\n\tcreds, err := google.CredentialsFromJSON(ctx, jsonServiceAccount, storage.ScopeReadOnly)\n\tif err != nil {\n\t\treturn func(m *gcsManager) error {\n\t\t\treturn fmt.Errorf(\"failed to load credentials: %w\", err)\n\t\t}\n\t}\n\n\tclient, err := storage.NewClient(ctx, option.WithCredentials(creds))\n\treturn func(m *gcsManager) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tm.client = client\n\t\treturn nil\n\t}\n}\n\n// withDefaultADC uses the default application credentials when creating a new GCS client.\nfunc withDefaultADC(ctx context.Context) gcsManagerOption {\n\tclient, err := defaultADC(ctx)\n\treturn func(m *gcsManager) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tm.client = client\n\t\treturn nil\n\t}\n}\n\n// withoutAuthentication uses an unauthenticated client when creating a new GCS client.\n// This can ONLY be used for public buckets.\nfunc withoutAuthentication() gcsManagerOption {\n\tclient, err := storage.NewClient(aCtx.Background(), option.WithoutAuthentication(), option.WithScopes(storage.ScopeReadOnly))\n\treturn func(m *gcsManager) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tm.client = client\n\t\tm.withoutAuth = true\n\t\treturn nil\n\t}\n}\n\nfunc defaultADC(ctx context.Context) (*storage.Client, error) {\n\tclient, err := storage.NewClient(ctx, option.WithScopes(storage.ScopeReadOnly))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn client, nil\n}\n\n// withIncludeBuckets sets the buckets that should be included in the scan.\n// If used in conjunction with withExcludeBuckets, the include buckets will\n// take precedence.\nfunc withIncludeBuckets(buckets []string) gcsManagerOption {\n\treturn func(m *gcsManager) error {\n\t\tif len(buckets) == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tif m.excludeBuckets != nil {\n\t\t\tm.excludeBuckets = nil\n\t\t}\n\n\t\tm.includeBuckets = make(map[string]struct{}, len(buckets))\n\t\tfor _, bucket := range buckets {\n\t\t\tm.includeBuckets[bucket] = struct{}{}\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// withExcludeBuckets sets the buckets that should be excluded from the scan.\n// If used in conjunction with withIncludeBuckets, the include buckets will\n// take precedence.\nfunc withExcludeBuckets(buckets []string) gcsManagerOption {\n\treturn func(m *gcsManager) error {\n\t\tif len(buckets) == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tif m.includeBuckets != nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tm.excludeBuckets = make(map[string]struct{}, len(buckets))\n\t\tfor _, bucket := range buckets {\n\t\t\tm.excludeBuckets[bucket] = struct{}{}\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// withIncludeObjects sets the objects that should be included in the scan.\n// Using this option in conjuection with withIncludeBuckets will result in\n// only specific buckets and objects being scanned.\n// If the bucket(s) are known, it is recommended to also use withIncludeBuckets\n// to increase the performance of the scan.\n// If used in conjunction with any of the withExclude* or withIncludeBuckets options, this\n// option will take precedence.\nfunc withIncludeObjects(objects []string) gcsManagerOption {\n\treturn func(m *gcsManager) error {\n\t\tif len(objects) == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tif m.excludeObjects != nil {\n\t\t\tm.excludeObjects = nil\n\t\t}\n\n\t\tm.includeObjects = make(map[string]struct{}, len(objects))\n\t\tfor _, object := range objects {\n\t\t\tm.includeObjects[object] = struct{}{}\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// withExcludeObjects sets the objects that should be excluded from the scan.\n// If used in conjunction with withIncludeObjects, the include objects will\n// take precedence.\nfunc withExcludeObjects(objects []string) gcsManagerOption {\n\treturn func(m *gcsManager) error {\n\t\tif len(objects) == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tif m.includeObjects != nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tm.excludeObjects = make(map[string]struct{}, len(objects))\n\t\tfor _, object := range objects {\n\t\t\tm.excludeObjects[object] = struct{}{}\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// withConcurrency sets the number of concurrent workers that will be used\n// to process objects.\n// If not set, or set to a negative number the default value is runtime.NumCPU().\nfunc withConcurrency(concurrency int) gcsManagerOption {\n\treturn func(m *gcsManager) error {\n\t\tif concurrency <= 0 {\n\t\t\tm.concurrency = defaultConcurrency\n\t\t} else {\n\t\t\tm.concurrency = concurrency\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// withMaxObjectSize sets the maximum size of objects that will be scanned.\n// If not set, set to a negative number, or set larger than 1GB,\n// the default value of 50MB will be used.\nfunc withMaxObjectSize(maxObjectSize int64) gcsManagerOption {\n\treturn func(m *gcsManager) error {\n\t\tif maxObjectSize <= 0 || maxObjectSize > maxObjectSizeLimit {\n\t\t\tm.maxObjectSize = defaultMaxObjectSize\n\t\t} else {\n\t\t\tm.maxObjectSize = maxObjectSize\n\t\t}\n\n\t\treturn nil\n\t}\n}\n\n// withBucketOffsets sets the offset for each bucket.\n// This is used to resume listing objects for a bucket.\nfunc withBucketOffsets(offsets map[string]offsetInfo) gcsManagerOption {\n\tbkts := make(map[string]bucket, len(offsets))\n\tfor bkt, offst := range offsets {\n\t\tbkts[bkt] = bucket{\n\t\t\tshouldInclude: !offst.isBucketProcessed,\n\t\t\tname:          bkt,\n\t\t\tstartOffset:   offst.lastProcessedObject,\n\t\t}\n\t}\n\treturn func(m *gcsManager) error {\n\t\tm.hasEnumerated = len(offsets) > 0 // offset means we are resuming.\n\t\tm.buckets = bkts\n\t\treturn nil\n\t}\n}\n\nfunc newGCSManager(projectID string, opts ...gcsManagerOption) (*gcsManager, error) {\n\t// Default values for the manager.\n\tgcs := &gcsManager{\n\t\tprojectID:     projectID,\n\t\tconcurrency:   defaultConcurrency,\n\t\tmaxObjectSize: defaultMaxObjectSize,\n\t\tbuckets:       make(map[string]bucket),\n\t}\n\n\tfor _, opt := range opts {\n\t\tif err := opt(gcs); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to apply option: %w\", err)\n\t\t}\n\t}\n\n\tif projectID == \"\" && !gcs.withoutAuth {\n\t\treturn nil, fmt.Errorf(\"project ID is required, when using authentication\")\n\t}\n\n\t// If no client was provided, use the default application credentials.\n\t// A client is required to perform any operations.\n\tif gcs.client == nil {\n\t\tc, err := defaultADC(context.Background())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgcs.client = c\n\t}\n\tconfigureWorkers(gcs)\n\n\treturn gcs, nil\n}\n\nfunc configureWorkers(gcs *gcsManager) {\n\tgcs.workerPool = new(errgroup.Group)\n\tgcs.workerPool.SetLimit(gcs.concurrency)\n}\n\n// object is a representation of a GCS object.\ntype object struct {\n\tname        string\n\tbucket      string\n\tcontentType string\n\towner       string\n\tlink        string\n\tmd5         string\n\t// acl represents an ACLEntities.\n\t// https://pkg.go.dev/cloud.google.com/go/storage#ACLEntity\n\tacl       []string\n\tsize      int64\n\tcreatedAt time.Time\n\tupdatedAt time.Time\n\n\tio.Reader\n}\n\nfunc (g *gcsManager) Attributes(ctx context.Context) (*attributes, error) {\n\t// Get all the buckets in the project.\n\tbuckets, err := g.listBuckets(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to list buckets: %w\", err)\n\t}\n\n\treturn g.enumerate(ctx, buckets)\n}\n\nfunc (g *gcsManager) enumerate(ctx context.Context, bkts []bucket) (*attributes, error) {\n\tlogger := ctx.Logger().WithValues(\"phase\", \"enumeration\")\n\n\tlogger.V(5).Info(\"enumerating buckets\", \"numBuckets\", len(bkts))\n\tstats := newStats(len(bkts))\n\n\tdefer func(start time.Time) {\n\t\tlogger.V(5).Info(\"finished enumerating buckets\", \"duration-seconds\", fmt.Sprintf(\"%.1f\", time.Since(start).Seconds()), \"num-buckets\", len(bkts), \"num-objects\", stats.numObjects)\n\t}(time.Now())\n\n\tfor _, bkt := range bkts {\n\t\tg.workerPool.Go(func() error {\n\t\t\t// List all the objects in the bucket and calculate attributes.\n\t\t\tg.setupBktHandle(&bkt)\n\n\t\t\tq, err := setObjectQuery(&bkt)\n\t\t\tif err != nil {\n\t\t\t\tlogger.Error(err, \"failed to set object query\", \"bucket\", bkt.name)\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tvar count uint64\n\t\t\tobjs := bkt.Objects(ctx, q)\n\t\t\tfor {\n\t\t\t\tobj, err := objs.Next()\n\t\t\t\tif errors.Is(err, iterator.Done) {\n\t\t\t\t\tlogger.V(5).Info(\"finished listing objects in bucket\")\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\tlogger.V(1).Info(\"failed to list objects\", \"bucket\", bkt.name, \"error\", err)\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tif obj == nil {\n\t\t\t\t\tlogger.V(5).Info(\"object is nil\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif !g.shouldIncludeObject(ctx, obj.Name) || g.shouldExcludeObject(ctx, obj.Name) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tcount++\n\t\t\t\tstats.incObjects()\n\t\t\t}\n\n\t\t\tstats.setBucketCnt(bkt.name, count)\n\t\t\treturn nil\n\t\t})\n\t}\n\n\t_ = g.workerPool.Wait()\n\tg.attr = stats\n\n\treturn stats, nil\n}\n\nfunc (g *gcsManager) ListObjects(ctx context.Context) (chan io.Reader, error) {\n\tch := make(chan io.Reader, 100) // TODO (ahrav): Determine optimal buffer size.\n\n\t// Get all the buckets in the project.\n\tbuckets, err := g.listBuckets(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to list buckets: %w\", err)\n\t}\n\n\t// TODO (ahrav): This can be optimized if we set the buckets from within\n\t// the listBuckets method. This way seems a little more clear as to the intent.\n\n\t// Update the gcsManager with all the buckets to list objects from.\n\t// This is in addition to any buckets that were provided via the\n\t// withBucketOffsets option.\n\tfor _, b := range buckets {\n\t\tg.buckets[b.name] = b\n\t}\n\n\tgo func() {\n\t\tgcsErrs := sources.NewScanErrors()\n\t\tfor _, bucket := range g.buckets {\n\t\t\tg.numBuckets++\n\t\t\tg.workerPool.Go(func() error {\n\t\t\t\tobjCh, errCh := g.listBucketObjects(ctx, &bucket)\n\t\t\t\tfor {\n\t\t\t\t\tselect {\n\t\t\t\t\tcase obj, ok := <-objCh:\n\t\t\t\t\t\tif !ok {\n\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t}\n\t\t\t\t\t\tch <- obj\n\t\t\t\t\tcase err := <-errCh:\n\t\t\t\t\t\tgcsErrs.Add(err)\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\t_ = g.workerPool.Wait()\n\t\tif gcsErrs.Count() > 0 {\n\t\t\tctx.Logger().V(2).Info(\"encountered gcsErrs while scanning GCS buckets\", \"error-count\", gcsErrs.Count(), \"gcsErrs\", gcsErrs.String())\n\t\t}\n\t\tclose(ch)\n\t}()\n\n\treturn ch, nil\n}\n\nfunc (g *gcsManager) listBuckets(ctx context.Context) ([]bucket, error) {\n\tvar buckets []bucket\n\tif g.withoutAuth {\n\t\tfor name := range g.includeBuckets {\n\t\t\tbuckets = append(buckets, bucket{name: name})\n\t\t}\n\t\treturn buckets, nil\n\t}\n\n\tbkts := g.client.Buckets(ctx, g.projectID)\n\tfor {\n\t\tbkt, err := bkts.Next()\n\t\tif errors.Is(err, iterator.Done) {\n\t\t\tctx.Logger().V(5).Info(\"finished listing buckets\", \"num_buckets\", len(buckets))\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to retrieve bucket: %w\", err)\n\t\t}\n\n\t\t// If the bucket is already in the map, skip it, it's already accounted for.\n\t\t// This is used to resume listing objects for a bucket.\n\t\tif _, ok := g.buckets[bkt.Name]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tif !g.shouldIncludeBucket(ctx, bkt.Name) || g.shouldExcludeBucket(ctx, bkt.Name) {\n\t\t\tcontinue\n\t\t}\n\t\tbuckets = append(buckets, bucket{name: bkt.Name})\n\n\t}\n\treturn buckets, nil\n}\n\nfunc (g *gcsManager) listBucketObjects(ctx context.Context, bkt *bucket) (chan io.Reader, chan error) {\n\tch := make(chan io.Reader, 100)\n\terrCh := make(chan error, 1)\n\n\tgo func() {\n\t\tdefer close(ch)\n\n\t\tlogger := ctx.Logger().WithValues(\"bucket\", bkt.name)\n\t\tlogger.V(5).Info(\"listing object(s) in bucket\")\n\n\t\tg.setupBktHandle(bkt)\n\n\t\t// TODO (ahrav): Look to extend gcsManager to allow for exact buckets/objects\n\t\t// include filters. This will increase performance substantially\n\n\t\tif err := g.bucketObjects(ctx, bkt, ch); err != nil {\n\t\t\terrCh <- fmt.Errorf(\"failed to list bucket objects: %w\", err)\n\t\t}\n\t}()\n\treturn ch, errCh\n}\n\nfunc (g *gcsManager) setupBktHandle(bkt *bucket) {\n\tb := g.client.Bucket(bkt.name).Retryer(\n\t\tstorage.WithBackoff(gax.Backoff{\n\t\t\tInitial:    2 * time.Second,\n\t\t\tMax:        30 * time.Second,\n\t\t\tMultiplier: 1.5,\n\t\t}),\n\t\tstorage.WithPolicy(storage.RetryAlways),\n\t)\n\tbkt.BucketHandle = b\n}\n\nfunc (g *gcsManager) bucketObjects(ctx context.Context, bkt *bucket, ch chan<- io.Reader) error {\n\tq, err := setObjectQuery(bkt)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to set object query: %w\", err)\n\t}\n\n\tobjs := bkt.Objects(ctx, q)\n\tfor {\n\t\tobj, err := objs.Next()\n\t\tif errors.Is(err, iterator.Done) {\n\t\t\tctx.Logger().V(5).Info(\"finished listing objects in bucket\")\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to retrieve object iterator: %w\", err)\n\t\t}\n\t\tif obj == nil {\n\t\t\tctx.Logger().V(5).Info(\"object is nil\")\n\t\t\tcontinue\n\t\t}\n\n\t\tif !g.shouldIncludeObject(ctx, obj.Name) || g.shouldExcludeObject(ctx, obj.Name) {\n\t\t\tcontinue\n\t\t}\n\n\t\to, err := g.constructObject(ctx, bkt.Object(obj.Name))\n\t\tif err != nil {\n\t\t\tctx.Logger().V(1).Info(\"failed to create object\", \"object-name\", obj.Name, \"error\", err)\n\t\t\tcontinue\n\t\t}\n\t\tch <- o\n\t}\n\treturn nil\n}\n\nfunc setObjectQuery(bkt *bucket) (*storage.Query, error) {\n\t// Setting the attribute selection is a performance optimization.\n\t// https://pkg.go.dev/cloud.google.com/go/storage#Query.SetAttrSelection\n\tq := new(storage.Query)\n\terr := q.SetAttrSelection([]string{\n\t\t\"Name\",\n\t\t\"ContentType\",\n\t\t\"Owner\",\n\t\t\"Size\",\n\t\t\"Updated\",\n\t\t\"Created\",\n\t\t\"ACL\",\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to set attribute selection: %w\", err)\n\t}\n\n\t// If a start offset is provided, then we need to set it on the query.\n\t// This will begin listing objects from the start offset.\n\tif bkt.startOffset != \"\" {\n\t\tq.StartOffset = bkt.startOffset\n\t}\n\treturn q, nil\n}\n\nfunc (g *gcsManager) constructObject(ctx context.Context, obj *storage.ObjectHandle) (object, error) {\n\to := object{}\n\tattrs, err := obj.Attrs(ctx)\n\tif err != nil {\n\t\treturn o, fmt.Errorf(\"failed to retrieve object attributes: %w\", err)\n\t}\n\n\tif !isObjectTypeValid(ctx, attrs.Name) || !g.isObjectSizeValid(ctx, attrs.Size) {\n\t\treturn o, fmt.Errorf(\"object is not valid\")\n\t}\n\n\trc, err := obj.NewReader(ctx)\n\tif err != nil {\n\t\treturn o, fmt.Errorf(\"failed to retrieve object reader: %w\", err)\n\t}\n\n\to.name = attrs.Name\n\to.bucket = attrs.Bucket\n\to.contentType = attrs.ContentType\n\to.owner = attrs.Owner\n\to.link = attrs.MediaLink\n\to.md5 = hex.EncodeToString(attrs.MD5)\n\to.createdAt = attrs.Created\n\to.updatedAt = attrs.Updated\n\to.acl = objectACLs(attrs.ACL)\n\to.size = attrs.Size\n\to.Reader = rc\n\n\tatomic.AddUint64(&g.numObjects, 1)\n\n\treturn o, nil\n}\n\nfunc objectACLs(acl []storage.ACLRule) []string {\n\tacls := make([]string, 0, len(acl))\n\tfor _, rule := range acl {\n\t\tacls = append(acls, string(rule.Entity))\n\t}\n\treturn acls\n}\n\nfunc isObjectTypeValid(ctx context.Context, name string) bool {\n\tisValid := !common.SkipFile(name)\n\tif !isValid {\n\t\tctx.Logger().V(2).Info(\"object type is invalid\", \"object-name\", name)\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (g *gcsManager) isObjectSizeValid(ctx context.Context, size int64) bool {\n\tisValid := size > 0 && size <= g.maxObjectSize\n\tif !isValid {\n\t\tctx.Logger().V(2).Info(\"object size is invalid\", \"object-size\", size)\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (g *gcsManager) shouldIncludeBucket(ctx context.Context, bkt string) bool {\n\tif len(g.includeBuckets) == 0 {\n\t\treturn true\n\t}\n\treturn shouldProcess(ctx, bkt, g.includeBuckets, globMatches)\n}\n\nfunc (g *gcsManager) shouldExcludeBucket(ctx context.Context, bkt string) bool {\n\treturn shouldProcess(ctx, bkt, g.excludeBuckets, globMatches)\n}\n\nfunc (g *gcsManager) shouldIncludeObject(ctx context.Context, obj string) bool {\n\tif len(g.includeObjects) == 0 {\n\t\treturn true\n\t}\n\treturn shouldProcess(ctx, obj, g.includeObjects, globMatches)\n}\n\nfunc (g *gcsManager) shouldExcludeObject(ctx context.Context, obj string) bool {\n\treturn shouldProcess(ctx, obj, g.excludeObjects, globMatches)\n}\n\ntype globMatcherFn func(string, glob.Glob) bool\n\nfunc shouldProcess(ctx context.Context, s string, matchers map[string]struct{}, matcherFn globMatcherFn) bool {\n\tif len(matchers) == 0 {\n\t\treturn false\n\t}\n\n\tfor m := range matchers {\n\t\tg, err := glob.Compile(m)\n\t\tif err != nil {\n\t\t\tctx.Logger().V(1).Info(\"failed to compile glob\", \"glob\", m, \"error\", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tif matcherFn(s, g) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc globMatches(s string, g glob.Glob) bool {\n\treturn g.Match(s)\n}\n"
  },
  {
    "path": "pkg/sources/gcs/gcs_manager_test.go",
    "content": "package gcs\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\nconst (\n\tpublicBucket       = \"public-trufflehog-test-bucket\"\n\ttestProjectID      = \"trufflehog-testing\"\n\ttestAPIKey         = \"somekeys\"\n\ttestBucket         = \"test-bkt-th\"\n\ttestBucket2        = \"test-bkt-th2\"\n\ttestBucket3        = \"test-bkt-th3\"\n\ttestBucket4        = \"test-bkt-th4\"\n\tperfTestBucketGlob = \"perf-test-bkt-th*\"\n\tbucket1            = \"bucket1\"\n\tbucket2            = \"bucket2\"\n\tobject1            = \"object1\"\n\tobject2            = \"object2\"\n)\n\nfunc TestNewGcsManager(t *testing.T) {\n\tctx := context.Background()\n\n\ttestCases := []struct {\n\t\tname    string\n\t\tprojID  string\n\t\topts    []gcsManagerOption\n\t\twant    *gcsManager\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:   \"new gcs manager, no options\",\n\t\t\tprojID: testProjectID,\n\t\t\twant:   &gcsManager{projectID: testProjectID, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize},\n\t\t},\n\t\t{\n\t\t\tname:    \"new gcs manager, no project id\",\n\t\t\tprojID:  \"\",\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, without auth\",\n\t\t\tprojID: \"\",\n\t\t\topts:   []gcsManagerOption{withoutAuthentication()},\n\t\t\twant: &gcsManager{\n\t\t\t\tconcurrency:   defaultConcurrency,\n\t\t\t\tmaxObjectSize: defaultMaxObjectSize,\n\t\t\t\twithoutAuth:   true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with api key\",\n\t\t\tprojID: testProjectID,\n\t\t\topts:   []gcsManagerOption{withAPIKey(ctx, testAPIKey)},\n\t\t\twant:   &gcsManager{projectID: testProjectID, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with json service account\",\n\t\t\tprojID: testProjectID,\n\t\t\topts: []gcsManagerOption{withJSONServiceAccount(ctx, []byte(`{\n\t\t\t\t\"type\": \"service_account\",\n\t\t\t\t\"project_id\": \"test-project\",\n\t\t\t\t\"client_email\": \"test@test-project.iam.gserviceaccount.com\",\n\t\t\t\t\"private_key\": \"-----BEGIN PRIVATE KEY-----\\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7VJTUt9Us8cKB\\nT1aFVMgL36LJVCGPGM7ke2dY67XR9/ln3tOXmNtHfZUGCLQRv3B0yTI/0mB4+DXu\\n-----END PRIVATE KEY-----\\n\"}`,\n\t\t\t))},\n\t\t\twant: &gcsManager{projectID: testProjectID, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with default ADC account\",\n\t\t\tprojID: testProjectID,\n\t\t\topts:   []gcsManagerOption{withDefaultADC(ctx)},\n\t\t\twant:   &gcsManager{projectID: testProjectID, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with bucket offsets\",\n\t\t\tprojID: testProjectID,\n\t\t\topts: []gcsManagerOption{\n\t\t\t\twithDefaultADC(ctx),\n\t\t\t\twithBucketOffsets(map[string]offsetInfo{\n\t\t\t\t\ttestBucket:  {lastProcessedObject: \"moar1.txt\", isBucketProcessed: false},\n\t\t\t\t\ttestBucket2: {lastProcessedObject: \"\", isBucketProcessed: true}}),\n\t\t\t},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:     testProjectID,\n\t\t\t\thasEnumerated: true,\n\t\t\t\tconcurrency:   defaultConcurrency,\n\t\t\t\tmaxObjectSize: defaultMaxObjectSize,\n\t\t\t\tbuckets: map[string]bucket{\n\t\t\t\t\ttestBucket:  {name: testBucket, startOffset: \"moar.txt\", shouldInclude: true},\n\t\t\t\t\ttestBucket2: {name: testBucket2, startOffset: \"\", shouldInclude: false},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with include buckets\",\n\t\t\tprojID: testProjectID,\n\t\t\topts: []gcsManagerOption{\n\t\t\t\twithDefaultADC(ctx),\n\t\t\t\twithIncludeBuckets([]string{bucket1, bucket2}),\n\t\t\t},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:      testProjectID,\n\t\t\t\tincludeBuckets: map[string]struct{}{bucket1: {}, bucket2: {}},\n\t\t\t\tconcurrency:    defaultConcurrency,\n\t\t\t\tmaxObjectSize:  defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with include buckets and api key\",\n\t\t\tprojID: testProjectID,\n\t\t\topts:   []gcsManagerOption{withIncludeBuckets([]string{bucket1, bucket2}), withAPIKey(ctx, testAPIKey)},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:      testProjectID,\n\t\t\t\tincludeBuckets: map[string]struct{}{bucket1: {}, bucket2: {}},\n\t\t\t\tconcurrency:    defaultConcurrency,\n\t\t\t\tmaxObjectSize:  defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with exclude buckets\",\n\t\t\tprojID: testProjectID,\n\t\t\topts:   []gcsManagerOption{withExcludeBuckets([]string{bucket1, bucket2}), withAPIKey(ctx, testAPIKey)},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:      testProjectID,\n\t\t\t\texcludeBuckets: map[string]struct{}{bucket1: {}, bucket2: {}},\n\t\t\t\tconcurrency:    defaultConcurrency,\n\t\t\t\tmaxObjectSize:  defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with exclude buckets and api key\",\n\t\t\tprojID: testProjectID,\n\t\t\topts:   []gcsManagerOption{withExcludeBuckets([]string{bucket1, bucket2}), withAPIKey(ctx, testAPIKey)},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:      testProjectID,\n\t\t\t\texcludeBuckets: map[string]struct{}{bucket1: {}, bucket2: {}},\n\t\t\t\tconcurrency:    defaultConcurrency,\n\t\t\t\tmaxObjectSize:  defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with include and exclude buckets\",\n\t\t\tprojID: testProjectID,\n\t\t\topts: []gcsManagerOption{\n\t\t\t\twithDefaultADC(ctx),\n\t\t\t\twithIncludeBuckets([]string{bucket1, bucket2}),\n\t\t\t\twithExcludeBuckets([]string{\"bucket3\", \"bucket4\"}),\n\t\t\t},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:      testProjectID,\n\t\t\t\tincludeBuckets: map[string]struct{}{bucket1: {}, bucket2: {}},\n\t\t\t\tconcurrency:    defaultConcurrency,\n\t\t\t\tmaxObjectSize:  defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with include and exclude buckets and api key\",\n\t\t\tprojID: testProjectID,\n\t\t\topts: []gcsManagerOption{\n\t\t\t\twithDefaultADC(ctx),\n\t\t\t\twithIncludeBuckets([]string{bucket1, bucket2}),\n\t\t\t\twithExcludeBuckets([]string{\"bucket3\", \"bucket4\"}),\n\t\t\t\twithAPIKey(ctx, testAPIKey),\n\t\t\t},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:      testProjectID,\n\t\t\t\tincludeBuckets: map[string]struct{}{bucket1: {}, bucket2: {}},\n\t\t\t\tconcurrency:    defaultConcurrency,\n\t\t\t\tmaxObjectSize:  defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with include objects, no bucket\",\n\t\t\tprojID: testProjectID,\n\t\t\topts:   []gcsManagerOption{withDefaultADC(ctx), withIncludeObjects([]string{\"object1\", \"object2\"})},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:      testProjectID,\n\t\t\t\tincludeObjects: map[string]struct{}{object1: {}, object2: {}},\n\t\t\t\tconcurrency:    defaultConcurrency,\n\t\t\t\tmaxObjectSize:  defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with include objects, include buckets\",\n\t\t\tprojID: testProjectID,\n\t\t\topts: []gcsManagerOption{\n\t\t\t\twithDefaultADC(ctx),\n\t\t\t\twithIncludeObjects([]string{\"object1\", \"object2\"}),\n\t\t\t\twithIncludeBuckets([]string{bucket1, bucket2}),\n\t\t\t},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:      testProjectID,\n\t\t\t\tincludeObjects: map[string]struct{}{object1: {}, object2: {}},\n\t\t\t\tincludeBuckets: map[string]struct{}{bucket1: {}, bucket2: {}},\n\t\t\t\tconcurrency:    defaultConcurrency,\n\t\t\t\tmaxObjectSize:  defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with include objects and api key, include buckets\",\n\t\t\tprojID: testProjectID,\n\t\t\topts: []gcsManagerOption{\n\t\t\t\twithIncludeObjects([]string{\"object1\", \"object2\"}),\n\t\t\t\twithIncludeBuckets([]string{bucket1, bucket2}),\n\t\t\t\twithAPIKey(ctx, testAPIKey),\n\t\t\t},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:      testProjectID,\n\t\t\t\tincludeObjects: map[string]struct{}{object1: {}, object2: {}},\n\t\t\t\tincludeBuckets: map[string]struct{}{bucket1: {}, bucket2: {}},\n\t\t\t\tconcurrency:    defaultConcurrency,\n\t\t\t\tmaxObjectSize:  defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with exclude objects\",\n\t\t\tprojID: testProjectID,\n\t\t\topts:   []gcsManagerOption{withDefaultADC(ctx), withExcludeObjects([]string{\"object1\", \"object2\"})},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:      testProjectID,\n\t\t\t\texcludeObjects: map[string]struct{}{object1: {}, object2: {}},\n\t\t\t\tconcurrency:    defaultConcurrency,\n\t\t\t\tmaxObjectSize:  defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with exclude objects and api key\",\n\t\t\tprojID: testProjectID,\n\t\t\topts:   []gcsManagerOption{withExcludeObjects([]string{\"object1\", \"object2\"}), withAPIKey(ctx, testAPIKey)},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:      testProjectID,\n\t\t\t\texcludeObjects: map[string]struct{}{object1: {}, object2: {}},\n\t\t\t\tconcurrency:    defaultConcurrency,\n\t\t\t\tmaxObjectSize:  defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with include and exclude objects\",\n\t\t\tprojID: testProjectID,\n\t\t\topts: []gcsManagerOption{\n\t\t\t\twithDefaultADC(ctx),\n\t\t\t\twithIncludeObjects([]string{\"object1\", \"object2\"}),\n\t\t\t\twithExcludeObjects([]string{\"object3\", \"object4\"}),\n\t\t\t},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:      testProjectID,\n\t\t\t\tincludeObjects: map[string]struct{}{object1: {}, object2: {}},\n\t\t\t\tconcurrency:    defaultConcurrency,\n\t\t\t\tmaxObjectSize:  defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with concurrency\",\n\t\t\tprojID: testProjectID,\n\t\t\topts:   []gcsManagerOption{withDefaultADC(ctx), withConcurrency(10)},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:     testProjectID,\n\t\t\t\tconcurrency:   10,\n\t\t\t\tmaxObjectSize: defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, default concurrency\",\n\t\t\tprojID: testProjectID,\n\t\t\topts:   []gcsManagerOption{withDefaultADC(ctx)},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:     testProjectID,\n\t\t\t\tconcurrency:   defaultConcurrency,\n\t\t\t\tmaxObjectSize: defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with negative concurrency\",\n\t\t\tprojID: testProjectID,\n\t\t\topts:   []gcsManagerOption{withDefaultADC(ctx), withConcurrency(-1)},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:     testProjectID,\n\t\t\t\tconcurrency:   defaultConcurrency,\n\t\t\t\tmaxObjectSize: defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with valid max object size\",\n\t\t\tprojID: testProjectID,\n\t\t\topts:   []gcsManagerOption{withDefaultADC(ctx), withMaxObjectSize(10)},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:     testProjectID,\n\t\t\t\tconcurrency:   defaultConcurrency,\n\t\t\t\tmaxObjectSize: 10,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with negative max object size\",\n\t\t\tprojID: testProjectID,\n\t\t\topts:   []gcsManagerOption{withDefaultADC(ctx), withMaxObjectSize(-1)},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:     testProjectID,\n\t\t\t\tconcurrency:   defaultConcurrency,\n\t\t\t\tmaxObjectSize: defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, max object size above limit\",\n\t\t\tprojID: testProjectID,\n\t\t\topts:   []gcsManagerOption{withDefaultADC(ctx), withMaxObjectSize(int64(2 * common.GB))},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:     testProjectID,\n\t\t\t\tconcurrency:   defaultConcurrency,\n\t\t\t\tmaxObjectSize: defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with max object size 0\",\n\t\t\tprojID: testProjectID,\n\t\t\topts:   []gcsManagerOption{withDefaultADC(ctx), withMaxObjectSize(0)},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:     testProjectID,\n\t\t\t\tconcurrency:   defaultConcurrency,\n\t\t\t\tmaxObjectSize: defaultMaxObjectSize,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"new gcs manager, with HTTP client\",\n\t\t\tprojID: testProjectID,\n\t\t\topts:   []gcsManagerOption{withHTTPClient(ctx, &http.Client{})},\n\t\t\twant:   &gcsManager{projectID: testProjectID, concurrency: defaultConcurrency, maxObjectSize: defaultMaxObjectSize},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot, err := newGCSManager(tc.projID, tc.opts...)\n\t\t\tif (err != nil) != tc.wantErr {\n\t\t\t\tt.Errorf(\"newGCSManager() error = %v, wantErr %v\", err, tc.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// The client should never be nil.\n\t\t\tif got.client == nil {\n\t\t\t\tt.Errorf(\"newGCSManager() client should not be nil\")\n\t\t\t}\n\n\t\t\tdiff := cmp.Diff(got, tc.want,\n\t\t\t\tcmp.AllowUnexported(gcsManager{}, bucket{}),\n\t\t\t\tcmpopts.IgnoreFields(gcsManager{}, \"client\", \"workerPool\", \"buckets\"),\n\t\t\t)\n\t\t\tif diff != \"\" {\n\t\t\t\tt.Errorf(\"newGCSManager(%v, %v) got: %v, want: %v, diff: %v\", tc.projID, tc.opts, got, tc.want, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGCSManagerStats(t *testing.T) {\n\tctx := context.Background()\n\n\ttestCases := []struct {\n\t\tname      string\n\t\tprojectID string\n\t\topts      []gcsManagerOption\n\t\twantStats *attributes\n\t}{\n\t\t{\n\t\t\tname:      \"enumerate, all buckets, with objects\",\n\t\t\tprojectID: testProjectID,\n\t\t\topts:      []gcsManagerOption{withDefaultADC(ctx), withExcludeBuckets([]string{perfTestBucketGlob, publicBucket})},\n\t\t\twantStats: &attributes{\n\t\t\t\tnumBuckets:    4,\n\t\t\t\tnumObjects:    5,\n\t\t\t\tbucketObjects: map[string]uint64{testBucket: 2, testBucket2: 1, testBucket3: 1, testBucket4: 1},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"list objects, with exclude objects\",\n\t\t\tprojectID: testProjectID,\n\t\t\topts: []gcsManagerOption{\n\t\t\t\twithDefaultADC(ctx),\n\t\t\t\twithExcludeObjects([]string{\"aws1.txt\", \"moar2.txt\"}),\n\t\t\t\twithExcludeBuckets([]string{perfTestBucketGlob, publicBucket}),\n\t\t\t},\n\t\t\twantStats: &attributes{\n\t\t\t\tnumBuckets:    4,\n\t\t\t\tnumObjects:    3,\n\t\t\t\tbucketObjects: map[string]uint64{testBucket: 0, testBucket2: 1, testBucket3: 1, testBucket4: 1},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"list objects, with include objects\",\n\t\t\tprojectID: testProjectID,\n\t\t\topts: []gcsManagerOption{\n\t\t\t\twithDefaultADC(ctx),\n\t\t\t\twithIncludeObjects([]string{\"aws1.txt\"}),\n\t\t\t\twithExcludeBuckets([]string{perfTestBucketGlob, publicBucket}),\n\t\t\t},\n\t\t\twantStats: &attributes{\n\t\t\t\tnumBuckets:    4,\n\t\t\t\tnumObjects:    1,\n\t\t\t\tbucketObjects: map[string]uint64{testBucket: 1, testBucket2: 0, testBucket3: 0, testBucket4: 0},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgm, err := newGCSManager(tc.projectID, tc.opts...)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"newGCSManager() error = %v\", err)\n\t\t\t}\n\n\t\t\tgot, err := gm.Attributes(ctx)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Attributes() error = %v\", err)\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(got, tc.wantStats, cmp.AllowUnexported(attributes{}), cmpopts.IgnoreFields(attributes{}, \"mu\")); diff != \"\" {\n\t\t\t\tt.Errorf(\"Attributes() got: %v, want: %v, diff: %v\", got, tc.wantStats, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGCSManagerStats_Time(t *testing.T) {\n\tctx := context.Background()\n\n\topts := []gcsManagerOption{withDefaultADC(ctx)}\n\tgm, err := newGCSManager(testProjectID, opts...)\n\tif err != nil {\n\t\tt.Fatalf(\"newGCSManager() error = %v\", err)\n\t}\n\n\tstart := time.Now()\n\tvar stats *attributes\n\tstats, err = gm.Attributes(ctx)\n\tif err != nil {\n\t\tt.Fatalf(\"Attributes() error = %v\", err)\n\t}\n\tend := time.Since(start).Seconds()\n\n\tfmt.Printf(\"Time taken to get %d objects: %f seconds\\n\", stats.numObjects, end)\n}\n\nfunc TestGCSManagerListObjects(t *testing.T) {\n\tctx := context.Background()\n\n\ttestCases := []struct {\n\t\tname       string\n\t\tprojectID  string\n\t\topts       []gcsManagerOption\n\t\twant       []object\n\t\twantNumBkt uint32\n\t\twantNumObj uint64\n\t\twantErr    bool\n\t}{\n\t\t{\n\t\t\tname:      \"list objects, all buckets, no objects\",\n\t\t\tprojectID: \"other\",\n\t\t\topts:      []gcsManagerOption{withDefaultADC(ctx)},\n\t\t\twant:      []object{},\n\t\t\twantErr:   true,\n\t\t},\n\t\t{\n\t\t\tname:      \"list objects, all buckets, with objects\",\n\t\t\tprojectID: testProjectID,\n\t\t\topts:      []gcsManagerOption{withDefaultADC(ctx), withExcludeBuckets([]string{perfTestBucketGlob, publicBucket})},\n\t\t\twant: []object{\n\t\t\t\t{\n\t\t\t\t\tname:        \"aws1.txt\",\n\t\t\t\t\tbucket:      testBucket,\n\t\t\t\t\tcontentType: \"text/plain\",\n\t\t\t\t\tsize:        150,\n\t\t\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/aws1.txt?generation=1677870994890594&alt=media\",\n\t\t\t\t\tacl:         []string{},\n\t\t\t\t\tmd5:         \"7a4df45f839cd594ac236b19524cec92\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname:        \"moar2.txt\",\n\t\t\t\t\tbucket:      testBucket,\n\t\t\t\t\tcontentType: \"text/plain\",\n\t\t\t\t\tsize:        12,\n\t\t\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/moar2.txt?generation=1677871000378542&alt=media\",\n\t\t\t\t\tacl:         []string{},\n\t\t\t\t\tmd5:         \"e59ff97941044f85df5297e1c302d260\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname:        \"aws3.txt\",\n\t\t\t\t\tbucket:      testBucket2,\n\t\t\t\t\tcontentType: \"text/plain\",\n\t\t\t\t\tsize:        150,\n\t\t\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th2/o/aws3.txt?generation=1677871022489611&alt=media\",\n\t\t\t\t\tacl:         []string{},\n\t\t\t\t\tmd5:         \"26eb03378a0c8e99ee0cecf9155ace81\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname:        \"moar.txt\",\n\t\t\t\t\tbucket:      testBucket3,\n\t\t\t\t\tcontentType: \"text/plain\",\n\t\t\t\t\tsize:        6,\n\t\t\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th3/o/moar.txt?generation=1677871042896804&alt=media\",\n\t\t\t\t\tacl:         []string{},\n\t\t\t\t\tmd5:         \"09f7e02f1290be211da707a266f153b3\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname:        \"AMAZON_FASHION_5.json\",\n\t\t\t\t\tbucket:      testBucket4,\n\t\t\t\t\tcontentType: \"application/json\",\n\t\t\t\t\tsize:        1413469,\n\t\t\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th4/o/AMAZON_FASHION_5.json?generation=1677871063457469&alt=media\",\n\t\t\t\t\tacl:         []string{},\n\t\t\t\t\tmd5:         \"5a1d8f483aa34695256379050d42a2b7\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantNumBkt: 4,\n\t\t\twantNumObj: 5,\n\t\t},\n\t\t{\n\t\t\tname:      \"list objects, include buckets, with objects\",\n\t\t\tprojectID: testProjectID,\n\t\t\topts:      []gcsManagerOption{withDefaultADC(ctx), withIncludeBuckets([]string{testBucket})},\n\t\t\twant: []object{\n\t\t\t\t{\n\t\t\t\t\tname:        \"aws1.txt\",\n\t\t\t\t\tbucket:      testBucket,\n\t\t\t\t\tcontentType: \"text/plain\",\n\t\t\t\t\tsize:        150,\n\t\t\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/aws1.txt?generation=1677870994890594&alt=media\",\n\t\t\t\t\tacl:         []string{},\n\t\t\t\t\tmd5:         \"7a4df45f839cd594ac236b19524cec92\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname:        \"moar2.txt\",\n\t\t\t\t\tbucket:      testBucket,\n\t\t\t\t\tcontentType: \"text/plain\",\n\t\t\t\t\tsize:        12,\n\t\t\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/moar2.txt?generation=1677871000378542&alt=media\",\n\t\t\t\t\tacl:         []string{},\n\t\t\t\t\tmd5:         \"e59ff97941044f85df5297e1c302d260\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantNumBkt: 1,\n\t\t\twantNumObj: 2,\n\t\t},\n\t\t{\n\t\t\tname:      \"list objects, exclude buckets, with objects\",\n\t\t\tprojectID: testProjectID,\n\t\t\topts:      []gcsManagerOption{withDefaultADC(ctx), withExcludeBuckets([]string{testBucket, testBucket2, perfTestBucketGlob, publicBucket})},\n\t\t\twant: []object{\n\t\t\t\t{\n\t\t\t\t\tname:        \"moar.txt\",\n\t\t\t\t\tbucket:      testBucket3,\n\t\t\t\t\tcontentType: \"text/plain\",\n\t\t\t\t\tsize:        6,\n\t\t\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th3/o/moar.txt?generation=1677871042896804&alt=media\",\n\t\t\t\t\tacl:         []string{},\n\t\t\t\t\tmd5:         \"09f7e02f1290be211da707a266f153b3\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname:        \"AMAZON_FASHION_5.json\",\n\t\t\t\t\tbucket:      testBucket4,\n\t\t\t\t\tcontentType: \"application/json\",\n\t\t\t\t\tsize:        1413469,\n\t\t\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th4/o/AMAZON_FASHION_5.json?generation=1677871063457469&alt=media\",\n\t\t\t\t\tacl:         []string{},\n\t\t\t\t\tmd5:         \"5a1d8f483aa34695256379050d42a2b7\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantNumBkt: 2,\n\t\t\twantNumObj: 2,\n\t\t},\n\t\t{\n\t\t\tname:      \"list objects, with exclude objects\",\n\t\t\tprojectID: testProjectID,\n\t\t\topts: []gcsManagerOption{\n\t\t\t\twithDefaultADC(ctx),\n\t\t\t\twithExcludeObjects([]string{\"aws1.txt\", \"moar2.txt\"}),\n\t\t\t\twithExcludeBuckets([]string{perfTestBucketGlob, publicBucket}),\n\t\t\t},\n\t\t\twant: []object{\n\t\t\t\t{\n\t\t\t\t\tname:        \"aws3.txt\",\n\t\t\t\t\tbucket:      testBucket2,\n\t\t\t\t\tcontentType: \"text/plain\",\n\t\t\t\t\tsize:        150,\n\t\t\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th2/o/aws3.txt?generation=1677871022489611&alt=media\",\n\t\t\t\t\tacl:         []string{},\n\t\t\t\t\tmd5:         \"26eb03378a0c8e99ee0cecf9155ace81\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname:        \"moar.txt\",\n\t\t\t\t\tbucket:      testBucket3,\n\t\t\t\t\tcontentType: \"text/plain\",\n\t\t\t\t\tsize:        6,\n\t\t\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th3/o/moar.txt?generation=1677871042896804&alt=media\",\n\t\t\t\t\tacl:         []string{},\n\t\t\t\t\tmd5:         \"09f7e02f1290be211da707a266f153b3\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname:        \"AMAZON_FASHION_5.json\",\n\t\t\t\t\tbucket:      testBucket4,\n\t\t\t\t\tcontentType: \"application/json\",\n\t\t\t\t\tsize:        1413469,\n\t\t\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th4/o/AMAZON_FASHION_5.json?generation=1677871063457469&alt=media\",\n\t\t\t\t\tacl:         []string{},\n\t\t\t\t\tmd5:         \"5a1d8f483aa34695256379050d42a2b7\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantNumBkt: 4, // We still list objects in all buckets.\n\t\t\twantNumObj: 3,\n\t\t},\n\t\t{\n\t\t\tname:      \"list objects, with include objects, include bucket\",\n\t\t\tprojectID: testProjectID,\n\t\t\topts: []gcsManagerOption{\n\t\t\t\twithDefaultADC(ctx),\n\t\t\t\twithIncludeObjects([]string{\"aws1.txt\"}),\n\t\t\t\twithIncludeBuckets([]string{testBucket}),\n\t\t\t},\n\t\t\twant: []object{\n\t\t\t\t{\n\t\t\t\t\tname:        \"aws1.txt\",\n\t\t\t\t\tbucket:      testBucket,\n\t\t\t\t\tcontentType: \"text/plain\",\n\t\t\t\t\tsize:        150,\n\t\t\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/aws1.txt?generation=1677870994890594&alt=media\",\n\t\t\t\t\tacl:         []string{},\n\t\t\t\t\tmd5:         \"7a4df45f839cd594ac236b19524cec92\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantNumBkt: 1,\n\t\t\twantNumObj: 1,\n\t\t},\n\t\t{\n\t\t\tname:      \"list objects, with include objects\",\n\t\t\tprojectID: testProjectID,\n\t\t\topts: []gcsManagerOption{\n\t\t\t\twithDefaultADC(ctx),\n\t\t\t\twithIncludeObjects([]string{\"aws1.txt\"}),\n\t\t\t\twithExcludeBuckets([]string{perfTestBucketGlob, publicBucket}),\n\t\t\t},\n\t\t\twant: []object{\n\t\t\t\t{\n\t\t\t\t\tname:        \"aws1.txt\",\n\t\t\t\t\tbucket:      testBucket,\n\t\t\t\t\tcontentType: \"text/plain\",\n\t\t\t\t\tsize:        150,\n\t\t\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/aws1.txt?generation=1677870994890594&alt=media\",\n\t\t\t\t\tacl:         []string{},\n\t\t\t\t\tmd5:         \"7a4df45f839cd594ac236b19524cec92\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantNumBkt: 4,\n\t\t\twantNumObj: 1,\n\t\t},\n\t\t{\n\t\t\tname:      \"list objects, with include bucket, exclude object\",\n\t\t\tprojectID: testProjectID,\n\t\t\topts: []gcsManagerOption{\n\t\t\t\twithDefaultADC(ctx),\n\t\t\t\twithIncludeBuckets([]string{testBucket}),\n\t\t\t\twithExcludeObjects([]string{\"aws1.txt\"}),\n\t\t\t\twithExcludeBuckets([]string{perfTestBucketGlob, publicBucket}),\n\t\t\t},\n\t\t\twant: []object{\n\t\t\t\t{\n\t\t\t\t\tname:        \"moar2.txt\",\n\t\t\t\t\tbucket:      testBucket,\n\t\t\t\t\tcontentType: \"text/plain\",\n\t\t\t\t\tsize:        12,\n\t\t\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/moar2.txt?generation=1677871000378542&alt=media\",\n\t\t\t\t\tacl:         []string{},\n\t\t\t\t\tmd5:         \"e59ff97941044f85df5297e1c302d260\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantNumBkt: 1,\n\t\t\twantNumObj: 1,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmgr, err := newGCSManager(tc.projectID, tc.opts...)\n\t\t\tassert.Nil(t, err)\n\n\t\t\tgot, err := mgr.ListObjects(ctx)\n\t\t\tif (err != nil) != tc.wantErr {\n\t\t\t\tt.Errorf(\"GCSManager.ListObjects() error = %v, wantErr %v\", err, tc.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// If we expect an error, we're done.\n\t\t\t// Short-circuit the rest of the test.\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tdoneCh := make(chan struct{})\n\t\t\tdefer close(doneCh)\n\t\t\tgo func() {\n\t\t\t\tdefer func() {\n\t\t\t\t\tdoneCh <- struct{}{}\n\t\t\t\t}()\n\t\t\t\tres := make([]object, 0, len(tc.want))\n\t\t\t\tfor obj := range got {\n\t\t\t\t\tres = append(res, obj.(object))\n\t\t\t\t}\n\n\t\t\t\tif len(res) != len(tc.want) {\n\t\t\t\t\tt.Errorf(\"gcsManager.ListObjects() got: %v, want: %v\", res, tc.want)\n\t\t\t\t}\n\n\t\t\t\t// Test the bucket and object counts.\n\t\t\t\tassert.Equal(t, tc.wantNumBkt, mgr.numBuckets)\n\t\t\t\tassert.Equal(t, tc.wantNumObj, mgr.numObjects)\n\n\t\t\t\t// Sort the objects by name to make the test deterministic.\n\t\t\t\t// This is necessary because we list bucket objects concurrently.\n\t\t\t\tsort.Slice(res, func(i, j int) bool { return res[i].name < res[j].name })\n\t\t\t\tsort.Slice(tc.want, func(i, j int) bool { return tc.want[i].name < tc.want[j].name })\n\n\t\t\t\t// Test the objects are equal.\n\t\t\t\tif diff := cmp.Diff(res, tc.want, cmp.AllowUnexported(object{}), cmpopts.IgnoreFields(object{}, \"Reader\", \"createdAt\", \"updatedAt\")); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"gcsManager.ListObjects() mismatch (-want +got):\\n%s\", diff)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t<-doneCh\n\t\t})\n\t}\n}\n\nfunc TestGCSManagerListObjects_Resuming(t *testing.T) {\n\tctx := context.Background()\n\n\ttestCases := []struct {\n\t\tname       string\n\t\tprojectID  string\n\t\topts       []gcsManagerOption\n\t\twant       []object\n\t\twantNumBkt uint32\n\t\twantNumObj uint64\n\t\twantErr    bool\n\t}{\n\t\t{\n\t\t\tname:      \"list objects, testBucket, resume from moar2.txt\",\n\t\t\tprojectID: testProjectID,\n\t\t\topts: []gcsManagerOption{\n\t\t\t\twithDefaultADC(ctx),\n\t\t\t\twithIncludeBuckets([]string{testBucket}),\n\t\t\t\twithBucketOffsets(map[string]offsetInfo{testBucket: {lastProcessedObject: \"moar2.txt\"}}),\n\t\t\t},\n\t\t\twant: []object{\n\t\t\t\t{\n\t\t\t\t\tname:        \"moar2.txt\",\n\t\t\t\t\tbucket:      testBucket,\n\t\t\t\t\tcontentType: \"text/plain\",\n\t\t\t\t\tsize:        12,\n\t\t\t\t\tlink:        \"https://storage.googleapis.com/download/storage/v1/b/test-bkt-th/o/moar2.txt?generation=1677871000378542&alt=media\",\n\t\t\t\t\tacl:         []string{},\n\t\t\t\t\tmd5:         \"e59ff97941044f85df5297e1c302d260\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantNumBkt: 1,\n\t\t\twantNumObj: 1,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmgr, err := newGCSManager(tc.projectID, tc.opts...)\n\t\t\tassert.Nil(t, err)\n\n\t\t\tgot, err := mgr.ListObjects(ctx)\n\t\t\tif (err != nil) != tc.wantErr {\n\t\t\t\tt.Errorf(\"GCSManager.ListObjects() error = %v, wantErr %v\", err, tc.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// If we expect an error, we're done.\n\t\t\t// Short-circuit the rest of the test.\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tdoneCh := make(chan struct{})\n\t\t\tdefer close(doneCh)\n\t\t\tgo func() {\n\t\t\t\tdefer func() {\n\t\t\t\t\tdoneCh <- struct{}{}\n\t\t\t\t}()\n\t\t\t\tres := make([]object, 0, len(tc.want))\n\t\t\t\tfor obj := range got {\n\t\t\t\t\tres = append(res, obj.(object))\n\t\t\t\t}\n\n\t\t\t\tif len(res) != len(tc.want) {\n\t\t\t\t\tt.Errorf(\"gcsManager.ListObjects() got: %v, want: %v\", res, tc.want)\n\t\t\t\t}\n\n\t\t\t\t// Test the bucket and object counts.\n\t\t\t\tassert.Equal(t, tc.wantNumBkt, mgr.numBuckets)\n\t\t\t\tassert.Equal(t, tc.wantNumObj, mgr.numObjects)\n\n\t\t\t\t// Sort the objects by name to make the test deterministic.\n\t\t\t\t// This is necessary because we list bucket objects concurrently.\n\t\t\t\tsort.Slice(res, func(i, j int) bool { return res[i].name < res[j].name })\n\t\t\t\tsort.Slice(tc.want, func(i, j int) bool { return tc.want[i].name < tc.want[j].name })\n\n\t\t\t\t// Test the objects are equal.\n\t\t\t\tif diff := cmp.Diff(res, tc.want, cmp.AllowUnexported(object{}), cmpopts.IgnoreFields(object{}, \"Reader\", \"createdAt\", \"updatedAt\")); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"gcsManager.ListObjects() mismatch (-want +got):\\n%s\", diff)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t<-doneCh\n\t\t})\n\t}\n}\n\nfunc Test_isObjectTypeValid(t *testing.T) {\n\ttestCases := []struct {\n\t\tname    string\n\t\tobjName string\n\t\twant    bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid object type, text\",\n\t\t\tobjName: \"aws1.txt\",\n\t\t\twant:    true,\n\t\t},\n\t\t{\n\t\t\tname:    \"valid object type, zip\",\n\t\t\tobjName: \"aws1.zip\",\n\t\t\twant:    true,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid object type\",\n\t\t\tobjName: \"video.mp4\",\n\t\t\twant:    false,\n\t\t},\n\t}\n\n\tctx := context.Background()\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := isObjectTypeValid(ctx, tc.objName)\n\t\t\tassert.Equal(t, tc.want, got)\n\t\t})\n\t}\n}\n\nfunc Test_isObjectSizeValid(t *testing.T) {\n\ttestCases := []struct {\n\t\tname    string\n\t\tobjSize int64\n\t\twant    bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid object size, 150 bytes\",\n\t\t\tobjSize: 150,\n\t\t\twant:    true,\n\t\t},\n\t\t{\n\t\t\tname:    \"valid object size, 1 MB\",\n\t\t\tobjSize: int64(1 * common.MB),\n\t\t\twant:    true,\n\t\t},\n\t\t{\n\t\t\tname:    \"valid object size, 49 MB\",\n\t\t\tobjSize: int64(49 * common.MB),\n\t\t\twant:    true,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid object size, 1 GB\",\n\t\t\tobjSize: int64(1 * common.GB),\n\t\t\twant:    false,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid object size, 2 GB\",\n\t\t\tobjSize: int64(2 * common.GB),\n\t\t\twant:    false,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid object size, empty object\",\n\t\t\tobjSize: 0,\n\t\t\twant:    false,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid object size, negative object size\",\n\t\t\tobjSize: -1,\n\t\t\twant:    false,\n\t\t},\n\t}\n\n\tctx := context.Background()\n\tfor _, tc := range testCases {\n\t\tg := &gcsManager{\n\t\t\tmaxObjectSize: maxObjectSizeLimit,\n\t\t}\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := g.isObjectSizeValid(ctx, tc.objSize)\n\t\t\tassert.Equal(t, tc.want, got)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/sources/gcs/gcs_test.go",
    "content": "package gcs\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/credentialspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nfunc createTestSource(src *sourcespb.GCS) (*Source, *anypb.Any) {\n\ts := &Source{}\n\tconn, err := anypb.New(src)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn s, conn\n}\n\nfunc TestSourceInit(t *testing.T) {\n\tsource, conn := createTestSource(&sourcespb.GCS{\n\t\tProjectId: testProjectID,\n\t\tIncludeBuckets: []string{\n\t\t\tpublicBucket,\n\t\t},\n\t\tExcludeBuckets: []string{\n\t\t\tperfTestBucketGlob,\n\t\t},\n\t\tExcludeObjects: []string{\n\t\t\t\"object1\",\n\t\t},\n\t\tCredential: &sourcespb.GCS_Unauthenticated{},\n\t})\n\n\terr := source.Init(context.Background(), \"test\", 1, 1, true, conn, 8)\n\tassert.Nil(t, err)\n\tassert.NotNil(t, source.gcsManager)\n}\n\nfunc TestConfigureGCSManager(t *testing.T) {\n\ttestCases := []struct {\n\t\tname    string\n\t\tconn    *sourcespb.GCS\n\t\twant    *gcsManager\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"nil conn\",\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"valid conn, bare config\",\n\t\t\tconn: &sourcespb.GCS{\n\t\t\t\tProjectId:  testProjectID,\n\t\t\t\tCredential: &sourcespb.GCS_Adc{},\n\t\t\t\tExcludeBuckets: []string{\n\t\t\t\t\tperfTestBucketGlob,\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:      testProjectID,\n\t\t\t\texcludeBuckets: map[string]struct{}{perfTestBucketGlob: {}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid conn, include and exclude buckets\",\n\t\t\tconn: &sourcespb.GCS{\n\t\t\t\tProjectId:  testProjectID,\n\t\t\t\tCredential: &sourcespb.GCS_Adc{},\n\t\t\t\tIncludeBuckets: []string{\n\t\t\t\t\t\"bucket1\",\n\t\t\t\t},\n\t\t\t\tExcludeBuckets: []string{\n\t\t\t\t\tperfTestBucketGlob,\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:      testProjectID,\n\t\t\t\tincludeBuckets: map[string]struct{}{\"bucket1\": {}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"valid conn, include and exclude objects\",\n\t\t\tconn: &sourcespb.GCS{\n\t\t\t\tProjectId:  testProjectID,\n\t\t\t\tCredential: &sourcespb.GCS_Adc{},\n\t\t\t\tIncludeObjects: []string{\n\t\t\t\t\t\"object1\",\n\t\t\t\t},\n\t\t\t\tExcludeObjects: []string{\n\t\t\t\t\t\"object2\",\n\t\t\t\t},\n\t\t\t\tExcludeBuckets: []string{\n\t\t\t\t\tperfTestBucketGlob,\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: &gcsManager{\n\t\t\t\tprojectID:      testProjectID,\n\t\t\t\tincludeObjects: map[string]struct{}{\"object1\": {}},\n\t\t\t\texcludeBuckets: map[string]struct{}{perfTestBucketGlob: {}},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tctx := context.Background()\n\t\t\tgot, err := configureGCSManager(ctx, tc.conn, 8)\n\t\t\tif err != nil && !tc.wantErr {\n\t\t\t\tt.Errorf(\"source.configureGCSManager() error = %v\", err)\n\t\t\t}\n\n\t\t\tif !tc.wantErr {\n\t\t\t\tif diff := cmp.Diff(tc.want, got,\n\t\t\t\t\tcmp.AllowUnexported(gcsManager{}),\n\t\t\t\t\tcmpopts.IgnoreFields(gcsManager{}, \"client\", \"workerPool\", \"concurrency\", \"buckets\", \"maxObjectSize\", \"attr\"),\n\t\t\t\t); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"source.Init() diff: (-want +got)\\n%s\", diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSourceOauth2Client(t *testing.T) {\n\ttestCases := []struct {\n\t\tname    string\n\t\tcreds   *credentialspb.Oauth2\n\t\twant    *http.Client\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"valid creds\",\n\t\t\tcreds: &credentialspb.Oauth2{\n\t\t\t\tRefreshToken: \"some-refresh-token\",\n\t\t\t\tClientId:     \"some-client-id\",\n\t\t\t\tAccessToken:  \"some-access-token\",\n\t\t\t},\n\t\t\twant: &http.Client{},\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid creds, nil creds\",\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid creds, empty refresh token\",\n\t\t\tcreds: &credentialspb.Oauth2{\n\t\t\t\tRefreshToken: \"\",\n\t\t\t\tAccessToken:  \"some-access-token\",\n\t\t\t\tClientId:     \"some-client-id\",\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid creds, empty client id\",\n\t\t\tcreds: &credentialspb.Oauth2{\n\t\t\t\tRefreshToken: \"some-refresh-token\",\n\t\t\t\tAccessToken:  \"some-access-token\",\n\t\t\t\tClientId:     \"\",\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid creds, empty access token\",\n\t\t\tcreds: &credentialspb.Oauth2{\n\t\t\t\tAccessToken:  \"\",\n\t\t\t\tRefreshToken: \"some-refresh-token\",\n\t\t\t\tClientId:     \"some-client-id\",\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tctx := context.Background()\n\t\t\tgot, err := oauth2Client(ctx, tc.creds)\n\t\t\tif (err != nil) != tc.wantErr {\n\t\t\t\tt.Errorf(\"source.oauth2Client() error = %v\", err)\n\t\t\t}\n\n\t\t\tif !tc.wantErr {\n\t\t\t\tif diff := cmp.Diff(tc.want, got,\n\t\t\t\t\tcmpopts.IgnoreFields(http.Client{}, \"Transport\"),\n\t\t\t\t); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"source.oauth2Client() diff: (-want +got)\\n%s\", diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype mockObjectManager struct {\n\t// numObjects is the number of objects to return in the listObjects call.\n\tnumObjects int\n\twantErr    bool\n}\n\nfunc (m *mockObjectManager) Attributes(_ context.Context) (*attributes, error) {\n\tif m.wantErr {\n\t\treturn nil, fmt.Errorf(\"some error\")\n\t}\n\n\treturn &attributes{\n\t\tnumObjects:    uint64(m.numObjects),\n\t\tnumBuckets:    1,\n\t\tbucketObjects: map[string]uint64{testBucket: 5},\n\t}, nil\n}\n\ntype mockReader struct {\n\toffset int\n\tdata   []byte\n}\n\nfunc (m *mockReader) Read(p []byte) (n int, err error) {\n\tif m.offset >= len(m.data) {\n\t\treturn 0, io.EOF\n\t}\n\n\tn = copy(p, m.data[m.offset:])\n\tm.offset += n\n\treturn\n}\n\nfunc (m *mockObjectManager) ListObjects(context.Context) (chan io.Reader, error) {\n\tif m.wantErr {\n\t\treturn nil, fmt.Errorf(\"some error\")\n\t}\n\n\tch := make(chan io.Reader)\n\tgo func() {\n\t\tdefer close(ch)\n\t\t// Add 5 objects to the channel.\n\t\tfor i := 0; i < m.numObjects; i++ {\n\t\t\tch <- createTestObject(i)\n\t\t}\n\t}()\n\n\treturn ch, nil\n}\n\nfunc createTestObject(id int) object {\n\treturn object{\n\t\tname:        fmt.Sprintf(\"object%d\", id),\n\t\tbucket:      testBucket,\n\t\tcontentType: \"plain/text\",\n\t\towner:       \"testman@test.com\",\n\t\tlink:        fmt.Sprintf(\"https://storage.googleapis.com/%s/%s\", testBucket, fmt.Sprintf(\"object%d\", id)),\n\t\tacl:         []string{\"authenticatedUsers\"},\n\t\tsize:        42,\n\t\tmd5:         fmt.Sprintf(\"md5hash%d\", id),\n\t\tReader:      &mockReader{data: []byte(fmt.Sprintf(\"hello world %d\", id))},\n\t\tcreatedAt:   time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),\n\t}\n}\n\nfunc createTestSourceChunk(id int) *sources.Chunk {\n\treturn &sources.Chunk{\n\t\tSourceName:   \"test\",\n\t\tSourceType:   sourcespb.SourceType_SOURCE_TYPE_GCS,\n\t\tSourceID:     0,\n\t\tSourceVerify: true,\n\t\tData:         []byte(fmt.Sprintf(\"hello world %d\", id)),\n\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\tData: &source_metadatapb.MetaData_Gcs{\n\t\t\t\tGcs: &source_metadatapb.GCS{\n\t\t\t\t\tFilename:    fmt.Sprintf(\"object%d\", id),\n\t\t\t\t\tBucket:      testBucket,\n\t\t\t\t\tContentType: \"plain/text\",\n\t\t\t\t\tEmail:       \"testman@test.com\",\n\t\t\t\t\tLink:        fmt.Sprintf(\"https://storage.googleapis.com/%s/%s\", testBucket, fmt.Sprintf(\"object%d\", id)),\n\t\t\t\t\tAcls:        []string{\"authenticatedUsers\"},\n\t\t\t\t\tCreatedAt:   \"1577836800\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\nfunc TestSourceChunks_ListObjects(t *testing.T) {\n\tctx := context.Background()\n\tchunksCh := make(chan *sources.Chunk, 1)\n\n\tsource := &Source{\n\t\tgcsManager: &mockObjectManager{numObjects: 5},\n\t\tchunksCh:   chunksCh,\n\t\tProgress:   sources.Progress{},\n\t}\n\n\terr := source.enumerate(ctx)\n\tassert.Nil(t, err)\n\n\tgo func() {\n\t\tdefer close(chunksCh)\n\t\terr := source.Chunks(ctx, chunksCh)\n\t\tassert.Nil(t, err)\n\t}()\n\n\twant := make([]*sources.Chunk, 0, 5)\n\tfor i := 0; i < 5; i++ {\n\t\twant = append(want, createTestSourceChunk(i))\n\t}\n\n\tcount := 0\n\tgot := make([]*sources.Chunk, 0, 5)\n\tfor ch := range chunksCh {\n\t\tgot = append(got, ch)\n\t\tcount++\n\t}\n\n\t// Ensure we get 5 objects back.\n\tassert.Equal(t, 5, count)\n\n\t// Sort the results to ensure deterministic ordering.\n\tsort.Slice(want, func(i, j int) bool {\n\t\treturn want[i].SourceMetadata.GetGcs().Filename < want[j].SourceMetadata.GetGcs().Filename\n\t})\n\tsort.Slice(got, func(i, j int) bool {\n\t\treturn got[i].SourceMetadata.GetGcs().Filename < got[j].SourceMetadata.GetGcs().Filename\n\t})\n\n\tfor i, c := range got {\n\t\tassert.Equal(t, want[i].SourceMetadata.GetGcs().Filename, c.SourceMetadata.GetGcs().Filename)\n\t\tassert.Equal(t, want[i].Data, c.Data)\n\t\tassert.Equal(t, want[i].SourceMetadata.GetGcs().CreatedAt, c.SourceMetadata.GetGcs().CreatedAt)\n\t}\n\n}\n\nfunc TestSourceInit_Enumerate(t *testing.T) {\n\tctx := context.Background()\n\tsource := &Source{gcsManager: &mockObjectManager{numObjects: 5}}\n\n\terr := source.enumerate(ctx)\n\tassert.Nil(t, err)\n\n\t// Ensure the attributes are set.\n\tassert.Equal(t, uint64(5), source.stats.numObjects)\n\tassert.Equal(t, uint32(1), source.stats.numBuckets)\n\tassert.Equal(t, uint64(5), source.stats.bucketObjects[testBucket])\n}\n\nfunc TestSourceChunks_ListObjects_Error(t *testing.T) {\n\tctx := context.Background()\n\tsource := &Source{gcsManager: &mockObjectManager{wantErr: true}}\n\n\tchunksCh := make(chan *sources.Chunk, 1)\n\n\tdefer close(chunksCh)\n\terr := source.Chunks(ctx, chunksCh)\n\tassert.True(t, err != nil)\n}\n\nfunc TestSourceChunks_ProgressSet(t *testing.T) {\n\tctx := context.Background()\n\tchunksCh := make(chan *sources.Chunk, 1)\n\tsource := &Source{\n\t\tgcsManager: &mockObjectManager{numObjects: defaultCachePersistIncrement},\n\t\tchunksCh:   chunksCh,\n\t\tProgress:   sources.Progress{},\n\t}\n\n\terr := source.enumerate(ctx)\n\tassert.Nil(t, err)\n\n\tgo func() {\n\t\tdefer close(chunksCh)\n\t\terr := source.Chunks(ctx, chunksCh)\n\t\tassert.Nil(t, err)\n\t}()\n\n\twant := make([]*sources.Chunk, 0, defaultCachePersistIncrement)\n\tfor i := 0; i < defaultCachePersistIncrement; i++ {\n\t\twant = append(want, createTestSourceChunk(i))\n\t}\n\n\tgot := make([]*sources.Chunk, 0, defaultCachePersistIncrement)\n\tfor ch := range chunksCh {\n\t\tgot = append(got, ch)\n\t}\n\n\t// Ensure we get 2500 objects back.\n\tassert.Equal(t, len(want), len(got))\n\n\t// Test that the resume progress is set.\n\tvar progress strings.Builder\n\tfor i := range got {\n\t\tprogress.WriteString(fmt.Sprintf(\"md5hash%d\", i))\n\t\t// Add a comma if not the last element.\n\t\tif i != len(got)-1 {\n\t\t\tprogress.WriteString(\",\")\n\t\t}\n\t}\n\n\tencodeResume := strings.Split(source.Progress.EncodedResumeInfo, \",\")\n\tsort.Slice(encodeResume, func(i, j int) bool {\n\t\tnumI, _ := strconv.Atoi(strings.TrimPrefix(encodeResume[i], \"md5hash\"))\n\t\tnumJ, _ := strconv.Atoi(strings.TrimPrefix(encodeResume[j], \"md5hash\"))\n\t\treturn numI < numJ\n\t})\n\n\tassert.Equal(t, progress.String(), strings.Join(encodeResume, \",\"))\n\tassert.Equal(t, int32(defaultCachePersistIncrement), source.Progress.SectionsCompleted)\n\tassert.Equal(t, int64(100), source.Progress.PercentComplete)\n\tassert.Equal(t, fmt.Sprintf(\"GCS source finished processing %d objects\", defaultCachePersistIncrement), source.Progress.Message)\n}\n\nfunc TestSource_CachePersistence(t *testing.T) {\n\tctx := context.Background()\n\n\twantObjCnt := 4 // ensure we have less objects than the cache increment\n\tmockObjManager := &mockObjectManager{numObjects: wantObjCnt}\n\n\tchunksCh := make(chan *sources.Chunk, 1)\n\tsource := &Source{\n\t\tgcsManager: mockObjManager,\n\t\tchunksCh:   chunksCh,\n\t\tProgress:   sources.Progress{},\n\t}\n\n\terr := source.enumerate(ctx)\n\tassert.Nil(t, err)\n\n\tgo func() {\n\t\tdefer close(chunksCh)\n\t\terr := source.Chunks(ctx, chunksCh)\n\t\tassert.Nil(t, err)\n\t}()\n\n\twant := make([]*sources.Chunk, 0, wantObjCnt)\n\tfor i := 0; i < wantObjCnt; i++ {\n\t\twant = append(want, createTestSourceChunk(i))\n\t}\n\n\tgot := make([]*sources.Chunk, 0, wantObjCnt)\n\tfor ch := range chunksCh {\n\t\tgot = append(got, ch)\n\t}\n\n\t// Ensure we get 4 objects back.\n\tassert.Equal(t, len(want), len(got))\n\n\t// Test that the resume progress is empty.\n\t// The cache should not have been persisted.\n\tassert.Equal(t, \"\", source.Progress.EncodedResumeInfo)\n\tassert.Equal(t, int32(wantObjCnt), source.Progress.SectionsCompleted)\n\tassert.Equal(t, int64(100), source.Progress.PercentComplete)\n\tassert.Equal(t, fmt.Sprintf(\"GCS source finished processing %d objects\", wantObjCnt), source.Progress.Message)\n}\n"
  },
  {
    "path": "pkg/sources/git/cmd_check.go",
    "content": "package git\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/go-errors/errors\"\n)\n\n// Extract the version string using a regex to find the version numbers\nvar regex = regexp.MustCompile(`\\d+\\.\\d+\\.\\d+`)\n\n// CmdCheck checks if git is installed and meets 2.20.0<=x<3.0.0 version requirements.\nfunc CmdCheck() error {\n\tif errors.Is(exec.Command(\"git\").Run(), exec.ErrNotFound) {\n\t\treturn fmt.Errorf(\"'git' command not found in $PATH. Make sure git is installed and included in $PATH\")\n\t}\n\n\t// Check the version is greater than or equal to 2.20.0\n\tout, err := exec.Command(\"git\", \"--version\").Output()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to check git version: %w\", err)\n\t}\n\n\tversionStr := regex.FindString(string(out))\n\tversionParts := strings.Split(versionStr, \".\")\n\n\t// Parse version numbers\n\tmajor, _ := strconv.Atoi(versionParts[0])\n\tminor, _ := strconv.Atoi(versionParts[1])\n\n\t// Compare with version 2.20.0<=x<3.0.0\n\tif major == 2 && minor >= 20 {\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"git version is %s, but must be greater than or equal to 2.20.0, and less than 3.0.0\", versionStr)\n}\n"
  },
  {
    "path": "pkg/sources/git/git.go",
    "content": "package git\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/go-git/go-git/v5\"\n\t\"github.com/go-git/go-git/v5/plumbing\"\n\t\"github.com/go-git/go-git/v5/plumbing/object\"\n\t\"github.com/google/go-github/v67/github\"\n\t\"golang.org/x/oauth2\"\n\t\"golang.org/x/sync/semaphore\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cleantemp\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/feature\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/gitparse\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/handlers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sanitizer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nconst SourceType = sourcespb.SourceType_SOURCE_TYPE_GIT\n\ntype Source struct {\n\tname     string\n\tsourceID sources.SourceID\n\tjobID    sources.JobID\n\tverify   bool\n\n\tuseCustomContentWriter bool\n\tgit                    *Git\n\tscanOptions            *ScanOptions\n\n\tsources.Progress\n\tconn *sourcespb.Git\n}\n\n// WithCustomContentWriter sets the useCustomContentWriter flag on the source.\nfunc (s *Source) WithCustomContentWriter() { s.useCustomContentWriter = true }\n\ntype Git struct {\n\tsourceType         sourcespb.SourceType\n\tsourceName         string\n\tsourceID           sources.SourceID\n\tjobID              sources.JobID\n\tsourceMetadataFunc func(file, email, commit, timestamp, repository, repositoryLocalPath string, line int64) *source_metadatapb.MetaData\n\tverify             bool\n\tmetrics            metricsCollector\n\tconcurrency        *semaphore.Weighted\n\tskipBinaries       bool\n\tskipArchives       bool\n\trepoCommitsScanned uint64 // Atomic counter for commits scanned in the current repo\n\n\tparser *gitparse.Parser\n}\n\n// Config for a Git source.\ntype Config struct {\n\tConcurrency        int\n\tSourceMetadataFunc func(file, email, commit, timestamp, repository, repositoryLocalPath string, line int64) *source_metadatapb.MetaData\n\n\tSourceName   string\n\tJobID        sources.JobID\n\tSourceID     sources.SourceID\n\tSourceType   sourcespb.SourceType\n\tVerify       bool\n\tSkipBinaries bool\n\tSkipArchives bool\n\n\t// UseCustomContentWriter indicates whether to use a custom contentWriter.\n\t// When set to true, the parser will use a custom contentWriter provided through the WithContentWriter option.\n\t// When false, the parser will use the default buffer (in-memory) contentWriter.\n\tUseCustomContentWriter bool\n\t// pass authentication embedded in the repository urls\n\tAuthInUrl bool\n}\n\n// NewGit creates a new Git instance with the provided configuration. The Git instance is used to interact with\n// Git repositories.\nfunc NewGit(config *Config) *Git {\n\tvar parser *gitparse.Parser\n\tif config.UseCustomContentWriter {\n\t\tparser = gitparse.NewParser(gitparse.UseCustomContentWriter())\n\t} else {\n\t\tparser = gitparse.NewParser()\n\t}\n\n\treturn &Git{\n\t\tsourceType:         config.SourceType,\n\t\tsourceName:         config.SourceName,\n\t\tsourceID:           config.SourceID,\n\t\tjobID:              config.JobID,\n\t\tsourceMetadataFunc: config.SourceMetadataFunc,\n\t\tverify:             config.Verify,\n\t\tmetrics:            metricsInstance,\n\t\tconcurrency:        semaphore.NewWeighted(int64(config.Concurrency)),\n\t\tskipBinaries:       config.SkipBinaries,\n\t\tskipArchives:       config.SkipArchives,\n\t\tparser:             parser,\n\t}\n}\n\n// Ensure the Source satisfies the interfaces at compile time.\nvar _ interface {\n\tsources.Source\n\tsources.SourceUnitEnumChunker\n\tsources.SourceUnitUnmarshaller\n} = (*Source)(nil)\n\n// Type returns the type of source.\n// It is used for matching source types in configuration and job input.\nfunc (s *Source) Type() sourcespb.SourceType {\n\treturn SourceType\n}\n\nfunc (s *Source) SourceID() sources.SourceID {\n\treturn s.sourceID\n}\n\nfunc (s *Source) JobID() sources.JobID {\n\treturn s.jobID\n}\n\n// withScanOptions sets the scan options.\nfunc (s *Source) withScanOptions(scanOptions *ScanOptions) {\n\ts.scanOptions = scanOptions\n}\n\n// Init returns an initialized Git source.\nfunc (s *Source) Init(aCtx context.Context, name string, jobId sources.JobID, sourceId sources.SourceID, verify bool, connection *anypb.Any, concurrency int) error {\n\ts.name = name\n\ts.sourceID = sourceId\n\ts.jobID = jobId\n\ts.verify = verify\n\tif s.scanOptions == nil {\n\t\ts.scanOptions = &ScanOptions{}\n\t}\n\n\tvar conn sourcespb.Git\n\tif err := anypb.UnmarshalTo(connection, &conn, proto.UnmarshalOptions{}); err != nil {\n\t\treturn fmt.Errorf(\"error unmarshalling connection: %w\", err)\n\t}\n\n\tif uri := conn.GetUri(); uri != \"\" {\n\t\trepoPath, _, err := prepareRepoSinceCommit(aCtx, uri, conn.GetClonePath(), conn.GetBase(), conn.GetTrustLocalGitConfig(), conn.GetBare())\n\t\tif err != nil || repoPath == \"\" {\n\t\t\treturn fmt.Errorf(\"error preparing repo: %w\", err)\n\t\t}\n\n\t\tconn.Directories = append(conn.Directories, repoPath)\n\t}\n\n\tfilter, err := common.FilterFromFiles(conn.IncludePathsFile, conn.ExcludePathsFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error creating filter: %w\", err)\n\t}\n\topts := []ScanOption{ScanOptionFilter(filter), ScanOptionLogOptions(new(git.LogOptions))}\n\n\tif depth := conn.GetMaxDepth(); depth != 0 {\n\t\topts = append(opts, ScanOptionMaxDepth(depth))\n\t}\n\tif base := conn.GetBase(); base != \"\" {\n\t\topts = append(opts, ScanOptionBaseHash(base))\n\t}\n\tif head := conn.GetHead(); head != \"\" {\n\t\topts = append(opts, ScanOptionHeadCommit(head))\n\t}\n\tif globs := conn.GetExcludeGlobs(); globs != \"\" {\n\t\texcludedGlobs := strings.Split(globs, \",\")\n\t\topts = append(opts, ScanOptionExcludeGlobs(excludedGlobs))\n\t}\n\tif isBare := conn.GetBare(); isBare {\n\t\topts = append(opts, ScanOptionBare(isBare))\n\t}\n\ts.withScanOptions(NewScanOptions(opts...))\n\n\ts.conn = &conn\n\n\tif concurrency == 0 {\n\t\tconcurrency = runtime.NumCPU()\n\t}\n\n\tif err = CmdCheck(); err != nil {\n\t\treturn err\n\t}\n\n\tcfg := &Config{\n\t\tSourceName:   s.name,\n\t\tJobID:        s.jobID,\n\t\tSourceID:     s.sourceID,\n\t\tSourceType:   s.Type(),\n\t\tVerify:       s.verify,\n\t\tSkipBinaries: conn.GetSkipBinaries(),\n\t\tSkipArchives: conn.GetSkipArchives(),\n\t\tConcurrency:  concurrency,\n\t\tSourceMetadataFunc: func(file, email, commit, timestamp, repository, repositoryLocalPath string, line int64) *source_metadatapb.MetaData {\n\t\t\treturn &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Git{\n\t\t\t\t\tGit: &source_metadatapb.Git{\n\t\t\t\t\t\tCommit:              sanitizer.UTF8(commit),\n\t\t\t\t\t\tFile:                sanitizer.UTF8(file),\n\t\t\t\t\t\tEmail:               sanitizer.UTF8(email),\n\t\t\t\t\t\tRepository:          sanitizer.UTF8(repository),\n\t\t\t\t\t\tTimestamp:           sanitizer.UTF8(timestamp),\n\t\t\t\t\t\tLine:                line,\n\t\t\t\t\t\tRepositoryLocalPath: sanitizer.UTF8(repositoryLocalPath),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t\tUseCustomContentWriter: s.useCustomContentWriter,\n\t}\n\ts.git = NewGit(cfg)\n\treturn nil\n}\n\n// Chunks emits chunks of bytes over a channel.\nfunc (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, _ ...sources.ChunkingTarget) error {\n\treporter := sources.ChanReporter{Ch: chunksChan}\n\tif err := s.scanRepos(ctx, reporter); err != nil {\n\t\treturn err\n\t}\n\n\tif err := s.scanDirs(ctx, reporter); err != nil {\n\t\treturn err\n\t}\n\n\ttotalRepos := len(s.conn.Repositories) + len(s.conn.Directories)\n\tctx.Logger().V(1).Info(\"Git source finished scanning\", \"repo_count\", totalRepos)\n\ts.SetProgressComplete(\n\t\ttotalRepos, totalRepos,\n\t\tfmt.Sprintf(\"Completed scanning source %s\", s.name), \"\",\n\t)\n\treturn nil\n}\n\n// scanRepos scans the configured repositories in s.conn.Repositories.\nfunc (s *Source) scanRepos(ctx context.Context, reporter sources.ChunkReporter) error {\n\tif len(s.conn.Repositories) == 0 {\n\t\treturn nil\n\t}\n\n\ttotalRepos := len(s.conn.Repositories) + len(s.conn.Directories)\n\tfor i, repoURI := range s.conn.Repositories {\n\t\ts.SetProgressComplete(i, totalRepos, fmt.Sprintf(\"Repo: %s\", repoURI), \"\")\n\n\t\tif len(repoURI) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif err := s.scanRepo(ctx, repoURI, reporter); err != nil {\n\t\t\tctx.Logger().Info(\"error scanning repository\", \"repo\", repoURI, \"error\", err)\n\t\t\tcontinue\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// scanRepo scans a single provided repository.\nfunc (s *Source) scanRepo(ctx context.Context, repoURI string, reporter sources.ChunkReporter) error {\n\tvar cloneFunc func() (string, *git.Repository, error)\n\tswitch cred := s.conn.GetCredential().(type) {\n\tcase *sourcespb.Git_BasicAuth:\n\t\tcloneFunc = func() (string, *git.Repository, error) {\n\t\t\tuser := cred.BasicAuth.Username\n\t\t\ttoken := cred.BasicAuth.Password\n\t\t\treturn CloneRepoUsingToken(ctx, token, repoURI, s.conn.GetClonePath(), user, true)\n\t\t}\n\tcase *sourcespb.Git_Unauthenticated:\n\t\tcloneFunc = func() (string, *git.Repository, error) {\n\t\t\treturn CloneRepoUsingUnauthenticated(ctx, repoURI, s.conn.GetClonePath())\n\t\t}\n\tcase *sourcespb.Git_SshAuth:\n\t\tcloneFunc = func() (string, *git.Repository, error) {\n\t\t\treturn CloneRepoUsingSSH(ctx, repoURI)\n\t\t}\n\tdefault:\n\t\treturn errors.New(\"invalid connection type for git source\")\n\t}\n\n\terr := func() error {\n\t\tpath, repo, err := cloneFunc()\n\t\t// remove the directory only if it was created as a temporary path, or if it is a clone path and --no-cleanup is not set.\n\t\t// if legacy JSON is enabled, don't remove the directory because we need it for outputting legacy JSON.\n\t\tif !s.conn.GetPrintLegacyJson() {\n\t\t\tif strings.HasPrefix(path, filepath.Join(os.TempDir(), \"trufflehog\")) || (!s.conn.GetNoCleanup() && s.conn.GetClonePath() != \"\") {\n\t\t\t\tdefer os.RemoveAll(path)\n\t\t\t}\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn s.git.ScanRepo(ctx, repo, path, s.scanOptions, reporter)\n\t}()\n\tif err != nil {\n\t\treturn reporter.ChunkErr(ctx, err)\n\t}\n\treturn nil\n}\n\n// scanDirs scans the configured directories in s.conn.Directories.\nfunc (s *Source) scanDirs(ctx context.Context, reporter sources.ChunkReporter) error {\n\ttotalRepos := len(s.conn.Repositories) + len(s.conn.Directories)\n\tfor i, gitDir := range s.conn.Directories {\n\t\ts.SetProgressComplete(len(s.conn.Repositories)+i, totalRepos, fmt.Sprintf(\"Repo: %s\", gitDir), \"\")\n\n\t\tif len(gitDir) == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tif err := s.scanDir(ctx, gitDir, reporter); err != nil {\n\t\t\tctx.Logger().Info(\"error scanning repository\", \"repo\", gitDir, \"error\", err)\n\t\t\tcontinue\n\t\t}\n\t}\n\treturn nil\n}\n\n// scanDir scans a single provided directory.\nfunc (s *Source) scanDir(ctx context.Context, gitDir string, reporter sources.ChunkReporter) error {\n\tif !s.scanOptions.Bare && strings.HasSuffix(gitDir, \"git\") {\n\t\t// TODO: Figure out why we skip directories ending in \"git\".\n\t\treturn nil\n\t}\n\n\tif _, err := os.Stat(gitDir); os.IsNotExist(err) {\n\t\treturn fmt.Errorf(\"directory does not exist: %s\", gitDir)\n\t}\n\n\trepo, err := RepoFromPath(gitDir)\n\tif err != nil {\n\t\treturn reporter.ChunkErr(ctx, err)\n\t}\n\n\terr = func() error {\n\t\t// remove the directory only if it was created as a temporary path, or if it is a clone path and --no-cleanup is not set.\n\t\t// if legacy JSON is enabled, don't remove the directory because we need it for outputting legacy JSON.\n\t\tif !s.conn.GetPrintLegacyJson() {\n\t\t\tif strings.HasPrefix(gitDir, filepath.Join(os.TempDir(), \"trufflehog\")) || (!s.conn.GetNoCleanup() && s.conn.GetClonePath() != \"\") {\n\t\t\t\tdefer os.RemoveAll(gitDir)\n\t\t\t}\n\t\t}\n\n\t\treturn s.git.ScanRepo(ctx, repo, gitDir, s.scanOptions, reporter)\n\t}()\n\tif err != nil {\n\t\treturn reporter.ChunkErr(ctx, err)\n\t}\n\treturn nil\n}\n\n// RepoFromPath opens a git repository from a given path.\n// If the repository is bare (--mirror or --bare), the directory referenced by the path variable\n// will contain the contents of the git directory (ex: path/HEAD, path/config, etc.). In this case,\n// detectDotGit and enableDotGitCommonDir need to be false.\n// Otherwise, they need to be true so git can find the git directory (path/.git)\n//\n// See: https://git-scm.com/docs/gitrepository-layout#_description\nfunc RepoFromPath(path string) (*git.Repository, error) {\n\tisBare := isRepoBare(path)\n\toptions := &git.PlainOpenOptions{}\n\tif !isBare {\n\t\toptions.DetectDotGit = true\n\t\toptions.EnableDotGitCommonDir = true\n\t}\n\treturn git.PlainOpenWithOptions(path, options)\n}\n\nfunc CleanOnError(err *error, path string) {\n\tif *err != nil {\n\t\tos.RemoveAll(path)\n\t}\n}\n\nfunc GitURLParse(gitURL string) (*url.URL, error) {\n\tparsedURL, originalError := url.Parse(gitURL)\n\tif originalError != nil {\n\t\tvar err error\n\t\tgitURLBytes := []byte(\"ssh://\" + gitURL)\n\t\tcolonIndex := bytes.LastIndex(gitURLBytes, []byte(\":\"))\n\t\tgitURLBytes[colonIndex] = byte('/')\n\t\tparsedURL, err = url.Parse(string(gitURLBytes))\n\t\tif err != nil {\n\t\t\treturn nil, originalError\n\t\t}\n\t}\n\n\treturn parsedURL, nil\n}\n\n// normalizeFileURI converts relative file URIs to absolute paths.\n// This ensures that file:// URIs work correctly with git clone operations.\nfunc normalizeFileURI(uri *url.URL) (*url.URL, error) {\n\tif uri.Scheme != \"file\" {\n\t\treturn uri, nil\n\t}\n\n\tvar rawPath string\n\tif uri.Host != \"\" {\n\t\t// Handle cases like file://. or file://./relative/path\n\t\tif uri.Path == \"\" {\n\t\t\trawPath = uri.Host\n\t\t} else {\n\t\t\trawPath = filepath.Join(uri.Host, uri.Path)\n\t\t}\n\t} else {\n\t\t// Handle cases like file:///absolute/path\n\t\trawPath = uri.Path\n\t}\n\n\tabsPath, err := filepath.Abs(rawPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to resolve absolute path for %q: %w\", rawPath, err)\n\t}\n\n\t// Convert to forward slashes (for Windows compatibility)\n\tnormalizedPath := filepath.ToSlash(absPath)\n\n\tnormalizedURI := &url.URL{\n\t\tScheme: \"file\",\n\t\tPath:   normalizedPath,\n\t}\n\n\treturn normalizedURI, nil\n}\n\ntype cloneParams struct {\n\tuserInfo  *url.Userinfo\n\tgitURL    string\n\targs      []string\n\tclonePath string\n\tauthInUrl bool\n\ttimeout   time.Duration\n}\n\n// CloneRepo orchestrates the cloning of a given Git repository, returning its local path\n// and a git.Repository object for further operations. The function sets up error handling\n// infrastructure, ensuring that any encountered errors trigger a cleanup of resources.\n// The core cloning logic is delegated to a nested function, which returns errors to the\n// outer function for centralized error handling and cleanup.\nfunc CloneRepo(ctx context.Context, userInfo *url.Userinfo, gitURL string, clonePath string, authInUrl bool, args ...string) (string, *git.Repository, error) {\n\tvar path string\n\tvar err error\n\n\t// If --clone-path is set, create a subdirectory <clonePath>/trufflehog-<repo-name> with permissions 0755.\n\tif clonePath != \"\" {\n\t\tpath = filepath.Join(clonePath, \"trufflehog-\"+strings.TrimSuffix(filepath.Base(gitURL), gitDirName))\n\t\tif err = os.MkdirAll(path, 0755); err != nil {\n\t\t\treturn \"\", nil, fmt.Errorf(\"failed to create clone path %s: %w\", clonePath, err)\n\t\t}\n\t} else {\n\t\t// otherwise, create a temporary directory in the system temp path.\n\t\tpath, err = cleantemp.MkdirTemp()\n\t\tif err != nil {\n\t\t\treturn \"\", nil, fmt.Errorf(\"failed to create temporary clone path: %w\", err)\n\t\t}\n\t}\n\n\ttimeout := time.Duration(feature.GitCloneTimeoutDuration.Load())\n\n\trepo, err := executeClone(ctx, cloneParams{userInfo, gitURL, args, path, authInUrl, timeout})\n\tif err != nil {\n\t\t// DO NOT FORGET TO CLEAN UP THE CLONE PATH HERE!!\n\t\t// If we don't, we'll end up with a bunch of orphaned directories in the temp dir.\n\t\tCleanOnError(&err, path)\n\n\t\t// Note: We don't need to record the clone failure here as it's already\n\t\t// recorded in executeClone when the error occurs\n\t\treturn \"\", nil, err\n\t}\n\n\treturn path, repo, nil\n}\n\n// executeClone prepares the Git URL, constructs, and executes the git clone command using the provided\n// clonePath. It then opens the cloned repository, returning a git.Repository object.\nfunc executeClone(ctx context.Context, params cloneParams) (*git.Repository, error) {\n\tstart := time.Now()\n\n\tcloneURL, err := GitURLParse(params.gitURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar gitArgs []string\n\n\tif params.authInUrl {\n\t\tif cloneURL.User == nil {\n\t\t\tcloneURL.User = params.userInfo\n\t\t}\n\t} else { // default\n\t\tcloneURL.User = nil // remove user information from the url\n\n\t\tpass, ok := params.userInfo.Password()\n\t\tif ok {\n\t\t\t/*\n\t\t\t\tSources:\n\t\t\t\t\t- https://medium.com/%40szpytfire/authenticating-with-github-via-a-personal-access-token-7c639a979eb3\n\t\t\t\t\t- https://trinhngocthuyen.com/posts/tech/50-shades-of-git-remotes-and-authentication/#using-httpextraheader-config\n\t\t\t*/\n\t\t\tauthHeader := base64.StdEncoding.EncodeToString(fmt.Appendf([]byte(\"\"), \"%s:%s\", params.userInfo.Username(), pass))\n\t\t\tgitArgs = append(gitArgs, \"-c\", fmt.Sprintf(\"http.extraHeader=Authorization: Basic %s\", authHeader))\n\t\t}\n\t}\n\n\tgitArgs = append(gitArgs, \"clone\")\n\tif feature.UseGitMirror.Load() && cloneURL.Scheme != \"file\" {\n\t\tgitArgs = append(gitArgs, \"--mirror\")\n\t} else {\n\t\tif !feature.SkipAdditionalRefs.Load() {\n\t\t\tgitArgs = append(gitArgs,\n\t\t\t\t\"-c\",\n\t\t\t\t\"remote.origin.fetch=+refs/*:refs/remotes/origin/*\")\n\t\t}\n\t}\n\n\tvar cancel context.CancelFunc\n\tif params.timeout > 0 {\n\t\tctx, cancel = context.WithTimeout(ctx, params.timeout)\n\t\tdefer cancel()\n\t}\n\n\tgitArgs = append(gitArgs, \"--quiet\")\n\tgitArgs = append(gitArgs, params.args...)\n\tgitArgs = append(gitArgs, cloneURL.String(), params.clonePath)\n\tcloneCmd := exec.CommandContext(ctx, \"git\", gitArgs...)\n\n\tsafeURL, secretForRedaction, err := stripPassword(params.gitURL)\n\tif err != nil {\n\t\tctx.Logger().V(1).Info(\"error stripping password from git url\", \"error\", err)\n\t}\n\tlogger := ctx.Logger().WithValues(\n\t\t\"subcommand\", \"git clone\",\n\t\t\"repo\", safeURL,\n\t\t\"path\", params.clonePath,\n\t\t\"args\", params.args,\n\t)\n\n\tlogger.V(3).Info(\"executing git clone command\")\n\toutputBytes, err := cloneCmd.CombinedOutput()\n\tvar output string\n\tif secretForRedaction != \"\" {\n\t\toutput = strings.ReplaceAll(string(outputBytes), secretForRedaction, \"<secret>\")\n\t} else {\n\t\toutput = string(outputBytes)\n\t}\n\n\tif err != nil {\n\t\terr = fmt.Errorf(\"error executing git clone: %w, %s\", err, output)\n\t}\n\tlogger.V(3).Info(\"git subcommand finished\", \"output\", output)\n\n\tif common.IsDone(ctx) {\n\t\treturn nil, fmt.Errorf(\"git clone timed out (after %s)\", time.Since(start))\n\t} else if cloneCmd.ProcessState == nil {\n\t\treturn nil, fmt.Errorf(\"clone command exited with no output\")\n\t} else if cloneCmd.ProcessState.ExitCode() != 0 {\n\t\tlogger.V(1).Info(\"git clone failed\", \"error\", err)\n\t\tfailureReason := ClassifyCloneError(output)\n\t\texitCode := cloneCmd.ProcessState.ExitCode()\n\t\tmetricsInstance.RecordCloneOperation(statusFailure, failureReason, exitCode)\n\t\treturn nil, fmt.Errorf(\"could not clone repo: %s, %w\", safeURL, err)\n\t}\n\n\trepo, err := RepoFromPath(params.clonePath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not open cloned repo: %w\", err)\n\t}\n\tlogger.V(1).Info(\"successfully cloned repo\", \"time_seconds\", time.Since(start).Seconds())\n\n\tmetricsInstance.RecordCloneOperation(statusSuccess, cloneSuccess, 0)\n\n\treturn repo, nil\n}\n\n// PingRepoUsingToken executes git ls-remote on a repo and returns any error that occurs. It can be used to validate\n// that a repo actually exists and is reachable.\n//\n// Pinging using other authentication methods is only unimplemented because there's been no pressing need for it yet.\nfunc PingRepoUsingToken(ctx context.Context, token, gitUrl, user string) error {\n\tif err := CmdCheck(); err != nil {\n\t\treturn err\n\t}\n\tlsUrl, err := GitURLParse(gitUrl)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif lsUrl.User == nil {\n\t\tlsUrl.User = url.UserPassword(user, token)\n\t}\n\n\t// We don't actually care about any refs on the remote, we just care whether can can list them at all. So we query\n\t// only for a ref that we know won't exist to minimize the search time on the remote. (By default, ls-remote exits\n\t// with 0 even if it doesn't find any matching refs.)\n\tfakeRef := \"TRUFFLEHOG_CHECK_GIT_REMOTE_URL_REACHABILITY\"\n\tgitArgs := []string{\"ls-remote\", lsUrl.String(), \"--quiet\", fakeRef}\n\tcmd := exec.Command(\"git\", gitArgs...)\n\toutput, err := cmd.CombinedOutput()\n\n\tif err != nil {\n\t\t// Record the ping failure with the appropriate reason and exit code\n\t\tfailureReason := ClassifyCloneError(string(output))\n\t\texitCode := 0\n\t\tif cmd.ProcessState != nil {\n\t\t\texitCode = cmd.ProcessState.ExitCode()\n\t\t}\n\t\tmetricsInstance.RecordCloneOperation(statusFailure, failureReason, exitCode)\n\t}\n\n\treturn err\n}\n\n// CloneRepoUsingToken clones a repo using a provided token.\nfunc CloneRepoUsingToken(ctx context.Context, token, gitUrl, clonePath, user string, authInUrl bool, args ...string) (string, *git.Repository, error) {\n\tuserInfo := url.UserPassword(user, token)\n\treturn CloneRepo(ctx, userInfo, gitUrl, clonePath, authInUrl, args...)\n}\n\n// CloneRepoUsingUnauthenticated clones a repo with no authentication required.\nfunc CloneRepoUsingUnauthenticated(ctx context.Context, url, clonePath string, args ...string) (string, *git.Repository, error) {\n\treturn CloneRepo(ctx, nil, url, clonePath, false, args...)\n}\n\n// CloneRepoUsingSSH clones a repo using SSH.\nfunc CloneRepoUsingSSH(ctx context.Context, gitURL string, args ...string) (string, *git.Repository, error) {\n\tif isCodeCommitURL(gitURL) {\n\t\treturn CloneRepo(ctx, nil, gitURL, \"\", true, args...)\n\t}\n\n\tuserInfo := url.User(\"git\")\n\treturn CloneRepo(ctx, userInfo, gitURL, \"\", true, args...)\n}\n\nvar codeCommitRE = regexp.MustCompile(`ssh://git-codecommit\\.[\\w-]+\\.amazonaws\\.com`)\n\nfunc isCodeCommitURL(gitURL string) bool { return codeCommitRE.MatchString(gitURL) }\n\n// CommitsScanned returns the number of commits scanned\nfunc (s *Git) CommitsScanned() uint64 {\n\treturn atomic.LoadUint64(&s.repoCommitsScanned)\n}\n\nconst gitDirName = \".git\"\n\n// getGitDir returns the likely path of the \".git\" directory.\n// If the repository is bare, it will be at the top-level; otherwise, it\n// exists in the \".git\" directory at the root of the working tree.\n//\n// See: https://git-scm.com/docs/gitrepository-layout#_description\nfunc getGitDir(path string) string {\n\tisBare := isRepoBare(path)\n\tif isBare {\n\t\treturn path\n\t} else {\n\t\treturn filepath.Join(path, gitDirName)\n\t}\n}\n\nfunc (s *Git) ScanCommits(ctx context.Context, repo *git.Repository, path string, scanOptions *ScanOptions, reporter sources.ChunkReporter) error {\n\t// Get the remote URL for reporting (may be empty)\n\tremoteURL := GetSafeRemoteURL(repo, \"origin\")\n\tvar repoCtx context.Context\n\n\tif ctx.Value(\"repo\") == nil {\n\t\tif remoteURL != \"\" {\n\t\t\trepoCtx = context.WithValue(ctx, \"repo\", remoteURL)\n\t\t} else {\n\t\t\trepoCtx = context.WithValue(ctx, \"repo\", path)\n\t\t}\n\t} else {\n\t\trepoCtx = ctx\n\t}\n\n\tlogger := repoCtx.Logger()\n\tvar logValues []any\n\tif scanOptions.BaseHash != \"\" {\n\t\tlogValues = append(logValues, \"base\", scanOptions.BaseHash)\n\t}\n\tif scanOptions.HeadHash != \"\" {\n\t\tlogValues = append(logValues, \"head\", scanOptions.HeadHash)\n\t}\n\tif scanOptions.MaxDepth > 0 {\n\t\tlogValues = append(logValues, \"max_depth\", scanOptions.MaxDepth)\n\t}\n\n\tdiffChan, err := s.parser.RepoPath(repoCtx, path, scanOptions.HeadHash, scanOptions.BaseHash == \"\", scanOptions.ExcludeGlobs, isRepoBare(path))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif diffChan == nil {\n\t\treturn nil\n\t}\n\n\tlogger.Info(\"scanning repo\", logValues...)\n\n\tvar (\n\t\tgitDir         = getGitDir(path)\n\t\tdepth          int64\n\t\tlastCommitHash string\n\t)\n\n\tfor diff := range diffChan {\n\t\tif scanOptions.MaxDepth > 0 && depth >= scanOptions.MaxDepth {\n\t\t\tlogger.V(1).Info(\"reached max depth\", \"depth\", depth)\n\t\t\tbreak\n\t\t}\n\n\t\tcommit := diff.Commit\n\t\tfullHash := commit.Hash\n\t\tif scanOptions.BaseHash != \"\" && scanOptions.BaseHash == fullHash {\n\t\t\tlogger.V(1).Info(\"reached base commit\", \"commit\", fullHash)\n\t\t\tbreak\n\t\t}\n\n\t\temail := commit.Author\n\t\twhen := commit.Date.UTC().Format(\"2006-01-02 15:04:05 -0700\")\n\n\t\tif fullHash != lastCommitHash {\n\t\t\tdepth++\n\t\t\tlastCommitHash = fullHash\n\t\t\ts.metrics.RecordCommitScanned()\n\t\t\t// Increment repo-specific commit counter\n\t\t\tatomic.AddUint64(&s.repoCommitsScanned, 1)\n\t\t\tlogger.V(5).Info(\"scanning commit\", \"commit\", fullHash)\n\n\t\t\t// Scan the commit metadata.\n\t\t\t// See https://github.com/trufflesecurity/trufflehog/issues/2683\n\t\t\tvar (\n\t\t\t\tmetadata = s.sourceMetadataFunc(\"\", email, fullHash, when, remoteURL, path, 0)\n\t\t\t\tsb       strings.Builder\n\t\t\t)\n\t\t\tsb.WriteString(email)\n\t\t\tsb.WriteString(\"\\n\")\n\t\t\tsb.WriteString(commit.Committer)\n\t\t\tsb.WriteString(\"\\n\")\n\t\t\tsb.WriteString(commit.Message.String())\n\t\t\tchunk := sources.Chunk{\n\t\t\t\tSourceName:     s.sourceName,\n\t\t\t\tSourceID:       s.sourceID,\n\t\t\t\tJobID:          s.jobID,\n\t\t\t\tSourceType:     s.sourceType,\n\t\t\t\tSourceMetadata: metadata,\n\t\t\t\tData:           []byte(sb.String()),\n\t\t\t\tSourceVerify:   s.verify,\n\t\t\t}\n\t\t\tif err := reporter.ChunkOk(ctx, chunk); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tfileName := diff.PathB\n\t\tif fileName == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tif !scanOptions.Filter.Pass(fileName) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Handle binary files by reading the entire file rather than using the diff.\n\t\tif diff.IsBinary {\n\t\t\tcommitHash := plumbing.NewHash(fullHash)\n\n\t\t\tif s.skipBinaries || feature.ForceSkipBinaries.Load() {\n\t\t\t\tlogger.V(5).Info(\"skipping binary file\",\n\t\t\t\t\t\"commit\", commitHash.String()[:7],\n\t\t\t\t\t\"path\", fileName)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmetadata := s.sourceMetadataFunc(fileName, email, fullHash, when, remoteURL, path, 0)\n\t\t\tchunkSkel := &sources.Chunk{\n\t\t\t\tSourceName:     s.sourceName,\n\t\t\t\tSourceID:       s.sourceID,\n\t\t\t\tJobID:          s.jobID,\n\t\t\t\tSourceType:     s.sourceType,\n\t\t\t\tSourceMetadata: metadata,\n\t\t\t\tSourceVerify:   s.verify,\n\t\t\t}\n\n\t\t\tif err := HandleBinary(ctx, gitDir, reporter, chunkSkel, commitHash, fileName, s.skipArchives); err != nil {\n\t\t\t\tlogger.Error(\n\t\t\t\t\terr,\n\t\t\t\t\t\"error handling binary file\",\n\t\t\t\t\t\"commit\", commitHash,\n\t\t\t\t\t\"path\", fileName,\n\t\t\t\t)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif diff.Len() > sources.DefaultChunkSize+sources.DefaultPeekSize {\n\t\t\ts.gitChunk(ctx, diff, fileName, email, fullHash, when, remoteURL, reporter)\n\t\t\tcontinue\n\t\t}\n\n\t\tchunkData := func(d *gitparse.Diff) error {\n\t\t\tmetadata := s.sourceMetadataFunc(fileName, email, fullHash, when, remoteURL, path, int64(diff.LineStart))\n\n\t\t\treader, err := d.ReadCloser()\n\t\t\tif err != nil {\n\t\t\t\tctx.Logger().Error(\n\t\t\t\t\terr, \"error creating reader for commits\",\n\t\t\t\t\t\"commit\", fullHash,\n\t\t\t\t\t\"path\", fileName,\n\t\t\t\t)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tdefer reader.Close()\n\n\t\t\tdata := make([]byte, d.Len())\n\t\t\tif _, err := io.ReadFull(reader, data); err != nil {\n\t\t\t\tlogger.Error(\n\t\t\t\t\terr, \"error reading diff content for commit\",\n\t\t\t\t\t\"commit\", fullHash,\n\t\t\t\t\t\"path\", fileName,\n\t\t\t\t)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tchunk := sources.Chunk{\n\t\t\t\tSourceName:     s.sourceName,\n\t\t\t\tSourceID:       s.sourceID,\n\t\t\t\tJobID:          s.jobID,\n\t\t\t\tSourceType:     s.sourceType,\n\t\t\t\tSourceMetadata: metadata,\n\t\t\t\tData:           data,\n\t\t\t\tSourceVerify:   s.verify,\n\t\t\t}\n\t\t\treturn reporter.ChunkOk(ctx, chunk)\n\t\t}\n\t\tif err := chunkData(diff); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Git) gitChunk(ctx context.Context, diff *gitparse.Diff, fileName, email, hash, when, urlMetadata string, reporter sources.ChunkReporter) {\n\treader, err := diff.ReadCloser()\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"error creating reader for chunk\", \"filename\", fileName, \"commit\", hash, \"file\", diff.PathB)\n\t\treturn\n\t}\n\tdefer reader.Close()\n\n\toriginalChunk := bufio.NewScanner(reader)\n\tnewChunkBuffer := bytes.Buffer{}\n\tlastOffset := 0\n\tfor offset := 0; originalChunk.Scan(); offset++ {\n\t\tline := make([]byte, len(originalChunk.Bytes())+1)\n\t\tcopy(line, originalChunk.Bytes())\n\t\tline[len(line)-1] = byte('\\n')\n\t\tif len(line) > sources.DefaultChunkSize || len(line)+newChunkBuffer.Len() > sources.DefaultChunkSize {\n\t\t\t// Add oversize chunk info\n\t\t\tif newChunkBuffer.Len() > 0 {\n\t\t\t\t// Send the existing fragment.\n\t\t\t\tmetadata := s.sourceMetadataFunc(fileName, email, hash, when, urlMetadata, \"\", int64(diff.LineStart+lastOffset))\n\t\t\t\tchunk := sources.Chunk{\n\t\t\t\t\tSourceName:     s.sourceName,\n\t\t\t\t\tSourceID:       s.sourceID,\n\t\t\t\t\tJobID:          s.jobID,\n\t\t\t\t\tSourceType:     s.sourceType,\n\t\t\t\t\tSourceMetadata: metadata,\n\t\t\t\t\tData:           append([]byte{}, newChunkBuffer.Bytes()...),\n\t\t\t\t\tSourceVerify:   s.verify,\n\t\t\t\t}\n\t\t\t\tif err := reporter.ChunkOk(ctx, chunk); err != nil {\n\t\t\t\t\t// TODO: Return error.\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tnewChunkBuffer.Reset()\n\t\t\t\tlastOffset = offset\n\t\t\t}\n\t\t\tif len(line) > sources.DefaultChunkSize {\n\t\t\t\t// Send the oversize line.\n\t\t\t\tmetadata := s.sourceMetadataFunc(fileName, email, hash, when, urlMetadata, \"\", int64(diff.LineStart+offset))\n\t\t\t\tchunk := sources.Chunk{\n\t\t\t\t\tSourceName:     s.sourceName,\n\t\t\t\t\tSourceID:       s.sourceID,\n\t\t\t\t\tJobID:          s.jobID,\n\t\t\t\t\tSourceType:     s.sourceType,\n\t\t\t\t\tSourceMetadata: metadata,\n\t\t\t\t\tData:           line,\n\t\t\t\t\tSourceVerify:   s.verify,\n\t\t\t\t}\n\t\t\t\tif err := reporter.ChunkOk(ctx, chunk); err != nil {\n\t\t\t\t\t// TODO: Return error.\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tif _, err := newChunkBuffer.Write(line); err != nil {\n\t\t\tctx.Logger().Error(err, \"error writing to chunk buffer\", \"filename\", fileName, \"commit\", hash, \"file\", diff.PathB)\n\t\t}\n\t}\n\t// Send anything still in the new chunk buffer\n\tif newChunkBuffer.Len() > 0 {\n\t\tmetadata := s.sourceMetadataFunc(fileName, email, hash, when, urlMetadata, \"\", int64(diff.LineStart+lastOffset))\n\t\tchunk := sources.Chunk{\n\t\t\tSourceName:     s.sourceName,\n\t\t\tSourceID:       s.sourceID,\n\t\t\tJobID:          s.jobID,\n\t\t\tSourceType:     s.sourceType,\n\t\t\tSourceMetadata: metadata,\n\t\t\tData:           append([]byte{}, newChunkBuffer.Bytes()...),\n\t\t\tSourceVerify:   s.verify,\n\t\t}\n\t\tif err := reporter.ChunkOk(ctx, chunk); err != nil {\n\t\t\t// TODO: Return error.\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// ScanStaged chunks staged changes.\nfunc (s *Git) ScanStaged(ctx context.Context, repo *git.Repository, path string, scanOptions *ScanOptions, reporter sources.ChunkReporter) error {\n\t// Get the URL metadata for reporting (may be empty).\n\turlMetadata := GetSafeRemoteURL(repo, \"origin\")\n\n\tdiffChan, err := s.parser.Staged(ctx, path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif diffChan == nil {\n\t\treturn nil\n\t}\n\n\tlogger := ctx.Logger()\n\tvar logValues []any\n\tlogValues = append(logValues, \"path\", path)\n\tif scanOptions.BaseHash != \"\" {\n\t\tlogValues = append(logValues, \"base\", scanOptions.BaseHash)\n\t}\n\tif scanOptions.HeadHash != \"\" {\n\t\tlogValues = append(logValues, \"head\", scanOptions.HeadHash)\n\t}\n\tif scanOptions.MaxDepth > 0 {\n\t\tlogValues = append(logValues, \"max_depth\", scanOptions.MaxDepth)\n\t}\n\n\tlogger.V(1).Info(\"scanning staged changes\", logValues...)\n\n\tvar (\n\t\treachedBase    = false\n\t\tgitDir         = getGitDir(path)\n\t\tdepth          int64\n\t\tlastCommitHash string\n\t)\n\tfor diff := range diffChan {\n\t\tfullHash := diff.Commit.Hash\n\t\tlogger := ctx.Logger().WithValues(\"commit\", fullHash, \"path\", diff.PathB)\n\t\tlogger.V(2).Info(\"scanning staged changes from git\")\n\n\t\tif scanOptions.MaxDepth > 0 && depth >= scanOptions.MaxDepth {\n\t\t\tlogger.V(1).Info(\"reached max depth\")\n\t\t\tbreak\n\t\t}\n\n\t\tif fullHash != lastCommitHash {\n\t\t\tdepth++\n\t\t\tlastCommitHash = fullHash\n\t\t\ts.metrics.RecordCommitScanned()\n\t\t\t// Increment repo-specific commit counter\n\t\t\tatomic.AddUint64(&s.repoCommitsScanned, 1)\n\t\t}\n\n\t\tif reachedBase && fullHash != scanOptions.BaseHash {\n\t\t\tbreak\n\t\t}\n\n\t\tif scanOptions.BaseHash != \"\" && fullHash == scanOptions.BaseHash {\n\t\t\tlogger.V(1).Info(\"reached base hash, finishing scanning files\")\n\t\t\treachedBase = true\n\t\t}\n\n\t\tif !scanOptions.Filter.Pass(diff.PathB) {\n\t\t\tcontinue\n\t\t}\n\n\t\tfileName := diff.PathB\n\t\tif fileName == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\temail := diff.Commit.Author\n\t\twhen := diff.Commit.Date.UTC().Format(\"2006-01-02 15:04:05 -0700\")\n\n\t\t// Handle binary files by reading the entire file rather than using the diff.\n\t\tif diff.IsBinary {\n\t\t\tcommitHash := plumbing.NewHash(fullHash)\n\n\t\t\tif s.skipBinaries || feature.ForceSkipBinaries.Load() {\n\t\t\t\tlogger.V(5).Info(\"skipping binary file\",\n\t\t\t\t\t\"commit\", commitHash.String()[:7],\n\t\t\t\t\t\"path\", fileName)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmetadata := s.sourceMetadataFunc(fileName, email, \"Staged\", when, urlMetadata, path, 0)\n\t\t\tchunkSkel := &sources.Chunk{\n\t\t\t\tSourceName:     s.sourceName,\n\t\t\t\tSourceID:       s.sourceID,\n\t\t\t\tJobID:          s.jobID,\n\t\t\t\tSourceType:     s.sourceType,\n\t\t\t\tSourceMetadata: metadata,\n\t\t\t\tSourceVerify:   s.verify,\n\t\t\t}\n\t\t\tif err := HandleBinary(ctx, gitDir, reporter, chunkSkel, commitHash, fileName, s.skipArchives); err != nil {\n\t\t\t\tlogger.Error(err, \"error handling binary file\")\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tchunkData := func(d *gitparse.Diff) error {\n\t\t\tmetadata := s.sourceMetadataFunc(fileName, email, \"Staged\", when, urlMetadata, path, int64(diff.LineStart))\n\n\t\t\treader, err := d.ReadCloser()\n\t\t\tif err != nil {\n\t\t\t\tlogger.Error(err, \"error creating reader for staged\")\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tdefer reader.Close()\n\n\t\t\tdata := make([]byte, d.Len())\n\t\t\tif _, err := reader.Read(data); err != nil {\n\t\t\t\tlogger.Error(err, \"error reading diff content for staged\")\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tchunk := sources.Chunk{\n\t\t\t\tSourceName:     s.sourceName,\n\t\t\t\tSourceID:       s.sourceID,\n\t\t\t\tJobID:          s.jobID,\n\t\t\t\tSourceType:     s.sourceType,\n\t\t\t\tSourceMetadata: metadata,\n\t\t\t\tData:           data,\n\t\t\t\tSourceVerify:   s.verify,\n\t\t\t}\n\t\t\treturn reporter.ChunkOk(ctx, chunk)\n\t\t}\n\t\tif err := chunkData(diff); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Git) ScanRepo(ctx context.Context, repo *git.Repository, repoPath string, scanOptions *ScanOptions, reporter sources.ChunkReporter) error {\n\tif scanOptions == nil {\n\t\tscanOptions = NewScanOptions()\n\t}\n\tif err := normalizeConfig(scanOptions, repo); err != nil {\n\t\treturn err\n\t}\n\tstart := time.Now().Unix()\n\n\t// Reset the repo-specific commit counter\n\tatomic.StoreUint64(&s.repoCommitsScanned, 0)\n\n\tif err := s.ScanCommits(ctx, repo, repoPath, scanOptions, reporter); err != nil {\n\t\t// Record that we've failed to scan this repo\n\t\ts.metrics.RecordRepoScanned(statusFailure)\n\t\treturn err\n\t}\n\t// Skip staged scanning for mirror/bare clones\n\tif !isRepoBare(repoPath) {\n\t\tif err := s.ScanStaged(ctx, repo, repoPath, scanOptions, reporter); err != nil {\n\t\t\tctx.Logger().V(1).Info(\"error scanning unstaged changes\", \"error\", err)\n\t\t}\n\t}\n\n\t// Get the number of commits scanned in this repo\n\tcommitsScannedInRepo := atomic.LoadUint64(&s.repoCommitsScanned)\n\n\tlogger := ctx.Logger()\n\t// We're logging time, but the repoPath is usually a dynamically generated folder in /tmp.\n\t// To make this duration logging useful, we need to log the remote as well.\n\t// Other sources may have included this info to the context, in which case we don't need to add it again.\n\tif ctx.Value(\"repo\") == nil {\n\t\tremotes, _ := repo.Remotes()\n\t\trepoURL := \"Could not get remote for repo\"\n\t\tif len(remotes) != 0 {\n\t\t\trepoURL = GetSafeRemoteURL(repo, remotes[0].Config().Name)\n\t\t}\n\t\tlogger = logger.WithValues(\"repo\", repoURL)\n\t}\n\n\tscanTime := time.Now().Unix() - start\n\tlogger.V(1).Info(\n\t\t\"scanning git repo complete\",\n\t\t\"path\", repoPath,\n\t\t\"time_seconds\", scanTime,\n\t\t\"commits_scanned\", commitsScannedInRepo,\n\t)\n\n\t// Record that we've scanned a repo successfully\n\ts.metrics.RecordRepoScanned(statusSuccess)\n\treturn nil\n}\n\n// normalizeConfig updates scanOptions with the resolved base and head commit hashes.\n// It's designed to handle scenarios where BaseHash and HeadHash in scanOptions might be branch names or\n// other non-hash references. This ensures that both the base and head commits are resolved to actual commit hashes.\n// If either commit cannot be resolved, it returns early.\n// If both are resolved, it finds and sets the merge base in scanOptions.\nfunc normalizeConfig(scanOptions *ScanOptions, repo *git.Repository) error {\n\tbaseCommit, err := resolveAndSetCommit(repo, &scanOptions.BaseHash)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\theadCommit, err := resolveAndSetCommit(repo, &scanOptions.HeadHash)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif baseCommit == nil || headCommit == nil {\n\t\treturn nil\n\t}\n\n\t// If baseCommit is an ancestor of headCommit, update c.BaseRef to be the common ancestor.\n\tmergeBase, err := headCommit.MergeBase(baseCommit)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to resolve merge base: %w\", err)\n\t}\n\tif len(mergeBase) == 0 {\n\t\treturn fmt.Errorf(\"unable to resolve merge base: no merge base found\")\n\t}\n\n\tscanOptions.BaseHash = mergeBase[0].Hash.String()\n\n\treturn nil\n}\n\n// resolveAndSetCommit resolves a Git reference to a commit object and updates the reference if it was not a direct hash.\n// Returns the commit object and any error encountered.\nfunc resolveAndSetCommit(repo *git.Repository, ref *string) (*object.Commit, error) {\n\tif repo == nil || ref == nil {\n\t\treturn nil, fmt.Errorf(\"repo and ref must be non-nil\")\n\t}\n\tif len(*ref) == 0 {\n\t\treturn nil, nil\n\t}\n\n\toriginalRef := *ref\n\tresolvedRef, err := resolveHash(repo, originalRef)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to resolve ref: %w\", err)\n\t}\n\n\tcommit, err := repo.CommitObject(plumbing.NewHash(resolvedRef))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to resolve commit: %w\", err)\n\t}\n\n\tif originalRef != resolvedRef {\n\t\t*ref = resolvedRef\n\t}\n\n\treturn commit, nil\n}\n\nfunc resolveHash(repo *git.Repository, ref string) (string, error) {\n\tif plumbing.IsHash(ref) {\n\t\treturn ref, nil\n\t}\n\n\tresolved, err := TryAdditionalBaseRefs(repo, ref)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn resolved.String(), nil\n}\n\n// stripPassword removes username:password contents from URLs. The first return value is the cleaned URL and the second\n// is the password that was returned, if any. Callers can therefore use this function to identify secret material to\n// redact elsewhere. If the argument begins with git@, it is returned unchanged, and the returned password is the empty\n// string. If the argument is otherwise not parseable by url.Parse, an error is returned.\nfunc stripPassword(u string) (string, string, error) {\n\tif strings.HasPrefix(u, \"git@\") {\n\t\treturn u, \"\", nil\n\t}\n\n\trepoURL, err := url.Parse(u)\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"repo remote is not a URI: %w\", err)\n\t}\n\n\tpassword, _ := repoURL.User.Password()\n\trepoURL.User = nil\n\n\treturn repoURL.String(), password, nil\n}\n\n// TryAdditionalBaseRefs looks for additional possible base refs for a repo and returns a hash if found.\nfunc TryAdditionalBaseRefs(repo *git.Repository, base string) (*plumbing.Hash, error) {\n\trevisionPrefixes := []string{\n\t\t\"\",\n\t\t\"refs/heads/\",\n\t\t\"refs/remotes/origin/\",\n\t}\n\tfor _, prefix := range revisionPrefixes {\n\t\toutHash, err := repo.ResolveRevision(plumbing.Revision(prefix + base))\n\t\tif errors.Is(err, plumbing.ErrReferenceNotFound) {\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn outHash, nil\n\t}\n\n\treturn nil, fmt.Errorf(\"no base refs succeeded for base: %q\", base)\n}\n\n// prepareRepoSinceCommit clones a repo starting at the given commitHash and returns the cloned repo path.\nfunc prepareRepoSinceCommit(ctx context.Context, uriString, clonePath, commitHash string, trustLocalGitConfig bool, isBare bool) (string, bool, error) {\n\tif commitHash == \"\" {\n\t\treturn PrepareRepo(ctx, uriString, clonePath, trustLocalGitConfig, isBare)\n\t}\n\t// TODO: refactor with PrepareRepo to remove duplicated logic\n\n\t// The git CLI doesn't have an option to shallow clone starting at a commit\n\t// hash, but it does have an option to shallow clone since a timestamp. If\n\t// the uriString is github.com, then we query the API for the timestamp of the\n\t// hash and use that to clone.\n\n\turi, err := GitURLParse(uriString)\n\tif err != nil {\n\t\treturn \"\", false, fmt.Errorf(\"unable to parse Git URI: %s\", err)\n\t}\n\n\tif uri.Scheme == \"file\" || uri.Host != \"github.com\" {\n\t\treturn PrepareRepo(ctx, uriString, \"\", trustLocalGitConfig, isBare)\n\t}\n\n\turiPath := strings.TrimPrefix(uri.Path, \"/\")\n\towner, repoName, found := strings.Cut(uriPath, \"/\")\n\tif !found {\n\t\treturn PrepareRepo(ctx, uriString, clonePath, trustLocalGitConfig, isBare)\n\t}\n\n\tclient := github.NewClient(nil)\n\tif token := os.Getenv(\"GITHUB_TOKEN\"); token != \"\" {\n\t\tts := oauth2.StaticTokenSource(\n\t\t\t&oauth2.Token{AccessToken: token},\n\t\t)\n\t\ttc := oauth2.NewClient(ctx, ts)\n\t\tclient = github.NewClient(tc)\n\t}\n\n\tcommit, _, err := client.Git.GetCommit(context.Background(), owner, repoName, commitHash)\n\tif err != nil {\n\t\treturn PrepareRepo(ctx, uriString, clonePath, trustLocalGitConfig, isBare)\n\t}\n\tvar timestamp string\n\t{\n\t\tauthor := commit.GetAuthor()\n\t\tif author == nil {\n\t\t\treturn PrepareRepo(ctx, uriString, clonePath, trustLocalGitConfig, isBare)\n\t\t}\n\t\ttimestamp = author.GetDate().Format(time.RFC3339)\n\t}\n\n\tremotePath := uri.String()\n\tvar path string\n\tswitch {\n\tcase uri.User != nil:\n\t\tctx.Logger().V(1).Info(\"cloning repo with authentication\", \"uri\", uri.Redacted())\n\t\tpassword, ok := uri.User.Password()\n\t\tif !ok {\n\t\t\treturn \"\", true, fmt.Errorf(\"password must be included in Git repo URL when username is provided\")\n\t\t}\n\n\t\tpath, _, err = CloneRepoUsingToken(ctx, password, remotePath, clonePath, uri.User.Username(), true, \"--shallow-since\", timestamp)\n\t\tif err != nil {\n\t\t\treturn path, true, fmt.Errorf(\"failed to clone authenticated Git repo (%s): %w\", uri.Redacted(), err)\n\t\t}\n\tdefault:\n\t\tctx.Logger().V(1).Info(\"cloning repo without authentication\", \"uri\", uri)\n\t\tpath, _, err = CloneRepoUsingUnauthenticated(ctx, remotePath, clonePath, \"--shallow-since\", timestamp)\n\t\tif err != nil {\n\t\t\treturn path, true, fmt.Errorf(\"failed to clone unauthenticated Git repo (%s): %w\", remotePath, err)\n\t\t}\n\t}\n\n\tctx.Logger().V(1).Info(\"cloned repo\", \"path\", path)\n\treturn path, true, nil\n}\n\n// PrepareRepo clones a repo if possible and returns the cloned repo path.\n// isBare and trustLocalGitConfig are only used for file:// URIs.\nfunc PrepareRepo(ctx context.Context, uriString, clonePath string, trustLocalGitConfig bool, isBare bool) (string, bool, error) {\n\tvar path string\n\turi, err := GitURLParse(uriString)\n\tif err != nil {\n\t\treturn \"\", false, fmt.Errorf(\"unable to parse Git URI: %s\", err)\n\t}\n\n\tremote := false\n\tswitch uri.Scheme {\n\tcase \"file\":\n\t\tswitch {\n\t\tcase trustLocalGitConfig:\n\t\t\tpath = fmt.Sprintf(\"%s%s\", uri.Host, uri.Path)\n\t\tdefault:\n\t\t\tnormalizedURI, err := normalizeFileURI(uri)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", remote, fmt.Errorf(\"failed to normalize file URI (%s): %w\", uriString, err)\n\t\t\t}\n\n\t\t\targs := []string{}\n\t\t\tif isBare {\n\t\t\t\targs = append(args, \"--bare\")\n\t\t\t}\n\t\t\tpath, _, err = CloneRepo(ctx, uri.User, normalizedURI.String(), clonePath, false, args...)\n\t\t\tif err != nil {\n\t\t\t\treturn path, remote, fmt.Errorf(\"failed to clone file Git repo (%s): %w\", normalizedURI.String(), err)\n\t\t\t}\n\n\t\t\tif !isRepoBare(path) {\n\t\t\t\t// Only copy index file for non-bare clones from working directory repos. This is used to see staged changes.\n\t\t\t\t// Note: To scan **un**staged changes in the future, we'd need to set core.worktree to the original path.\n\t\t\t\turiPath := normalizedURI.Path\n\n\t\t\t\toriginalIndexPath := filepath.Join(uriPath, gitDirName, \"index\")\n\n\t\t\t\tclonedIndexPath := filepath.Join(path, gitDirName, \"index\")\n\n\t\t\t\tindexData, err := os.ReadFile(originalIndexPath)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn path, remote, fmt.Errorf(\"failed to read index file: %w\", err)\n\t\t\t\t}\n\t\t\t\tif err := os.WriteFile(clonedIndexPath, indexData, 0644); err != nil {\n\t\t\t\t\treturn path, remote, fmt.Errorf(\"failed to write index file: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\tcase \"http\", \"https\":\n\t\tremotePath := uri.String()\n\t\tremote = true\n\t\tswitch {\n\t\tcase uri.User != nil:\n\t\t\tctx.Logger().V(1).Info(\"cloning repo with authentication\", \"uri\", uri.Redacted())\n\t\t\tpassword, ok := uri.User.Password()\n\t\t\tif !ok {\n\t\t\t\treturn \"\", remote, fmt.Errorf(\"password must be included in Git repo URL when username is provided\")\n\t\t\t}\n\n\t\t\tpath, _, err = CloneRepoUsingToken(ctx, password, remotePath, clonePath, uri.User.Username(), true)\n\t\t\tif err != nil {\n\t\t\t\treturn path, remote, fmt.Errorf(\"failed to clone authenticated Git repo (%s): %w\", uri.Redacted(), err)\n\t\t\t}\n\t\tdefault:\n\t\t\tctx.Logger().V(1).Info(\"cloning repo without authentication\", \"uri\", uri)\n\t\t\tpath, _, err = CloneRepoUsingUnauthenticated(ctx, remotePath, clonePath)\n\t\t\tif err != nil {\n\t\t\t\treturn path, remote, fmt.Errorf(\"failed to clone unauthenticated Git repo (%s): %w\", remotePath, err)\n\t\t\t}\n\t\t}\n\tcase \"ssh\":\n\t\tremotePath := uri.String()\n\t\tremote = true\n\t\tpath, _, err = CloneRepoUsingSSH(ctx, remotePath)\n\t\tif err != nil {\n\t\t\treturn path, remote, fmt.Errorf(\"failed to clone unauthenticated Git repo (%s): %w\", remotePath, err)\n\t\t}\n\tdefault:\n\t\treturn \"\", remote, fmt.Errorf(\"unsupported Git URI: %s\", uriString)\n\t}\n\n\tctx.Logger().V(1).Info(\"cloned repo\", \"path\", path)\n\treturn path, remote, nil\n}\n\n// GetSafeRemoteURL is a helper function that will attempt to get a safe URL first\n// from the preferred remote name, falling back to the first remote name\n// available, or an empty string if there are no remotes.\nfunc GetSafeRemoteURL(repo *git.Repository, preferred string) string {\n\tremote, err := repo.Remote(preferred)\n\tif err != nil {\n\t\tvar remotes []*git.Remote\n\t\tif remotes, err = repo.Remotes(); err != nil {\n\t\t\treturn \"\"\n\t\t}\n\t\tif len(remotes) == 0 {\n\t\t\treturn \"\"\n\t\t}\n\t\tremote = remotes[0]\n\t}\n\t// URLs is guaranteed to be non-empty\n\tsafeURL, _, err := stripPassword(remote.Config().URLs[0])\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\treturn safeURL\n}\n\nfunc HandleBinary(\n\tctx context.Context,\n\tgitDir string,\n\treporter sources.ChunkReporter,\n\tchunkSkel *sources.Chunk,\n\tcommitHash plumbing.Hash,\n\tpath string,\n\tskipArchives bool,\n) (err error) {\n\tfileCtx := context.WithValues(ctx, \"commit\", commitHash.String()[:7], \"path\", path)\n\tfileCtx.Logger().V(5).Info(\"handling binary file\")\n\n\tif common.SkipFile(path) {\n\t\tfileCtx.Logger().V(5).Info(\"file contains ignored extension\")\n\t\treturn nil\n\t}\n\n\tconst (\n\t\tcmdTimeout = 60 * time.Second\n\t\twaitDelay  = 5 * time.Second\n\t)\n\t// NOTE: This kludge ensures the context timeout for the 'git cat-file' command\n\t// matches the timeout for the HandleFile operation.\n\t// By setting both timeouts to the same value, we can be more confident\n\t// that both operations will run for the same duration.\n\t// The command execution includes a small Wait delay before terminating the process,\n\t// giving HandleFile time to respect the context\n\t// and return before the process is forcibly killed.\n\t// This approach helps prevent premature termination and allows for more complete processing.\n\n\t// TODO: Develop a more robust mechanism to ensure consistent timeout behavior between the command execution\n\t// and the HandleFile operation. This should prevent premature termination and allow for complete processing.\n\thandlers.SetArchiveMaxTimeout(cmdTimeout)\n\n\t// Create a timeout context for the 'git cat-file' command to ensure it does not run indefinitely.\n\t// This prevents potential resource exhaustion by terminating the command if it exceeds the specified duration.\n\tcatFileCtx, cancel := context.WithTimeoutCause(fileCtx, cmdTimeout, errors.New(\"git cat-file timeout\"))\n\tdefer cancel()\n\n\tcmd := exec.CommandContext(catFileCtx, \"git\", \"-C\", gitDir, \"cat-file\", \"blob\", commitHash.String()+\":\"+path)\n\tvar stderr bytes.Buffer\n\tcmd.Stderr = &stderr\n\tcmd.WaitDelay = waitDelay // give the command a chance to finish before the timeout :)\n\n\tstdout, err := cmd.StdoutPipe()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error running git cat-file: %w\\n%s\", err, stderr.Bytes())\n\t}\n\n\tif err := cmd.Start(); err != nil {\n\t\treturn fmt.Errorf(\"error starting git cat-file: %w\\n%s\", err, stderr.Bytes())\n\t}\n\n\t// Ensure all data from the reader (stdout) is consumed to prevent broken pipe errors.\n\t// This operation discards any remaining data after HandleFile completion.\n\t// If the reader is fully consumed, the copy is essentially a no-op.\n\t// If an error occurs while discarding, it will be logged and combined with any existing error.\n\t// The command's completion is then awaited and any execution errors are handled.\n\tdefer func() {\n\t\tn, copyErr := io.Copy(io.Discard, stdout)\n\t\tif copyErr != nil {\n\t\t\tctx.Logger().Error(\n\t\t\t\tcopyErr,\n\t\t\t\t\"Failed to discard remaining stdout data after HandleFile completion\",\n\t\t\t)\n\t\t}\n\n\t\tif n > 0 {\n\t\t\tctx.Logger().V(3).Info(\n\t\t\t\t\"HandleFile did not consume all stdout data; excess discarded\",\n\t\t\t\t\"bytes_discarded\", n)\n\t\t}\n\n\t\t// Wait for the command to finish and handle any errors.\n\t\twaitErr := cmd.Wait()\n\t\terr = errors.Join(err, copyErr, waitErr)\n\t}()\n\n\treturn handlers.HandleFile(catFileCtx, stdout, chunkSkel, reporter, handlers.WithSkipArchives(skipArchives))\n}\n\nfunc (s *Source) Enumerate(ctx context.Context, reporter sources.UnitReporter) error {\n\tfor _, repo := range s.conn.GetDirectories() {\n\t\tif repo == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tunit := SourceUnit{ID: repo, Kind: UnitDir}\n\t\tif err := reporter.UnitOk(ctx, unit); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tfor _, repo := range s.conn.GetRepositories() {\n\t\tif repo == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tunit := SourceUnit{ID: repo, Kind: UnitRepo}\n\t\tif err := reporter.UnitOk(ctx, unit); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Source) ChunkUnit(ctx context.Context, unit sources.SourceUnit, reporter sources.ChunkReporter) error {\n\tunitID, kind := unit.SourceUnitID()\n\n\tswitch kind {\n\tcase UnitRepo:\n\t\treturn s.scanRepo(ctx, unitID, reporter)\n\tcase UnitDir:\n\t\treturn s.scanDir(ctx, unitID, reporter)\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected git unit kind: %q\", kind)\n\t}\n}\n\nfunc (s *Source) UnmarshalSourceUnit(data []byte) (sources.SourceUnit, error) {\n\treturn UnmarshalUnit(data)\n}\n\n// isRepoBare returns true if the repo path does NOT contain a .git directory.\n// This is a helper function used outside of the source struct.\nfunc isRepoBare(repoPath string) bool {\n\t_, err := os.Stat(filepath.Join(repoPath, gitDirName))\n\treturn os.IsNotExist(err)\n}\n"
  },
  {
    "path": "pkg/sources/git/metrics.go",
    "content": "package git\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\n// metricsCollector defines the interface for recording Git scan metrics.\ntype metricsCollector interface {\n\t// Clone metrics\n\tRecordCloneOperation(status string, reason string, exitCode int)\n\n\t// Scan metrics\n\tRecordCommitScanned()\n\tRecordRepoScanned(status string)\n}\n\n// Predefined status values\nconst (\n\tstatusSuccess = \"success\"\n\tstatusFailure = \"failure\"\n)\n\n// Predefined clone success reason\nconst (\n\tcloneSuccess = \"success\"\n)\n\n// Predefined clone failure reasons to avoid high cardinality\nconst (\n\t// Authentication/redirection errors\n\tcloneFailureAuth = \"auth_error\"\n\n\t// Rate limiting errors\n\tcloneFailureRateLimit = \"rate_limit\"\n\n\t// Permission errors\n\tcloneFailurePermission = \"permission_denied\"\n\n\t// Network/connection errors\n\tcloneFailureNetwork = \"network_error\"\n\n\t// Git reference errors\n\tcloneFailureReference = \"reference_error\"\n\n\t// Other/unknown errors\n\tcloneFailureOther = \"other_error\"\n)\n\ntype collector struct {\n\tcloneOperations *prometheus.CounterVec\n\tcommitsScanned  prometheus.Counter\n\treposScanned    *prometheus.CounterVec\n}\n\nvar metricsInstance metricsCollector\n\nfunc init() {\n\t// These are package-level metrics that are incremented by all git scans across the lifetime of the process.\n\tmetricsInstance = &collector{\n\t\tcloneOperations: promauto.NewCounterVec(prometheus.CounterOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"git_clone_operations_total\",\n\t\t\tHelp:      \"Total number of git clone operations by status, reason, and exit code\",\n\t\t}, []string{\"status\", \"reason\", \"exit_code\"}),\n\n\t\tcommitsScanned: promauto.NewCounter(prometheus.CounterOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"git_commits_scanned_total\",\n\t\t\tHelp:      \"Total number of git commits scanned\",\n\t\t}),\n\n\t\treposScanned: promauto.NewCounterVec(prometheus.CounterOpts{\n\t\t\tNamespace: common.MetricsNamespace,\n\t\t\tSubsystem: common.MetricsSubsystem,\n\t\t\tName:      \"git_repos_scanned_total\",\n\t\t\tHelp:      \"Total number of git repositories scanned by status (success/failure)\",\n\t\t}, []string{\"status\"}),\n\t}\n}\n\nfunc (c *collector) RecordCloneOperation(status string, reason string, exitCode int) {\n\tc.cloneOperations.WithLabelValues(status, reason, fmt.Sprintf(\"%d\", exitCode)).Inc()\n}\n\nfunc (c *collector) RecordCommitScanned() {\n\tc.commitsScanned.Inc()\n}\n\nfunc (c *collector) RecordRepoScanned(status string) {\n\tc.reposScanned.WithLabelValues(status).Inc()\n}\n\n// ClassifyCloneError analyzes the error message and returns the appropriate failure reason\nfunc ClassifyCloneError(errMsg string) string {\n\tswitch {\n\tcase strings.Contains(errMsg, \"unable to update url base from redirection\") &&\n\t\tstrings.Contains(errMsg, \"redirect:\") && strings.Contains(errMsg, \"users/sign_in\"):\n\t\treturn cloneFailureAuth\n\n\tcase strings.Contains(errMsg, \"The requested URL returned error: 429\") ||\n\t\tstrings.Contains(errMsg, \"remote: Retry later\"):\n\t\treturn cloneFailureRateLimit\n\n\tcase strings.Contains(errMsg, \"The requested URL returned error: 403\") ||\n\t\tstrings.Contains(errMsg, \"remote: You are not allowed to download code from this project\"):\n\t\treturn cloneFailurePermission\n\n\tcase strings.Contains(errMsg, \"RPC failed\") ||\n\t\tstrings.Contains(errMsg, \"unexpected disconnect\") ||\n\t\tstrings.Contains(errMsg, \"early EOF\") ||\n\t\tstrings.Contains(errMsg, \"Problem (3) in the Chunked-Encoded data\"):\n\t\treturn cloneFailureNetwork\n\n\tcase strings.Contains(errMsg, \"cannot process\") ||\n\t\tstrings.Contains(errMsg, \"multiple updates for ref\") ||\n\t\tstrings.Contains(errMsg, \"invalid index-pack output\"):\n\t\treturn cloneFailureReference\n\n\tdefault:\n\t\treturn cloneFailureOther\n\t}\n}\n"
  },
  {
    "path": "pkg/sources/git/scan_options.go",
    "content": "package git\n\nimport (\n\t\"github.com/go-git/go-git/v5\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\ntype ScanOptions struct {\n\tFilter       *common.Filter\n\tBaseHash     string // When scanning a git.Log, this is the oldest/first commit.\n\tHeadHash     string\n\tMaxDepth     int64\n\tBare         bool\n\tExcludeGlobs []string\n\tLogOptions   *git.LogOptions\n}\n\ntype ScanOption func(*ScanOptions)\n\nfunc ScanOptionFilter(filter *common.Filter) ScanOption {\n\treturn func(scanOptions *ScanOptions) {\n\t\tscanOptions.Filter = filter\n\t}\n}\n\nfunc ScanOptionBaseHash(hash string) ScanOption {\n\treturn func(scanOptions *ScanOptions) {\n\t\tscanOptions.BaseHash = hash\n\t}\n}\n\nfunc ScanOptionHeadCommit(hash string) ScanOption {\n\treturn func(scanOptions *ScanOptions) {\n\t\tscanOptions.HeadHash = hash\n\t}\n}\n\nfunc ScanOptionMaxDepth(maxDepth int64) ScanOption {\n\treturn func(scanOptions *ScanOptions) {\n\t\tscanOptions.MaxDepth = maxDepth\n\t}\n}\n\nfunc ScanOptionExcludeGlobs(globs []string) ScanOption {\n\treturn func(scanOptions *ScanOptions) {\n\t\tscanOptions.ExcludeGlobs = globs\n\t}\n}\n\nfunc ScanOptionLogOptions(logOptions *git.LogOptions) ScanOption {\n\treturn func(scanOptions *ScanOptions) {\n\t\tscanOptions.LogOptions = logOptions\n\t}\n}\n\nfunc ScanOptionBare(bare bool) ScanOption {\n\treturn func(scanOptions *ScanOptions) {\n\t\tscanOptions.Bare = bare\n\t}\n}\n\nfunc NewScanOptions(options ...ScanOption) *ScanOptions {\n\tscanOptions := &ScanOptions{\n\t\tFilter:   common.FilterEmpty(),\n\t\tBaseHash: \"\",\n\t\tHeadHash: \"\",\n\t\tMaxDepth: -1,\n\t\tLogOptions: &git.LogOptions{\n\t\t\tAll: true,\n\t\t},\n\t}\n\tfor _, option := range options {\n\t\toption(scanOptions)\n\t}\n\treturn scanOptions\n}\n"
  },
  {
    "path": "pkg/sources/git/unit.go",
    "content": "package git\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nconst (\n\tUnitRepo sources.SourceUnitKind = \"repo\"\n\tUnitDir  sources.SourceUnitKind = \"dir\"\n)\n\n// Ensure SourceUnit implements the interface at compile time.\nvar _ sources.SourceUnit = SourceUnit{}\n\n// A git source unit can be two kinds of units: either a local directory path\n// or a remote repository.\ntype SourceUnit struct {\n\tKind sources.SourceUnitKind `json:\"kind\"`\n\tID   string                 `json:\"id\"`\n}\n\n// Implement sources.SourceUnit interface.\nfunc (u SourceUnit) SourceUnitID() (string, sources.SourceUnitKind) {\n\treturn u.ID, u.Kind\n}\n\n// Provide a custom Display method.\nfunc (u SourceUnit) Display() string {\n\tswitch u.Kind {\n\tcase UnitRepo:\n\t\trepo := u.ID\n\t\tif parsedURL, err := url.Parse(u.ID); err == nil {\n\t\t\t// scheme://host/owner/repo\n\t\t\trepo = strings.TrimPrefix(parsedURL.Path, \"/\")\n\t\t} else if _, path, found := strings.Cut(u.ID, \":\"); found {\n\t\t\t// git@host:owner/repo\n\t\t\t// TODO: Is this possible? We should maybe canonicalize\n\t\t\t// the URL before getting here.\n\t\t\trepo = path\n\t\t}\n\t\treturn strings.TrimSuffix(repo, \".git\")\n\tcase UnitDir:\n\t\treturn filepath.Base(u.ID)\n\tdefault:\n\t\treturn \"mysterious git unit\"\n\t}\n}\n\n// Helper function to unmarshal raw bytes into our SourceUnit struct.\nfunc UnmarshalUnit(data []byte) (sources.SourceUnit, error) {\n\tvar unit SourceUnit\n\tif err := json.Unmarshal(data, &unit); err != nil {\n\t\treturn nil, err\n\t}\n\tif unit.ID == \"\" || (unit.Kind != UnitRepo && unit.Kind != UnitDir) {\n\t\treturn nil, fmt.Errorf(\"not a git.SourceUnit\")\n\t}\n\treturn unit, nil\n}\n"
  },
  {
    "path": "pkg/sources/git/unit_test.go",
    "content": "package git\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestUnmarshalUnit(t *testing.T) {\n\ts := `{\"kind\":\"repo\",\"id\":\"https://github.com/trufflesecurity/test_keys.git\"}`\n\texpectedUnit := SourceUnit{ID: \"https://github.com/trufflesecurity/test_keys.git\", Kind: UnitRepo}\n\tgotUnit, err := UnmarshalUnit([]byte(s))\n\tassert.NoError(t, err)\n\n\tassert.Equal(t, expectedUnit, gotUnit)\n\n\t_, err = UnmarshalUnit(nil)\n\tassert.Error(t, err)\n\n\t_, err = UnmarshalUnit([]byte(`{\"kind\":\"idk\",\"id\":\"id\"}`))\n\tassert.Error(t, err)\n}\n\nfunc TestMarshalUnit(t *testing.T) {\n\tunit := SourceUnit{ID: \"https://github.com/trufflesecurity/test_keys.git\", Kind: UnitRepo}\n\tb, err := json.Marshal(unit)\n\tassert.NoError(t, err)\n\n\tassert.Equal(t, `{\"kind\":\"repo\",\"id\":\"https://github.com/trufflesecurity/test_keys.git\"}`, string(b))\n}\n\nfunc TestDisplayUnit(t *testing.T) {\n\tunit := SourceUnit{ID: \"https://github.com/trufflesecurity/test_keys.git\", Kind: UnitRepo}\n\tassert.Equal(t, \"trufflesecurity/test_keys\", unit.Display())\n\n\tunit = SourceUnit{ID: \"/path/to/repo\", Kind: UnitDir}\n\tassert.Equal(t, \"repo\", unit.Display())\n\n\tunit = SourceUnit{ID: \"ssh://github.com/trufflesecurity/test_keys\", Kind: UnitRepo}\n\tassert.Equal(t, \"trufflesecurity/test_keys\", unit.Display())\n}\n"
  },
  {
    "path": "pkg/sources/github/connector.go",
    "content": "package github\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\tgogit \"github.com/go-git/go-git/v5\"\n\t\"github.com/google/go-github/v67/github\"\n\t\"github.com/shurcooL/githubv4\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/log\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n)\n\nconst (\n\tcloudV3Endpoint      = \"https://api.github.com\"\n\tcloudGraphqlEndpoint = \"https://api.github.com/graphql\" // https://docs.github.com/en/graphql/guides/forming-calls-with-graphql#the-graphql-endpoint\n)\n\n// Connector abstracts over the authenticated ways to interact with GitHub: cloning and API operations.\ntype Connector interface {\n\t// APIClient returns a configured GitHub client that can be used for GitHub API operations.\n\tAPIClient() *github.Client\n\t// GraphQLClient returns a client that can be used for GraphQL operations.\n\tGraphQLClient() *githubv4.Client\n\t// Clone clones a repository using the configured authentication information.\n\tClone(ctx context.Context, repoURL string, args ...string) (string, *gogit.Repository, error)\n}\n\nfunc newConnector(ctx context.Context, source *Source) (Connector, error) {\n\tapiEndpoint := source.conn.Endpoint\n\tif apiEndpoint == \"\" || endsWithGithub.MatchString(apiEndpoint) {\n\t\tapiEndpoint = cloudV3Endpoint\n\t}\n\n\tswitch cred := source.conn.GetCredential().(type) {\n\tcase *sourcespb.GitHub_GithubApp:\n\t\tlog.RedactGlobally(cred.GithubApp.GetPrivateKey())\n\t\treturn NewAppConnector(ctx, apiEndpoint, cred.GithubApp)\n\tcase *sourcespb.GitHub_BasicAuth:\n\t\tlog.RedactGlobally(cred.BasicAuth.GetPassword())\n\t\treturn NewBasicAuthConnector(ctx, apiEndpoint, source.conn.GetClonePath(), cred.BasicAuth)\n\tcase *sourcespb.GitHub_Token:\n\t\tlog.RedactGlobally(cred.Token)\n\t\treturn NewTokenConnector(ctx, apiEndpoint, cred.Token, source.conn.GetClonePath(), source.useAuthInUrl, func(c context.Context, err error) bool {\n\t\t\treturn source.handleRateLimit(c, err)\n\t\t})\n\tcase *sourcespb.GitHub_Unauthenticated:\n\t\treturn NewUnauthenticatedConnector(ctx, apiEndpoint, source.conn.GetClonePath())\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown connection type %T\", source.conn.GetCredential())\n\t}\n}\n\nfunc createAPIClient(ctx context.Context, httpClient *http.Client, apiEndpoint string) (*github.Client, error) {\n\tctx.Logger().V(2).Info(\"Creating API client\", \"url\", apiEndpoint)\n\n\t// If we're using public GitHub, make a regular client.\n\t// Otherwise, make an enterprise client.\n\tif strings.EqualFold(apiEndpoint, cloudV3Endpoint) {\n\t\treturn github.NewClient(httpClient), nil\n\t}\n\n\treturn github.NewClient(httpClient).WithEnterpriseURLs(apiEndpoint, apiEndpoint)\n}\n\nfunc createGraphqlClient(ctx context.Context, client *http.Client, apiEndpoint string) (*githubv4.Client, error) {\n\tvar graphqlEndpoint string\n\tif apiEndpoint == cloudV3Endpoint {\n\t\tgraphqlEndpoint = cloudGraphqlEndpoint\n\t} else {\n\t\t// Use the root endpoint for the host.\n\t\t// https://docs.github.com/en/enterprise-server@3.11/graphql/guides/introduction-to-graphql\n\t\tparsedURL, err := url.Parse(apiEndpoint)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error parsing URL: %w\", err)\n\t\t}\n\n\t\t// GitHub Enterprise uses `/api/v3` for the base. (https://github.com/google/go-github/issues/958)\n\t\t// Swap it, and anything before `/api`, with GraphQL.\n\t\tbefore, _ := strings.CutSuffix(parsedURL.Path, \"/api/v3\")\n\t\tparsedURL.Path = before + \"/api/graphql\"\n\t\tgraphqlEndpoint = parsedURL.String()\n\t}\n\n\tctx.Logger().V(2).Info(\"Creating GraphQL client\", \"url\", graphqlEndpoint)\n\n\treturn githubv4.NewEnterpriseClient(graphqlEndpoint, client), nil\n}\n"
  },
  {
    "path": "pkg/sources/github/connector_app.go",
    "content": "package github\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/bradleyfalzon/ghinstallation/v2\"\n\tgogit \"github.com/go-git/go-git/v5\"\n\t\"github.com/google/go-github/v67/github\"\n\t\"github.com/shurcooL/githubv4\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/credentialspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git\"\n)\n\ntype appConnector struct {\n\tapiClient          *github.Client\n\tgraphqlClient      *githubv4.Client\n\tinstallationClient *github.Client\n\tinstallationID     int64\n}\n\nvar _ Connector = (*appConnector)(nil)\n\nfunc NewAppConnector(ctx context.Context, apiEndpoint string, app *credentialspb.GitHubApp) (Connector, error) {\n\tinstallationID, err := strconv.ParseInt(app.InstallationId, 10, 64)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not parse app installation ID %q: %w\", app.InstallationId, err)\n\t}\n\n\tappID, err := strconv.ParseInt(app.AppId, 10, 64)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not parse app ID %q: %w\", appID, err)\n\t}\n\n\tconst httpTimeoutSeconds = 60\n\thttpClient := common.RetryableHTTPClientTimeout(int64(httpTimeoutSeconds))\n\n\tinstallationTransport, err := ghinstallation.NewAppsTransport(\n\t\thttpClient.Transport,\n\t\tappID,\n\t\t[]byte(app.PrivateKey))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not create installation client transport: %w\", err)\n\t}\n\tinstallationTransport.BaseURL = apiEndpoint\n\n\tinstallationHttpClient := common.RetryableHTTPClientTimeout(60)\n\tinstallationHttpClient.Transport = installationTransport\n\tinstallationClient, err := github.NewClient(installationHttpClient).WithEnterpriseURLs(apiEndpoint, apiEndpoint)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not create installation client: %w\", err)\n\t}\n\n\tapiTransport, err := ghinstallation.New(\n\t\thttpClient.Transport,\n\t\tappID,\n\t\tinstallationID,\n\t\t[]byte(app.PrivateKey))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not create API client transport: %w\", err)\n\t}\n\tapiTransport.BaseURL = apiEndpoint\n\n\thttpClient.Transport = apiTransport\n\tapiClient, err := github.NewClient(httpClient).WithEnterpriseURLs(apiEndpoint, apiEndpoint)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not create API client: %w\", err)\n\t}\n\n\tgraphqlClient, err := createGraphqlClient(ctx, httpClient, apiEndpoint)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error creating GraphQL client: %w\", err)\n\t}\n\n\treturn &appConnector{\n\t\tapiClient:          apiClient,\n\t\tgraphqlClient:      graphqlClient,\n\t\tinstallationClient: installationClient,\n\t\tinstallationID:     installationID,\n\t}, nil\n}\n\nfunc (c *appConnector) APIClient() *github.Client {\n\treturn c.apiClient\n}\n\nfunc (c *appConnector) Clone(ctx context.Context, repoURL string, args ...string) (string, *gogit.Repository, error) {\n\t// TODO: Check rate limit for this call.\n\ttoken, _, err := c.installationClient.Apps.CreateInstallationToken(\n\t\tctx,\n\t\tc.installationID,\n\t\t&github.InstallationTokenOptions{})\n\tif err != nil {\n\t\treturn \"\", nil, fmt.Errorf(\"could not create installation token: %w\", err)\n\t}\n\n\treturn git.CloneRepoUsingToken(ctx, token.GetToken(), repoURL, \"\", \"x-access-token\", true, args...)\n}\n\nfunc (c *appConnector) GraphQLClient() *githubv4.Client {\n\treturn c.graphqlClient\n}\n\nfunc (c *appConnector) InstallationClient() *github.Client {\n\treturn c.installationClient\n}\n"
  },
  {
    "path": "pkg/sources/github/connector_basicauth.go",
    "content": "package github\n\nimport (\n\t\"fmt\"\n\n\tgogit \"github.com/go-git/go-git/v5\"\n\t\"github.com/google/go-github/v67/github\"\n\t\"github.com/shurcooL/githubv4\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/credentialspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git\"\n)\n\ntype basicAuthConnector struct {\n\tapiClient     *github.Client\n\tgraphqlClient *githubv4.Client\n\tusername      string\n\tpassword      string\n\tclonePath     string\n}\n\nvar _ Connector = (*basicAuthConnector)(nil)\n\nfunc NewBasicAuthConnector(ctx context.Context, apiEndpoint, clonePath string, cred *credentialspb.BasicAuth) (Connector, error) {\n\tconst httpTimeoutSeconds = 60\n\thttpClient := common.RetryableHTTPClientTimeout(int64(httpTimeoutSeconds))\n\thttpClient.Transport = &github.BasicAuthTransport{\n\t\tUsername: cred.Username,\n\t\tPassword: cred.Password,\n\t}\n\n\tapiClient, err := createAPIClient(ctx, httpClient, apiEndpoint)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not create API client: %w\", err)\n\t}\n\n\tgraphqlClient, err := createGraphqlClient(ctx, httpClient, apiEndpoint)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error creating GraphQL client: %w\", err)\n\t}\n\n\treturn &basicAuthConnector{\n\t\tapiClient:     apiClient,\n\t\tgraphqlClient: graphqlClient,\n\t\tusername:      cred.Username,\n\t\tpassword:      cred.Password,\n\t\tclonePath:     clonePath,\n\t}, nil\n}\n\nfunc (c *basicAuthConnector) APIClient() *github.Client {\n\treturn c.apiClient\n}\n\nfunc (c *basicAuthConnector) Clone(ctx context.Context, repoURL string, args ...string) (string, *gogit.Repository, error) {\n\treturn git.CloneRepoUsingToken(ctx, c.password, repoURL, c.clonePath, c.username, true, args...)\n}\n\nfunc (c *basicAuthConnector) GraphQLClient() *githubv4.Client {\n\treturn c.graphqlClient\n}\n"
  },
  {
    "path": "pkg/sources/github/connector_token.go",
    "content": "package github\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\n\tgogit \"github.com/go-git/go-git/v5\"\n\t\"github.com/google/go-github/v67/github\"\n\t\"github.com/shurcooL/githubv4\"\n\t\"golang.org/x/oauth2\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git\"\n)\n\ntype tokenConnector struct {\n\ttoken         string\n\tapiClient     *github.Client\n\tgraphqlClient *githubv4.Client\n\n\tisGitHubEnterprise bool\n\thandleRateLimit    func(context.Context, error) bool\n\tuser               string\n\tuserMu             sync.Mutex\n\tauthInUrl          bool\n\tclonePath          string\n}\n\nvar _ Connector = (*tokenConnector)(nil)\n\nfunc NewTokenConnector(ctx context.Context, apiEndpoint, token, clonePath string, authInUrl bool, handleRateLimit func(context.Context, error) bool) (Connector, error) {\n\tconst httpTimeoutSeconds = 60\n\thttpClient := common.RetryableHTTPClientTimeout(int64(httpTimeoutSeconds))\n\ttokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})\n\thttpClient.Transport = &oauth2.Transport{\n\t\tBase:   httpClient.Transport,\n\t\tSource: tokenSource,\n\t}\n\n\tapiClient, err := createAPIClient(ctx, httpClient, apiEndpoint)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not create API client: %w\", err)\n\t}\n\n\tgraphqlClient, err := createGraphqlClient(ctx, httpClient, apiEndpoint)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error creating GraphQL client: %w\", err)\n\t}\n\n\treturn &tokenConnector{\n\t\tapiClient:          apiClient,\n\t\tgraphqlClient:      graphqlClient,\n\t\ttoken:              token,\n\t\tisGitHubEnterprise: !strings.EqualFold(apiEndpoint, cloudV3Endpoint),\n\t\thandleRateLimit:    handleRateLimit,\n\t\tauthInUrl:          authInUrl,\n\t\tclonePath:          clonePath,\n\t}, nil\n}\n\nfunc (c *tokenConnector) APIClient() *github.Client {\n\treturn c.apiClient\n}\n\nfunc (c *tokenConnector) Clone(ctx context.Context, repoURL string, args ...string) (string, *gogit.Repository, error) {\n\tif err := c.setUserIfUnset(ctx); err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\treturn git.CloneRepoUsingToken(ctx, c.token, repoURL, c.clonePath, c.user, c.authInUrl, args...)\n}\n\nfunc (c *tokenConnector) GraphQLClient() *githubv4.Client {\n\treturn c.graphqlClient\n}\n\nfunc (c *tokenConnector) IsGithubEnterprise() bool {\n\treturn c.isGitHubEnterprise\n}\n\nfunc (c *tokenConnector) getUser(ctx context.Context) (string, error) {\n\tvar (\n\t\tuser *github.User\n\t\terr  error\n\t)\n\tfor {\n\t\tuser, _, err = c.apiClient.Users.Get(ctx, \"\")\n\t\tif c.handleRateLimit(ctx, err) {\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"could not get GitHub user: %w\", err)\n\t\t}\n\t\tbreak\n\t}\n\treturn user.GetLogin(), nil\n}\n\nfunc (c *tokenConnector) setUserIfUnset(ctx context.Context) error {\n\tc.userMu.Lock()\n\tdefer c.userMu.Unlock()\n\n\tif c.user != \"\" {\n\t\treturn nil\n\t}\n\n\tuser, err := c.getUser(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.user = user\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/sources/github/connector_unauthenticated.go",
    "content": "package github\n\nimport (\n\t\"fmt\"\n\n\tgogit \"github.com/go-git/go-git/v5\"\n\t\"github.com/google/go-github/v67/github\"\n\t\"github.com/shurcooL/githubv4\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git\"\n)\n\ntype unauthenticatedConnector struct {\n\tapiClient     *github.Client\n\tgraphqlClient *githubv4.Client\n\n\tclonePath string\n}\n\nvar _ Connector = (*unauthenticatedConnector)(nil)\n\nfunc NewUnauthenticatedConnector(ctx context.Context, apiEndpoint, clonePath string) (Connector, error) {\n\tconst httpTimeoutSeconds = 60\n\thttpClient := common.RetryableHTTPClientTimeout(int64(httpTimeoutSeconds))\n\tapiClient, err := createAPIClient(ctx, httpClient, apiEndpoint)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not create API client: %w\", err)\n\t}\n\n\tgraphqlClient, err := createGraphqlClient(ctx, httpClient, apiEndpoint)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error creating GraphQL client: %w\", err)\n\t}\n\n\treturn &unauthenticatedConnector{\n\t\tapiClient:     apiClient,\n\t\tgraphqlClient: graphqlClient,\n\t\tclonePath:     clonePath,\n\t}, nil\n}\n\nfunc (c *unauthenticatedConnector) APIClient() *github.Client {\n\treturn c.apiClient\n}\n\nfunc (c *unauthenticatedConnector) Clone(ctx context.Context, repoURL string, args ...string) (string, *gogit.Repository, error) {\n\treturn git.CloneRepoUsingUnauthenticated(ctx, repoURL, c.clonePath, args...)\n}\n\nfunc (c *unauthenticatedConnector) GraphQLClient() *githubv4.Client {\n\treturn c.graphqlClient\n}\n"
  },
  {
    "path": "pkg/sources/github/github.go",
    "content": "package github\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand/v2\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"slices\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/gobwas/glob\"\n\t\"github.com/google/go-github/v67/github\"\n\t\"golang.org/x/sync/errgroup\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/feature\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/giturl\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/handlers\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sanitizer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git\"\n)\n\nconst (\n\tSourceType = sourcespb.SourceType_SOURCE_TYPE_GITHUB\n\n\tunauthGithubOrgRateLimt = 30\n\tdefaultPagination       = 100\n\tmembersAppPagination    = 500\n)\n\ntype Source struct {\n\tname string\n\n\tsourceID          sources.SourceID\n\tjobID             sources.JobID\n\tverify            bool\n\torgsCache         cache.Cache[string]\n\tmemberCache       map[string]struct{}\n\trepos             []string\n\tfilteredRepoCache *filteredRepoCache\n\trepoInfoCache     repoInfoCache\n\ttotalRepoSize     int // total size of all repos in kb\n\n\tuseCustomContentWriter bool\n\tgit                    *git.Git\n\n\tscanOptMu   sync.Mutex // protects the scanOptions\n\tscanOptions *git.ScanOptions\n\n\tconn            *sourcespb.GitHub\n\tjobPool         *errgroup.Group\n\tresumeInfoMutex sync.Mutex\n\tresumeInfoSlice []string\n\tconnector       Connector\n\n\tincludePRComments     bool\n\tincludeIssueComments  bool\n\tignoreGists           bool\n\tincludeGistComments   bool\n\tcommentsTimeframeDays uint32\n\n\tsources.Progress\n\tsources.CommonSourceUnitUnmarshaller\n\n\tuseAuthInUrl bool // pass credentials in the repository urls for cloning\n}\n\n// --------------------------------------------------------------------------------\n// RepoUnit and GistUnit are implementations of SourceUnit used during\n// enumeration. The different types aren't strictly necessary, but are a bit\n// more explicit and allow type checking/safety.\n\nvar _ sources.SourceUnit = (*RepoUnit)(nil)\nvar _ sources.SourceUnit = (*GistUnit)(nil)\n\ntype RepoUnit struct {\n\tName string `json:\"name\"`\n\tURL  string `json:\"url\"`\n}\n\nfunc (r RepoUnit) SourceUnitID() (string, sources.SourceUnitKind) { return r.URL, \"repo\" }\nfunc (r RepoUnit) Display() string                                { return r.Name }\n\ntype GistUnit struct {\n\tName string `json:\"name\"`\n\tURL  string `json:\"url\"`\n}\n\nfunc (g GistUnit) SourceUnitID() (string, sources.SourceUnitKind) { return g.URL, \"gist\" }\nfunc (g GistUnit) Display() string                                { return g.Name }\n\n// --------------------------------------------------------------------------------\n\n// WithCustomContentWriter sets the useCustomContentWriter flag on the source.\nfunc (s *Source) WithCustomContentWriter() { s.useCustomContentWriter = true }\n\nfunc (s *Source) WithScanOptions(scanOptions *git.ScanOptions) {\n\ts.scanOptions = scanOptions\n}\n\nfunc (s *Source) setScanOptions(base, head string) {\n\ts.scanOptMu.Lock()\n\tdefer s.scanOptMu.Unlock()\n\ts.scanOptions.BaseHash = base\n\ts.scanOptions.HeadHash = head\n}\n\n// Ensure the Source satisfies the interfaces at compile time\nvar _ sources.Source = (*Source)(nil)\nvar _ sources.SourceUnitUnmarshaller = (*Source)(nil)\nvar _ sources.SourceUnitEnumChunker = (*Source)(nil)\n\nvar endsWithGithub = regexp.MustCompile(`github\\.com/?$`)\n\n// Type returns the type of source.\n// It is used for matching source types in configuration and job input.\nfunc (s *Source) Type() sourcespb.SourceType {\n\treturn SourceType\n}\n\nfunc (s *Source) SourceID() sources.SourceID {\n\treturn s.sourceID\n}\n\nfunc (s *Source) JobID() sources.JobID {\n\treturn s.jobID\n}\n\n// filteredRepoCache is a wrapper around cache.Cache that filters out repos\n// based on include and exclude globs.\ntype filteredRepoCache struct {\n\tcache.Cache[string]\n\tinclude, exclude []glob.Glob\n}\n\nfunc (s *Source) newFilteredRepoCache(ctx context.Context, c cache.Cache[string], include, exclude []string) *filteredRepoCache {\n\tincludeGlobs := make([]glob.Glob, 0, len(include))\n\texcludeGlobs := make([]glob.Glob, 0, len(exclude))\n\tfor _, ig := range include {\n\t\tg, err := glob.Compile(ig)\n\t\tif err != nil {\n\t\t\tctx.Logger().V(1).Info(\"invalid include glob\", \"include_value\", ig, \"err\", err)\n\t\t\tcontinue\n\t\t}\n\t\tincludeGlobs = append(includeGlobs, g)\n\t}\n\tfor _, eg := range exclude {\n\t\tg, err := glob.Compile(eg)\n\t\tif err != nil {\n\t\t\tctx.Logger().V(1).Info(\"invalid exclude glob\", \"exclude_value\", eg, \"err\", err)\n\t\t\tcontinue\n\t\t}\n\t\texcludeGlobs = append(excludeGlobs, g)\n\t}\n\treturn &filteredRepoCache{Cache: c, include: includeGlobs, exclude: excludeGlobs}\n}\n\n// Set overrides the cache.Cache Set method to filter out repos based on\n// include and exclude globs.\nfunc (c *filteredRepoCache) Set(key, val string) {\n\tif c.ignoreRepo(key) {\n\t\treturn\n\t}\n\tif !c.includeRepo(key) {\n\t\treturn\n\t}\n\tc.Cache.Set(key, val)\n}\n\nfunc (c *filteredRepoCache) ignoreRepo(s string) bool {\n\tfor _, g := range c.exclude {\n\t\tif g.Match(s) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (c *filteredRepoCache) includeRepo(s string) bool {\n\tif len(c.include) == 0 {\n\t\treturn true\n\t}\n\n\tfor _, g := range c.include {\n\t\tif g.Match(s) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// wantRepo returns true if the repository should be included based on include/exclude patterns\nfunc (c *filteredRepoCache) wantRepo(s string) bool {\n\treturn !c.ignoreRepo(s) && c.includeRepo(s)\n}\n\n// Init returns an initialized GitHub source.\nfunc (s *Source) Init(aCtx context.Context, name string, jobID sources.JobID, sourceID sources.SourceID, verify bool, connection *anypb.Any, concurrency int) error {\n\terr := git.CmdCheck()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.name = name\n\ts.sourceID = sourceID\n\ts.jobID = jobID\n\ts.verify = verify\n\ts.jobPool = &errgroup.Group{}\n\ts.jobPool.SetLimit(concurrency)\n\n\t// Setup scan options if it wasn't provided.\n\tif s.scanOptions == nil {\n\t\ts.scanOptions = &git.ScanOptions{}\n\t}\n\n\tvar conn sourcespb.GitHub\n\terr = anypb.UnmarshalTo(connection, &conn, proto.UnmarshalOptions{})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error unmarshalling connection: %w\", err)\n\t}\n\ts.conn = &conn\n\n\t// configuration uses the inverse logic of the `useAuthInUrl` flag.\n\ts.useAuthInUrl = !s.conn.RemoveAuthInUrl\n\n\tconnector, err := newConnector(aCtx, s)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not create connector: %w\", err)\n\t}\n\ts.connector = connector\n\n\ts.orgsCache = simple.NewCache[string]()\n\tfor _, org := range s.conn.Organizations {\n\t\ts.orgsCache.Set(org, org)\n\t}\n\ts.memberCache = make(map[string]struct{})\n\n\ts.filteredRepoCache = s.newFilteredRepoCache(aCtx,\n\t\tsimple.NewCache[string](),\n\t\tappend(s.conn.GetRepositories(), s.conn.GetIncludeRepos()...),\n\t\ts.conn.GetIgnoreRepos(),\n\t)\n\ts.repos = s.conn.Repositories\n\tfor _, repo := range s.repos {\n\t\tr, err := s.normalizeRepo(repo)\n\t\tif err != nil {\n\t\t\taCtx.Logger().Error(err, \"invalid repository\", \"repo\", repo)\n\t\t\tcontinue\n\t\t}\n\t\ts.filteredRepoCache.Set(repo, r)\n\t}\n\ts.repoInfoCache = newRepoInfoCache()\n\n\ts.includeIssueComments = s.conn.IncludeIssueComments\n\ts.includePRComments = s.conn.IncludePullRequestComments\n\ts.ignoreGists = s.conn.GetIgnoreGists()\n\ts.includeGistComments = s.conn.IncludeGistComments\n\ts.commentsTimeframeDays = s.conn.CommentsTimeframeDays\n\n\t// Head or base should only be used with incoming webhooks\n\tif (len(s.conn.Head) > 0 || len(s.conn.Base) > 0) && len(s.repos) != 1 {\n\t\treturn fmt.Errorf(\"cannot specify head or base with multiple repositories\")\n\t}\n\n\tcfg := &git.Config{\n\t\tSourceName:   s.name,\n\t\tJobID:        s.jobID,\n\t\tSourceID:     s.sourceID,\n\t\tSourceType:   s.Type(),\n\t\tVerify:       s.verify,\n\t\tSkipBinaries: conn.GetSkipBinaries(),\n\t\tSkipArchives: conn.GetSkipArchives(),\n\t\tConcurrency:  concurrency,\n\t\tSourceMetadataFunc: func(file, email, commit, timestamp, repository, repositoryLocalPath string, line int64) *source_metadatapb.MetaData {\n\t\t\treturn &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\tCommit:              sanitizer.UTF8(commit),\n\t\t\t\t\t\tFile:                sanitizer.UTF8(file),\n\t\t\t\t\t\tEmail:               sanitizer.UTF8(email),\n\t\t\t\t\t\tRepository:          sanitizer.UTF8(repository),\n\t\t\t\t\t\tLink:                giturl.GenerateLink(repository, commit, file, line),\n\t\t\t\t\t\tTimestamp:           sanitizer.UTF8(timestamp),\n\t\t\t\t\t\tLine:                line,\n\t\t\t\t\t\tVisibility:          s.visibilityOf(aCtx, repository),\n\t\t\t\t\t\tRepositoryLocalPath: sanitizer.UTF8(repositoryLocalPath),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t\tUseCustomContentWriter: s.useCustomContentWriter,\n\t\tAuthInUrl:              s.useAuthInUrl,\n\t}\n\ts.git = git.NewGit(cfg)\n\n\treturn nil\n}\n\n// Validate is used by enterprise CLI to validate the GitHub config file.\nfunc (s *Source) Validate(ctx context.Context) []error {\n\t/*\n\t\tUses the rate limit API (docs: https://docs.github.com/en/rest/rate-limit) because:\n\t\t- Works with all auth types: user tokens, PATs, App credentials, and unauthenticated requests\n\t\t- Returns 401 for invalid credentials but works with no auth (as unauthenticated)\n\t\t- Doesn't consume API quota when called\n\t*/\n\tif _, _, err := s.connector.APIClient().RateLimit.Get(ctx); err != nil {\n\t\treturn []error{err}\n\t}\n\n\treturn nil\n}\n\nfunc (s *Source) visibilityOf(ctx context.Context, repoURL string) source_metadatapb.Visibility {\n\t// It isn't possible to get the visibility of a wiki.\n\t// We must use the visibility of the corresponding repository.\n\tif strings.HasSuffix(repoURL, \".wiki.git\") {\n\t\trepoURL = strings.TrimSuffix(repoURL, \".wiki.git\") + \".git\"\n\t}\n\n\trepoInfo, ok := s.repoInfoCache.get(repoURL)\n\tif !ok {\n\t\t// This should never happen.\n\t\terr := fmt.Errorf(\"no repoInfo for URL: %s\", repoURL)\n\t\tctx.Logger().Error(err, \"failed to get repository visibility\")\n\t\treturn source_metadatapb.Visibility_unknown\n\t}\n\n\treturn repoInfo.visibility\n}\n\n// Chunks emits chunks of bytes over a channel.\nfunc (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, targets ...sources.ChunkingTarget) error {\n\tchunksReporter := sources.ChanReporter{Ch: chunksChan}\n\t// If targets are provided, we're only scanning the data in those targets.\n\t// Otherwise, we're scanning all data.\n\t// This allows us to only scan the commit where a vulnerability was found.\n\tif len(targets) > 0 {\n\t\terrs := s.scanTargets(ctx, targets, chunksReporter)\n\t\treturn errors.Join(errs...)\n\t}\n\n\t// Reset consumption and rate limit metrics on each run.\n\tgithubNumRateLimitEncountered.WithLabelValues(s.name).Set(0)\n\tgithubSecondsSpentRateLimited.WithLabelValues(s.name).Set(0)\n\tgithubReposScanned.WithLabelValues(s.name).Set(0)\n\n\t// We don't care about handling enumerated values as they happen during\n\t// the normal Chunks flow because we enumerate and scan in two steps.\n\tnoopReporter := sources.VisitorReporter{\n\t\tVisitUnit: func(context.Context, sources.SourceUnit) error {\n\t\t\treturn nil\n\t\t},\n\t}\n\terr := s.Enumerate(ctx, noopReporter)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error enumerating: %w\", err)\n\t}\n\n\treturn s.scan(ctx, chunksReporter)\n}\n\n// Enumerate enumerates the GitHub source based on authentication method and\n// user configuration. It populates s.filteredRepoCache, s.repoInfoCache,\n// s.memberCache, s.totalRepoSize, s.orgsCache, and s.repos. Additionally,\n// repositories and gists are reported to the provided UnitReporter.\nfunc (s *Source) Enumerate(ctx context.Context, reporter sources.UnitReporter) error {\n\tseenUnits := make(map[sources.SourceUnit]struct{})\n\t// Wrapper reporter to deduplicate and filter found units.\n\tdedupeReporter := sources.VisitorReporter{\n\t\tVisitUnit: func(ctx context.Context, su sources.SourceUnit) error {\n\t\t\t// Only report units that passed the user configured filter.\n\t\t\tname := su.Display()\n\t\t\tif !s.filteredRepoCache.Exists(name) {\n\t\t\t\treturn ctx.Err()\n\t\t\t}\n\t\t\t// Only report a unit once.\n\t\t\tif _, ok := seenUnits[su]; ok {\n\t\t\t\treturn ctx.Err()\n\t\t\t}\n\t\t\tseenUnits[su] = struct{}{}\n\t\t\treturn reporter.UnitOk(ctx, su)\n\t\t},\n\t\tVisitErr: reporter.UnitErr,\n\t}\n\t// Report any values that were already configured.\n\t// This compensates for differences in enumeration logic between `--org` and `--repo`.\n\t// See: https://github.com/trufflesecurity/trufflehog/pull/2379#discussion_r1487454788\n\tfor _, name := range s.filteredRepoCache.Keys() {\n\t\turl, _ := s.filteredRepoCache.Get(name)\n\t\turl, err := s.ensureRepoInfoCache(ctx, url, &unitErrorReporter{reporter})\n\t\tif err != nil {\n\t\t\tif err := dedupeReporter.UnitErr(ctx, err); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif err := dedupeReporter.UnitOk(ctx, RepoUnit{Name: name, URL: url}); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// I'm not wild about switching on the connector type here (as opposed to dispatching to the connector itself) but\n\t// this felt like a compromise that allowed me to isolate connection logic without rewriting the entire source.\n\tswitch c := s.connector.(type) {\n\tcase *appConnector:\n\t\tif err := s.enumerateWithApp(ctx, c.InstallationClient(), dedupeReporter); err != nil {\n\t\t\treturn err\n\t\t}\n\tcase *basicAuthConnector:\n\t\tif err := s.enumerateBasicAuth(ctx, dedupeReporter); err != nil {\n\t\t\treturn err\n\t\t}\n\tcase *tokenConnector:\n\t\tif err := s.enumerateWithToken(ctx, c.IsGithubEnterprise(), dedupeReporter); err != nil {\n\t\t\treturn err\n\t\t}\n\tcase *unauthenticatedConnector:\n\t\ts.enumerateUnauthenticated(ctx, dedupeReporter)\n\t}\n\t// If explicit repositories were provided, use them directly without filtering\n\t// Otherwise, rebuild s.repos from the filteredRepoCache\n\tif len(s.conn.Repositories) > 0 {\n\t\t// Explicit repositories bypass filtering - use them as-is\n\t\ts.repos = s.conn.Repositories\n\t\tctx.Logger().V(1).Info(\"Using explicit repositories\", \"count\", len(s.repos))\n\t} else {\n\t\t// No explicit repositories - rebuild from enumerated cache with filtering\n\t\ts.repos = make([]string, 0, s.filteredRepoCache.Count())\n\n\t\t// Double make sure that all enumerated repositories in the\n\t\t// filteredRepoCache have an entry in the repoInfoCache.\n\t\tfor _, repo := range s.filteredRepoCache.Values() {\n\t\t\t// Extract the repository name from the URL for filtering\n\t\t\trepoName := extractRepoNameFromUrl(repo)\n\n\t\t\t// Final filter check - only include repositories that pass the filter\n\t\t\tif s.filteredRepoCache.wantRepo(repoName) {\n\t\t\t\tctx := context.WithValue(ctx, \"repo\", repo)\n\n\t\t\t\trepo, err := s.ensureRepoInfoCache(ctx, repo, &unitErrorReporter{reporter})\n\t\t\t\tif err != nil {\n\t\t\t\t\tctx.Logger().Error(err, \"error caching repo info\")\n\t\t\t\t\t_ = dedupeReporter.UnitErr(ctx, fmt.Errorf(\"error caching repo info: %w\", err))\n\t\t\t\t}\n\t\t\t\ts.repos = append(s.repos, repo)\n\t\t\t}\n\t\t}\n\t}\n\tgithubReposEnumerated.WithLabelValues(s.name).Set(float64(len(s.repos)))\n\tctx.Logger().Info(\"Completed enumeration\", \"num_repos\", len(s.repos), \"num_orgs\", s.orgsCache.Count(), \"num_members\", len(s.memberCache))\n\t// We must sort the repos so we can resume later if necessary.\n\tsort.Strings(s.repos)\n\treturn nil\n}\n\n// ensureRepoInfoCache checks that s.repoInfoCache has an entry for the\n// provided repository URL. If not, it fetches and stores the metadata for the\n// repository. In some cases, the gist URL needs to be normalized, which is\n// returned by this function.\nfunc (s *Source) ensureRepoInfoCache(ctx context.Context, repo string, reporter errorReporter) (string, error) {\n\tif _, ok := s.repoInfoCache.get(repo); ok {\n\t\treturn repo, nil\n\t}\n\tctx.Logger().V(2).Info(\"Caching repository info\")\n\n\t_, urlParts, err := getRepoURLParts(repo)\n\tif err != nil {\n\t\treturn repo, fmt.Errorf(\"failed to parse repository URL: %w\", err)\n\t}\n\n\tif !s.ignoreGists && isGistUrl(urlParts) {\n\t\t// Cache gist info.\n\t\tfor {\n\t\t\tgistID := extractGistID(urlParts)\n\t\t\tgist, _, err := s.connector.APIClient().Gists.Get(ctx, gistID)\n\t\t\t// Normalize the URL to the Gist's pull URL.\n\t\t\t// See https://github.com/trufflesecurity/trufflehog/pull/2625#issuecomment-2025507937\n\t\t\trepo = gist.GetGitPullURL()\n\n\t\t\tif s.handleRateLimit(ctx, err, reporter) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\treturn repo, fmt.Errorf(\"failed to fetch gist: %w\", err)\n\t\t\t}\n\n\t\t\ts.cacheGistInfo(gist)\n\t\t\tbreak\n\t\t}\n\t} else {\n\t\t// Cache repository info.\n\t\tfor {\n\t\t\tghRepo, _, err := s.connector.APIClient().Repositories.Get(ctx, urlParts[1], urlParts[2])\n\t\t\tif s.handleRateLimit(ctx, err, reporter) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn repo, fmt.Errorf(\"failed to fetch repository: %w\", err)\n\t\t\t}\n\t\t\ts.cacheRepoInfo(ghRepo)\n\t\t\tbreak\n\t\t}\n\t}\n\treturn repo, nil\n}\n\nfunc (s *Source) enumerateBasicAuth(ctx context.Context, reporter sources.UnitReporter) error {\n\tfor _, org := range s.orgsCache.Keys() {\n\t\torgCtx := context.WithValue(ctx, \"account\", org)\n\t\tuserType, err := s.getReposByOrgOrUser(ctx, org, true, reporter)\n\t\tif err != nil {\n\t\t\torgCtx.Logger().Error(err, \"error fetching repos for org or user\")\n\t\t\tcontinue\n\t\t}\n\n\t\t// TODO: This modifies s.memberCache but it doesn't look like\n\t\t// we do anything with it.\n\t\tif userType == organization && s.conn.ScanUsers {\n\t\t\tif err := s.addMembersByOrg(ctx, org, reporter); err != nil {\n\t\t\t\torgCtx.Logger().Error(err, \"Unable to add members by org\")\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *Source) enumerateUnauthenticated(ctx context.Context, reporter sources.UnitReporter) {\n\tif s.orgsCache.Count() > unauthGithubOrgRateLimt {\n\t\tctx.Logger().Info(\"You may experience rate limiting when using the unauthenticated GitHub api. Consider using an authenticated scan instead.\")\n\t}\n\n\tfor _, org := range s.orgsCache.Keys() {\n\t\torgCtx := context.WithValue(ctx, \"account\", org)\n\t\tuserType, err := s.getReposByOrgOrUser(ctx, org, false, reporter)\n\t\tif err != nil {\n\t\t\torgCtx.Logger().Error(err, \"error fetching repos for org or user\")\n\t\t\tcontinue\n\t\t}\n\n\t\tif userType == organization && s.conn.ScanUsers {\n\t\t\torgCtx.Logger().Info(\"WARNING: Enumerating unauthenticated does not support scanning organization members (--include-members)\")\n\t\t}\n\t}\n}\n\nfunc (s *Source) enumerateWithToken(ctx context.Context, isGithubEnterprise bool, reporter sources.UnitReporter) error {\n\tctx.Logger().V(1).Info(\"Enumerating with token\")\n\n\tvar ghUser *github.User\n\tvar err error\n\tfor {\n\t\tghUser, _, err = s.connector.APIClient().Users.Get(ctx, \"\")\n\t\tif s.handleRateLimitWithUnitReporter(ctx, reporter, err) {\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error getting user: %w\", err)\n\t\t}\n\t\tbreak\n\t}\n\n\tspecificScope := len(s.repos) > 0 || s.orgsCache.Count() > 0\n\tif !specificScope {\n\t\t// Enumerate the user's orgs and repos if none were specified.\n\t\tif err := s.getReposByUser(ctx, ghUser.GetLogin(), true, reporter); err != nil {\n\t\t\tctx.Logger().Error(err, \"Unable to fetch repos for the current user\", \"user\", ghUser.GetLogin())\n\t\t}\n\t\tif err := s.addUserGistsToCache(ctx, ghUser.GetLogin(), reporter); err != nil {\n\t\t\tctx.Logger().Error(err, \"Unable to fetch gists for the current user\", \"user\", ghUser.GetLogin())\n\t\t}\n\n\t\tif isGithubEnterprise {\n\t\t\ts.addAllVisibleOrgs(ctx, reporter)\n\t\t} else {\n\t\t\t// Scan for orgs is default with a token.\n\t\t\t// GitHub App enumerates the repos that were assigned to it in GitHub App settings.\n\t\t\ts.addOrgsByUser(ctx, ghUser.GetLogin(), reporter)\n\t\t}\n\t}\n\n\tif len(s.orgsCache.Keys()) > 0 {\n\t\tfor _, org := range s.orgsCache.Keys() {\n\t\t\torgCtx := context.WithValue(ctx, \"account\", org)\n\t\t\tuserType, err := s.getReposByOrgOrUser(ctx, org, true, reporter)\n\t\t\tif err != nil {\n\t\t\t\torgCtx.Logger().Error(err, \"Unable to fetch repos for org or user\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif userType == organization && s.conn.ScanUsers {\n\t\t\t\tif err := s.addMembersByOrg(ctx, org, reporter); err != nil {\n\t\t\t\t\torgCtx.Logger().Error(err, \"Unable to add members for org\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif s.conn.ScanUsers && len(s.memberCache) > 0 {\n\t\t\tctx.Logger().Info(\"Fetching repos for org members\", \"org_count\", s.orgsCache.Count(), \"member_count\", len(s.memberCache))\n\t\t\ts.addReposForMembers(ctx, reporter)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *Source) enumerateWithApp(ctx context.Context, installationClient *github.Client, reporter sources.UnitReporter) error {\n\t// If no repos were provided, enumerate them.\n\tif len(s.repos) == 0 {\n\t\tif err := s.getReposByApp(ctx, reporter); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Check if we need to find user repos.\n\t\tif s.conn.ScanUsers {\n\t\t\terr := s.addMembersByApp(ctx, installationClient, reporter)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tctx.Logger().Info(\"Scanning repos\", \"org_members\", len(s.memberCache))\n\t\t\t// TODO: Replace loop below with a call to s.addReposForMembers(ctx, reporter)\n\t\t\tfor member := range s.memberCache {\n\t\t\t\tlogger := ctx.Logger().WithValues(\"member\", member)\n\t\t\t\tif err := s.addUserGistsToCache(ctx, member, reporter); err != nil {\n\t\t\t\t\tlogger.Error(err, \"error fetching gists by user\")\n\t\t\t\t}\n\t\t\t\t// TODO: Add authenticated user list repo for app token. It does support as per docs but need to test it before we enable it here.\n\t\t\t\t// docs: https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repositories-for-the-authenticated-user\n\t\t\t\tif err := s.getReposByUser(ctx, member, false, reporter); err != nil {\n\t\t\t\t\tlogger.Error(err, \"error fetching repos by user\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *Source) scan(ctx context.Context, reporter sources.ChunkReporter) error {\n\tvar scannedCount uint64 = 1\n\n\tctx.Logger().V(2).Info(\"Found repos to scan\", \"count\", len(s.repos))\n\n\t// If there is resume information available, limit this scan to only the repos that still need scanning.\n\treposToScan, progressIndexOffset := sources.FilterReposToResume(s.repos, s.GetProgress().EncodedResumeInfo)\n\ts.repos = reposToScan\n\n\tfor i, repoURL := range s.repos {\n\t\ts.jobPool.Go(func() error {\n\t\t\tif common.IsDone(ctx) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tctx := context.WithValue(ctx, \"repo\", repoURL)\n\n\t\t\t// TODO: set progress complete is being called concurrently with i\n\t\t\ts.setProgressCompleteWithRepo(i, progressIndexOffset, repoURL)\n\t\t\t// Ensure the repo is removed from the resume info after being scanned.\n\t\t\tdefer func(s *Source, repoURL string) {\n\t\t\t\ts.resumeInfoMutex.Lock()\n\t\t\t\tdefer s.resumeInfoMutex.Unlock()\n\t\t\t\ts.resumeInfoSlice = sources.RemoveRepoFromResumeInfo(s.resumeInfoSlice, repoURL)\n\t\t\t}(s, repoURL)\n\n\t\t\tif err := s.scanRepo(ctx, repoURL, reporter); err != nil {\n\t\t\t\tctx.Logger().Error(err, \"error scanning repo\")\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tatomic.AddUint64(&scannedCount, 1)\n\t\t\treturn nil\n\t\t})\n\t}\n\n\t_ = s.jobPool.Wait()\n\ts.SetProgressComplete(len(s.repos), len(s.repos), \"Completed GitHub scan\", \"\")\n\n\treturn nil\n}\n\n// scanRepo attempts to scan the provided URL and any associated wiki and\n// comments if configured. An error is returned if we could not find necessary\n// repository metadata or clone the repo, otherwise all errors are reported to\n// the ChunkReporter.\nfunc (s *Source) scanRepo(ctx context.Context, repoURL string, reporter sources.ChunkReporter) error {\n\tif !strings.HasSuffix(repoURL, \".git\") {\n\t\treturn fmt.Errorf(\"repo does not end in .git\")\n\t}\n\t// Scan the repository\n\trepoInfo, ok := s.repoInfoCache.get(repoURL)\n\tif !ok {\n\t\t// This should never happen.\n\t\treturn fmt.Errorf(\"no repoInfo for URL: %s\", repoURL)\n\t}\n\tduration, err := s.cloneAndScanRepo(ctx, repoURL, repoInfo, reporter)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Scan the wiki, if enabled, and the repo has one.\n\tif s.conn.IncludeWikis && repoInfo.hasWiki && s.wikiIsReachable(ctx, repoURL) {\n\t\twikiURL := strings.TrimSuffix(repoURL, \".git\") + \".wiki.git\"\n\t\twikiCtx := context.WithValue(ctx, \"repo\", wikiURL)\n\n\t\t_, err := s.cloneAndScanRepo(wikiCtx, wikiURL, repoInfo, reporter)\n\t\tif err != nil {\n\t\t\t// Ignore \"Repository not found\" errors.\n\t\t\t// It's common for GitHub's API to say a repo has a wiki when it doesn't.\n\t\t\tif !strings.Contains(err.Error(), \"not found\") {\n\t\t\t\tif err := reporter.ChunkErr(ctx, fmt.Errorf(\"error scanning wiki: %w\", err)); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Don't return, it still might be possible to scan comments.\n\t\t}\n\t}\n\n\t// Scan comments, if enabled.\n\tif s.includeGistComments || s.includeIssueComments || s.includePRComments {\n\t\tif err := s.scanComments(ctx, repoURL, repoInfo, reporter); err != nil {\n\t\t\terr := fmt.Errorf(\"error scanning comments: %w\", err)\n\t\t\tif err := reporter.ChunkErr(ctx, err); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tctx.Logger().V(2).Info(\"finished scanning repo\", \"duration_seconds\", duration)\n\tgithubReposScanned.WithLabelValues(s.name).Inc()\n\treturn nil\n}\n\nfunc (s *Source) cloneAndScanRepo(ctx context.Context, repoURL string, repoInfo repoInfo, reporter sources.ChunkReporter) (time.Duration, error) {\n\tvar duration time.Duration\n\n\tctx.Logger().V(2).Info(\"attempting to clone repo\", \"repo_name\", repoInfo.name)\n\tpath, repo, err := s.cloneRepo(ctx, repoURL)\n\tif err != nil {\n\t\treturn duration, err\n\t}\n\t// remove the path only if it was created as a temporary path, or if it is a clone path and --no-cleanup is not set.\n\t// if legacy JSON is enabled, don't remove the directory because we need it for outputting legacy JSON.\n\tif !s.conn.GetPrintLegacyJson() {\n\t\tif strings.HasPrefix(path, filepath.Join(os.TempDir(), \"trufflehog\")) || (!s.conn.NoCleanup && s.conn.GetClonePath() != \"\") {\n\t\t\tdefer os.RemoveAll(path)\n\t\t}\n\t}\n\n\t// TODO: Can this be set once or does it need to be set on every iteration? Is |s.scanOptions| set every clone?\n\ts.setScanOptions(s.conn.Base, s.conn.Head)\n\n\tstart := time.Now()\n\tif err = s.git.ScanRepo(ctx, repo, path, s.scanOptions, reporter); err != nil {\n\t\treturn duration, fmt.Errorf(\"error scanning repo %s: %w\", repoURL, err)\n\t}\n\tduration = time.Since(start)\n\treturn duration, nil\n}\n\nvar (\n\trateLimitMu         sync.RWMutex\n\trateLimitResumeTime time.Time\n)\n\n// errorReporter is an interface that captures just the error reporting functionality\ntype errorReporter interface {\n\tErr(ctx context.Context, err error) error\n}\n\n// wrapper to adapt UnitReporter to errorReporter\ntype unitErrorReporter struct {\n\treporter sources.UnitReporter\n}\n\nfunc (u unitErrorReporter) Err(ctx context.Context, err error) error {\n\treturn u.reporter.UnitErr(ctx, err)\n}\n\n// wrapper to adapt ChunkReporter to errorReporter\ntype chunkErrorReporter struct {\n\treporter sources.ChunkReporter\n}\n\nfunc (c chunkErrorReporter) Err(ctx context.Context, err error) error {\n\treturn c.reporter.ChunkErr(ctx, err)\n}\n\n// handleRateLimit handles GitHub API rate limiting with an optional error reporter.\n// Returns true if a rate limit was handled.\n//\n// Unauthenticated users have a rate limit of 60 requests per hour.\n// Authenticated users have a rate limit of 5,000 requests per hour,\n// however, certain actions are subject to a stricter \"secondary\" limit.\n// https://docs.github.com/en/rest/overview/rate-limits-for-the-rest-api\nfunc (s *Source) handleRateLimit(ctx context.Context, errIn error, reporters ...errorReporter) bool {\n\tif errIn == nil {\n\t\treturn false\n\t}\n\n\trateLimitMu.RLock()\n\tresumeTime := rateLimitResumeTime\n\trateLimitMu.RUnlock()\n\n\tvar retryAfter time.Duration\n\tif resumeTime.IsZero() || time.Now().After(resumeTime) {\n\t\trateLimitMu.Lock()\n\t\tvar (\n\t\t\tnow = time.Now()\n\n\t\t\t// GitHub has both primary (RateLimit) and secondary (AbuseRateLimit) errors.\n\t\t\tlimitType  string\n\t\t\trateLimit  *github.RateLimitError\n\t\t\tabuseLimit *github.AbuseRateLimitError\n\t\t)\n\t\tif errors.As(errIn, &rateLimit) {\n\t\t\tlimitType = \"primary\"\n\t\t\trate := rateLimit.Rate\n\t\t\tif rate.Remaining == 0 { // TODO: Will we ever receive a |RateLimitError| when remaining > 0?\n\t\t\t\tretryAfter = rate.Reset.Sub(now)\n\t\t\t}\n\t\t} else if errors.As(errIn, &abuseLimit) {\n\t\t\tlimitType = \"secondary\"\n\t\t\tretryAfter = abuseLimit.GetRetryAfter()\n\t\t} else {\n\t\t\trateLimitMu.Unlock()\n\t\t\treturn false\n\t\t}\n\n\t\tjitter := time.Duration(rand.IntN(10)+1) * time.Second\n\t\tif retryAfter > 0 {\n\t\t\tretryAfter = retryAfter + jitter\n\t\t\trateLimitResumeTime = now.Add(retryAfter)\n\t\t\tctx.Logger().Info(fmt.Sprintf(\"exceeded %s rate limit\", limitType), \"retry_after\", retryAfter.String(), \"resume_time\", rateLimitResumeTime.Format(time.RFC3339))\n\t\t\t// Only report the error if a reporter was provided\n\t\t\tfor _, reporter := range reporters {\n\t\t\t\t_ = reporter.Err(ctx, fmt.Errorf(\"exceeded %s rate limit\", limitType))\n\t\t\t}\n\t\t} else {\n\t\t\tretryAfter = (5 * time.Minute) + jitter\n\t\t\trateLimitResumeTime = now.Add(retryAfter)\n\t\t\t// TODO: Use exponential backoff instead of static retry time.\n\t\t\tctx.Logger().Error(errIn, \"unexpected rate limit error\", \"retry_after\", retryAfter.String(), \"resume_time\", rateLimitResumeTime.Format(time.RFC3339))\n\t\t}\n\n\t\trateLimitMu.Unlock()\n\t} else {\n\t\tretryAfter = time.Until(resumeTime)\n\t}\n\n\tgithubNumRateLimitEncountered.WithLabelValues(s.name).Inc()\n\ttime.Sleep(retryAfter)\n\tgithubSecondsSpentRateLimited.WithLabelValues(s.name).Add(retryAfter.Seconds())\n\treturn true\n}\n\n// handleRateLimitWithUnitReporter is a wrapper around handleRateLimit that includes unit reporting\nfunc (s *Source) handleRateLimitWithUnitReporter(ctx context.Context, reporter sources.UnitReporter, errIn error) bool {\n\treturn s.handleRateLimit(ctx, errIn, &unitErrorReporter{reporter: reporter})\n}\n\n// handleRateLimitWithChunkReporter is a wrapper around handleRateLimit that includes chunk reporting\nfunc (s *Source) handleRateLimitWithChunkReporter(ctx context.Context, reporter sources.ChunkReporter, errIn error) bool {\n\treturn s.handleRateLimit(ctx, errIn, &chunkErrorReporter{reporter: reporter})\n}\n\nfunc (s *Source) addReposForMembers(ctx context.Context, reporter sources.UnitReporter) {\n\tctx.Logger().Info(\"Fetching repos from members\", \"members\", len(s.memberCache))\n\tfor member := range s.memberCache {\n\t\tif err := s.addUserGistsToCache(ctx, member, reporter); err != nil {\n\t\t\tctx.Logger().Info(\"Unable to fetch gists by user\", \"user\", member, \"error\", err)\n\t\t}\n\t\tif err := s.getReposByUser(ctx, member, false, reporter); err != nil {\n\t\t\tctx.Logger().Info(\"Unable to fetch repos by user\", \"user\", member, \"error\", err)\n\t\t}\n\t}\n}\n\n// addUserGistsToCache collects all the gist urls for a given user,\n// and adds them to the filteredRepoCache.\nfunc (s *Source) addUserGistsToCache(ctx context.Context, user string, reporter sources.UnitReporter) error {\n\tif s.ignoreGists {\n\t\treturn nil\n\t}\n\n\tgistOpts := &github.GistListOptions{}\n\tlogger := ctx.Logger().WithValues(\"user\", user)\n\n\tfor {\n\t\tgists, res, err := s.connector.APIClient().Gists.List(ctx, user, gistOpts)\n\t\tif s.handleRateLimitWithUnitReporter(ctx, reporter, err) {\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not list gists for user %s: %w\", user, err)\n\t\t}\n\n\t\tfor _, gist := range gists {\n\t\t\ts.filteredRepoCache.Set(gist.GetID(), gist.GetGitPullURL())\n\t\t\ts.cacheGistInfo(gist)\n\t\t\tif err := reporter.UnitOk(ctx, GistUnit{Name: gist.GetID(), URL: gist.GetGitPullURL()}); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tif res == nil || res.NextPage == 0 {\n\t\t\tbreak\n\t\t}\n\t\tlogger.V(2).Info(\"Listed gists\", \"page\", gistOpts.Page, \"last_page\", res.LastPage)\n\t\tgistOpts.Page = res.NextPage\n\t}\n\treturn nil\n}\n\nfunc (s *Source) addMembersByApp(ctx context.Context, installationClient *github.Client, reporter sources.UnitReporter) error {\n\topts := &github.ListOptions{\n\t\tPerPage: membersAppPagination,\n\t}\n\n\t// TODO: Check rate limit for this call.\n\tinstalls, _, err := installationClient.Apps.ListInstallations(ctx, opts)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not enumerate installed orgs: %w\", err)\n\t}\n\n\tfor _, org := range installs {\n\t\tif org.Account.GetType() != \"Organization\" {\n\t\t\tcontinue\n\t\t}\n\t\tif err := s.addMembersByOrg(ctx, *org.Account.Login, reporter); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *Source) addAllVisibleOrgs(ctx context.Context, reporter sources.UnitReporter) {\n\tctx.Logger().V(2).Info(\"enumerating all visible organizations on GHE\")\n\t// Enumeration on this endpoint does not use pages it uses a since ID.\n\t// The endpoint will return organizations with an ID greater than the given since ID.\n\t// Empty org response is our cue to break the enumeration loop.\n\torgOpts := &github.OrganizationsListOptions{\n\t\tSince: 0,\n\t\tListOptions: github.ListOptions{\n\t\t\tPerPage: defaultPagination,\n\t\t},\n\t}\n\tfor {\n\t\torgs, _, err := s.connector.APIClient().Organizations.ListAll(ctx, orgOpts)\n\t\tif s.handleRateLimitWithUnitReporter(ctx, reporter, err) {\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\tctx.Logger().Error(err, \"could not list all organizations\")\n\t\t\treturn\n\t\t}\n\n\t\tif len(orgs) == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tlastOrgID := *orgs[len(orgs)-1].ID\n\t\tctx.Logger().V(2).Info(fmt.Sprintf(\"listed organization IDs %d through %d\", orgOpts.Since, lastOrgID))\n\t\torgOpts.Since = lastOrgID\n\n\t\tfor _, org := range orgs {\n\t\t\tvar name string\n\t\t\tswitch {\n\t\t\tcase org.Name != nil:\n\t\t\t\tname = *org.Name\n\t\t\tcase org.Login != nil:\n\t\t\t\tname = *org.Login\n\t\t\tdefault:\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ts.orgsCache.Set(name, name)\n\t\t\tctx.Logger().V(2).Info(\"adding organization for repository enumeration\", \"id\", org.ID, \"name\", name)\n\t\t}\n\t}\n}\n\nfunc (s *Source) addOrgsByUser(ctx context.Context, user string, reporter sources.UnitReporter) {\n\torgOpts := &github.ListOptions{\n\t\tPerPage: defaultPagination,\n\t}\n\tlogger := ctx.Logger().WithValues(\"user\", user)\n\tfor {\n\t\torgs, resp, err := s.connector.APIClient().Organizations.List(ctx, \"\", orgOpts)\n\t\tif s.handleRateLimitWithUnitReporter(ctx, reporter, err) {\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\tlogger.Error(err, \"Could not list organizations\")\n\t\t\treturn\n\t\t}\n\n\t\tlogger.V(2).Info(\"Listed orgs\", \"page\", orgOpts.Page, \"last_page\", resp.LastPage)\n\t\tfor _, org := range orgs {\n\t\t\tif org.Login == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ts.orgsCache.Set(*org.Login, *org.Login)\n\t\t}\n\t\tif resp.NextPage == 0 {\n\t\t\tbreak\n\t\t}\n\t\torgOpts.Page = resp.NextPage\n\t}\n}\n\nfunc (s *Source) addMembersByOrg(ctx context.Context, org string, reporter sources.UnitReporter) error {\n\topts := &github.ListMembersOptions{\n\t\tPublicOnly: false,\n\t\tListOptions: github.ListOptions{\n\t\t\tPerPage: membersAppPagination,\n\t\t},\n\t}\n\n\tlogger := ctx.Logger().WithValues(\"org\", org)\n\tfor {\n\t\tmembers, res, err := s.connector.APIClient().Organizations.ListMembers(ctx, org, opts)\n\t\tif s.handleRateLimitWithUnitReporter(ctx, reporter, err) {\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not list organization (%q) members: account may not have access to list organization members: %w\", org, err)\n\t\t}\n\t\tif len(members) == 0 {\n\t\t\treturn fmt.Errorf(\"organization (%q) had 0 members: account may not have access to list organization members\", org)\n\t\t}\n\n\t\tlogger.V(2).Info(\"Listed members\", \"page\", opts.Page, \"last_page\", res.LastPage)\n\t\tfor _, m := range members {\n\t\t\tusr := m.Login\n\t\t\tif usr == nil || *usr == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, ok := s.memberCache[*usr]; !ok {\n\t\t\t\ts.memberCache[*usr] = struct{}{}\n\t\t\t}\n\t\t}\n\t\tif res.NextPage == 0 {\n\t\t\tbreak\n\t\t}\n\t\topts.Page = res.NextPage\n\t}\n\n\treturn nil\n}\n\n// setProgressCompleteWithRepo calls the s.SetProgressComplete after safely setting up the encoded resume info string.\nfunc (s *Source) setProgressCompleteWithRepo(index int, offset int, repoURL string) {\n\ts.resumeInfoMutex.Lock()\n\tdefer s.resumeInfoMutex.Unlock()\n\n\t// Add the repoURL to the resume info slice.\n\ts.resumeInfoSlice = append(s.resumeInfoSlice, repoURL)\n\tsort.Strings(s.resumeInfoSlice)\n\n\t// Make the resume info string from the slice.\n\tencodedResumeInfo := sources.EncodeResumeInfo(s.resumeInfoSlice)\n\n\ts.SetProgressComplete(index+offset, len(s.repos)+offset, fmt.Sprintf(\"Repo: %s\", repoURL), encodedResumeInfo)\n}\n\nfunc (s *Source) scanComments(ctx context.Context, repoPath string, repoInfo repoInfo, reporter sources.ChunkReporter) error {\n\turlString, urlParts, err := getRepoURLParts(repoPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar cutoffTime *time.Time\n\tif s.commentsTimeframeDays > 0 {\n\t\tdaysToFilter := int(s.commentsTimeframeDays)\n\t\tt := time.Now().AddDate(0, 0, -daysToFilter)\n\t\tcutoffTime = &t\n\t}\n\n\tif s.includeGistComments && isGistUrl(urlParts) && !s.ignoreGists {\n\t\treturn s.processGistComments(ctx, urlString, urlParts, repoInfo, reporter, cutoffTime)\n\t} else if s.includeIssueComments || s.includePRComments {\n\t\t// if we need to use graphql api for repo issues, prs and comments\n\t\tif feature.UseGithubGraphQLAPI.Load() {\n\t\t\treturn s.processRepoIssueandPRsWithCommentsGraphql(ctx, repoInfo, reporter, cutoffTime)\n\t\t}\n\n\t\treturn s.processIssueandPRsWithCommentsREST(ctx, repoInfo, reporter, cutoffTime)\n\t}\n\n\treturn nil\n}\n\n// trimURLAndSplit removes extraneous information from the |url| and splits it into segments.\n// This is typically 3 segments: host, owner, and name/ID; however, Gists have some edge cases.\n//\n// Examples:\n// - \"https://github.com/trufflesecurity/trufflehog\" => [\"github.com\", \"trufflesecurity\", \"trufflehog\"]\n// - \"https://gist.github.com/nat/5fdbb7f945d121f197fb074578e53948\" => [\"gist.github.com\", \"nat\", \"5fdbb7f945d121f197fb074578e53948\"]\n// - \"https://gist.github.com/ff0e5e8dc8ec22f7a25ddfc3492d3451.git\" => [\"gist.github.com\", \"ff0e5e8dc8ec22f7a25ddfc3492d3451\"]\n// - \"https://github.company.org/gist/nat/5fdbb7f945d121f197fb074578e53948.git\" => [\"github.company.org\", \"gist\", \"nat\", \"5fdbb7f945d121f197fb074578e53948\"]\nfunc getRepoURLParts(repoURLString string) (string, []string, error) {\n\t// Support ssh and https URLs.\n\trepoURL, err := git.GitURLParse(repoURLString)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\t// Remove the user information.\n\t// e.g., `git@github.com` -> `github.com`\n\tif repoURL.User != nil {\n\t\trepoURL.User = nil\n\t}\n\n\t// Use Host and Path directly instead of reconstructing via String().\n\t// This preserves special characters like trailing hyphens in repo names\n\t// that might be lost during URL reconstruction.\n\t// See: https://github.com/trufflesecurity/trufflehog/issues/4679\n\thost := repoURL.Host\n\tpath := strings.TrimPrefix(repoURL.Path, \"/\")\n\tpath = strings.TrimSuffix(path, \".git\")\n\n\turlString := repoURL.String()\n\turlParts := append([]string{host}, strings.Split(path, \"/\")...)\n\n\t// Validate\n\tswitch len(urlParts) {\n\tcase 2:\n\t\t// gist.github.com/<gist_id>\n\t\tif !strings.EqualFold(urlParts[0], \"gist.github.com\") {\n\t\t\terr = fmt.Errorf(\"failed to parse repository or gist URL (%s): 2 path segments are only expected if the host is 'gist.github.com' ('gist.github.com', '<gist_id>')\", urlString)\n\t\t}\n\tcase 3:\n\t\t// github.com/<user>/repo>\n\t\t// gist.github.com/<user>/<gist_id>\n\t\t// github.company.org/<user>/repo>\n\t\t// github.company.org/gist/<gist_id>\n\tcase 4:\n\t\t// github.company.org/gist/<user/<id>\n\t\tif !strings.EqualFold(urlParts[1], \"gist\") || (strings.EqualFold(urlParts[0], \"github.com\") && strings.EqualFold(urlParts[1], \"gist\")) {\n\t\t\terr = fmt.Errorf(\"failed to parse repository or gist URL (%s): 4 path segments are only expected if the host isn't 'github.com' and the path starts with 'gist' ('github.example.com', 'gist', '<owner>', '<gist_id>')\", urlString)\n\t\t}\n\tdefault:\n\t\terr = fmt.Errorf(\"invalid repository or gist URL (%s): length of URL segments should be between 2 and 4, not %d (%v)\", urlString, len(urlParts), urlParts)\n\t}\n\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\treturn urlString, urlParts, nil\n}\n\nconst initialPage = 1 // page to start listing from\n\nfunc (s *Source) processGistComments(ctx context.Context, gistURL string, urlParts []string, repoInfo repoInfo, reporter sources.ChunkReporter, cutoffTime *time.Time) error {\n\tctx.Logger().V(2).Info(\"Scanning GitHub Gist comments\")\n\n\t// GitHub Gist URL.\n\tgistID := extractGistID(urlParts)\n\n\toptions := &github.ListOptions{\n\t\tPerPage: defaultPagination,\n\t\tPage:    initialPage,\n\t}\n\tfor {\n\t\tcomments, _, err := s.connector.APIClient().Gists.ListComments(ctx, gistID, options)\n\t\tif s.handleRateLimitWithChunkReporter(ctx, reporter, err) {\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err = s.chunkGistComments(ctx, gistURL, repoInfo, comments, reporter, cutoffTime); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\toptions.Page++\n\t\tif len(comments) < options.PerPage {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc extractGistID(urlParts []string) string {\n\treturn urlParts[len(urlParts)-1]\n}\n\n// isGistUrl returns true if the URL path is of a gist\nfunc isGistUrl(urlParts []string) bool {\n\tif len(urlParts) == 0 {\n\t\treturn false\n\t}\n\n\t// standard github gists: gist.github.com/user/abc123\n\tif strings.EqualFold(urlParts[0], \"gist.github.com\") {\n\t\treturn true\n\t}\n\n\t// github enterprise: any 3 or 4 parts url with 'gist'\n\tif (len(urlParts) == 3 || len(urlParts) == 4) && slices.Contains(urlParts, \"gist\") {\n\t\t// enterprise.company.com/gist/gist-id\n\t\t// gist.company.com/gist/gist-id\n\t\t// gist.company.com/path/gist/gist-id\n\t\treturn true\n\t}\n\n\treturn false\n}\n\nfunc (s *Source) chunkGistComments(ctx context.Context, gistURL string, gistInfo repoInfo, comments []*github.GistComment, reporter sources.ChunkReporter, cutoffTime *time.Time) error {\n\tfor _, comment := range comments {\n\t\t// Stop processing comments as soon as one created before the cutoff time is detected, as these are sorted\n\t\tif cutoffTime != nil && comment.GetCreatedAt().Before(*cutoffTime) {\n\t\t\tbreak\n\t\t}\n\n\t\t// Create chunk and send it to the channel.\n\t\tchunk := sources.Chunk{\n\t\t\tSourceName: s.name,\n\t\t\tSourceID:   s.SourceID(),\n\t\t\tSourceType: s.Type(),\n\t\t\tJobID:      s.JobID(),\n\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\tLink:       sanitizer.UTF8(comment.GetURL()),\n\t\t\t\t\t\tUsername:   sanitizer.UTF8(comment.GetUser().GetLogin()),\n\t\t\t\t\t\tEmail:      sanitizer.UTF8(comment.GetUser().GetEmail()),\n\t\t\t\t\t\tRepository: sanitizer.UTF8(gistURL),\n\t\t\t\t\t\tTimestamp:  sanitizer.UTF8(comment.GetCreatedAt().String()),\n\t\t\t\t\t\tVisibility: gistInfo.visibility,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tData:         []byte(sanitizer.UTF8(comment.GetBody())),\n\t\t\tSourceVerify: s.verify,\n\t\t}\n\n\t\tif err := reporter.ChunkOk(ctx, chunk); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// Note: these can't be consts because the address is needed when using with the GitHub library.\nvar (\n\t// sortType defines the criteria for sorting comments.\n\t// By setting this to \"updated\" we can use this to reliably manage the comment timeframe filtering below\n\tsortType = \"updated\"\n\t// directionType defines the direction of sorting.\n\t// \"desc\" means comments will be sorted in descending order, showing the latest comments first, which is critical for managing the comment timeframe filtering\n\tdirectionType = \"desc\"\n\t// allComments is a placeholder for specifying the comment ID to start listing from.\n\t// A value of 0 means that all comments will be listed.\n\tallComments = 0\n\t// state of \"all\" for the ListByRepo captures both open and closed issues.\n\tstate = \"all\"\n)\n\nfunc (s *Source) processIssueandPRsWithCommentsREST(\n\tctx context.Context, repoInfo repoInfo,\n\treporter sources.ChunkReporter, cutoffTime *time.Time,\n) error {\n\tif s.includeIssueComments {\n\t\tctx.Logger().V(2).Info(\"Scanning issues\")\n\t\tif err := s.processIssues(ctx, repoInfo, reporter); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := s.processIssueComments(ctx, repoInfo, reporter, cutoffTime); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif s.includePRComments {\n\t\tctx.Logger().V(2).Info(\"Scanning pull requests\")\n\t\tif err := s.processPRs(ctx, repoInfo, reporter); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := s.processPRComments(ctx, repoInfo, reporter, cutoffTime); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *Source) processRepoIssueandPRsWithCommentsGraphql(\n\tctx context.Context, repoInfo repoInfo,\n\treporter sources.ChunkReporter, cutoffTime *time.Time,\n) error {\n\tif s.includeIssueComments {\n\t\tctx.Logger().V(2).Info(\"Scanning issues\")\n\t\tif err := s.processIssuesWithComments(ctx, repoInfo, reporter, cutoffTime); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif s.includePRComments {\n\t\tctx.Logger().V(2).Info(\"Scanning pull requests\")\n\t\tif err := s.processPRWithComments(ctx, repoInfo, reporter, cutoffTime); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err := s.processReviewThreads(ctx, repoInfo, reporter, cutoffTime); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *Source) processIssues(ctx context.Context, repoInfo repoInfo, reporter sources.ChunkReporter) error {\n\tbodyTextsOpts := &github.IssueListByRepoOptions{\n\t\tSort:      sortType,\n\t\tDirection: directionType,\n\t\tState:     state,\n\t\tListOptions: github.ListOptions{\n\t\t\tPerPage: defaultPagination,\n\t\t\tPage:    initialPage,\n\t\t},\n\t}\n\n\tfor {\n\t\tissues, _, err := s.connector.APIClient().Issues.ListByRepo(ctx, repoInfo.owner, repoInfo.name, bodyTextsOpts)\n\t\tif s.handleRateLimitWithChunkReporter(ctx, reporter, err) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err = s.chunkIssues(ctx, repoInfo, issues, reporter); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tbodyTextsOpts.ListOptions.Page++\n\n\t\tif len(issues) < defaultPagination {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Source) chunkIssues(ctx context.Context, repoInfo repoInfo, issues []*github.Issue, reporter sources.ChunkReporter) error {\n\tfor _, issue := range issues {\n\t\t// Skip pull requests since covered by processPRs.\n\t\tif issue.IsPullRequest() {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Create chunk and send it to the channel.\n\t\tchunk := sources.Chunk{\n\t\t\tSourceName: s.name,\n\t\t\tSourceID:   s.SourceID(),\n\t\t\tJobID:      s.JobID(),\n\t\t\tSourceType: s.Type(),\n\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\tLink:       sanitizer.UTF8(issue.GetHTMLURL()),\n\t\t\t\t\t\tUsername:   sanitizer.UTF8(issue.GetUser().GetLogin()),\n\t\t\t\t\t\tEmail:      sanitizer.UTF8(issue.GetUser().GetEmail()),\n\t\t\t\t\t\tRepository: sanitizer.UTF8(repoInfo.fullName),\n\t\t\t\t\t\tTimestamp:  sanitizer.UTF8(issue.GetCreatedAt().String()),\n\t\t\t\t\t\tVisibility: repoInfo.visibility,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tData:         []byte(sanitizer.UTF8(issue.GetTitle() + \"\\n\" + issue.GetBody())),\n\t\t\tSourceVerify: s.verify,\n\t\t}\n\n\t\tif err := reporter.ChunkOk(ctx, chunk); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Source) processIssueComments(ctx context.Context, repoInfo repoInfo, reporter sources.ChunkReporter, cutoffTime *time.Time) error {\n\tissueOpts := &github.IssueListCommentsOptions{\n\t\tSort:      &sortType,\n\t\tDirection: &directionType,\n\t\tListOptions: github.ListOptions{\n\t\t\tPerPage: defaultPagination,\n\t\t\tPage:    initialPage,\n\t\t},\n\t}\n\n\tfor {\n\t\tissueComments, _, err := s.connector.APIClient().Issues.ListComments(ctx, repoInfo.owner, repoInfo.name, allComments, issueOpts)\n\t\tif s.handleRateLimitWithChunkReporter(ctx, reporter, err) {\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err = s.chunkIssueComments(ctx, repoInfo, issueComments, reporter, cutoffTime); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tissueOpts.ListOptions.Page++\n\t\tif len(issueComments) < defaultPagination {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Source) chunkIssueComments(ctx context.Context, repoInfo repoInfo, comments []*github.IssueComment, reporter sources.ChunkReporter, cutoffTime *time.Time) error {\n\tfor _, comment := range comments {\n\t\t// Stop processing comments as soon as one created before the cutoff time is detected, as these are sorted\n\t\tif cutoffTime != nil && comment.GetUpdatedAt().Before(*cutoffTime) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Create chunk and send it to the channel.\n\t\tchunk := sources.Chunk{\n\t\t\tSourceName: s.name,\n\t\t\tSourceID:   s.SourceID(),\n\t\t\tJobID:      s.JobID(),\n\t\t\tSourceType: s.Type(),\n\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\tLink:       sanitizer.UTF8(comment.GetHTMLURL()),\n\t\t\t\t\t\tUsername:   sanitizer.UTF8(comment.GetUser().GetLogin()),\n\t\t\t\t\t\tEmail:      sanitizer.UTF8(comment.GetUser().GetEmail()),\n\t\t\t\t\t\tRepository: sanitizer.UTF8(repoInfo.fullName),\n\t\t\t\t\t\tTimestamp:  sanitizer.UTF8(comment.GetCreatedAt().String()),\n\t\t\t\t\t\tVisibility: repoInfo.visibility,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tData:         []byte(sanitizer.UTF8(comment.GetBody())),\n\t\t\tSourceVerify: s.verify,\n\t\t}\n\n\t\tif err := reporter.ChunkOk(ctx, chunk); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Source) processPRs(ctx context.Context, repoInfo repoInfo, reporter sources.ChunkReporter) error {\n\tprOpts := &github.PullRequestListOptions{\n\t\tSort:      sortType,\n\t\tDirection: directionType,\n\t\tState:     state,\n\t\tListOptions: github.ListOptions{\n\t\t\tPerPage: defaultPagination,\n\t\t\tPage:    initialPage,\n\t\t},\n\t}\n\n\tfor {\n\t\tprs, _, err := s.connector.APIClient().PullRequests.List(ctx, repoInfo.owner, repoInfo.name, prOpts)\n\t\tif s.handleRateLimitWithChunkReporter(ctx, reporter, err) {\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err = s.chunkPullRequests(ctx, repoInfo, prs, reporter); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tprOpts.ListOptions.Page++\n\n\t\tif len(prs) < defaultPagination {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Source) processPRComments(ctx context.Context, repoInfo repoInfo, reporter sources.ChunkReporter, cutoffTime *time.Time) error {\n\tprOpts := &github.PullRequestListCommentsOptions{\n\t\tSort:      sortType,\n\t\tDirection: directionType,\n\t\tListOptions: github.ListOptions{\n\t\t\tPerPage: defaultPagination,\n\t\t\tPage:    initialPage,\n\t\t},\n\t}\n\n\tfor {\n\t\tprComments, _, err := s.connector.APIClient().PullRequests.ListComments(ctx, repoInfo.owner, repoInfo.name, allComments, prOpts)\n\t\tif s.handleRateLimitWithChunkReporter(ctx, reporter, err) {\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif err = s.chunkPullRequestComments(ctx, repoInfo, prComments, reporter, cutoffTime); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tprOpts.ListOptions.Page++\n\n\t\tif len(prComments) < defaultPagination {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Source) chunkPullRequests(ctx context.Context, repoInfo repoInfo, prs []*github.PullRequest, reporter sources.ChunkReporter) error {\n\tfor _, pr := range prs {\n\t\t// Create chunk and send it to the channel.\n\t\tchunk := sources.Chunk{\n\t\t\tSourceName: s.name,\n\t\t\tSourceID:   s.SourceID(),\n\t\t\tSourceType: s.Type(),\n\t\t\tJobID:      s.JobID(),\n\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\tLink:       sanitizer.UTF8(pr.GetHTMLURL()),\n\t\t\t\t\t\tUsername:   sanitizer.UTF8(pr.GetUser().GetLogin()),\n\t\t\t\t\t\tEmail:      sanitizer.UTF8(pr.GetUser().GetEmail()),\n\t\t\t\t\t\tRepository: sanitizer.UTF8(repoInfo.fullName),\n\t\t\t\t\t\tTimestamp:  sanitizer.UTF8(pr.GetCreatedAt().String()),\n\t\t\t\t\t\tVisibility: repoInfo.visibility,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tData:         []byte(sanitizer.UTF8(pr.GetTitle() + \"\\n\" + pr.GetBody())),\n\t\t\tSourceVerify: s.verify,\n\t\t}\n\n\t\tif err := reporter.ChunkOk(ctx, chunk); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Source) chunkPullRequestComments(ctx context.Context, repoInfo repoInfo, comments []*github.PullRequestComment, reporter sources.ChunkReporter, cutoffTime *time.Time) error {\n\tfor _, comment := range comments {\n\t\t// Stop processing comments as soon as one created before the cutoff time is detected, as these are sorted\n\t\tif cutoffTime != nil && comment.GetUpdatedAt().Before(*cutoffTime) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Create chunk and send it to the channel.\n\t\tchunk := sources.Chunk{\n\t\t\tSourceName: s.name,\n\t\t\tSourceID:   s.SourceID(),\n\t\t\tJobID:      s.JobID(),\n\t\t\tSourceType: s.Type(),\n\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\tLink:       sanitizer.UTF8(comment.GetHTMLURL()),\n\t\t\t\t\t\tUsername:   sanitizer.UTF8(comment.GetUser().GetLogin()),\n\t\t\t\t\t\tEmail:      sanitizer.UTF8(comment.GetUser().GetEmail()),\n\t\t\t\t\t\tRepository: sanitizer.UTF8(repoInfo.fullName),\n\t\t\t\t\t\tTimestamp:  sanitizer.UTF8(comment.GetCreatedAt().String()),\n\t\t\t\t\t\tVisibility: repoInfo.visibility,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tData:         []byte(sanitizer.UTF8(comment.GetBody())),\n\t\t\tSourceVerify: s.verify,\n\t\t}\n\n\t\tif err := reporter.ChunkOk(ctx, chunk); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Source) scanTargets(ctx context.Context, targets []sources.ChunkingTarget, reporter sources.ChunkReporter) []error {\n\tvar errs []error\n\tfor _, tgt := range targets {\n\t\tif err := s.scanTarget(ctx, tgt, reporter); err != nil {\n\t\t\tctx.Logger().Error(err, \"error scanning target\")\n\t\t\terrs = append(errs, &sources.TargetedScanError{Err: err, SecretID: tgt.SecretID})\n\t\t}\n\t}\n\n\treturn errs\n}\n\nfunc (s *Source) scanTarget(ctx context.Context, target sources.ChunkingTarget, reporter sources.ChunkReporter) error {\n\tmetaType, ok := target.QueryCriteria.GetData().(*source_metadatapb.MetaData_Github)\n\tif !ok {\n\t\treturn fmt.Errorf(\"unable to cast metadata type for targeted scan\")\n\t}\n\tmeta := metaType.Github\n\n\tchunkSkel := sources.Chunk{\n\t\tSourceType: s.Type(),\n\t\tSourceName: s.name,\n\t\tSourceID:   s.SourceID(),\n\t\tJobID:      s.JobID(),\n\t\tSecretID:   target.SecretID,\n\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\tData: &source_metadatapb.MetaData_Github{Github: meta},\n\t\t},\n\t\tSourceVerify: s.verify,\n\t}\n\n\tu, err := url.Parse(meta.GetLink())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to parse GitHub URL: %w\", err)\n\t}\n\n\t// The owner is the second segment and the repo is the third segment of the path.\n\t// Ex: https://github.com/owner/repo/.....\n\tsegments := strings.Split(u.Path, \"/\")\n\tif len(segments) < 3 {\n\t\treturn fmt.Errorf(\"invalid GitHub URL\")\n\t}\n\n\tif meta.GetFile() == \"\" && meta.GetCommit() != \"\" {\n\t\tctx := context.WithValues(ctx, \"commit_hash\", meta.GetCommit())\n\t\tctx.Logger().V(2).Info(\"secret metadata has no file; scanning commit metadata instead\")\n\n\t\treturn s.scanCommitMetadata(ctx, segments[1], segments[2], meta, &chunkSkel, reporter)\n\t}\n\n\t// else try downloading the file content to scan\n\treadCloser, resp, err := s.connector.APIClient().Repositories.DownloadContents(\n\t\tctx,\n\t\tsegments[1],\n\t\tsegments[2],\n\t\tmeta.GetFile(),\n\t\t&github.RepositoryContentGetOptions{Ref: meta.GetCommit()})\n\t// As of this writing, if the returned readCloser is not nil, it's just the Body of the returned github.Response, so\n\t// there's no need to independently close it.\n\tif resp != nil && resp.Body != nil {\n\t\tdefer resp.Body.Close()\n\t}\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not download file for scan: %w\", err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"unexpected HTTP response status when trying to download file for scan: %v\", resp.Status)\n\t}\n\n\tfileCtx := context.WithValues(ctx, \"path\", meta.GetFile())\n\treturn handlers.HandleFile(fileCtx, readCloser, &chunkSkel, reporter)\n}\n\nfunc (s *Source) scanCommitMetadata(ctx context.Context, owner, repo string, meta *source_metadatapb.Github, chunkSkel *sources.Chunk, reporter sources.ChunkReporter) error {\n\t// fetch the commit\n\tcommit, resp, err := s.connector.APIClient().Repositories.GetCommit(ctx, owner, repo, meta.GetCommit(), nil)\n\tif resp != nil && resp.Body != nil {\n\t\tdefer resp.Body.Close()\n\t}\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not fetch commit for metadata scan: %w\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"unexpected HTTP response status when fetching commit: %v\", resp.Status)\n\t}\n\n\t// create the string with the exact format we use in Git.ScanCommits()\n\t// author email + \"\\n\" + committer + \"\\n\" + commit message\n\tvar sb strings.Builder\n\n\tsb.WriteString(commit.GetCommit().Author.GetEmail())\n\tsb.WriteString(\"\\n\")\n\tsb.WriteString(commit.GetCommitter().GetEmail())\n\tsb.WriteString(\"\\n\")\n\tsb.WriteString(commit.GetCommit().GetMessage())\n\n\tcontent := strings.NewReader(sb.String())\n\treturn handlers.HandleFile(ctx, io.NopCloser(content), chunkSkel, reporter)\n}\n\nfunc (s *Source) ChunkUnit(ctx context.Context, unit sources.SourceUnit, reporter sources.ChunkReporter) error {\n\trepoURL, _ := unit.SourceUnitID()\n\tctx = context.WithValue(ctx, \"repo\", repoURL)\n\t// ChunkUnit is not guaranteed to be called from Enumerate, so we must\n\t// check and fetch the repoInfoCache for this repo.\n\trepoURL, err := s.ensureRepoInfoCache(ctx, repoURL, &chunkErrorReporter{reporter: reporter})\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn s.scanRepo(ctx, repoURL, reporter)\n}\n"
  },
  {
    "path": "pkg/sources/github/github_integration_test.go",
    "content": "//go:build integration\n// +build integration\n\npackage github\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/feature\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/credentialspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nfunc TestSource_Token(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*300)\n\tdefer cancel()\n\n\tsecret, err := common.GetTestSecret(ctx)\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\n\tgithubPrivateKeyB64New := secret.MustGetField(\"GITHUB_PRIVATE_KEY_NEW\")\n\tgithubPrivateKeyBytesNew, err := base64.StdEncoding.DecodeString(githubPrivateKeyB64New)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgithubPrivateKeyNew := string(githubPrivateKeyBytesNew)\n\tgithubInstallationIDNew := secret.MustGetField(\"GITHUB_INSTALLATION_ID_NEW\")\n\tgithubAppIDNew := secret.MustGetField(\"GITHUB_APP_ID_NEW\")\n\n\tsrc := &sourcespb.GitHub{\n\t\tEndpoint: \"https://api.github.com\",\n\t\tCredential: &sourcespb.GitHub_GithubApp{\n\t\t\tGithubApp: &credentialspb.GitHubApp{\n\t\t\t\tPrivateKey:     githubPrivateKeyNew,\n\t\t\t\tInstallationId: githubInstallationIDNew,\n\t\t\t\tAppId:          githubAppIDNew,\n\t\t\t},\n\t\t},\n\t}\n\tconn, err := anypb.New(src)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\ts := Source{\n\t\tconn:          src,\n\t\tmemberCache:   map[string]struct{}{},\n\t\trepoInfoCache: newRepoInfoCache(),\n\t}\n\ts.Init(ctx, \"github integration test source\", 0, 0, false, conn, 1)\n\ts.filteredRepoCache = s.newFilteredRepoCache(ctx, simple.NewCache[string](), nil, nil)\n\n\terr = s.enumerateWithApp(ctx, s.connector.(*appConnector).InstallationClient(), noopReporter())\n\tassert.NoError(t, err)\n\n\t_, _, err = s.cloneRepo(ctx, \"https://github.com/truffle-test-integration-org/another-test-repo.git\")\n\tassert.NoError(t, err)\n}\n\nfunc TestSource_ScanComments(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\n\tsecret, err := common.GetTestSecret(ctx)\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\n\t// For the personal access token test\n\tgithubToken := secret.MustGetField(\"GITHUB_TOKEN\")\n\n\tconst totalPRChunks = 3\n\tconst totalIssueChunks = 2\n\n\ttype init struct {\n\t\tname       string\n\t\tverify     bool\n\t\tconnection *sourcespb.GitHub\n\t}\n\ttests := []struct {\n\t\tname              string\n\t\tinit              init\n\t\twantChunk         *sources.Chunk\n\t\twantErr           bool\n\t\tminRepo           int\n\t\tminOrg            int\n\t\tnumExpectedChunks int\n\t}{\n\t\t{\n\t\t\tname: \"token authenticated, single repo, single issue comment\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{\n\t\t\t\t\tRepositories:               []string{\"https://github.com/truffle-test-integration-org/another-test-repo.git\"},\n\t\t\t\t\tIncludeIssueComments:       true,\n\t\t\t\t\tIncludePullRequestComments: false,\n\t\t\t\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\t\t\t\tToken: githubToken,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tnumExpectedChunks: totalIssueChunks,\n\t\t\twantChunk: &sources.Chunk{\n\t\t\t\tSourceType: sourcespb.SourceType_SOURCE_TYPE_GITHUB,\n\t\t\t\tSourceName: \"test source\",\n\t\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\t\tLink:      \"https://github.com/truffle-test-integration-org/another-test-repo/issues/1\",\n\t\t\t\t\t\t\tUsername:  \"truffle-sandbox\",\n\t\t\t\t\t\t\tTimestamp: \"2023-06-22 23:33:46 +0000 UTC\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSourceVerify: false,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"token authenticated, single repo, pull request comment\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{\n\t\t\t\t\tRepositories:               []string{\"https://github.com/truffle-test-integration-org/another-test-repo.git\"},\n\t\t\t\t\tIncludePullRequestComments: true,\n\t\t\t\t\tIncludeIssueComments:       false,\n\t\t\t\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\t\t\t\tToken: githubToken,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tnumExpectedChunks: totalPRChunks,\n\t\t\twantChunk: &sources.Chunk{\n\t\t\t\tSourceType: sourcespb.SourceType_SOURCE_TYPE_GITHUB,\n\t\t\t\tSourceName: \"test source\",\n\t\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\t\tLink:      \"https://github.com/truffle-test-integration-org/another-test-repo/pull/2#discussion_r1242763304\",\n\t\t\t\t\t\t\tUsername:  \"truffle-sandbox\",\n\t\t\t\t\t\t\tTimestamp: \"2023-06-26 21:00:11 +0000 UTC\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSourceVerify: false,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Source{}\n\n\t\t\tconn, err := anypb.New(tt.init.connection)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\terr = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 4)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Source.Init() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif tt.wantErr {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tchunksCh := make(chan *sources.Chunk, 1)\n\t\t\tgo func() {\n\t\t\t\t// Close the channel\n\t\t\t\tdefer close(chunksCh)\n\t\t\t\terr = s.Chunks(ctx, chunksCh)\n\t\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\t\tif ctx.Err() != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tt.Errorf(\"Source.Chunks() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\ti := 0\n\t\t\tfor gotChunk := range chunksCh {\n\t\t\t\t// Skip chunks that are not comments.\n\t\t\t\tif gotChunk.SourceMetadata.GetGithub().GetCommit() != \"\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\ti++\n\t\t\t\tgithubCommentCheckFunc(gotChunk, tt.wantChunk, i, t, tt.name)\n\t\t\t}\n\n\t\t\t// Confirm all comments were processed.\n\t\t\tif i != tt.numExpectedChunks {\n\t\t\t\tt.Errorf(\"did not complete all chunks, got %d, want %d\", i, tt.numExpectedChunks)\n\t\t\t}\n\n\t\t})\n\t}\n}\n\nfunc TestSource_ScanChunks(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*30)\n\tdefer cancel()\n\n\tsecret, err := common.GetTestSecret(ctx)\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\n\t// For the personal access token test.\n\tgithubToken := secret.MustGetField(\"GITHUB_TOKEN\")\n\n\ttype init struct {\n\t\tname       string\n\t\tverify     bool\n\t\tconnection *sourcespb.GitHub\n\t}\n\ttests := []struct {\n\t\tname       string\n\t\tinit       init\n\t\twantChunks int\n\t}{\n\t\t{\n\t\t\tname: \"token authenticated, 4 repos\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{\n\t\t\t\t\tRepositories: []string{\n\t\t\t\t\t\t\"https://github.com/truffle-test-integration-org/another-test-repo.git\",\n\t\t\t\t\t\t\"https://github.com/trufflesecurity/trufflehog.git\",\n\t\t\t\t\t\t\"https://github.com/Akash-goyal-github/Inventory-Management-System.git\",\n\t\t\t\t\t\t\"https://github.com/R1ck404/Crypto-Exchange-Example.git\",\n\t\t\t\t\t\t\"https://github.com/Stability-AI/generative-models.git\",\n\t\t\t\t\t\t\"https://github.com/bloomberg/blazingmq.git\",\n\t\t\t\t\t\t\"https://github.com/Kong/kong.git\",\n\t\t\t\t\t},\n\t\t\t\t\tCredential: &sourcespb.GitHub_Token{Token: githubToken},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunks: 20000,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Source{}\n\n\t\t\tconn, err := anypb.New(tt.init.connection)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\terr = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 8)\n\t\t\tassert.Nil(t, err)\n\n\t\t\tchunksCh := make(chan *sources.Chunk, 1)\n\t\t\tgo func() {\n\t\t\t\tdefer close(chunksCh)\n\t\t\t\terr = s.Chunks(ctx, chunksCh)\n\t\t\t\tassert.Nil(t, err)\n\t\t\t}()\n\n\t\t\ti := 0\n\t\t\tfor range chunksCh {\n\t\t\t\ti++\n\t\t\t}\n\t\t\tassert.GreaterOrEqual(t, i, tt.wantChunks)\n\t\t})\n\t}\n}\n\nfunc TestSource_Scan(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*300)\n\tdefer cancel()\n\n\tsecret, err := common.GetTestSecret(ctx)\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\n\t// For the personal access token test\n\tgithubToken := secret.MustGetField(\"GITHUB_TOKEN\")\n\n\t// For the  NEW github app test (+Member enum)\n\tgithubPrivateKeyB64New := secret.MustGetField(\"GITHUB_PRIVATE_KEY_NEW\")\n\tgithubPrivateKeyBytesNew, err := base64.StdEncoding.DecodeString(githubPrivateKeyB64New)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgithubPrivateKeyNew := string(githubPrivateKeyBytesNew)\n\tgithubInstallationIDNew := secret.MustGetField(\"GITHUB_INSTALLATION_ID_NEW\")\n\tgithubAppIDNew := secret.MustGetField(\"GITHUB_APP_ID_NEW\")\n\n\ttype init struct {\n\t\tname       string\n\t\tverify     bool\n\t\tconnection *sourcespb.GitHub\n\t}\n\ttests := []struct {\n\t\tname      string\n\t\tinit      init\n\t\twantChunk *sources.Chunk\n\t\twantErr   bool\n\t\tminRepo   int\n\t\tminOrg    int\n\t}{\n\t\t{\n\t\t\tname: \"token authenticated, single repo\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{\n\t\t\t\t\tRepositories: []string{\"https://github.com/truffle-test-integration-org/another-test-repo.git\"},\n\t\t\t\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\t\t\t\tToken: githubToken,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: &sources.Chunk{\n\t\t\t\tSourceType: sourcespb.SourceType_SOURCE_TYPE_GITHUB,\n\t\t\t\tSourceName: \"test source\",\n\t\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\t\tRepository: \"https://github.com/truffle-test-integration-org/another-test-repo.git\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSourceVerify: false,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"token authenticated, single repo, no .git\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{\n\t\t\t\t\tRepositories: []string{\"https://github.com/truffle-test-integration-org/another-test-repo\"},\n\t\t\t\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\t\t\t\tToken: githubToken,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: &sources.Chunk{\n\t\t\t\tSourceType: sourcespb.SourceType_SOURCE_TYPE_GITHUB,\n\t\t\t\tSourceName: \"test source\",\n\t\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\t\tRepository: \"https://github.com/truffle-test-integration-org/another-test-repo.git\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSourceVerify: false,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"token authenticated, single org\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{\n\t\t\t\t\tOrganizations: []string{\"truffle-test-integration-org\"},\n\t\t\t\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\t\t\t\tToken: githubToken,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: nil,\n\t\t\twantErr:   false,\n\t\t\tminRepo:   1,\n\t\t\tminOrg:    1,\n\t\t},\n\t\t{\n\t\t\tname: \"token authenticated, username in org\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{\n\t\t\t\t\tOrganizations: []string{\"truffle-sandbox\"},\n\t\t\t\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\t\t\t\tToken: githubToken,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: nil,\n\t\t\twantErr:   false,\n\t\t\tminRepo:   2,\n\t\t\tminOrg:    1,\n\t\t},\n\t\t{\n\t\t\tname: \"token authenticated, no org or user (enum)\",\n\t\t\t// This configuration currently will only find gists from the user. No repos or orgs will be scanned.\n\t\t\tinit: init{\n\t\t\t\tname: \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{\n\t\t\t\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\t\t\t\tToken: githubToken,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: nil,\n\t\t\twantErr:   false,\n\t\t\tminRepo:   2,\n\t\t\tminOrg:    0,\n\t\t},\n\t\t{\n\t\t\tname: \"unauthenticated, single org\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{\n\t\t\t\t\tOrganizations: []string{\"trufflesecurity\"},\n\t\t\t\t\tCredential:    &sourcespb.GitHub_Unauthenticated{},\n\t\t\t\t\tIncludeForks:  true,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: nil,\n\t\t\twantErr:   false,\n\t\t\tminRepo:   40,\n\t\t\tminOrg:    1,\n\t\t},\n\t\t{\n\t\t\tname: \"unauthenticated, single repo\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{\n\t\t\t\t\tRepositories: []string{\"https://github.com/trufflesecurity/driftwood.git\"},\n\t\t\t\t\tCredential:   &sourcespb.GitHub_Unauthenticated{},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: &sources.Chunk{\n\t\t\t\tSourceType: sourcespb.SourceType_SOURCE_TYPE_GITHUB,\n\t\t\t\tSourceName: \"test source\",\n\t\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\t\tRepository: \"https://github.com/trufflesecurity/driftwood.git\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSourceVerify: false,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"app authenticated, no repo or org\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{\n\t\t\t\t\tScanUsers: true,\n\t\t\t\t\tCredential: &sourcespb.GitHub_GithubApp{\n\t\t\t\t\t\tGithubApp: &credentialspb.GitHubApp{\n\t\t\t\t\t\t\tPrivateKey:     githubPrivateKeyNew,\n\t\t\t\t\t\t\tInstallationId: githubInstallationIDNew,\n\t\t\t\t\t\t\tAppId:          githubAppIDNew,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: nil,\n\t\t\twantErr:   false,\n\t\t\tminRepo:   32,\n\t\t\tminOrg:    0,\n\t\t},\n\t\t{\n\t\t\tname: \"app authenticated, single repo\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{\n\t\t\t\t\tRepositories: []string{\"https://github.com/truffle-test-integration-org/another-test-repo.git\"},\n\t\t\t\t\tCredential: &sourcespb.GitHub_GithubApp{\n\t\t\t\t\t\tGithubApp: &credentialspb.GitHubApp{\n\t\t\t\t\t\t\tPrivateKey:     githubPrivateKeyNew,\n\t\t\t\t\t\t\tInstallationId: githubInstallationIDNew,\n\t\t\t\t\t\t\tAppId:          githubAppIDNew,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: &sources.Chunk{\n\t\t\t\tSourceType: sourcespb.SourceType_SOURCE_TYPE_GITHUB,\n\t\t\t\tSourceName: \"test source\",\n\t\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\t\tRepository: \"https://github.com/truffle-test-integration-org/another-test-repo.git\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSourceVerify: false,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t\tminRepo: 1,\n\t\t\tminOrg:  0,\n\t\t},\n\t\t{\n\t\t\tname: \"app authenticated, single org\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{\n\t\t\t\t\tOrganizations: []string{\"truffle-test-integration-org\"},\n\t\t\t\t\tCredential: &sourcespb.GitHub_GithubApp{\n\t\t\t\t\t\tGithubApp: &credentialspb.GitHubApp{\n\t\t\t\t\t\t\tPrivateKey:     githubPrivateKeyNew,\n\t\t\t\t\t\t\tInstallationId: githubInstallationIDNew,\n\t\t\t\t\t\t\tAppId:          githubAppIDNew,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: nil,\n\t\t\twantErr:   false,\n\t\t\tminRepo:   1,\n\t\t\tminOrg:    0,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Source{}\n\n\t\t\tconn, err := anypb.New(tt.init.connection)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\terr = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 4)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Source.Init() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tchunksCh := make(chan *sources.Chunk, 5)\n\t\t\tgo func() {\n\t\t\t\terr = s.Chunks(ctx, chunksCh)\n\t\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\t\tif ctx.Err() != nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tt.Errorf(\"Source.Chunks() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}()\n\t\t\tif err = sources.HandleTestChannel(chunksCh, basicCheckFunc(tt.minOrg, tt.minRepo, tt.wantChunk, &s)); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSource_paginateGists(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*30)\n\tdefer cancel()\n\n\ttype init struct {\n\t\tname       string\n\t\tverify     bool\n\t\tconnection *sourcespb.GitHub\n\t}\n\ttests := []struct {\n\t\tname      string\n\t\tinit      init\n\t\twantChunk *sources.Chunk\n\t\twantErr   bool\n\t\tuser      string\n\t\tminRepos  int\n\t}{\n\t\t{\n\t\t\tname: \"get gist\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{\n\t\t\t\t\tCredential: &sourcespb.GitHub_Unauthenticated{},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: &sources.Chunk{\n\t\t\t\tSourceName: \"test source\",\n\t\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\t\tRepository: \"https://gist.github.com/fecf272c606ddbc5f8486f9c44821312.git\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSourceVerify: false,\n\t\t\t},\n\t\t\twantErr:  false,\n\t\t\tuser:     \"truffle-sandbox\",\n\t\t\tminRepos: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"get multiple pages of gists\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{\n\t\t\t\t\tCredential: &sourcespb.GitHub_Unauthenticated{},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: nil,\n\t\t\twantErr:   false,\n\t\t\tuser:      \"andrew\",\n\t\t\tminRepos:  101,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Source{}\n\n\t\t\tconn, err := anypb.New(tt.init.connection)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\terr = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 4)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Source.Init() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tchunksCh := make(chan *sources.Chunk, 5)\n\t\t\tgo func() {\n\t\t\t\tassert.NoError(t, s.addUserGistsToCache(ctx, tt.user, noopReporter()))\n\t\t\t\tchunksCh <- &sources.Chunk{}\n\t\t\t}()\n\t\t\tvar wantedRepo string\n\t\t\tif tt.wantChunk != nil {\n\t\t\t\twantedRepo = tt.wantChunk.SourceMetadata.GetGithub().Repository\n\t\t\t}\n\t\t\tif err = sources.HandleTestChannel(chunksCh, gistsCheckFunc(wantedRepo, tt.minRepos, &s)); err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc gistsCheckFunc(expected string, minRepos int, s *Source) sources.ChunkFunc {\n\treturn func(chunk *sources.Chunk) error {\n\t\tif minRepos != 0 && minRepos > s.filteredRepoCache.Count() {\n\t\t\treturn fmt.Errorf(\"didn't find enough repos. expected: %d, got :%d\", minRepos, len(s.repos))\n\t\t}\n\t\tif expected != \"\" {\n\t\t\tfor _, repo := range s.filteredRepoCache.Values() {\n\t\t\t\tif repo == expected {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"expected repo not included: %s\", expected)\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc basicCheckFunc(minOrg, minRepo int, wantChunk *sources.Chunk, s *Source) sources.ChunkFunc {\n\treturn func(chunk *sources.Chunk) error {\n\t\tif minOrg != 0 && minOrg > s.orgsCache.Count() {\n\t\t\treturn fmt.Errorf(\"incorrect number of orgs. expected at least: %d, got %d\", minOrg, s.orgsCache.Count())\n\t\t}\n\t\tif minRepo != 0 && minRepo > len(s.repos) {\n\t\t\treturn fmt.Errorf(\"incorrect number of repos. expected at least: %d, got %d\", minRepo, len(s.repos))\n\t\t}\n\t\tif wantChunk != nil {\n\t\t\tif diff := pretty.Compare(chunk.SourceMetadata.GetGithub().Repository, wantChunk.SourceMetadata.GetGithub().Repository); diff == \"\" {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn sources.MatchError\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc githubCommentCheckFunc(gotChunk, wantChunk *sources.Chunk, i int, t *testing.T, name string) {\n\tif gotChunk.SourceType != wantChunk.SourceType {\n\t\tt.Errorf(\"want SourceType %v, got %v\", wantChunk.SourceType, gotChunk.SourceType)\n\t}\n\n\tassert.NotEmpty(t, gotChunk.SourceMetadata.Data, \"SourceMetadata.Data should not be empty\")\n\n\t// First Chunk should be a Issue Comment, Second Chunk should be a PR Comment.\n\tif i == 1 && name == \"token authenticated, single repo, single issue comment\" &&\n\t\twantChunk.SourceMetadata.GetGithub().GetLink() != gotChunk.SourceMetadata.GetGithub().GetLink() {\n\t\tt.Errorf(\"want %+v \\n got %+v \\n\", wantChunk.SourceMetadata.GetGithub().GetLink(), gotChunk.SourceMetadata.GetGithub().GetLink())\n\t} else if i == 2 && name == \"token authenticated, single repo, single pr comment\" &&\n\t\twantChunk.SourceMetadata.GetGithub().GetLink() != gotChunk.SourceMetadata.GetGithub().GetLink() {\n\t\tt.Errorf(\"want %+v \\n got %+v \\n\", wantChunk.SourceMetadata.GetGithub().GetLink(), gotChunk.SourceMetadata.GetGithub().GetLink())\n\t}\n}\n\nfunc TestSource_Chunks_TargetedScan(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*3000)\n\tdefer cancel()\n\n\tsecret, err := common.GetTestSecret(ctx)\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\n\tgithubToken := secret.MustGetField(\"GITHUB_TOKEN\")\n\n\ttype init struct {\n\t\tname          string\n\t\tverify        bool\n\t\tconnection    *sourcespb.GitHub\n\t\tqueryCriteria *source_metadatapb.MetaData\n\t}\n\ttests := []struct {\n\t\tname       string\n\t\tinit       init\n\t\twantChunks int\n\t\twantErr    bool\n\t}{\n\t\t{\n\t\t\tname: \"targeted scan, one file in small commit\",\n\t\t\tinit: init{\n\t\t\t\tname:       \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{Credential: &sourcespb.GitHub_Token{Token: githubToken}},\n\t\t\t\tqueryCriteria: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\t\tRepository: \"test-secrets\",\n\t\t\t\t\t\t\tLink:       \"https://github.com/truffle-sandbox/test-secrets/blob/0416560b1330d8ac42045813251d85c688717eaf/new_key#L2\",\n\t\t\t\t\t\t\tCommit:     \"0416560b1330d8ac42045813251d85c688717eaf\",\n\t\t\t\t\t\t\tFile:       \"new_key\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunks: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"targeted scan, one file in med commit\",\n\t\t\tinit: init{\n\t\t\t\tname:       \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{Credential: &sourcespb.GitHub_Unauthenticated{}},\n\t\t\t\tqueryCriteria: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\t\tRepository: \"https://github.com/trufflesecurity/trufflehog.git\",\n\t\t\t\t\t\t\tLink:       \"https://github.com/trufflesecurity/trufflehog/blob/33eed42e17fda8b1a66feaeafcd57efccff26c11/pkg/sources/s3/s3_test.go#L78\",\n\t\t\t\t\t\t\tCommit:     \"33eed42e17fda8b1a66feaeafcd57efccff26c11\",\n\t\t\t\t\t\t\tFile:       \"pkg/sources/s3/s3_test.go\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunks: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"targeted scan, binary file\",\n\t\t\tinit: init{\n\t\t\t\tname:       \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{Credential: &sourcespb.GitHub_Token{Token: githubToken}},\n\t\t\t\tqueryCriteria: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\t\tRepository: \"https://github.com/truffle-sandbox/test-secrets.git\",\n\t\t\t\t\t\t\tLink:       \"https://github.com/truffle-sandbox/test-secrets/blob/70bef8590f87257c0992eecc7db529827a12b801/null_text_w_ptp.ipynb\",\n\t\t\t\t\t\t\tCommit:     \"70bef8590f87257c0992eecc7db529827a12b801\",\n\t\t\t\t\t\t\tFile:       \"null_text_w_ptp.ipynb\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunks: 607,\n\t\t},\n\t\t{\n\t\t\tname: \"targeted scan, commit metadata\",\n\t\t\tinit: init{\n\t\t\t\tname:       \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{Credential: &sourcespb.GitHub_Token{Token: githubToken}},\n\t\t\t\tqueryCriteria: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\t\tRepository: \"https://github.com/trufflesecurity/trufflehog.git\",\n\t\t\t\t\t\t\tLink:       \"https://github.com/trufflesecurity/trufflehog/commit/1c51106e35c3b3c327fe12e358177c03079bb771\",\n\t\t\t\t\t\t\tCommit:     \"1c51106e35c3b3c327fe12e358177c03079bb771\",\n\t\t\t\t\t\t\tFile:       \"\", // no file\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunks: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"no file in commit\",\n\t\t\tinit: init{\n\t\t\t\tname:       \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{Credential: &sourcespb.GitHub_Unauthenticated{}},\n\t\t\t\tqueryCriteria: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\t\tRepository: \"test_keys\",\n\t\t\t\t\t\t\tLink:       \"https://github.com/trufflesecurity/test_keys/blob/fbc14303ffbf8fb1c2c1914e8dda7d0121633aca/keys#L4\",\n\t\t\t\t\t\t\tCommit:     \"fbc14303ffbf8fb1c2c1914e8dda7d0121633aca\",\n\t\t\t\t\t\t\tFile:       \"not-the-file\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunks: 0,\n\t\t\twantErr:    true,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid query criteria, malformed link\",\n\t\t\tinit: init{\n\t\t\t\tname:       \"test source\",\n\t\t\t\tconnection: &sourcespb.GitHub{Credential: &sourcespb.GitHub_Unauthenticated{}},\n\t\t\t\tqueryCriteria: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\t\tRepository: \"test_keys\",\n\t\t\t\t\t\t\tLink:       \"malformed-link\",\n\t\t\t\t\t\t\tCommit:     \"fbc14303ffbf8fb1c2c1914e8dda7d0121633aca\",\n\t\t\t\t\t\t\tFile:       \"not-the-file\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunks: 0,\n\t\t\twantErr:    true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Source{}\n\n\t\t\tconn, err := anypb.New(tt.init.connection)\n\t\t\tassert.Nil(t, err)\n\n\t\t\terr = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 8)\n\t\t\tassert.Nil(t, err)\n\n\t\t\tchunksCh := make(chan *sources.Chunk, 1)\n\t\t\tgo func() {\n\t\t\t\tdefer close(chunksCh)\n\t\t\t\terr = s.Chunks(ctx, chunksCh, sources.ChunkingTarget{QueryCriteria: tt.init.queryCriteria})\n\t\t\t\tif tt.wantErr {\n\t\t\t\t\tassert.Error(t, err)\n\t\t\t\t} else {\n\t\t\t\t\tassert.NoError(t, err)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\ti := 0\n\t\t\tfor range chunksCh {\n\t\t\t\ti++\n\t\t\t}\n\t\t\tassert.Equal(t, tt.wantChunks, i)\n\t\t})\n\t}\n}\n\nfunc TestChunkUnit(t *testing.T) {\n\tctx := context.Background()\n\tconn, _ := anypb.New(&sourcespb.GitHub{\n\t\tRepositories: []string{\"https://github.com/trufflesecurity/driftwood.git\"},\n\t\tCredential:   &sourcespb.GitHub_Unauthenticated{},\n\t})\n\ts := Source{}\n\tif err := s.Init(ctx, \"github integration test source\", 0, 0, false, conn, 1); err != nil {\n\t\tt.Errorf(\"Init() failed: %v\", err)\n\t}\n\n\tunit := RepoUnit{Name: \"driftwood\", URL: \"https://github.com/trufflesecurity/driftwood.git\"}\n\treporter := &countChunkReporter{}\n\tif err := s.ChunkUnit(ctx, unit, reporter); err != nil {\n\t\tt.Errorf(\"ChunkUnit() failed: %v\", err)\n\t}\n\tassert.GreaterOrEqual(t, reporter.chunkCount, 65)\n\tassert.Equal(t, 0, reporter.errCount)\n}\n\nfunc TestSource_Validate(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*30)\n\tdefer cancel()\n\n\tsecret, err := common.GetTestSecret(ctx)\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\n\tgithubToken := secret.MustGetField(\"GITHUB_TOKEN\")\n\tgithubPrivateKeyB64New := secret.MustGetField(\"GITHUB_PRIVATE_KEY_NEW\")\n\tgithubInstallationIDNew := secret.MustGetField(\"GITHUB_INSTALLATION_ID_NEW\")\n\tgithubAppIDNew := secret.MustGetField(\"GITHUB_APP_ID_NEW\")\n\n\tgithubPrivateKeyBytesNew, err := base64.StdEncoding.DecodeString(githubPrivateKeyB64New)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgithubPrivateKeyNew := string(githubPrivateKeyBytesNew)\n\n\ttype args struct {\n\t\tctx context.Context\n\t}\n\ttests := []struct {\n\t\tname         string\n\t\targs         args\n\t\tsourceConfig *Source\n\t\twantErr      bool\n\t}{\n\t\t{\n\t\t\tname:         \"success - validate - unauthenticated\",\n\t\t\targs:         args{ctx: context.Background()},\n\t\t\tsourceConfig: &Source{conn: &sourcespb.GitHub{Credential: &sourcespb.GitHub_Unauthenticated{}}},\n\t\t\twantErr:      false,\n\t\t},\n\t\t{\n\t\t\tname:         \"success - validate - token authentication\",\n\t\t\targs:         args{ctx: context.Background()},\n\t\t\tsourceConfig: &Source{conn: &sourcespb.GitHub{Credential: &sourcespb.GitHub_Token{Token: githubToken}}},\n\t\t\twantErr:      false,\n\t\t},\n\t\t{\n\t\t\tname: \"sucess- validate - app token authentication\",\n\t\t\targs: args{ctx: context.Background()},\n\t\t\tsourceConfig: &Source{conn: &sourcespb.GitHub{Credential: &sourcespb.GitHub_GithubApp{\n\t\t\t\tGithubApp: &credentialspb.GitHubApp{\n\t\t\t\t\tPrivateKey:     githubPrivateKeyNew,\n\t\t\t\t\tInstallationId: githubInstallationIDNew,\n\t\t\t\t\tAppId:          githubAppIDNew,\n\t\t\t\t},\n\t\t\t}}},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:         \"fail - validate - token authentication\",\n\t\t\targs:         args{ctx: context.Background()},\n\t\t\tsourceConfig: &Source{conn: &sourcespb.GitHub{Credential: &sourcespb.GitHub_Token{Token: githubToken + \"fake\"}}},\n\t\t\twantErr:      true,\n\t\t},\n\t\t{\n\t\t\tname: \"fail- validate - app token authentication\",\n\t\t\targs: args{ctx: context.Background()},\n\t\t\tsourceConfig: &Source{conn: &sourcespb.GitHub{Credential: &sourcespb.GitHub_GithubApp{\n\t\t\t\tGithubApp: &credentialspb.GitHubApp{\n\t\t\t\t\tPrivateKey:     githubPrivateKeyNew + \"fake\",\n\t\t\t\t\tInstallationId: githubInstallationIDNew + \"0\",\n\t\t\t\t\tAppId:          githubAppIDNew,\n\t\t\t\t},\n\t\t\t}}},\n\t\t\twantErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tconnector, err := newConnector(ctx, tt.sourceConfig)\n\t\t\trequire.NoError(t, err)\n\t\t\ttt.sourceConfig.connector = connector\n\n\t\t\tif err := tt.sourceConfig.Validate(tt.args.ctx); err != nil && !tt.wantErr {\n\t\t\t\tt.Errorf(\"Source.Validate() = %v, wantErr %t\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSource_ScanCommentsWithGraphql(t *testing.T) {\n\tfeature.UseGithubGraphQLAPI.Store(true)\n\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\n\tsource := &sourcespb.GitHub{\n\t\tRepositories:               []string{\"https://github.com/trufflesecurity/driftwood.git\"},\n\t\tIncludeIssueComments:       true,\n\t\tIncludePullRequestComments: true,\n\t\tCredential:                 &sourcespb.GitHub_Unauthenticated{},\n\t}\n\n\twantChunk := sources.Chunk{\n\t\tSourceType: sourcespb.SourceType_SOURCE_TYPE_GITHUB,\n\t\tSourceName: \"test source\",\n\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\tLink:      \"https://github.com/trufflesecurity/driftwood.git/issues/1\",\n\t\t\t\t\tUsername:  \"truffle-sandbox\",\n\t\t\t\t\tTimestamp: \"2023-06-22 23:33:46 +0000 UTC\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tSourceVerify: false,\n\t}\n\n\ts := Source{}\n\n\tconn, err := anypb.New(source)\n\tassert.NoError(t, err)\n\n\terr = s.Init(ctx, \"test-source\", 0, 0, false, conn, 4)\n\tassert.NoError(t, err)\n\n\tchunksCh := make(chan *sources.Chunk, 1)\n\tgo func() {\n\t\t// Close the channel\n\t\tdefer close(chunksCh)\n\t\terr = s.Chunks(ctx, chunksCh)\n\t\tassert.NoError(t, err)\n\t}()\n\n\ti := 0\n\tfor gotChunk := range chunksCh {\n\t\t// Skip chunks that are not comments.\n\t\tif gotChunk.SourceMetadata.GetGithub().GetCommit() != \"\" {\n\t\t\tcontinue\n\t\t}\n\t\ti++\n\t\tgithubCommentCheckFunc(gotChunk, &wantChunk, i, t, \"test-source\")\n\t}\n\n\t// Confirm all comments were processed.\n\tassert.Equal(t, i, 5)\n}\n\ntype countChunkReporter struct {\n\tchunkCount int\n\terrCount   int\n}\n\nfunc (m *countChunkReporter) ChunkOk(ctx context.Context, chunk sources.Chunk) error {\n\tm.chunkCount++\n\treturn nil\n}\n\nfunc (m *countChunkReporter) ChunkErr(ctx context.Context, err error) error {\n\tm.errCount++\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/sources/github/github_test.go",
    "content": "package github\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-github/v67/github\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"golang.org/x/sync/errgroup\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\t\"gopkg.in/h2non/gock.v1\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/credentialspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\nfunc createPrivateKey() string {\n\tkey, err := rsa.GenerateKey(rand.Reader, 2048)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tdata := x509.MarshalPKCS1PrivateKey(key)\n\tvar pemKey bytes.Buffer\n\tif err := pem.Encode(&pemKey, &pem.Block{\n\t\tType:  \"RSA PRIVATE KEY\",\n\t\tBytes: data,\n\t}); err != nil {\n\t\tpanic(err)\n\t}\n\treturn pemKey.String()\n}\n\nfunc createTestSource(src *sourcespb.GitHub) (*Source, *anypb.Any) {\n\ts := &Source{}\n\tconn, err := anypb.New(src)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn s, conn\n}\n\nfunc initTestSource(src *sourcespb.GitHub) *Source {\n\ts, conn := createTestSource(src)\n\tif err := s.Init(context.Background(), \"test - github\", 0, 1337, false, conn, 1); err != nil {\n\t\tpanic(err)\n\t}\n\tgock.InterceptClient(s.connector.APIClient().Client())\n\tif appConnector, ok := s.connector.(*appConnector); ok {\n\t\tgock.InterceptClient(appConnector.InstallationClient().Client())\n\t}\n\treturn s\n}\n\nfunc TestInit(t *testing.T) {\n\tsource, conn := createTestSource(&sourcespb.GitHub{\n\t\tRepositories: []string{\"https://github.com/dustin-decker/secretsandstuff.git\"},\n\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t})\n\n\terr := source.Init(context.Background(), \"test - github\", 0, 1337, false, conn, 1)\n\tassert.Nil(t, err)\n\n\t// TODO: test error case\n}\n\nfunc TestAddReposByOrg(t *testing.T) {\n\tdefer gock.Off()\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/orgs/super-secret-org/repos\").\n\t\tReply(200).\n\t\tJSON([]map[string]string{\n\t\t\t{\"clone_url\": \"https://github.com/super-secret-repo.git\", \"full_name\": \"super-secret-repo\"},\n\t\t\t{\"clone_url\": \"https://github.com/super-secret-repo2.git\", \"full_name\": \"secret/super-secret-repo2\"},\n\t\t})\n\n\ts := initTestSource(&sourcespb.GitHub{\n\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tRepositories:          nil,\n\t\tIgnoreRepos:           []string{\"secret/super-*-repo2\"},\n\t\tCommentsTimeframeDays: 10,\n\t})\n\terr := s.getReposByOrg(context.Background(), \"super-secret-org\", noopReporter())\n\tassert.Nil(t, err)\n\tassert.Equal(t, 1, s.filteredRepoCache.Count())\n\tok := s.filteredRepoCache.Exists(\"super-secret-repo\")\n\tassert.True(t, ok)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestAddReposByOrg_Repositories(t *testing.T) {\n\tdefer gock.Off()\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/orgs/super-secret-org/repos\").\n\t\tReply(200).\n\t\tJSON(`[\n\t\t\t{\"full_name\": \"super-secret-org/super-secret-repo\", \"clone_url\": \"https://github.com/super-secret-org/super-secret-repo.git\", \"size\": 1},\n\t\t\t{\"full_name\": \"super-secret-org/super-secret-repo2\", \"clone_url\": \"https://github.com/super-secret-org/super-secret-repo2.git\", \"size\": 1},\n\t\t\t{\"full_name\": \"super-secret-org/not-super-secret-repo\", \"clone_url\": \"https://github.com/super-secret-org/not-super-secret-repo.git\", \"size\": 1}\n\t\t]`)\n\n\ts := initTestSource(&sourcespb.GitHub{\n\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tRepositories:  []string{\"super-secret-org/super-secret-repo\", \"super-secret-org/super-secret-repo2\"},\n\t\tOrganizations: []string{\"super-secret-org\"},\n\t})\n\terr := s.getReposByOrg(context.Background(), \"super-secret-org\", noopReporter())\n\tassert.Nil(t, err)\n\tassert.Equal(t, 2, s.filteredRepoCache.Count())\n\tok := s.filteredRepoCache.Exists(\"super-secret-org/super-secret-repo\")\n\tassert.True(t, ok)\n\tok = s.filteredRepoCache.Exists(\"super-secret-org/super-secret-repo2\")\n\tassert.True(t, ok)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestAddReposByOrg_IncludeRepos(t *testing.T) {\n\tdefer gock.Off()\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/orgs/super-secret-org/repos\").\n\t\tReply(200).\n\t\tJSON(`[\n\t\t\t{\"full_name\": \"super-secret-org/super-secret-repo\", \"clone_url\": \"https://github.com/super-secret-org/super-secret-repo.git\", \"size\": 1},\n\t\t\t{\"full_name\": \"super-secret-org/super-secret-repo2\", \"clone_url\": \"https://github.com/super-secret-org/super-secret-repo2.git\", \"size\": 1},\n\t\t\t{\"full_name\": \"super-secret-org/not-super-secret-repo\", \"clone_url\": \"https://github.com/super-secret-org/not-super-secret-repo.git\", \"size\": 1}\n\t\t]`)\n\n\ts := initTestSource(&sourcespb.GitHub{\n\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tIncludeRepos:  []string{\"super-secret-org/super*\"},\n\t\tOrganizations: []string{\"super-secret-org\"},\n\t})\n\terr := s.getReposByOrg(context.Background(), \"super-secret-org\", noopReporter())\n\tassert.Nil(t, err)\n\tassert.Equal(t, 2, s.filteredRepoCache.Count())\n\tok := s.filteredRepoCache.Exists(\"super-secret-org/super-secret-repo\")\n\tassert.True(t, ok)\n\tok = s.filteredRepoCache.Exists(\"super-secret-org/super-secret-repo2\")\n\tassert.True(t, ok)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestAddReposByUser(t *testing.T) {\n\tdefer gock.Off()\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/users/super-secret-user/repos\").\n\t\tReply(200).\n\t\tJSON([]map[string]string{\n\t\t\t{\"full_name\": \"super-secret-user/super-secret-repo\", \"clone_url\": \"https://github.com/super-secret-user/super-secret-repo.git\"},\n\t\t\t{\"full_name\": \"super-secret-user/super-secret-repo2\", \"clone_url\": \"https://github.com/super-secret-user/super-secret-repo2.git\"},\n\t\t})\n\n\ts := initTestSource(&sourcespb.GitHub{\n\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tIgnoreRepos: []string{\"super-secret-user/super-secret-repo2\"},\n\t})\n\terr := s.getReposByUser(context.Background(), \"super-secret-user\", false, noopReporter())\n\tassert.Nil(t, err)\n\tassert.Equal(t, 1, s.filteredRepoCache.Count())\n\tok := s.filteredRepoCache.Exists(\"super-secret-user/super-secret-repo\")\n\tassert.True(t, ok)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestAddGistsByUser(t *testing.T) {\n\tdefer gock.Off()\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/users/super-secret-user/gists\").\n\t\tReply(200).\n\t\tJSON([]map[string]string{{\"id\": \"aa5a315d61ae9438b18d\", \"git_pull_url\": \"https://gist.github.com/aa5a315d61ae9438b18d.git\"}})\n\n\ts := initTestSource(&sourcespb.GitHub{Credential: &sourcespb.GitHub_Unauthenticated{}})\n\terr := s.addUserGistsToCache(context.Background(), \"super-secret-user\", noopReporter())\n\tassert.Nil(t, err)\n\tassert.Equal(t, 1, s.filteredRepoCache.Count())\n\tok := s.filteredRepoCache.Exists(\"aa5a315d61ae9438b18d\")\n\tassert.True(t, ok)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestIgnoreGistsByUser(t *testing.T) {\n\tdefer gock.Off()\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/users/super-secret-user/gists\").\n\t\tReply(200).\n\t\tJSON([]map[string]string{{\"id\": \"aa5a315d61ae9438b18d\", \"git_pull_url\": \"https://gist.github.com/aa5a315d61ae9438b18d.git\"}})\n\n\ts := initTestSource(&sourcespb.GitHub{Credential: &sourcespb.GitHub_Unauthenticated{}})\n\ts.ignoreGists = true\n\terr := s.addUserGistsToCache(context.Background(), \"super-secret-user\", noopReporter())\n\tassert.Nil(t, err)\n\tassert.Equal(t, 0, s.filteredRepoCache.Count())\n\tok := s.filteredRepoCache.Exists(\"aa5a315d61ae9438b18d\")\n\tassert.False(t, ok)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.False(t, gock.IsDone())\n}\n\nfunc TestAddMembersByOrg(t *testing.T) {\n\tdefer gock.Off()\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/orgs/org1/members\").\n\t\tReply(200).\n\t\tJSON([]map[string]string{\n\t\t\t{\"login\": \"testman1\"},\n\t\t\t{\"login\": \"testman2\"},\n\t\t})\n\n\ts := initTestSource(&sourcespb.GitHub{Credential: &sourcespb.GitHub_Unauthenticated{}})\n\terr := s.addMembersByOrg(context.Background(), \"org1\", noopReporter())\n\tassert.Nil(t, err)\n\tassert.Equal(t, 2, len(s.memberCache))\n\t_, ok := s.memberCache[\"testman1\"]\n\tassert.True(t, ok)\n\t_, ok = s.memberCache[\"testman2\"]\n\tassert.True(t, ok)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestAddMembersByOrg_AuthFailure(t *testing.T) {\n\tdefer gock.Off()\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/orgs/org1/members\").\n\t\tReply(401).\n\t\tJSON([]map[string]string{{\n\t\t\t\"message\":           \"Bad credentials\",\n\t\t\t\"documentation_url\": \"https://docs.github.com/rest\",\n\t\t\t\"status\":            \"401\",\n\t\t}})\n\n\ts := initTestSource(&sourcespb.GitHub{Credential: &sourcespb.GitHub_Unauthenticated{}})\n\terr := s.addMembersByOrg(context.Background(), \"org1\", noopReporter())\n\tassert.True(t, strings.HasPrefix(err.Error(), \"could not list organization\"))\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestAddMembersByOrg_NoMembers(t *testing.T) {\n\tdefer gock.Off()\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/orgs/org1/members\").\n\t\tReply(200).\n\t\tJSON([]map[string]string{})\n\n\ts := initTestSource(&sourcespb.GitHub{Credential: &sourcespb.GitHub_Unauthenticated{}})\n\terr := s.addMembersByOrg(context.Background(), \"org1\", noopReporter())\n\n\tassert.Equal(t, fmt.Sprintf(\"organization (%q) had 0 members: account may not have access to list organization members\", \"org1\"), err.Error())\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestAddMembersByApp(t *testing.T) {\n\tdefer gock.Off()\n\n\tprivateKey := createPrivateKey()\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/app/installations\").\n\t\tReply(200).\n\t\tJSON([]map[string]any{\n\t\t\t{\"account\": map[string]string{\"login\": \"super-secret-org\", \"type\": \"Organization\"}},\n\t\t})\n\tgock.New(\"https://api.github.com\").\n\t\tPost(\"/app/installations/1337/access_tokens\").\n\t\tReply(200).\n\t\tJSON(map[string]string{\"token\": \"dontlook\"})\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/orgs/super-secret-org/members\").\n\t\tReply(200).\n\t\tJSON([]map[string]any{\n\t\t\t{\"login\": \"ssm1\"},\n\t\t\t{\"login\": \"ssm2\"},\n\t\t\t{\"login\": \"ssm3\"},\n\t\t})\n\n\ts := initTestSource(&sourcespb.GitHub{\n\t\tEndpoint: \"https://api.github.com\",\n\t\tCredential: &sourcespb.GitHub_GithubApp{\n\t\t\tGithubApp: &credentialspb.GitHubApp{\n\t\t\t\tPrivateKey:     privateKey,\n\t\t\t\tInstallationId: \"1337\",\n\t\t\t\tAppId:          \"4141\",\n\t\t\t},\n\t\t}})\n\terr := s.addMembersByApp(context.Background(), s.connector.(*appConnector).InstallationClient(), noopReporter())\n\tassert.Nil(t, err)\n\tassert.Equal(t, 3, len(s.memberCache))\n\t_, ok := s.memberCache[\"ssm1\"]\n\tassert.True(t, ok)\n\t_, ok = s.memberCache[\"ssm2\"]\n\tassert.True(t, ok)\n\t_, ok = s.memberCache[\"ssm3\"]\n\tassert.True(t, ok)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestAddReposByApp(t *testing.T) {\n\tdefer gock.Off()\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/installation/repositories\").\n\t\tReply(200).\n\t\tJSON(map[string]any{\n\t\t\t\"repositories\": []map[string]string{\n\t\t\t\t{\"clone_url\": \"https://github/ssr1.git\", \"full_name\": \"ssr1\"},\n\t\t\t\t{\"clone_url\": \"https://github/ssr2.git\", \"full_name\": \"ssr2\"},\n\t\t\t},\n\t\t})\n\n\ts := initTestSource(&sourcespb.GitHub{Credential: &sourcespb.GitHub_Unauthenticated{}})\n\terr := s.getReposByApp(context.Background(), noopReporter())\n\tassert.Nil(t, err)\n\tassert.Equal(t, 2, s.filteredRepoCache.Count())\n\tok := s.filteredRepoCache.Exists(\"ssr1\")\n\tassert.True(t, ok)\n\tok = s.filteredRepoCache.Exists(\"ssr2\")\n\tassert.True(t, ok)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestAddOrgsByUser(t *testing.T) {\n\tdefer gock.Off()\n\n\t// NOTE: addOrgsByUser calls /user/orgs to get the orgs of the\n\t// authenticated user\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/user/orgs\").\n\t\tReply(200).\n\t\tJSON([]map[string]any{\n\t\t\t{\"login\": \"sso2\"},\n\t\t})\n\n\ts := initTestSource(&sourcespb.GitHub{Credential: &sourcespb.GitHub_Unauthenticated{}})\n\ts.addOrgsByUser(context.Background(), \"super-secret-user\", noopReporter())\n\tassert.Equal(t, 1, s.orgsCache.Count())\n\tok := s.orgsCache.Exists(\"sso2\")\n\tassert.True(t, ok)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestNormalizeRepos(t *testing.T) {\n\tdefer gock.Off()\n\n\ttests := []struct {\n\t\tname     string\n\t\tsetup    func()\n\t\trepos    []string\n\t\texpected map[string]struct{}\n\t\twantErr  bool\n\t}{\n\t\t{\n\t\t\tname:  \"repo url\",\n\t\t\tsetup: func() {},\n\t\t\trepos: []string{\"https://github.com/super-secret-user/super-secret-repo\"},\n\t\t\texpected: map[string]struct{}{\n\t\t\t\t\"https://github.com/super-secret-user/super-secret-repo.git\": {},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"not found\",\n\t\t\tsetup: func() {\n\t\t\t\tgock.New(\"https://api.github.com\").\n\t\t\t\t\tGet(\"/users/not-found/gists\").\n\t\t\t\t\tReply(404)\n\t\t\t\tgock.New(\"https://api.github.com\").\n\t\t\t\t\tGet(\"/users/not-found/repos\").\n\t\t\t\t\tReply(404)\n\t\t\t},\n\t\t\trepos:    []string{\"not-found\"},\n\t\t\texpected: map[string]struct{}{},\n\t\t\twantErr:  true,\n\t\t},\n\t\t{\n\t\t\tname:     \"unexpected format\",\n\t\t\tsetup:    func() {},\n\t\t\trepos:    []string{\"/foo/\"},\n\t\t\texpected: map[string]struct{}{},\n\t\t\twantErr:  true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdefer gock.Off()\n\t\t\ttt.setup()\n\t\t\ts := initTestSource(&sourcespb.GitHub{Credential: &sourcespb.GitHub_Unauthenticated{}})\n\n\t\t\tgot, err := s.normalizeRepo(tt.repos[0])\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"normalizeRepo() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got != \"\" {\n\t\t\t\tfor k := range tt.expected {\n\t\t\t\t\tassert.Equal(t, got, k)\n\t\t\t\t}\n\t\t\t}\n\t\t\tres := make(map[string]struct{}, s.filteredRepoCache.Count())\n\t\t\tfor _, v := range s.filteredRepoCache.Keys() {\n\t\t\t\tres[v] = struct{}{}\n\t\t\t}\n\n\t\t\tif got == \"\" && !cmp.Equal(res, tt.expected) {\n\t\t\t\tt.Errorf(\"normalizeRepo() got = %v, want %v\", s.repos, tt.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNormalizeRepo(t *testing.T) {\n\t// Test that normalizeRepo correctly identifies URLs with protocols\n\tsource := &Source{}\n\n\t// Test case 1: HTTP URL\n\tresult, err := source.normalizeRepo(\"https://github.com/org/repo.git\")\n\tassert.NoError(t, err)\n\tassert.Contains(t, result, \"github.com/org/repo\")\n\n\t// Test case 2: HTTP URL without .git\n\tresult, err = source.normalizeRepo(\"http://github.com/org/repo\")\n\tassert.NoError(t, err)\n\tassert.Contains(t, result, \"github.com/org/repo\")\n\n\t// Test case 3: Git protocol URL\n\tresult, err = source.normalizeRepo(\"git://github.com/org/repo.git\")\n\tassert.NoError(t, err)\n\tassert.Contains(t, result, \"github.com/org/repo\")\n\n\t// Test case 4: SSH URL\n\tresult, err = source.normalizeRepo(\"ssh://git@github.com/org/repo.git\")\n\tassert.NoError(t, err)\n\tassert.Contains(t, result, \"github.com/org/repo\")\n\n\t// Test case 5: Org/repo format (should convert to full URL)\n\tresult, err = source.normalizeRepo(\"org/repo\")\n\tassert.NoError(t, err)\n\tassert.Contains(t, result, \"github.com/org/repo\")\n\n\t// Test case 6: Invalid format (no protocol, no slash)\n\t_, err = source.normalizeRepo(\"invalid\")\n\tassert.Error(t, err)\n\tassert.Contains(t, err.Error(), \"no repositories found\")\n}\n\nfunc TestNormalizeRepo_Enterprise(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\tendpoint   string\n\t\twantResult string\n\t}{\n\t\t{\n\t\t\tname:       \"only host\",\n\t\t\tendpoint:   \"https://example.com\",\n\t\t\twantResult: \"https://example.com/org/repo.git\",\n\t\t},\n\t\t{\n\t\t\tname:       \"host with path\",\n\t\t\tendpoint:   \"https://example.com/api/v3\",\n\t\t\twantResult: \"https://example.com/org/repo.git\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tsource := Source{\n\t\t\t\tconn: &sourcespb.GitHub{\n\t\t\t\t\tEndpoint: tt.endpoint,\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tresult, err := source.normalizeRepo(\"org/repo\")\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Equal(t, tt.wantResult, result)\n\t\t})\n\t}\n}\n\nfunc TestHandleRateLimit(t *testing.T) {\n\ts := initTestSource(&sourcespb.GitHub{Credential: &sourcespb.GitHub_Unauthenticated{}})\n\tctx := context.Background()\n\tassert.False(t, s.handleRateLimit(ctx, nil))\n\n\t// Request\n\treqUrl, _ := url.Parse(\"https://github.com/trufflesecurity/trufflehog\")\n\tres := &github.Response{\n\t\tResponse: &http.Response{\n\t\t\tStatusCode: 429,\n\t\t\tHeader:     make(http.Header),\n\t\t\tRequest: &http.Request{\n\t\t\t\tMethod: \"GET\",\n\t\t\t\tURL:    reqUrl,\n\t\t\t},\n\t\t},\n\t}\n\tres.Header.Set(\"x-ratelimit-remaining\", \"0\")\n\tres.Header.Set(\"x-ratelimit-reset\", strconv.FormatInt(time.Now().Unix()+1, 10))\n\n\t// Error\n\tresetTime := github.Timestamp{\n\t\tTime: time.Now().Add(time.Millisecond),\n\t}\n\terr := &github.RateLimitError{\n\t\tRate: github.Rate{\n\t\t\tLimit:     5000,\n\t\t\tRemaining: 0,\n\t\t\tReset:     resetTime,\n\t\t},\n\t\tResponse: res.Response,\n\t\tMessage:  \"Too Many Requests\",\n\t}\n\n\tassert.True(t, s.handleRateLimit(ctx, err))\n}\n\nfunc TestEnumerateUnauthenticated(t *testing.T) {\n\tdefer gock.Off()\n\n\tapiEndpoint := \"https://api.github.com\"\n\tgock.New(apiEndpoint).\n\t\tGet(\"/orgs/super-secret-org/repos\").\n\t\tReply(200).\n\t\tJSON([]map[string]string{{\"full_name\": \"super-secret-org/super-secret-repo\", \"clone_url\": \"https://github.com/super-secret-org/super-secret-repo.git\"}})\n\n\ts := initTestSource(&sourcespb.GitHub{\n\t\tEndpoint:   apiEndpoint,\n\t\tCredential: &sourcespb.GitHub_Unauthenticated{},\n\t})\n\ts.orgsCache = simple.NewCache[string]()\n\ts.orgsCache.Set(\"super-secret-org\", \"super-secret-org\")\n\t// s.enumerateUnauthenticated(context.Background(), apiEndpoint)\n\ts.enumerateUnauthenticated(context.Background(), noopReporter())\n\tassert.Equal(t, 1, s.filteredRepoCache.Count())\n\tok := s.filteredRepoCache.Exists(\"super-secret-org/super-secret-repo\")\n\tassert.True(t, ok)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestEnumerateWithToken(t *testing.T) {\n\tdefer gock.Off()\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/user\").\n\t\tReply(200).\n\t\tJSON(map[string]string{\"login\": \"super-secret-user\"})\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/user/repos\").\n\t\tMatchParam(\"per_page\", \"100\").\n\t\tReply(200).\n\t\tJSON([]map[string]string{{\"clone_url\": \"https://github.com/super-secret-user/super-secret-repo.git\", \"full_name\": \"super-secret-user/super-secret-repo\"}})\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/user/orgs\").\n\t\tMatchParam(\"per_page\", \"100\").\n\t\tReply(200).\n\t\tJSON(`[]`)\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/users/super-secret-user/gists\").\n\t\tReply(200).\n\t\tJSON([]map[string]string{{\"id\": \"super-secret-gist\", \"git_pull_url\": \"https://gist.github.com/super-secret-gist.git\"}})\n\n\ts := initTestSource(&sourcespb.GitHub{\n\t\tEndpoint: \"https://api.github.com\",\n\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\tToken: \"token\",\n\t\t},\n\t})\n\terr := s.enumerateWithToken(context.Background(), false, noopReporter())\n\tassert.Nil(t, err)\n\tassert.Equal(t, 2, s.filteredRepoCache.Count())\n\tok := s.filteredRepoCache.Exists(\"super-secret-user/super-secret-repo\")\n\tassert.True(t, ok)\n\tok = s.filteredRepoCache.Exists(\"super-secret-gist\")\n\tassert.True(t, ok)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc BenchmarkEnumerateWithToken(b *testing.B) {\n\tdefer gock.Off()\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/user\").\n\t\tReply(200).\n\t\tJSON(map[string]string{\"login\": \"super-secret-user\"})\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/users/super-secret-user/repos\").\n\t\tReply(200).\n\t\tJSON([]map[string]string{{\"clone_url\": \"https://github.com/super-secret-repo.git\"}})\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/user/orgs\").\n\t\tMatchParam(\"per_page\", \"100\").\n\t\tReply(200).\n\t\tJSON([]map[string]string{{\"clone_url\": \"https://github.com/super-secret-repo.git\"}})\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/users/super-secret-user/gists\").\n\t\tReply(200).\n\t\tJSON([]map[string]string{{\"git_pull_url\": \"https://github.com/super-secret-gist.git\"}})\n\n\ts := initTestSource(&sourcespb.GitHub{\n\t\tEndpoint: \"https://api.github.com\",\n\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\tToken: \"token\",\n\t\t},\n\t})\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = s.enumerateWithToken(context.Background(), false, noopReporter())\n\t}\n}\n\nfunc TestEnumerate(t *testing.T) {\n\tdefer gock.Off()\n\n\t// Arrange\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/user\").\n\t\tReply(200).\n\t\tJSON(map[string]string{\"login\": \"super-secret-user\"})\n\n\t//\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/user/repos\").\n\t\tReply(200).\n\t\tJSON(`[{\"name\": \"super-secret-repo\", \"full_name\": \"super-secret-user/super-secret-repo\", \"owner\": {\"login\": \"super-secret-user\"}, \"clone_url\": \"https://github.com/super-secret-user/super-secret-repo.git\", \"has_wiki\": false, \"size\": 1}]`)\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/user/orgs\").\n\t\tMatchParam(\"per_page\", \"100\").\n\t\tReply(200).\n\t\tJSON([]map[string]string{{\"clone_url\": \"https://github.com/super-secret-user/super-secret-repo.git\", \"full_name\": \"super-secret-user/super-secret-repo\"}})\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/users/super-secret-user/gists\").\n\t\tReply(200).\n\t\tJSON(`[{\"git_pull_url\": \"https://gist.github.com/2801a2b0523099d0614a951579d99ba9.git\", \"id\": \"2801a2b0523099d0614a951579d99ba9\"}]`)\n\n\ts := initTestSource(&sourcespb.GitHub{\n\t\tEndpoint: \"https://api.github.com\",\n\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t})\n\n\t// Manually cache a repository to ensure that enumerate\n\t// doesn't make duplicate API calls.\n\t// See https://github.com/trufflesecurity/trufflehog/pull/2625\n\trepo := func() *github.Repository {\n\t\tvar (\n\t\t\tname     = \"cached-repo\"\n\t\t\tfullName = \"cached-user/cached-repo\"\n\t\t\tlogin    = \"cached-user\"\n\t\t\tcloneUrl = \"https://github.com/cached-user/cached-repo.git\"\n\t\t\towner    = &github.User{\n\t\t\t\tLogin: &login,\n\t\t\t}\n\t\t\thasWiki = false\n\t\t\tsize    = 1234\n\t\t)\n\t\treturn &github.Repository{\n\t\t\tName:     &name,\n\t\t\tFullName: &fullName,\n\t\t\tOwner:    owner,\n\t\t\tHasWiki:  &hasWiki,\n\t\t\tSize:     &size,\n\t\t\tCloneURL: &cloneUrl,\n\t\t}\n\t}()\n\ts.cacheRepoInfo(repo)\n\ts.filteredRepoCache.Set(repo.GetFullName(), repo.GetCloneURL())\n\n\tvar reportedRepos []string\n\treporter := sources.VisitorReporter{\n\t\tVisitUnit: func(ctx context.Context, su sources.SourceUnit) error {\n\t\t\turl, _ := su.SourceUnitID()\n\t\t\treportedRepos = append(reportedRepos, url)\n\t\t\treturn nil\n\t\t},\n\t}\n\n\t// Act\n\terr := s.Enumerate(context.Background(), reporter)\n\tslices.Sort(reportedRepos)\n\n\t// Assert\n\tassert.Nil(t, err)\n\t// Enumeration found all repos.\n\tassert.Equal(t, 3, s.filteredRepoCache.Count())\n\tassert.True(t, s.filteredRepoCache.Exists(\"super-secret-user/super-secret-repo\"))\n\tassert.True(t, s.filteredRepoCache.Exists(\"cached-user/cached-repo\"))\n\tassert.True(t, s.filteredRepoCache.Exists(\"2801a2b0523099d0614a951579d99ba9\"))\n\tassert.Equal(t, 3, len(s.repos))\n\tassert.Equal(t, s.repos, reportedRepos)\n\t// Enumeration cached all repos.\n\tassert.Equal(t, 3, len(s.repoInfoCache.cache))\n\t_, ok := s.repoInfoCache.get(\"https://github.com/super-secret-user/super-secret-repo.git\")\n\tassert.True(t, ok)\n\t_, ok = s.repoInfoCache.get(\"https://github.com/cached-user/cached-repo.git\")\n\tassert.True(t, ok)\n\t_, ok = s.repoInfoCache.get(\"https://gist.github.com/2801a2b0523099d0614a951579d99ba9.git\")\n\tassert.True(t, ok)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc setupMocks(b *testing.B) {\n\tb.Helper()\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/user\").\n\t\tReply(200).\n\t\tJSON(map[string]string{\"login\": \"super-secret-user\"})\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/users/super-secret-user/repos\").\n\t\tReply(200).\n\t\tJSON(mockRepos())\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/user/orgs\").\n\t\tMatchParam(\"per_page\", \"100\").\n\t\tReply(200).\n\t\tJSON([]map[string]string{{\"clone_url\": \"https://github.com/super-secret-repo.git\"}})\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/users/super-secret-user/gists\").\n\t\tReply(200).\n\t\tJSON(mockGists())\n}\n\nfunc mockRepos() []map[string]string {\n\tres := make([]map[string]string, 0, 10000)\n\tfor i := 0; i < 10000; i++ {\n\t\tres = append(res, map[string]string{\"clone_url\": fmt.Sprintf(\"https://githu/super-secret-repo-%d.git\", i)})\n\t}\n\treturn res\n}\n\nfunc mockGists() []map[string]string {\n\tres := make([]map[string]string, 0, 100)\n\tfor i := 0; i < 100; i++ {\n\t\tres = append(res, map[string]string{\"git_pull_url\": fmt.Sprintf(\"https://githu/super-secret-gist-%d.git\", i)})\n\t}\n\treturn res\n}\n\nfunc BenchmarkEnumerate(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\ts := initTestSource(&sourcespb.GitHub{\n\t\t\tEndpoint: \"https://api.github.com\",\n\t\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\t\tToken: \"super secret token\",\n\t\t\t},\n\t\t})\n\t\tsetupMocks(b)\n\n\t\tb.StartTimer()\n\t\t_ = s.Enumerate(context.Background(), noopReporter())\n\t}\n}\n\nfunc TestEnumerateWithToken_Repositories(t *testing.T) {\n\tdefer gock.Off()\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/user\").\n\t\tReply(200).\n\t\tJSON(map[string]string{\"login\": \"super-secret-user\"})\n\n\ts := initTestSource(&sourcespb.GitHub{\n\t\tEndpoint: \"https://api.github.com\",\n\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\tToken: \"token\",\n\t\t},\n\t})\n\ts.repos = []string{\"some-special-repo\"}\n\n\terr := s.enumerateWithToken(context.Background(), false, noopReporter())\n\tassert.Nil(t, err)\n\tassert.Equal(t, 1, len(s.repos))\n\tassert.Equal(t, []string{\"some-special-repo\"}, s.repos)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestEnumerateWithToken_IncludeRepos(t *testing.T) {\n\tdefer gock.Off()\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/user\").\n\t\tReply(200).\n\t\tJSON(map[string]string{\"login\": \"super-secret-user\"})\n\n\ts := initTestSource(&sourcespb.GitHub{\n\t\tEndpoint: \"https://api.github.com\",\n\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\tToken: \"token\",\n\t\t},\n\t})\n\ts.repos = []string{\"some-special-repo\"}\n\n\terr := s.enumerateWithToken(context.Background(), false, noopReporter())\n\tassert.Nil(t, err)\n\tassert.Equal(t, 1, len(s.repos))\n\tassert.Equal(t, []string{\"some-special-repo\"}, s.repos)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestEnumerateWithApp(t *testing.T) {\n\tdefer gock.Off()\n\n\tprivateKey := createPrivateKey()\n\n\tgock.New(\"https://api.github.com\").\n\t\tPost(\"/app/installations/1337/access_tokens\").\n\t\tReply(200).\n\t\tJSON(map[string]string{\"token\": \"dontlook\"})\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/installation/repositories\").\n\t\tReply(200).\n\t\tJSON(map[string]string{})\n\n\ts := initTestSource(&sourcespb.GitHub{\n\t\tEndpoint: \"https://api.github.com\",\n\t\tCredential: &sourcespb.GitHub_GithubApp{\n\t\t\tGithubApp: &credentialspb.GitHubApp{\n\t\t\t\tPrivateKey:     privateKey,\n\t\t\t\tInstallationId: \"1337\",\n\t\t\t\tAppId:          \"4141\",\n\t\t\t},\n\t\t},\n\t})\n\terr := s.enumerateWithApp(context.Background(), s.connector.(*appConnector).InstallationClient(), noopReporter())\n\tassert.Nil(t, err)\n\tassert.Equal(t, 0, len(s.repos))\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\n// This only tests the resume info slice portion of setProgressCompleteWithRepo.\nfunc Test_setProgressCompleteWithRepo_resumeInfo(t *testing.T) {\n\ttests := []struct {\n\t\tstartingResumeInfoSlice []string\n\t\trepoURL                 string\n\t\twantResumeInfoSlice     []string\n\t}{\n\t\t{\n\t\t\tstartingResumeInfoSlice: []string{},\n\t\t\trepoURL:                 \"a\",\n\t\t\twantResumeInfoSlice:     []string{\"a\"},\n\t\t},\n\t\t{\n\t\t\tstartingResumeInfoSlice: []string{\"b\"},\n\t\t\trepoURL:                 \"a\",\n\t\t\twantResumeInfoSlice:     []string{\"a\", \"b\"},\n\t\t},\n\t}\n\n\ts := &Source{\n\t\trepos: []string{},\n\t}\n\n\tfor _, tt := range tests {\n\t\ts.resumeInfoSlice = tt.startingResumeInfoSlice\n\t\ts.setProgressCompleteWithRepo(0, 0, tt.repoURL)\n\t\tif !reflect.DeepEqual(s.resumeInfoSlice, tt.wantResumeInfoSlice) {\n\t\t\tt.Errorf(\"s.setProgressCompleteWithRepo() got: %v, want: %v\", s.resumeInfoSlice, tt.wantResumeInfoSlice)\n\t\t}\n\t}\n}\n\nfunc Test_setProgressCompleteWithRepo_Progress(t *testing.T) {\n\trepos := []string{\"a\", \"b\", \"c\", \"d\", \"e\"}\n\ttests := map[string]struct {\n\t\trepos                 []string\n\t\tindex                 int\n\t\toffset                int\n\t\twantPercentComplete   int64\n\t\twantSectionsCompleted int32\n\t\twantSectionsRemaining int32\n\t}{\n\t\t\"starting from the beginning, no offset\": {\n\t\t\trepos:                 repos,\n\t\t\tindex:                 0,\n\t\t\toffset:                0,\n\t\t\twantPercentComplete:   0,\n\t\t\twantSectionsCompleted: 0,\n\t\t\twantSectionsRemaining: 5,\n\t\t},\n\t\t\"resume from the third, offset 2\": {\n\t\t\trepos:                 repos[2:],\n\t\t\tindex:                 0,\n\t\t\toffset:                2,\n\t\t\twantPercentComplete:   40,\n\t\t\twantSectionsCompleted: 2,\n\t\t\twantSectionsRemaining: 5,\n\t\t},\n\t\t\"resume from the third, on last repo, offset 2\": {\n\t\t\trepos:                 repos[2:],\n\t\t\tindex:                 2,\n\t\t\toffset:                2,\n\t\t\twantPercentComplete:   80,\n\t\t\twantSectionsCompleted: 4,\n\t\t\twantSectionsRemaining: 5,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\ts := &Source{\n\t\t\trepos: tt.repos,\n\t\t}\n\n\t\ts.setProgressCompleteWithRepo(tt.index, tt.offset, \"\")\n\t\tgotProgress := s.GetProgress()\n\t\tif gotProgress.PercentComplete != tt.wantPercentComplete {\n\t\t\tt.Errorf(\"s.setProgressCompleteWithRepo() PercentComplete got: %v want: %v\", gotProgress.PercentComplete, tt.wantPercentComplete)\n\t\t}\n\t\tif gotProgress.SectionsCompleted != tt.wantSectionsCompleted {\n\t\t\tt.Errorf(\"s.setProgressCompleteWithRepo() PercentComplete got: %v want: %v\", gotProgress.SectionsCompleted, tt.wantSectionsCompleted)\n\t\t}\n\t\tif gotProgress.SectionsRemaining != tt.wantSectionsRemaining {\n\t\t\tt.Errorf(\"s.setProgressCompleteWithRepo() PercentComplete got: %v want: %v\", gotProgress.SectionsRemaining, tt.wantSectionsRemaining)\n\t\t}\n\t}\n}\n\nfunc Test_scan_SetProgressComplete(t *testing.T) {\n\ttestCases := []struct {\n\t\tname         string\n\t\trepos        []string\n\t\twantComplete bool\n\t\twantErr      bool\n\t}{\n\t\t{\n\t\t\tname:         \"no repos\",\n\t\t\twantComplete: true,\n\t\t},\n\t\t{\n\t\t\tname:         \"one valid repo\",\n\t\t\trepos:        []string{\"https://github.com/super-secret-user/super-secret-repo.git\"},\n\t\t\twantComplete: true,\n\t\t\twantErr:      true,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tsrc := initTestSource(&sourcespb.GitHub{\n\t\t\t\tRepositories: tc.repos,\n\t\t\t\tCredential:   &sourcespb.GitHub_Unauthenticated{},\n\t\t\t})\n\t\t\tsrc.jobPool = &errgroup.Group{}\n\n\t\t\t_ = src.scan(context.Background(), nil)\n\t\t\tif !tc.wantErr {\n\t\t\t\tassert.Equal(t, \"\", src.GetProgress().EncodedResumeInfo)\n\t\t\t}\n\n\t\t\tgotComplete := src.GetProgress().PercentComplete == 100\n\t\t\tif gotComplete != tc.wantComplete {\n\t\t\t\tt.Errorf(\"got: %v, want: %v\", gotComplete, tc.wantComplete)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetRepoURLParts(t *testing.T) {\n\trepoURLs := []string{\n\t\t\"https://github.com/trufflesecurity/trufflehog.git\",\n\t\t\"git+https://github.com/trufflesecurity/trufflehog.git\",\n\t\t\"ssh://github.com/trufflesecurity/trufflehog.git\",\n\t\t\"ssh://git@github.com/trufflesecurity/trufflehog.git\",\n\t\t\"git+ssh://git@github.com/trufflesecurity/trufflehog.git\",\n\t\t\"git://github.com/trufflesecurity/trufflehog.git\",\n\t}\n\texpected := []string{\"github.com\", \"trufflesecurity\", \"trufflehog\"}\n\tfor _, tt := range repoURLs {\n\t\t_, parts, err := getRepoURLParts(tt)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed: %v\", err)\n\t\t}\n\t\tassert.Equal(t, expected, parts)\n\t}\n\n\tgistURLs := map[string][]string{\n\t\t// Gists\n\t\t\"ssh://github.com/6df198861306313246466d23aa4102aa.git\":                           nil,\n\t\t\"ssh://gist.github.com/6df198861306313246466d23aa4102aa.git\":                      {\"gist.github.com\", \"6df198861306313246466d23aa4102aa\"},\n\t\t\"https://gist.github.com/6df198861306313246466d23aa4102aa.git\":                    {\"gist.github.com\", \"6df198861306313246466d23aa4102aa\"},\n\t\t\"https://gist.github.com/john-smith/6df198861306313246466d23aa4102aa.git\":         {\"gist.github.com\", \"john-smith\", \"6df198861306313246466d23aa4102aa\"},\n\t\t\"ssh://github.contoso.com/gist/6df198861306313246466d23aa4102aa.git\":              {\"github.contoso.com\", \"gist\", \"6df198861306313246466d23aa4102aa\"},\n\t\t\"https://github.contoso.com/gist/6df198861306313246466d23aa4102aa.git\":            {\"github.contoso.com\", \"gist\", \"6df198861306313246466d23aa4102aa\"},\n\t\t\"https://github.contoso.com/gist/john-smith/6df198861306313246466d23aa4102aa.git\": {\"github.contoso.com\", \"gist\", \"john-smith\", \"6df198861306313246466d23aa4102aa\"},\n\t\t\"https://github.com/gist/john-smith/6df198861306313246466d23aa4102aa.git\":         nil,\n\t}\n\tfor tt, expected := range gistURLs {\n\t\t_, parts, err := getRepoURLParts(tt)\n\t\tif err != nil {\n\t\t\tif expected == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tt.Fatalf(\"failed: %v\", err)\n\t\t}\n\t\tassert.Equal(t, expected, parts)\n\t}\n}\n\nfunc TestGetRepoURLPartsWithTrailingHyphen(t *testing.T) {\n\t// Test for https://github.com/trufflesecurity/trufflehog/issues/4679\n\t// Repository names ending with a hyphen should be preserved correctly.\n\ttestCases := []struct {\n\t\tname     string\n\t\turl      string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\tname:     \"https with trailing hyphen\",\n\t\t\turl:      \"https://github.com/MYORG/my-repo-name-.git\",\n\t\t\texpected: []string{\"github.com\", \"MYORG\", \"my-repo-name-\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"https with trailing hyphen no .git\",\n\t\t\turl:      \"https://github.com/MYORG/my-repo-.git\",\n\t\t\texpected: []string{\"github.com\", \"MYORG\", \"my-repo-\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"ssh with trailing hyphen\",\n\t\t\turl:      \"ssh://git@github.com/MYORG/test-repo-.git\",\n\t\t\texpected: []string{\"github.com\", \"MYORG\", \"test-repo-\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"multiple hyphens with trailing\",\n\t\t\turl:      \"https://github.com/org-name/my-test-repo-.git\",\n\t\t\texpected: []string{\"github.com\", \"org-name\", \"my-test-repo-\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"single trailing hyphen repo\",\n\t\t\turl:      \"https://github.com/Org/-.git\",\n\t\t\texpected: []string{\"github.com\", \"Org\", \"-\"},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t_, parts, err := getRepoURLParts(tc.url)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t\tassert.Equal(t, tc.expected, parts)\n\t\t})\n\t}\n}\n\nfunc TestGetGistID(t *testing.T) {\n\ttests := []struct {\n\t\ttrimmedURL []string\n\t\texpected   string\n\t}{\n\t\t{[]string{\"https://gist.github.com\", \"12345\"}, \"12345\"},\n\t\t{[]string{\"https://gist.github.com\", \"owner\", \"12345\"}, \"12345\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tgot := extractGistID(tt.trimmedURL)\n\t\tassert.Equal(t, tt.expected, got)\n\t}\n}\n\n// This isn't really a GitHub test, but GitHub is the only source that supports scan targeting right now, so this is\n// where I've put this targeted scan test.\nfunc Test_ScanMultipleTargets_MultipleErrors(t *testing.T) {\n\ts := &Source{conn: &sourcespb.GitHub{}} // This test doesn't require initialization\n\tctx := context.Background()\n\tchunksChan := make(chan *sources.Chunk)\n\n\ttargets := []sources.ChunkingTarget{\n\t\t{SecretID: 1},\n\t\t{SecretID: 2},\n\t}\n\n\t// The specific error text doesn't matter for the test, but it has to match what the source generates\n\twant := []*sources.TargetedScanError{\n\t\t{SecretID: 1, Err: errors.New(\"unable to cast metadata type for targeted scan\")},\n\t\t{SecretID: 2, Err: errors.New(\"unable to cast metadata type for targeted scan\")},\n\t}\n\n\terr := s.Chunks(ctx, chunksChan, targets...)\n\tunwrappable, ok := err.(interface{ Unwrap() []error })\n\tif assert.True(t, ok, \"returned error was not unwrappable\") {\n\t\tgot := unwrappable.Unwrap()\n\t\tassert.ElementsMatch(t, got, want)\n\t}\n}\n\nfunc TestRepositoryFiltering(t *testing.T) {\n\t// Test that the filteredRepoCache correctly filters repositories\n\tsource := &Source{}\n\n\t// Test case 1: No filters specified (should include everything)\n\tcache1 := source.newFilteredRepoCache(context.Background(), simple.NewCache[string](), []string{}, []string{})\n\tassert.True(t, cache1.wantRepo(\"org/repo1\"))\n\tassert.True(t, cache1.wantRepo(\"org/repo2\"))\n\tassert.True(t, cache1.wantRepo(\"org/repo3\"))\n\n\t// Test case 2: Include filter specified (should only include matching repos)\n\tcache2 := source.newFilteredRepoCache(context.Background(), simple.NewCache[string](), []string{\"org/repo1\", \"org/repo2\"}, []string{})\n\tassert.True(t, cache2.wantRepo(\"org/repo1\"))\n\tassert.True(t, cache2.wantRepo(\"org/repo2\"))\n\tassert.False(t, cache2.wantRepo(\"org/repo3\"))\n\n\t// Test case 3: Exclude filter specified (should exclude matching repos)\n\tcache3 := source.newFilteredRepoCache(context.Background(), simple.NewCache[string](), []string{}, []string{\"org/repo1\"})\n\tassert.False(t, cache3.wantRepo(\"org/repo1\"))\n\tassert.True(t, cache3.wantRepo(\"org/repo2\"))\n\tassert.True(t, cache3.wantRepo(\"org/repo3\"))\n\n\t// Test case 4: Both include and exclude filters (exclude takes precedence)\n\tcache4 := source.newFilteredRepoCache(context.Background(), simple.NewCache[string](), []string{\"org/repo1\"}, []string{\"org/repo1\"})\n\tassert.False(t, cache4.wantRepo(\"org/repo1\"))\n\n\t// Test case 5: Wildcard patterns\n\tcache5 := source.newFilteredRepoCache(context.Background(), simple.NewCache[string](), []string{\"org/*\"}, []string{})\n\tassert.True(t, cache5.wantRepo(\"org/repo1\"))\n\tassert.True(t, cache5.wantRepo(\"org/repo2\"))\n\tassert.False(t, cache5.wantRepo(\"other/repo1\"))\n}\n\nfunc TestExplicitRepositoryBypass(t *testing.T) {\n\t// Test that explicit repositories are included in enumeration\n\tctx := context.Background()\n\n\t// Set up mocks for the API calls\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/user\").\n\t\tReply(200).\n\t\tJSON(map[string]string{\"login\": \"test-user\"})\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/repos/org/explicit-repo\").\n\t\tReply(200).\n\t\tJSON(map[string]any{\n\t\t\t\"full_name\": \"org/explicit-repo\",\n\t\t\t\"clone_url\": \"https://github.com/org/explicit-repo.git\",\n\t\t\t\"size\":      1,\n\t\t})\n\n\tgock.New(\"https://api.github.com\").\n\t\tGet(\"/repos/org/another-explicit\").\n\t\tReply(200).\n\t\tJSON(map[string]any{\n\t\t\t\"full_name\": \"org/another-explicit\",\n\t\t\t\"clone_url\": \"https://github.com/org/another-explicit.git\",\n\t\t\t\"size\":      1,\n\t\t})\n\n\t// Create a source with explicit repositories\n\tsource := initTestSource(&sourcespb.GitHub{\n\t\tCredential: &sourcespb.GitHub_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tRepositories: []string{\n\t\t\t\"https://github.com/org/explicit-repo.git\",\n\t\t\t\"https://github.com/org/another-explicit.git\",\n\t\t},\n\t})\n\n\t// Test the Enumerate method\n\terr := source.Enumerate(ctx, noopReporter())\n\trequire.NoError(t, err)\n\n\t// Verify that explicit repositories are included in the enumeration\n\tassert.Len(t, source.repos, 2, \"Should have 2 explicit repositories\")\n\tassert.Contains(t, source.repos, \"https://github.com/org/explicit-repo.git\")\n\tassert.Contains(t, source.repos, \"https://github.com/org/another-explicit.git\")\n}\n\nfunc noopReporter() sources.UnitReporter {\n\treturn sources.VisitorReporter{\n\t\tVisitUnit: func(context.Context, sources.SourceUnit) error {\n\t\t\treturn nil\n\t\t},\n\t}\n}\n\n// This tests reproduces a bug where both VisitUnit and VisitErr were called\n// for the same repository when caching the repository info failed.\nfunc TestFixBothUnitErrAndUnitOKCalled(t *testing.T) {\n\tcache := simple.NewCache[string]()\n\tcache.Set(\"myorg/myrepo\", \"an invalid url that will cause an error\")\n\ts := &Source{\n\t\tfilteredRepoCache: &filteredRepoCache{\n\t\t\tCache: cache,\n\t\t},\n\t\tconn: &sourcespb.GitHub{\n\t\t\tRepositories: []string{\"myorg/myrepo\"},\n\t\t},\n\t\torgsCache: simple.NewCache[string](),\n\t}\n\n\tvar okCalled, errCalled bool\n\treporter := sources.VisitorReporter{\n\t\tVisitUnit: func(ctx context.Context, su sources.SourceUnit) error {\n\t\t\tokCalled = true\n\t\t\treturn nil\n\t\t},\n\t\tVisitErr: func(ctx context.Context, err error) error {\n\t\t\terrCalled = true\n\t\t\treturn nil\n\t\t},\n\t}\n\terr := s.Enumerate(context.Background(), reporter)\n\trequire.NoError(t, err)\n\n\t// expectation is that only VisitErr is called\n\tassert.True(t, errCalled)\n\tassert.False(t, okCalled)\n}\n\nfunc TestExtractRepoNameFromURL(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\turl      string\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname:     \"git URL\",\n\t\t\turl:      \"https://github.com/org/repo.git\",\n\t\t\texpected: \"org/repo\",\n\t\t},\n\t\t{\n\t\t\tname:     \"git URL with trailing slash\",\n\t\t\turl:      \"https://github.com/org/repo.git/\",\n\t\t\texpected: \"org/repo\",\n\t\t},\n\t\t{\n\t\t\tname:     \"git URL without .git\",\n\t\t\turl:      \"https://github.com/org/repo\",\n\t\t\texpected: \"org/repo\",\n\t\t},\n\t\t{\n\t\t\tname:     \"git enterprise URL\",\n\t\t\turl:      \"https://example-enterprise.com/org/repo.git\",\n\t\t\texpected: \"org/repo\",\n\t\t},\n\t\t{\n\t\t\tname:     \"just org/repo\",\n\t\t\turl:      \"org/repo\",\n\t\t\texpected: \"org/repo\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := extractRepoNameFromUrl(tt.url)\n\t\t\tassert.Equal(t, tt.expected, result)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/sources/github/graphql.go",
    "content": "package github\n\nimport (\n\t\"cmp\"\n\t\"fmt\"\n\t\"math/rand/v2\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/shurcooL/githubv4\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sanitizer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\n// processIssuesWithComments process github repo issues with comments using graphql API\nfunc (s *Source) processIssuesWithComments(\n\tctx context.Context, repoInfo repoInfo,\n\treporter sources.ChunkReporter, cutoffTime *time.Time,\n) error {\n\tvars := map[string]any{\n\t\towner:              githubv4.String(repoInfo.owner),\n\t\trepository:         githubv4.String(repoInfo.name),\n\t\tissuesPerPage:      githubv4.Int(defaultPagination),\n\t\tissuesPagination:   (*githubv4.String)(nil),\n\t\tcommentsPerPage:    githubv4.Int(defaultPagination),\n\t\tcommentsPagination: (*githubv4.String)(nil),\n\t}\n\n\tvar totalIssues int\n\n\t// loop will continue as long as there are issues in the repository\n\tfor {\n\t\tvar query issuesWithComments\n\t\terr := s.connector.GraphQLClient().Query(ctx, &query, vars)\n\t\tif s.handleGraphqlRateLimitWithChunkReporter(ctx, reporter, &query.RateLimit, err) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error fetching issues: %w\", err)\n\t\t}\n\n\t\ttotalIssues += len(query.GetIssues())\n\n\t\tctx.Logger().V(5).Info(\"Scanning Issues\",\n\t\t\t\"total_issues\", len(query.GetIssues()))\n\n\t\tif err := s.chunkGraphqlIssues(ctx, repoInfo, query.Repository.Issues.Nodes, reporter); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// process each issue comments\n\t\tfor _, issue := range query.GetIssues() {\n\t\t\tctx.Logger().V(5).Info(\"Scanning Issue Comments\",\n\t\t\t\t\"issue_id\", issue.Number,\n\t\t\t\t\"total_comments\", len(issue.GetIssueComments()),\n\t\t\t)\n\n\t\t\tif err := s.chunkComments(ctx, repoInfo, issue.GetIssueComments(), reporter, cutoffTime); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t// if issue has more than 100 comments, we need to send another request for that specific issue to pull more comments\n\t\t\tfor issue.Comments.PageInfo.HasNextPage {\n\t\t\t\tcommentVars := map[string]any{\n\t\t\t\t\towner:              githubv4.String(repoInfo.owner),\n\t\t\t\t\trepository:         githubv4.String(repoInfo.name),\n\t\t\t\t\tissueNumber:        githubv4.Int(issue.Number),\n\t\t\t\t\tcommentsPerPage:    githubv4.Int(defaultPagination),\n\t\t\t\t\tcommentsPagination: issue.Comments.PageInfo.EndCursor,\n\t\t\t\t}\n\n\t\t\t\t// request this issue more comments\n\t\t\t\tvar commentsQuery singleIssueComments\n\t\t\t\terr := s.connector.GraphQLClient().Query(ctx, &commentsQuery, commentVars)\n\t\t\t\tif s.handleGraphqlRateLimitWithChunkReporter(ctx, reporter, &commentsQuery.RateLimit, err) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error fetching issue: %w\", err)\n\t\t\t\t}\n\n\t\t\t\tctx.Logger().V(5).Info(\"Scanning additional issue comments\",\n\t\t\t\t\t\"issue_id\", issue.Number,\n\t\t\t\t\t\"total_comments\", len(commentsQuery.GetIssueComments()))\n\n\t\t\t\tif err := s.chunkComments(ctx, repoInfo, commentsQuery.GetIssueComments(), reporter, cutoffTime); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\t// update page info for loop\n\t\t\t\tissue.Comments.PageInfo = commentsQuery.Repository.Issue.Comments.PageInfo\n\t\t\t}\n\t\t}\n\n\t\t// paginate issues\n\t\tif !query.Repository.Issues.PageInfo.HasNextPage {\n\t\t\tctx.Logger().V(4).Info(\"Scanned all repository issues with comments\", \"total_issues_scanned\", totalIssues)\n\t\t\tbreak\n\t\t}\n\n\t\t// update issues pagination to go to next page\n\t\tvars[issuesPagination] = githubv4.NewString(query.Repository.Issues.PageInfo.EndCursor)\n\t}\n\n\treturn nil\n}\n\n// processPRWithComments process github repo pull requests with inline comments\nfunc (s *Source) processPRWithComments(ctx context.Context, repoInfo repoInfo, reporter sources.ChunkReporter, cutoffTime *time.Time) error {\n\tvars := map[string]any{\n\t\towner:                 githubv4.String(repoInfo.owner),\n\t\trepository:            githubv4.String(repoInfo.name),\n\t\tpullRequestPerPage:    githubv4.Int(defaultPagination),\n\t\tpullRequestPagination: (*githubv4.String)(nil),\n\t\tcommentsPerPage:       githubv4.Int(defaultPagination),\n\t\tcommentsPagination:    (*githubv4.String)(nil),\n\t}\n\n\tvar totalPRs int\n\n\t// continue loop as long as there are pull requests remaining\n\tfor {\n\t\tvar query pullRequestWithComments\n\t\terr := s.connector.GraphQLClient().Query(ctx, &query, vars)\n\t\tif s.handleGraphqlRateLimitWithChunkReporter(ctx, reporter, &query.RateLimit, err) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error fetching pull requests with comments: %w\", err)\n\t\t}\n\n\t\ttotalPRs += len(query.GetPullRequests())\n\n\t\tctx.Logger().V(5).Info(\"Scanning pull requests\",\n\t\t\t\"total_pull_requests\", len(query.GetPullRequests()))\n\n\t\tif err := s.chunkGraphqlPullRequests(ctx, repoInfo, query.GetPullRequests(), reporter); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// process each pr comments\n\t\tfor _, pr := range query.GetPullRequests() {\n\t\t\tctx.Logger().V(5).Info(\"Scanning pull request comments\",\n\t\t\t\t\"pull_request_no\", pr.Number,\n\t\t\t\t\"total_comments\", len(pr.Comments.Nodes))\n\n\t\t\tif err := s.chunkComments(ctx, repoInfo, pr.GetPRComments(), reporter, cutoffTime); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\t// if a pull request has more than 100 comments - some interns pull request might endup here :)\n\t\t\tfor pr.Comments.PageInfo.HasNextPage {\n\t\t\t\t// request this pull request more comments\n\t\t\t\tvar commentQuery singlePRComments\n\t\t\t\tsinglePRVars := map[string]any{\n\t\t\t\t\towner:              githubv4.String(repoInfo.owner),\n\t\t\t\t\trepository:         githubv4.String(repoInfo.name),\n\t\t\t\t\tpullRequestNumber:  pr.Number,\n\t\t\t\t\tcommentsPerPage:    githubv4.Int(defaultPagination),\n\t\t\t\t\tcommentsPagination: pr.Comments.PageInfo.EndCursor,\n\t\t\t\t}\n\n\t\t\t\terr := s.connector.GraphQLClient().Query(ctx, &commentQuery, singlePRVars)\n\t\t\t\tif s.handleGraphqlRateLimitWithChunkReporter(ctx, reporter, &commentQuery.RateLimit, err) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"error fetching pull request with comments: %w\", err)\n\t\t\t\t}\n\n\t\t\t\tctx.Logger().V(5).Info(\"Scanning additional comments\",\n\t\t\t\t\t\"pull_request_no\", pr.Number,\n\t\t\t\t\t\"total_comments\", len(commentQuery.GetPRComments()))\n\n\t\t\t\tif err := s.chunkComments(ctx, repoInfo, commentQuery.GetPRComments(), reporter, cutoffTime); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\t// update pr.Comments.PageInfo so loop condition reflects new state\n\t\t\t\tpr.Comments.PageInfo = commentQuery.Repository.PullRequest.Comments.PageInfo\n\t\t\t}\n\t\t}\n\n\t\t// move to next page of PRs\n\t\tif !query.Repository.PullRequests.PageInfo.HasNextPage {\n\t\t\tctx.Logger().V(4).Info(\"Scanned all repository pull requests with comments\", \"total_pullrequests_scanned\", totalPRs)\n\t\t\tbreak\n\t\t}\n\n\t\t// update pull request pagination to go to next page\n\t\tvars[pullRequestPagination] = githubv4.NewString(query.Repository.PullRequests.PageInfo.EndCursor)\n\t}\n\n\treturn nil\n}\n\n// processReviewThreads process github repo pull request review threads\nfunc (s *Source) processReviewThreads(ctx context.Context, repoInfo repoInfo, reporter sources.ChunkReporter, cutoffTime *time.Time) error {\n\tvars := map[string]any{\n\t\towner:                 githubv4.String(repoInfo.owner),\n\t\trepository:            githubv4.String(repoInfo.name),\n\t\tpullRequestPerPage:    githubv4.Int(defaultPagination),\n\t\tpullRequestPagination: (*githubv4.String)(nil),\n\t\tthreadPerPage:         githubv4.Int(defaultPagination),\n\t\tthreadPagination:      (*githubv4.String)(nil),\n\t}\n\n\tvar threadIDs = make([]string, 0)\n\n\t// continue as long as pull requests have threads\n\tfor {\n\t\tvar query prWithReviewThreadIDs\n\t\terr := s.connector.GraphQLClient().Query(ctx, &query, vars)\n\t\tif s.handleGraphqlRateLimitWithChunkReporter(ctx, reporter, &query.RateLimit, err) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error fetching pr thread reviews: %w\", err)\n\t\t}\n\n\t\t// collect thread ids\n\t\tfor _, pr := range query.GetMinimalPullRequests() {\n\t\t\tprThreadIDs := pr.ReviewThreads.GetThreadIDs()\n\t\t\tthreadIDs = append(threadIDs, prThreadIDs...)\n\t\t}\n\n\t\tif !query.Repository.PullRequests.PageInfo.HasNextPage {\n\t\t\tctx.Logger().V(4).Info(\"Pulled all repository PR's threads IDs\")\n\t\t\tbreak\n\t\t}\n\n\t\t// update pull request pagination to go to next page\n\t\tvars[pullRequestPagination] = githubv4.NewString(query.Repository.PullRequests.PageInfo.EndCursor)\n\t}\n\n\tctx.Logger().V(4).Info(\"Pulled all thread IDs\", \"total_threads\", len(threadIDs))\n\n\t// if we got more than 0 threads unfortunately :( than we need to pull their comments in batches\n\tif len(threadIDs) > 0 {\n\t\t// process in batches of max 100\n\t\tfor _, batch := range chunkIDs(threadIDs, 100) {\n\t\t\tctx.Logger().V(5).Info(\"Processing Thread comments in Batches\", \"batch_length\", len(batch))\n\t\t\t// fetch comments for the batch of threads\n\t\t\tif err := s.fetchThreadComments(ctx, batch, repoInfo, reporter, cutoffTime); err != nil {\n\t\t\t\treturn fmt.Errorf(\"error fetching thread review comments: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// fetchThreadComments process github repo pull request threads and their comments\nfunc (s *Source) fetchThreadComments(ctx context.Context, threadIDs []string, repoInfo repoInfo, reporter sources.ChunkReporter, cutoffTime *time.Time) error {\n\t// Process in batches of 100\n\tvar query multiReviewThreadComments\n\tvars := map[string]any{\n\t\t\"ids\":              threadIDs,\n\t\tcommentsPerPage:    githubv4.Int(defaultPagination),\n\t\tcommentsPagination: (*githubv4.String)(nil),\n\t}\n\n\tif err := s.connector.GraphQLClient().Query(ctx, &query, vars); err != nil {\n\t\treturn fmt.Errorf(\"multi-thread query failed: %w\", err)\n\t}\n\n\t// process each thread in batch\n\tfor _, thread := range query.GetThreads() {\n\t\tif err := s.chunkComments(ctx, repoInfo, thread.GetThreadComments(), reporter, cutoffTime); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// if a thread has more than 100 comments :)\n\t\tfor thread.Comments.PageInfo.HasNextPage {\n\t\t\t// request this thread more comments\n\t\t\tvar query singleReviewThreadComments\n\t\t\treviewThreadVars := map[string]any{\n\t\t\t\tthreadID:           thread.ID,\n\t\t\t\tcommentsPerPage:    githubv4.Int(defaultPagination),\n\t\t\t\tcommentsPagination: (*githubv4.String)(nil),\n\t\t\t}\n\n\t\t\terr := s.connector.GraphQLClient().Query(ctx, &query, reviewThreadVars)\n\t\t\tif s.handleGraphqlRateLimitWithChunkReporter(ctx, reporter, &query.RateLimit, err) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"single-thread query failed: %w\", err)\n\t\t\t}\n\n\t\t\tnode := query.Node\n\t\t\tif err := s.chunkComments(ctx, repoInfo, node.Comments.Nodes, reporter, cutoffTime); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif !node.Comments.PageInfo.HasNextPage {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// update thread comments pagination\n\t\t\treviewThreadVars[commentsPagination] = &node.Comments.PageInfo.EndCursor\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// chunkIDs splits a slice of IDs into multiple chunks of at most `size` elements.\n// For example, if you have 250 IDs and size=100, this will return\n//\n//\t[][]string{\n//\t  {id0 ... id99},   // first 100\n//\t  {id100 ... id199},// next 100\n//\t  {id200 ... id249} // last 50\n//\t}\nfunc chunkIDs(ids []string, size int) [][]string {\n\tvar chunks [][]string\n\tfor size < len(ids) {\n\t\tids, chunks = ids[size:], append(chunks, ids[0:size:size])\n\t}\n\treturn append(chunks, ids)\n}\n\n// handleRateLimitWithChunkReporter is a wrapper around handleRateLimit that includes chunk reporting\nfunc (s *Source) handleGraphqlRateLimitWithChunkReporter(\n\tctx context.Context, reporter sources.ChunkReporter,\n\trl *rateLimit, errIn error,\n) bool {\n\treturn s.handleGraphQLRateLimit(ctx, rl, errIn, &chunkErrorReporter{reporter: reporter})\n}\n\n// handleGraphQLRateLimit inspects the rateLimit info returned in GraphQL queries.\nfunc (s *Source) handleGraphQLRateLimit(ctx context.Context, rl *rateLimit, errIn error, reporters ...errorReporter) bool {\n\t// check global resume time first (in case another request already set it)\n\trateLimitMu.RLock()\n\tresumeTime := rateLimitResumeTime\n\trateLimitMu.RUnlock()\n\n\t// if resume time is not empty and is after current time, than put the request to sleep till that.\n\tif !resumeTime.IsZero() && time.Now().Before(resumeTime) {\n\t\tretryAfter := time.Until(resumeTime)\n\t\ttime.Sleep(retryAfter)\n\t\treturn true\n\t}\n\n\tvar retryAfter time.Duration\n\tif errIn != nil && strings.Contains(errIn.Error(), \"rate limit exceeded\") {\n\t\tnow := time.Now()\n\n\t\trateLimitMu.Lock()\n\t\trateLimitResumeTime = now.Add(1 * time.Minute)\n\t\tretryAfter = time.Until(rateLimitResumeTime)\n\t\tctx.Logger().Info(\"GraphQL RATE_LIMITED error (fallback)\",\n\t\t\t\"retry_after\", retryAfter.String())\n\t\trateLimitMu.Unlock()\n\t} else if rl != nil {\n\t\t// if rate limit remaining is more than 3 continue using graphql api\n\t\tif rl.Remaining > 3 {\n\t\t\treturn false\n\t\t}\n\n\t\t// === only reach here if error is nil and rate limit remaining is less than 3 (safety)\n\t\tnow := time.Now()\n\t\tretryAfter = time.Until(rl.ResetAt)\n\t\t// never negative and enforce a sane minimum backoff (avoid thrashing with 1s/2s retries)\n\t\tif cmp.Less(retryAfter, 5*time.Second) {\n\t\t\tretryAfter = 5 * time.Second\n\t\t}\n\n\t\tjitter := time.Duration(rand.IntN(10)+1) * time.Second\n\t\tretryAfter += jitter\n\n\t\t// update global resume time\n\t\trateLimitMu.Lock()\n\t\trateLimitResumeTime = now.Add(retryAfter)\n\t\tctx.Logger().Info(\"exceeded GraphQL rate limit\",\n\t\t\t\"retry_after\", retryAfter.String(),\n\t\t\t\"resume_time\", rateLimitResumeTime.Format(time.RFC3339))\n\t\trateLimitMu.Unlock()\n\n\t\tfor _, reporter := range reporters {\n\t\t\t_ = reporter.Err(ctx, fmt.Errorf(\"exceeded GraphQL rate limit\"))\n\t\t}\n\t}\n\n\tgithubNumRateLimitEncountered.WithLabelValues(s.name).Inc()\n\ttime.Sleep(retryAfter)\n\tgithubSecondsSpentRateLimited.WithLabelValues(s.name).Add(retryAfter.Seconds())\n\n\treturn true\n}\n\nfunc (s *Source) chunkGraphqlIssues(ctx context.Context, repoInfo repoInfo, issues []issue, reporter sources.ChunkReporter) error {\n\tfor _, issue := range issues {\n\t\t// Create chunk and send it to the channel.\n\t\tchunk := sources.Chunk{\n\t\t\tSourceName: s.name,\n\t\t\tSourceID:   s.SourceID(),\n\t\t\tJobID:      s.JobID(),\n\t\t\tSourceType: s.Type(),\n\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\tLink:       sanitizer.UTF8(issue.URL),\n\t\t\t\t\t\tUsername:   sanitizer.UTF8(issue.Author.Login),\n\t\t\t\t\t\tRepository: sanitizer.UTF8(repoInfo.fullName),\n\t\t\t\t\t\tTimestamp:  sanitizer.UTF8(issue.CreatedAt.String()),\n\t\t\t\t\t\tVisibility: repoInfo.visibility,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tData:         []byte(sanitizer.UTF8(issue.Title + \"\\n\" + issue.Body)),\n\t\t\tSourceVerify: s.verify,\n\t\t}\n\n\t\tif err := reporter.ChunkOk(ctx, chunk); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Source) chunkComments(ctx context.Context, repoInfo repoInfo, comments []comment, reporter sources.ChunkReporter, cutoffTime *time.Time) error {\n\tfor _, comment := range comments {\n\t\t// Stop processing comments as soon as one created before the cutoff time is detected, as these are sorted\n\t\tif cutoffTime != nil && comment.UpdatedAt.Before(*cutoffTime) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Create chunk and send it to the channel.\n\t\tchunk := sources.Chunk{\n\t\t\tSourceName: s.name,\n\t\t\tSourceID:   s.SourceID(),\n\t\t\tJobID:      s.JobID(),\n\t\t\tSourceType: s.Type(),\n\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\tLink:       sanitizer.UTF8(comment.URL),\n\t\t\t\t\t\tUsername:   sanitizer.UTF8(comment.Author.Login),\n\t\t\t\t\t\tRepository: sanitizer.UTF8(repoInfo.fullName),\n\t\t\t\t\t\tTimestamp:  sanitizer.UTF8(comment.CreatedAt.String()),\n\t\t\t\t\t\tVisibility: repoInfo.visibility,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tData:         []byte(sanitizer.UTF8(comment.Body)),\n\t\t\tSourceVerify: s.verify,\n\t\t}\n\n\t\tif err := reporter.ChunkOk(ctx, chunk); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Source) chunkGraphqlPullRequests(ctx context.Context, repoInfo repoInfo, prs []pullRequest, reporter sources.ChunkReporter) error {\n\tfor _, pr := range prs {\n\t\t// Create chunk and send it to the channel.\n\t\tchunk := sources.Chunk{\n\t\t\tSourceName: s.name,\n\t\t\tSourceID:   s.SourceID(),\n\t\t\tSourceType: s.Type(),\n\t\t\tJobID:      s.JobID(),\n\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\tLink:       sanitizer.UTF8(pr.URL),\n\t\t\t\t\t\tUsername:   sanitizer.UTF8(pr.Author.Login),\n\t\t\t\t\t\tRepository: sanitizer.UTF8(repoInfo.fullName),\n\t\t\t\t\t\tTimestamp:  sanitizer.UTF8(pr.CreatedAt.String()),\n\t\t\t\t\t\tVisibility: repoInfo.visibility,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tData:         []byte(sanitizer.UTF8(pr.Title + \"\\n\" + pr.Body)),\n\t\t\tSourceVerify: s.verify,\n\t\t}\n\n\t\tif err := reporter.ChunkOk(ctx, chunk); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/sources/github/repo.go",
    "content": "package github\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n\n\tgogit \"github.com/go-git/go-git/v5\"\n\t\"github.com/google/go-github/v67/github\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/giturl\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\n// repoInfoCache is a thread-safe cache to store information about repositories.\ntype repoInfoCache struct {\n\tmu    sync.RWMutex\n\tcache map[string]repoInfo // the actual cache storing the repository information by URL.\n}\n\n// newRepoInfoCache creates a new instance of repoInfoCache with an empty cache.\nfunc newRepoInfoCache() repoInfoCache {\n\treturn repoInfoCache{\n\t\tcache: make(map[string]repoInfo),\n\t}\n}\n\n// put adds repository information to the cache, locking for thread safety.\nfunc (r *repoInfoCache) put(repoURL string, info repoInfo) {\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\tr.cache[repoURL] = info\n}\n\n// get retrieves repository information from the cache, locking for thread safety.\n// it returns the info and a boolean indicating whether it was found.\nfunc (r *repoInfoCache) get(repoURL string) (repoInfo, bool) {\n\tr.mu.RLock()\n\tdefer r.mu.RUnlock()\n\n\tinfo, ok := r.cache[repoURL]\n\treturn info, ok\n}\n\n// repoInfo holds basic metadata about a repository.\ntype repoInfo struct {\n\towner      string                       // repository owner (user|organization).\n\tname       string                       // repository name.\n\tfullName   string                       // full repository name (owner/repo).\n\thasWiki    bool                         // whether the repository is likely to have a wiki.\n\tsize       int                          // size of the repository in kilobytes.\n\tvisibility source_metadatapb.Visibility // visibility of the repository (public/private).\n}\n\n// cloneRepo clones a repository given its URL, returns the path and the repository object.\nfunc (s *Source) cloneRepo(ctx context.Context, repoURL string) (string, *gogit.Repository, error) {\n\treturn s.connector.Clone(ctx, repoURL)\n}\n\n// repoListOptions is an interface for types that provide options for listing repositories.\ntype repoListOptions interface {\n\tgetListOptions() *github.ListOptions\n}\n\n// repoLister is a function signature for listing repositories based on certain options.\ntype repoLister func(ctx context.Context, target string, opts repoListOptions) ([]*github.Repository, *github.Response, error)\n\n// === GitHub App Repositories ===\n\n// appListOptions represents options for listing repositories by GitHub Apps.\ntype appListOptions struct {\n\tgithub.ListOptions\n}\n\n// getListOptions returns the ListOptions for appListOptions.\nfunc (a *appListOptions) getListOptions() *github.ListOptions {\n\treturn &a.ListOptions\n}\n\n// appListReposWrapper lists repositories for a GitHub App, using the provided options.\nfunc (s *Source) appListReposWrapper(ctx context.Context, _ string, opts repoListOptions) ([]*github.Repository, *github.Response, error) {\n\tsomeRepos, res, err := s.connector.APIClient().Apps.ListRepos(ctx, opts.getListOptions())\n\tif someRepos != nil {\n\t\treturn someRepos.Repositories, res, err\n\t}\n\treturn nil, res, err\n}\n\n// getReposByApp retrieves repositories by a GitHub App.\nfunc (s *Source) getReposByApp(ctx context.Context, reporter sources.UnitReporter) error {\n\treturn s.processRepos(ctx, \"\", reporter, s.appListReposWrapper, &appListOptions{\n\t\tListOptions: github.ListOptions{\n\t\t\tPerPage: defaultPagination, // Default pagination setting for API requests.\n\t\t},\n\t})\n}\n\n// === GitHub User Repositories ===\n\n// userListOptions represents options for listing repositories by user.\ntype userListOptions struct {\n\tgithub.RepositoryListByUserOptions // embedded options for listing repositories by user.\n}\n\n// getListOptions returns the ListOptions for userListOptions.\nfunc (u *userListOptions) getListOptions() *github.ListOptions {\n\treturn &u.ListOptions\n}\n\n// authenticatedUserListOption represents options for listing repositories by authenticated user.\ntype authenticatedUserListOption struct {\n\tgithub.RepositoryListByAuthenticatedUserOptions // embedded options for listing repositories by authenticated user.\n}\n\n// getListOptions returns the ListOptions for authenticatedUserListOption.\nfunc (a *authenticatedUserListOption) getListOptions() *github.ListOptions {\n\treturn &a.ListOptions\n}\n\n// userListReposWrapper lists repositories for a user, using the provided options.\nfunc (s *Source) userListReposWrapper(ctx context.Context, user string, opts repoListOptions) ([]*github.Repository, *github.Response, error) {\n\treturn s.connector.APIClient().Repositories.ListByUser(ctx, user, &opts.(*userListOptions).RepositoryListByUserOptions)\n}\n\n// authenticatedUserListReposWrapper lists repositories for an authenticated user, using the provided options.\nfunc (s *Source) authenticatedUserListReposWrapper(ctx context.Context, user string, opts repoListOptions) ([]*github.Repository, *github.Response, error) {\n\treturn s.connector.APIClient().Repositories.ListByAuthenticatedUser(ctx, &opts.(*authenticatedUserListOption).RepositoryListByAuthenticatedUserOptions)\n}\n\n// getReposByUser retrieves repositories for a given user.\nfunc (s *Source) getReposByUser(ctx context.Context, user string, authenticated bool, reporter sources.UnitReporter) error {\n\tif authenticated {\n\t\treturn s.processRepos(ctx, user, reporter, s.authenticatedUserListReposWrapper, &authenticatedUserListOption{\n\t\t\tRepositoryListByAuthenticatedUserOptions: github.RepositoryListByAuthenticatedUserOptions{\n\t\t\t\tListOptions: github.ListOptions{\n\t\t\t\t\tPerPage: defaultPagination,\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t}\n\n\treturn s.processRepos(ctx, user, reporter, s.userListReposWrapper, &userListOptions{\n\t\tRepositoryListByUserOptions: github.RepositoryListByUserOptions{\n\t\t\tListOptions: github.ListOptions{\n\t\t\t\tPerPage: defaultPagination,\n\t\t\t},\n\t\t},\n\t})\n}\n\n// === GitHub Organization Repositories ===\n\n// orgListOptions represents options for listing repositories by organization.\ntype orgListOptions struct {\n\tgithub.RepositoryListByOrgOptions // Embedded options for listing repositories by organization.\n}\n\n// getListOptions returns the ListOptions for orgListOptions.\nfunc (o *orgListOptions) getListOptions() *github.ListOptions {\n\treturn &o.ListOptions\n}\n\n// orgListReposWrapper lists repositories for an organization, using the provided options.\nfunc (s *Source) orgListReposWrapper(ctx context.Context, org string, opts repoListOptions) ([]*github.Repository, *github.Response, error) {\n\t// TODO: It's possible to exclude forks when making the API request rather than doing post-request filtering.\n\treturn s.connector.APIClient().Repositories.ListByOrg(ctx, org, &opts.(*orgListOptions).RepositoryListByOrgOptions)\n}\n\n// getReposByOrg retrieves repositories for a given organization.\nfunc (s *Source) getReposByOrg(ctx context.Context, org string, reporter sources.UnitReporter) error {\n\treturn s.processRepos(ctx, org, reporter, s.orgListReposWrapper, &orgListOptions{\n\t\tRepositoryListByOrgOptions: github.RepositoryListByOrgOptions{\n\t\t\tListOptions: github.ListOptions{\n\t\t\t\tPerPage: defaultPagination,\n\t\t\t},\n\t\t},\n\t})\n}\n\n// userType indicates whether an account belongs to a person or organization.\n//\n// See:\n// - https://docs.github.com/en/get-started/learning-about-github/types-of-github-accounts\n// - https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-a-user\ntype userType int\n\n// Constants for userType.\nconst (\n\tunknown      userType = iota // default invalid state.\n\tuser                         // the account is a person (https://docs.github.com/en/rest/users/users).\n\torganization                 // the account is an organization (https://docs.github.com/en/rest/orgs/orgs).\n)\n\n// getReposByOrgOrUser retrieves repositories for an organization or user.\nfunc (s *Source) getReposByOrgOrUser(ctx context.Context, name string, authenticated bool, reporter sources.UnitReporter) (userType, error) {\n\tvar err error\n\n\t// try to get repositories for the organization first.\n\terr = s.getReposByOrg(ctx, name, reporter)\n\tif err == nil {\n\t\treturn organization, nil\n\t} else if !isGitHub404Error(err) { // if the error is not a \"not found\" error, report it.\n\t\tif err := reporter.UnitErr(ctx, fmt.Errorf(\"error getting repos by org: %w\", err)); err != nil {\n\t\t\treturn unknown, err\n\t\t}\n\n\t\treturn unknown, err\n\t}\n\n\t// if organization repos aren't found, try user repos.\n\terr = s.getReposByUser(ctx, name, authenticated, reporter)\n\tif err == nil {\n\t\tif err := s.addUserGistsToCache(ctx, name, reporter); err != nil {\n\t\t\tctx.Logger().Error(err, \"Unable to add user to cache\")\n\t\t}\n\t\treturn user, nil\n\t} else if !isGitHub404Error(err) { // if the error is not a \"not found\" error, report it.\n\t\treturn unknown, err\n\t}\n\n\t// if neither organization nor user repos are found, return an error.\n\treturn unknown, fmt.Errorf(\"account '%s' not found\", name)\n}\n\n// isGitHub404Error checks if the error is a GitHub API error with a 404 status.\nfunc isGitHub404Error(err error) bool {\n\tvar ghErr *github.ErrorResponse\n\tif !errors.As(err, &ghErr) {\n\t\treturn false\n\t}\n\n\treturn ghErr.Response.StatusCode == http.StatusNotFound\n}\n\n// processRepos processes repositories from a source, handling pagination and rate limits.\nfunc (s *Source) processRepos(ctx context.Context, target string, reporter sources.UnitReporter, listRepos repoLister, listOpts repoListOptions) error {\n\tlogger := ctx.Logger().WithValues(\"target\", target)\n\topts := listOpts.getListOptions()\n\n\tvar (\n\t\tnumRepos, numForks int\n\t\tuniqueOrgs         = map[string]struct{}{}\n\t)\n\n\t// loop to handle pagination.\n\tfor {\n\t\tsomeRepos, res, err := listRepos(ctx, target, listOpts)\n\t\tif s.handleRateLimitWithUnitReporter(ctx, reporter, err) {\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tctx.Logger().V(2).Info(\"Listed repos\", \"page\", opts.Page, \"last_page\", res.LastPage)\n\t\tfor _, r := range someRepos {\n\t\t\tif r.GetFork() {\n\t\t\t\tif !s.conn.IncludeForks {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tnumForks++\n\t\t\t}\n\t\t\tnumRepos++\n\n\t\t\t// track unique organizations.\n\t\t\tif r.GetOwner().GetType() == \"Organization\" {\n\t\t\t\tuniqueOrgs[r.GetOwner().GetLogin()] = struct{}{}\n\t\t\t}\n\n\t\t\trepoName, repoURL := r.GetFullName(), r.GetCloneURL()\n\n\t\t\t// Check if we should process this repository based on the filter\n\t\t\tif !s.filteredRepoCache.wantRepo(repoName) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ts.totalRepoSize += r.GetSize()\n\t\t\ts.filteredRepoCache.Set(repoName, repoURL)\n\t\t\ts.cacheRepoInfo(r)\n\t\t\tif err := reporter.UnitOk(ctx, RepoUnit{Name: repoName, URL: repoURL}); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tlogger.V(3).Info(\"repo attributes\", \"name\", repoName, \"kb_size\", r.GetSize(), \"repo_url\", repoURL)\n\t\t}\n\n\t\tif res.NextPage == 0 {\n\t\t\tbreak\n\t\t}\n\t\topts.Page = res.NextPage\n\t}\n\n\t// final logging of repository stats.\n\tlogger.V(2).Info(\"found repos\", \"total\", numRepos, \"num_forks\", numForks, \"num_orgs\", len(uniqueOrgs))\n\tgithubOrgsEnumerated.WithLabelValues(s.name).Add(float64(len(uniqueOrgs)))\n\n\treturn nil\n}\n\n// cacheRepoInfo caches basic information about a repository for later use.\nfunc (s *Source) cacheRepoInfo(r *github.Repository) {\n\tinfo := repoInfo{\n\t\towner:    r.GetOwner().GetLogin(),\n\t\tname:     r.GetName(),\n\t\tfullName: r.GetFullName(),\n\t\thasWiki:  r.GetHasWiki(),\n\t\tsize:     r.GetSize(),\n\t}\n\tif r.GetPrivate() {\n\t\tinfo.visibility = source_metadatapb.Visibility_private\n\t} else {\n\t\tinfo.visibility = source_metadatapb.Visibility_public\n\t}\n\ts.repoInfoCache.put(r.GetCloneURL(), info)\n}\n\n// cacheGistInfo caches information about a Gist.\nfunc (s *Source) cacheGistInfo(g *github.Gist) {\n\tinfo := repoInfo{\n\t\towner: g.GetOwner().GetLogin(),\n\t}\n\tif g.GetPublic() {\n\t\tinfo.visibility = source_metadatapb.Visibility_public\n\t} else {\n\t\tinfo.visibility = source_metadatapb.Visibility_private\n\t}\n\ts.repoInfoCache.put(g.GetGitPullURL(), info)\n}\n\n// wikiIsReachable checks if the wiki for a repository is reachable by sending a HEAD request.\n// Unfortunately, this isn't 100% accurate. Some repositories have `has_wiki: true` and don't redirect their wiki page,\n// but still don't have a cloneable wiki.\nfunc (s *Source) wikiIsReachable(ctx context.Context, repoURL string) bool {\n\twikiURL := strings.TrimSuffix(repoURL, \".git\") + \"/wiki\"\n\treq, err := http.NewRequestWithContext(ctx, http.MethodHead, wikiURL, nil)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tres, err := s.connector.APIClient().Client().Do(req)\n\tif err != nil {\n\t\treturn false\n\t}\n\t_, _ = io.Copy(io.Discard, res.Body)\n\t_ = res.Body.Close()\n\n\t// if the wiki is disabled or unreachable, the request will be redirected.\n\treturn wikiURL == res.Request.URL.String()\n}\n\n// normalizeRepo normalizes a GitHub repository URL or name to its canonical form.\nfunc (s *Source) normalizeRepo(repo string) (string, error) {\n\n\t// If it's a full URL (has protocol), normalize it\n\tif regexp.MustCompile(`^[a-z]+://`).MatchString(repo) {\n\n\t\treturn giturl.NormalizeGithubRepo(repo)\n\t}\n\t// If it's a repository name (contains / but not http), convert to full URL first\n\tif strings.Contains(repo, \"/\") && !regexp.MustCompile(`^[a-z]+://`).MatchString(repo) {\n\t\tfullURL := \"https://github.com/\" + repo\n\t\t// If using GitHub Enterprise, adjust the URL accordingly\n\t\tif s.conn != nil && s.conn.Endpoint != \"\" && !endsWithGithub.MatchString(s.conn.Endpoint) {\n\t\t\tu, err := url.Parse(s.conn.Endpoint)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", fmt.Errorf(\"invalid enterprise endpoint: %w\", err)\n\t\t\t}\n\t\t\t// we want to remove any path components from the endpoint and just use the host\n\t\t\tu.Path = \"/\" + repo\n\t\t\tfullURL = u.String()\n\t\t}\n\t\treturn giturl.NormalizeGithubRepo(fullURL)\n\t}\n\n\treturn \"\", fmt.Errorf(\"no repositories found for %s\", repo)\n}\n\n// extractRepoNameFromUrl extracts the \"owner/repo\" name from a GitHub repository URL.\n// Example: http://github.com/owner/repo.git -> owner/repo\n// If an invalid URL is provided, it returns the original string.\nfunc extractRepoNameFromUrl(repoURL string) string {\n\tu, err := url.Parse(repoURL)\n\tif err != nil {\n\t\treturn repoURL\n\t}\n\tcleanedPath := strings.Trim(u.Path, \"/\")\n\treturn strings.TrimSuffix(cleanedPath, \".git\")\n}\n"
  },
  {
    "path": "pkg/sources/github_experimental/github_experimental.go",
    "content": "package github_experimental\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/go-logr/logr\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/giturl\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/log\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sanitizer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git\"\n\tgithubsource \"github.com/trufflesecurity/trufflehog/v3/pkg/sources/github\"\n)\n\nconst (\n\tSourceType      = sourcespb.SourceType_SOURCE_TYPE_GITHUB_EXPERIMENTAL\n\tcloudV3Endpoint = \"https://api.github.com\"\n)\n\ntype Source struct {\n\tname                   string\n\tsourceID               sources.SourceID\n\tjobID                  sources.JobID\n\tverify                 bool\n\trepoInfoCache          repoInfoCache\n\tuseCustomContentWriter bool\n\tgit                    *git.Git\n\tscanOptions            *git.ScanOptions\n\tlog                    logr.Logger\n\tconn                   *sourcespb.GitHubExperimental\n\tconnector              githubsource.Connector\n\n\tsources.Progress\n\tsources.CommonSourceUnitUnmarshaller\n}\n\n// WithCustomContentWriter sets the useCustomContentWriter flag on the source.\nfunc (s *Source) WithCustomContentWriter() { s.useCustomContentWriter = true }\n\nfunc (s *Source) WithScanOptions(scanOptions *git.ScanOptions) {\n\ts.scanOptions = scanOptions\n}\n\n// Ensure the Source satisfies the interfaces at compile time\nvar _ sources.Source = (*Source)(nil)\nvar _ sources.SourceUnitUnmarshaller = (*Source)(nil)\n\n// Type returns the type of source.\n// It is used for matching source types in configuration and job input.\nfunc (s *Source) Type() sourcespb.SourceType {\n\treturn SourceType\n}\n\nfunc (s *Source) SourceID() sources.SourceID {\n\treturn s.sourceID\n}\n\nfunc (s *Source) JobID() sources.JobID {\n\treturn s.jobID\n}\n\n// Init returns an initialized GitHubExperimental source.\nfunc (s *Source) Init(aCtx context.Context, name string, jobID sources.JobID, sourceID sources.SourceID, verify bool, connection *anypb.Any, concurrency int) error {\n\terr := git.CmdCheck()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.log = aCtx.Logger()\n\n\ts.name = name\n\ts.sourceID = sourceID\n\ts.jobID = jobID\n\ts.verify = verify\n\n\tvar conn sourcespb.GitHubExperimental\n\terr = anypb.UnmarshalTo(connection, &conn, proto.UnmarshalOptions{})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error unmarshalling connection: %w\", err)\n\t}\n\ts.conn = &conn\n\ts.conn.Repository, err = s.normalizeRepo(s.conn.Repository)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error normalizing repo: %w\", err)\n\t}\n\n\t// Get the token from the connection\n\ttoken := s.conn.GetToken()\n\tif token == \"\" {\n\t\treturn fmt.Errorf(\"token is required for GitHub Experimental source\")\n\t}\n\n\t// Redact token from logs for security\n\tlog.RedactGlobally(token)\n\n\t// Create authenticated connector using the TokenConnector pattern\n\tconnector, err := githubsource.NewTokenConnector(\n\t\taCtx,\n\t\tcloudV3Endpoint, // API endpoint\n\t\ttoken,           // GitHub token\n\t\t\"\",              // clonePath (empty for default)\n\t\ttrue,            // authInUrl\n\t\tfunc(ctx context.Context, err error) bool {\n\t\t\t// Simple rate limit handler - can be enhanced later\n\t\t\treturn false\n\t\t},\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not create GitHub connector: %w\", err)\n\t}\n\ts.connector = connector\n\n\ts.repoInfoCache = newRepoInfoCache()\n\n\tcfg := &git.Config{\n\t\tSourceName:   s.name,\n\t\tJobID:        s.jobID,\n\t\tSourceID:     s.sourceID,\n\t\tSourceType:   s.Type(),\n\t\tVerify:       s.verify,\n\t\tSkipBinaries: false,\n\t\tSkipArchives: false,\n\t\tConcurrency:  concurrency,\n\t\tSourceMetadataFunc: func(file, email, commit, timestamp, repository, repositoryLocalPath string, line int64) *source_metadatapb.MetaData {\n\t\t\treturn &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Github{\n\t\t\t\t\tGithub: &source_metadatapb.Github{\n\t\t\t\t\t\tCommit:     sanitizer.UTF8(commit),\n\t\t\t\t\t\tFile:       sanitizer.UTF8(file),\n\t\t\t\t\t\tEmail:      sanitizer.UTF8(email),\n\t\t\t\t\t\tRepository: sanitizer.UTF8(repository),\n\t\t\t\t\t\tLink:       giturl.GenerateLink(repository, commit, file, line),\n\t\t\t\t\t\tTimestamp:  sanitizer.UTF8(timestamp),\n\t\t\t\t\t\tLine:       line,\n\t\t\t\t\t\tVisibility: s.visibilityOf(aCtx, repository),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t\tUseCustomContentWriter: s.useCustomContentWriter,\n\t}\n\ts.git = git.NewGit(cfg)\n\n\treturn nil\n}\n\nfunc (s *Source) visibilityOf(ctx context.Context, repoURL string) source_metadatapb.Visibility {\n\t// It isn't possible to get the visibility of a wiki.\n\t// We must use the visibility of the corresponding repository.\n\tif strings.HasSuffix(repoURL, \".wiki.git\") {\n\t\trepoURL = strings.TrimSuffix(repoURL, \".wiki.git\") + \".git\"\n\t}\n\n\trepoInfo, ok := s.repoInfoCache.get(repoURL)\n\tif !ok {\n\t\t// This should never happen.\n\t\terr := fmt.Errorf(\"no repoInfo for URL: %s\", repoURL)\n\t\tctx.Logger().Error(err, \"failed to get repository visibility\")\n\t\treturn source_metadatapb.Visibility_unknown\n\t}\n\n\treturn repoInfo.visibility\n}\n\n// Chunks emits chunks of bytes over a channel.\nfunc (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, targets ...sources.ChunkingTarget) error {\n\tif s.conn.ObjectDiscovery {\n\t\terr := s.EnumerateAndScanAllObjects(ctx, chunksChan)\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc getRepoURLParts(repoURLString string) (string, []string, error) {\n\t// Support ssh and https URLs.\n\trepoURL, err := git.GitURLParse(repoURLString)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\t// Remove the user information.\n\t// e.g., `git@github.com` -> `github.com`\n\tif repoURL.User != nil {\n\t\trepoURL.User = nil\n\t}\n\n\turlString := repoURL.String()\n\ttrimmedURL := strings.TrimPrefix(urlString, repoURL.Scheme+\"://\")\n\ttrimmedURL = strings.TrimSuffix(trimmedURL, \".git\")\n\turlParts := strings.Split(trimmedURL, \"/\")\n\n\t// Validate\n\tswitch len(urlParts) {\n\tcase 2:\n\t\t// gist.github.com/<gist_id>\n\t\tif !strings.EqualFold(urlParts[0], \"gist.github.com\") {\n\t\t\terr = fmt.Errorf(\"failed to parse repository or gist URL (%s): 2 path segments are only expected if the host is 'gist.github.com' ('gist.github.com', '<gist_id>')\", urlString)\n\t\t}\n\tcase 3:\n\t\t// github.com/<user>/repo>\n\t\t// gist.github.com/<user>/<gist_id>\n\t\t// github.company.org/<user>/repo>\n\t\t// github.company.org/gist/<gist_id>\n\tcase 4:\n\t\t// github.company.org/gist/<user/<id>\n\t\tif !strings.EqualFold(urlParts[1], \"gist\") || (strings.EqualFold(urlParts[0], \"github.com\") && strings.EqualFold(urlParts[1], \"gist\")) {\n\t\t\terr = fmt.Errorf(\"failed to parse repository or gist URL (%s): 4 path segments are only expected if the host isn't 'github.com' and the path starts with 'gist' ('github.example.com', 'gist', '<owner>', '<gist_id>')\", urlString)\n\t\t}\n\tdefault:\n\t\terr = fmt.Errorf(\"invalid repository or gist URL (%s): length of URL segments should be between 2 and 4, not %d (%v)\", urlString, len(urlParts), urlParts)\n\t}\n\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\treturn urlString, urlParts, nil\n}\n"
  },
  {
    "path": "pkg/sources/github_experimental/object_discovery.go",
    "content": "package github_experimental\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/google/go-github/v67/github\"\n\t\"github.com/k0kubun/go-ansi\"\n\t\"github.com/schollz/progressbar/v3\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\n// Assumption: sleeping for 60 seconds is enough to reset the secondary rate limit\n// see https://docs.github.com/en/graphql/overview/rate-limits-and-node-limits-for-the-graphql-api#secondary-rate-limits\nconst secondaryRateLimitSleep = 60\n\n// Assumption: on average, a fork contributes 0.1% additional commits\nconst forkCommitMultiplier = 0.001\n\n// Threshold for estimated Short SHA-1 hash collisions (default to 1...so basically none)\n// as calculated using the Birthday Paradox\n// Adjust this to a higher value if you're willing to accept more collisions (and shorter runtime).\nvar collisionThreshold float64\n\n// Starting character length (4 is the minimum required by git)\nconst startingCharLen = 4\n\n// Max character length (6 is the default maximum)\n// 6 chars == 16M possibilities --> which will take 18k-55k queries.\n// that's really the max that's tolerable since it will take a long time to run.\n// If you increase this to accommodate a MASSIVE repository, it will take a long time to run.\nconst maxCharLen = 6\n\n// Starting GraphQL query chunk size.\n// Max that worked was 900.\n// 350 is a safe starting point.\nconst maxChunkSize = 900\nconst initialChunkSize = 350\n\n// Max number of commits to fetch from the repository in one command\n// ex: git fetch origin <commit1> <commit2> ... <commit1000>\nconst gitFetchMax = 1000\n\n// Constants for commit types\nconst (\n\tinvalidCommit     = \"invalid\"\n\tvalidHiddenCommit = \"valid_hidden\"\n)\n\ntype backoff struct {\n\tvalue              float64\n\tdecreasePercentage float64\n\tincreasePercentage float64\n\tsuccessThreshold   int\n\tsuccessCount       int\n}\n\nfunc newBackoff(initialValue, decreasePercentage, increasePercentage float64, successThreshold int) *backoff {\n\treturn &backoff{\n\t\tvalue:              initialValue,\n\t\tdecreasePercentage: decreasePercentage,\n\t\tincreasePercentage: increasePercentage,\n\t\tsuccessThreshold:   successThreshold,\n\t}\n}\n\nfunc (b *backoff) errorOccurred() float64 {\n\tb.value -= b.value * (b.decreasePercentage / 100)\n\tb.successCount = 0 // Reset success count on error\n\tif b.value < 100 {\n\t\tb.value = 100\n\t}\n\treturn b.value\n}\n\nfunc (b *backoff) successOccurred() float64 {\n\tb.successCount++\n\tif b.successCount >= b.successThreshold {\n\t\tb.value += b.value * (b.increasePercentage / 100)\n\t\tb.successCount = 0 // Reset success count after increasing the value\n\t}\n\tif b.value > maxChunkSize {\n\t\tb.value = maxChunkSize\n\t}\n\treturn b.value\n}\n\nfunc (b *backoff) getValue() int {\n\treturn int(b.value)\n}\n\nfunc getForksCount(ctx context.Context, client *github.Client, owner, repoName string) (int, error) {\n\trepo, _, err := client.Repositories.Get(ctx, owner, repoName)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn repo.GetForksCount(), nil\n}\n\n// runGitCommand runs a git command\nfunc runGitCommand(args []string) ([]byte, error) {\n\tcmd := exec.Command(\"git\", args...)\n\tout, err := cmd.CombinedOutput()\n\treturn out, err\n}\n\nfunc getExistingHashes(path string) ([]string, error) {\n\tvar hashes []string\n\tgitArgs := []string{\n\t\t\"-C\",\n\t\tpath,\n\t\t\"--work-tree\",\n\t\tpath,\n\t\t\"cat-file\",\n\t\t\"--batch-check\",\n\t\t\"--batch-all-objects\",\n\t}\n\toutputBytes, err := runGitCommand(gitArgs)\n\tif err != nil {\n\t\treturn hashes, err\n\t}\n\n\toutput := string(outputBytes)\n\tlines := strings.Split(output, \"\\n\")\n\tfor _, line := range lines {\n\t\tif len(line) > 0 {\n\t\t\tparts := strings.Fields(line)\n\t\t\tif len(parts) > 0 {\n\t\t\t\thashes = append(hashes, parts[0])\n\t\t\t}\n\t\t}\n\t}\n\treturn hashes, nil\n}\n\n// calculateUsedKeySet Estimates the total used key set -\n// meaning how many used hashes are in the repository.\nfunc calculateUsedKeySet(commitCount, forksCount int) int {\n\t// Calculate total known key set\n\tcommits := float64(commitCount)\n\tforks := float64(forksCount)\n\tknownKeySet := (commits + (commits * forkCommitMultiplier * forks))\n\n\treturn int(knownKeySet)\n}\n\n// Estimate the number of collisions using the Birthday Paradox\nfunc estimateCollisions(keySpace, knownKeySet int) float64 {\n\tkeySpaceF := float64(keySpace)\n\tknownKeySetF := float64(knownKeySet)\n\treturn (knownKeySetF * (knownKeySetF - 1)) / (2 * keySpaceF)\n}\n\nfunc getShortShaLen(knownKeySet int) int {\n\t// Calculate the length of the short SHA-1 hash\n\t// This is the minimum length required to avoid collisions\n\t// in the estimated known key set\n\tshortShaLen := startingCharLen\n\tkeySpace := 1 << (shortShaLen * 4)\n\tcollisions := estimateCollisions(keySpace, knownKeySet)\n\tfor collisions > collisionThreshold {\n\t\tif shortShaLen >= maxCharLen {\n\t\t\tbreak\n\t\t}\n\t\tshortShaLen++\n\t\tkeySpace = 1 << (shortShaLen * 4)\n\t\tcollisions = estimateCollisions(keySpace, knownKeySet)\n\t}\n\n\treturn shortShaLen\n}\n\n// Generate all possible min commit hashes\nfunc generateShortSHAStrings(charLen int) []string {\n\thexDigits := \"0123456789abcdef\"\n\tvar hexStrings []string\n\tvar generateCombinations func(prefix string, length int)\n\n\tgenerateCombinations = func(prefix string, length int) {\n\t\tif length == 0 {\n\t\t\thexStrings = append(hexStrings, prefix)\n\t\t\treturn\n\t\t}\n\t\tfor _, digit := range hexDigits {\n\t\t\tgenerateCombinations(prefix+string(digit), length-1)\n\t\t}\n\t}\n\n\tgenerateCombinations(\"\", charLen)\n\treturn hexStrings\n}\n\n// Write commits to disk\nfunc writeCommitsToDisk(commits []string, commitsType, folder string) error {\n\tfilename := fmt.Sprintf(\"%s/%s.txt\", folder, commitsType)\n\n\t// Open file in append mode, create if it doesn't exist\n\tfile, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer file.Close()\n\n\tfor _, commit := range commits {\n\t\tif _, err := file.WriteString(commit + \"\\n\"); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// Read commits from disk\nfunc readCommitsFromDisk(commitsType, folder string) ([]string, error) {\n\tfilename := fmt.Sprintf(\"%s/%s.txt\", folder, commitsType)\n\tif _, err := os.Stat(filename); os.IsNotExist(err) {\n\t\treturn nil, nil\n\t}\n\n\tdata, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlines := strings.Split(string(data), \"\\n\")\n\tvar commits []string\n\tfor _, line := range lines {\n\t\tif line != \"\" {\n\t\t\tcommits = append(commits, strings.TrimSpace(line))\n\t\t}\n\t}\n\treturn removeNewlineAndUnique(commits), nil\n}\n\n// Remove newlines from commits and make them unique\nfunc removeNewlineAndUnique(commits []string) []string {\n\tcommitMap := make(map[string]struct{})\n\tfor _, commit := range commits {\n\t\tcleanCommit := strings.TrimSpace(commit)\n\t\tcommitMap[cleanCommit] = struct{}{}\n\t}\n\tvar uniqueCommits []string\n\tfor commit := range commitMap {\n\t\tuniqueCommits = append(uniqueCommits, commit)\n\t}\n\treturn uniqueCommits\n}\n\n// Remove commits that are already in the existing_commits list\nfunc removeByShortSHA(existingCommits, newCommits []string) []string {\n\texistingSet := make(map[string]struct{})\n\tfor _, commit := range existingCommits {\n\t\texistingSet[commit] = struct{}{}\n\t}\n\tvar filteredCommits []string\n\tfor _, commit := range newCommits {\n\t\tif _, exists := existingSet[commit]; !exists {\n\t\t\tfilteredCommits = append(filteredCommits, commit)\n\t\t}\n\t}\n\treturn filteredCommits\n}\n\n// Remove commits that are already in the existing_commits list (by char_len)\nfunc removeBySHA(existingCommits, newCommits []string, charLen int) []string {\n\texistingSet := make(map[string]struct{})\n\tfor _, commit := range existingCommits {\n\t\tshortSHA := commit\n\t\tif len(commit) > charLen {\n\t\t\tshortSHA = commit[:charLen]\n\t\t}\n\t\texistingSet[shortSHA] = struct{}{}\n\t}\n\tvar filteredCommits []string\n\tfor _, commit := range newCommits {\n\t\tshortSHA := commit\n\t\tif len(commit) > charLen {\n\t\t\tshortSHA = commit[:charLen]\n\t\t}\n\t\tif _, exists := existingSet[shortSHA]; !exists {\n\t\t\tfilteredCommits = append(filteredCommits, commit)\n\t\t}\n\t}\n\treturn filteredCommits\n}\n\nfunc processCommits(ctx context.Context, apiClient *github.Client, needsProcessing []string, owner, repo, path string) {\n\trepoCtx := context.WithValue(ctx, \"repo\", repo)\n\n\tstartingSize := float64(len(needsProcessing))\n\tqueryChunkSize := newBackoff(initialChunkSize, 10, 10, 1)\n\n\t// Initialize the progress bar for commit processing\n\tbar := progressbar.NewOptions(int(startingSize),\n\t\tprogressbar.OptionSetDescription(\"[green]Processing commits[reset]\"),\n\t\tprogressbar.OptionSetWriter(ansi.NewAnsiStderr()),\n\t\tprogressbar.OptionEnableColorCodes(true),\n\t)\n\n\tfor len(needsProcessing) > 0 {\n\t\tif len(needsProcessing) < queryChunkSize.getValue() {\n\t\t\tqueryChunkSize.value = float64(len(needsProcessing))\n\t\t}\n\t\tchunkSize := queryChunkSize.getValue()\n\t\tchunk := needsProcessing[:chunkSize]\n\t\tneedsProcessing = needsProcessing[chunkSize:]\n\n\t\tcommitData, err := checkHashes(repoCtx, apiClient, owner, repo, chunk)\n\t\tif err != nil {\n\t\t\trepoCtx.Logger().V(2).Info(\"Temporary error occurred in guessing commits\", \"error\", err)\n\t\t\t// Prepend the failed chunk to the FRONT of the queue for immediate retry\n\t\t\t// This ensures we retry the same hashes instead of moving to the next batch\n\t\t\tneedsProcessing = append(chunk, needsProcessing...)\n\t\t\tqueryChunkSize.errorOccurred()\n\t\t\tif strings.Contains(err.Error(), \"You have exceeded a secondary rate limit\") {\n\t\t\t\trepoCtx.Logger().V(2).Info(\"Reached secondary GitHub Rate Limit. Sleeping for 60 seconds.\")\n\t\t\t\ttime.Sleep(secondaryRateLimitSleep * time.Second)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tpercentCompleted := (1 - (float64(len(needsProcessing)) / startingSize)) * 100\n\n\t\trepoCtx.Logger().V(2).Info(\"Progress\", \"percent_completed\", percentCompleted, \"needs_processing\", len(needsProcessing))\n\n\t\tqueryChunkSize.successOccurred()\n\t\terr = writeCommitsToDisk(commitData[validHiddenCommit], validHiddenCommit, path)\n\t\tif err != nil {\n\t\t\trepoCtx.Logger().V(2).Info(\"Failed to write valid hidden commits to disk\", \"error\", err)\n\t\t}\n\t\terr = writeCommitsToDisk(commitData[invalidCommit], invalidCommit, path)\n\t\tif err != nil {\n\t\t\trepoCtx.Logger().V(2).Info(\"Failed to write invalid commits to disk\", \"error\", err)\n\t\t}\n\n\t\t// Update the progress bar\n\t\t_ = bar.Add(chunkSize)\n\t}\n\n\t// Finish the progress bar\n\t_ = bar.Finish()\n}\n\ntype commitData struct {\n\tOID string `json:\"oid\"`\n}\n\ntype responseData struct {\n\tData struct {\n\t\tRepository map[string]commitData `json:\"repository\"`\n\t} `json:\"data\"`\n\tErrors []struct {\n\t\tMessage string `json:\"message\"`\n\t} `json:\"errors\"`\n\tMessage string `json:\"message\"`\n}\n\nfunc checkHashes(ctx context.Context, client *github.Client, owner, repo string, hashes []string) (map[string][]string, error) {\n\ttestCases := \"\"\n\tfor _, h := range hashes {\n\t\ttestCase := fmt.Sprintf(`\n        commit%s: object(expression: \"%s\") {\n          ... on Commit {\n            oid\n          }\n        }\n      `, h, h)\n\t\ttestCases += testCase\n\t}\n\n\tquery := fmt.Sprintf(`\n      query {\n        repository(owner: \"%s\", name: \"%s\") {\n          %s\n        }\n      }\n    `, owner, repo, testCases)\n\n\theaders := map[string]string{\n\t\t\"Content-Type\":          \"application/json\",\n\t\t\"Github-Verified-Fetch\": \"true\",\n\t\t\"X-Requested-With\":      \"XMLHttpRequest\",\n\t\t\"Accept-Language\":       \"en-US,en;q=0.9\",\n\t\t\"Priority\":              \"u=1, i\",\n\t}\n\n\trequestBody, err := json.Marshal(map[string]string{\"query\": query})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to marshal request body: %w\", err)\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, \"POST\", \"https://api.github.com/graphql\", bytes.NewBuffer(requestBody))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create request: %w\", err)\n\t}\n\n\tfor key, value := range headers {\n\t\treq.Header.Set(key, value)\n\t}\n\n\t// Use the authenticated HTTP client from the GitHub API client\n\t// This client already has the Bearer token configured via OAuth2 transport\n\tresp, err := client.Client().Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"python request error: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read response body: %w\", err)\n\t}\n\n\tvar data responseData\n\tif err := json.Unmarshal(body, &data); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal response: %w\", err)\n\t}\n\n\tif len(data.Errors) > 0 {\n\t\treturn nil, fmt.Errorf(\"%s (GitHub Request Error)\", strings.Split(data.Errors[0].Message, \".\")[0])\n\t}\n\tif data.Message != \"\" {\n\t\treturn nil, fmt.Errorf(\"%s (GitHub Request Error)\", strings.Split(data.Message, \".\")[0])\n\t}\n\n\tcommits := data.Data.Repository\n\n\tvalid_cfor := []string{}\n\tinvalid := []string{}\n\n\tfor commit, value := range commits {\n\t\tcommit = strings.Replace(commit, \"commit\", \"\", 1)\n\t\tif value.OID == \"{}\" || value.OID == \"\" {\n\t\t\tinvalid = append(invalid, commit)\n\t\t} else {\n\t\t\tvalid_cfor = append(valid_cfor, value.OID)\n\t\t}\n\t}\n\n\tres := map[string][]string{\n\t\tvalidHiddenCommit: valid_cfor,\n\t\tinvalidCommit:     invalid,\n\t}\n\n\treturn res, nil\n}\n\n// createBatches divides a slice into batches of a specified size\nfunc createBatches(items []string, batchSize int) <-chan []string {\n\tout := make(chan []string)\n\tgo func() {\n\t\tdefer close(out)\n\t\titemsCopy := append([]string(nil), items...)\n\t\tfor len(itemsCopy) > 0 {\n\t\t\tend := batchSize\n\t\t\tif len(itemsCopy) < batchSize {\n\t\t\t\tend = len(itemsCopy)\n\t\t\t}\n\t\t\tbatch := itemsCopy[:end]\n\t\t\titemsCopy = itemsCopy[end:]\n\t\t\tout <- batch\n\t\t}\n\t}()\n\treturn out\n}\n\n// downloadPatches fetches and checks out cfor commits\nfunc downloadPatches(valid_cfor []string, path string) error {\n\t// Download all patches\n\tfor batch := range createBatches(valid_cfor, gitFetchMax) {\n\t\tgitArgs := []string{\n\t\t\t\"-C\",\n\t\t\tpath,\n\t\t\t\"--work-tree\",\n\t\t\tpath,\n\t\t\t\"fetch\",\n\t\t\t\"--quiet\",\n\t\t\t\"origin\",\n\t\t}\n\t\tgitArgs = append(gitArgs, batch...)\n\t\t_, err := runGitCommand(gitArgs)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Checkout each commit\n\t// Note: path and worktree are needed or else git will do something funny with the actual cwd\n\tfor _, commit := range valid_cfor {\n\t\tbranchName := fmt.Sprintf(\"_%s\", commit)\n\t\tgitArgs := []string{\n\t\t\t\"-C\",\n\t\t\tpath,\n\t\t\t\"--work-tree\",\n\t\t\tpath,\n\t\t\t\"checkout\",\n\t\t\t\"--quiet\",\n\t\t\t\"-b\",\n\t\t\tbranchName,\n\t\t\tcommit,\n\t\t}\n\t\t_, err := runGitCommand(gitArgs)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to checkout commit %s: %v\", commit, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// scanHiddenData scans hidden data (and non-hidden data) for secrets in a GitHub repository\nfunc (s *Source) EnumerateAndScanAllObjects(ctx context.Context, chunksChan chan *sources.Chunk) error {\n\t// set collision threshold to user input\n\tcollisionThreshold = float64(s.conn.CollisionThreshold)\n\n\t// parse the repo URL\n\trepoURL, urlParts, err := getRepoURLParts(s.conn.Repository)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get repo URL parts: %w\", err)\n\t}\n\n\t// read in the owner and repo name\n\towner := urlParts[1]\n\trepoName := urlParts[2]\n\n\t// get repo metadata and store in cacheRepoInfo\n\trepoCtx := context.WithValue(ctx, \"repo\", owner+\"/\"+repoName)\n\tghRepo, _, err := s.connector.APIClient().Repositories.Get(repoCtx, owner, repoName)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to fetch repository: %w\", err)\n\t}\n\ts.cacheRepoInfo(ghRepo)\n\n\t// Create a folder housing the repo and commit data\n\tuserHomeDir, err := os.UserHomeDir()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get user home directory: %w\", err)\n\t}\n\n\tfolderPath := userHomeDir + \"/.trufflehog/\" + owner + \"/\" + repoName\n\terr = os.MkdirAll(folderPath, 0755)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create .trufflehog folder in user's home directory: %w\", err)\n\t}\n\n\t// get the number of forks\n\tforksCount, err := getForksCount(repoCtx, s.connector.APIClient(), owner, repoName)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get forks count: %w\", err)\n\t}\n\n\t// download the repo using the authenticated connector\n\tpath, repo, err := s.connector.Clone(ctx, repoURL)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to clone the repository: %w\", err)\n\t}\n\n\tdefer os.RemoveAll(path)\n\n\t// count total valid hashes\n\tvalidHashes, err := getExistingHashes(path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to enumerate existing commit object hashes: %w\", err)\n\t}\n\n\t// Calculate estimated used key set\n\testimatedUsedKeySet := calculateUsedKeySet(len(validHashes), forksCount)\n\n\t// Calculate Short SHA-1 Length for Unambiguous Commit Identifiers\n\tshortShaLen := getShortShaLen(estimatedUsedKeySet)\n\n\t// Log stats\n\trepoCtx.Logger().V(2).Info(\"Estimated used keys\", \"count\", estimatedUsedKeySet)\n\trepoCtx.Logger().V(2).Info(\"Target Short SHA-1 length\", \"length\", shortShaLen)\n\trepoCtx.Logger().V(2).Info(\"Estimated collisions\", \"count\", estimateCollisions(1<<(shortShaLen*4), estimatedUsedKeySet))\n\n\t// Read in existing commits (if any)\n\tvalidHiddenCommits, err := readCommitsFromDisk(validHiddenCommit, folderPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read valid hidden commits from disk: %w\", err)\n\t}\n\n\tinvalidCommits, err := readCommitsFromDisk(invalidCommit, folderPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read invalid commits from disk: %w\", err)\n\t}\n\n\t// Generate all possible commit hashes using the short SHA-1 length\n\tpossibleCommits := generateShortSHAStrings(shortShaLen)\n\n\t// Remove commits that are already used by the repo or previously calculated (on restart)\n\tpossibleCommits = removeBySHA(validHashes, possibleCommits, shortShaLen)\n\tpossibleCommits = removeBySHA(validHiddenCommits, possibleCommits, shortShaLen)\n\tpossibleCommits = removeByShortSHA(invalidCommits, possibleCommits)\n\n\t// Guess all possible commit hashes\n\tprocessCommits(ctx, s.connector.APIClient(), possibleCommits, owner, repoName, folderPath)\n\n\t// Read in the new commits\n\tvalidHiddenCommits, err = readCommitsFromDisk(validHiddenCommit, folderPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read valid hidden commits from disk: %w\", err)\n\t}\n\n\t// Download commit hashes and checkout into branches (only way scanner will pick them up)\n\terr = downloadPatches(validHiddenCommits, path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to download patches: %w\", err)\n\t}\n\n\t// Scan git for secrets\n\trepoCtx.Logger().V(2).Info(\"scanning for secrets in repo\", \"repo_url\", repoURL)\n\tstart := time.Now()\n\terr = s.git.ScanRepo(ctx, repo, path, s.scanOptions, sources.ChanReporter{Ch: chunksChan})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to scan repo: %w\", err)\n\t}\n\tduration := time.Since(start)\n\trepoCtx.Logger().V(2).Info(\"scanned 1 repo for hidden data\", \"duration_seconds\", duration)\n\n\t// Remove the folder if user requests\n\tif s.conn.DeleteCachedData {\n\t\terr = os.RemoveAll(folderPath)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to delete cached data: %w\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/sources/github_experimental/repo.go",
    "content": "package github_experimental\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/google/go-github/v67/github\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/giturl\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n)\n\ntype repoInfoCache struct {\n\tmu    sync.RWMutex\n\tcache map[string]repoInfo\n}\n\nfunc newRepoInfoCache() repoInfoCache {\n\treturn repoInfoCache{\n\t\tcache: make(map[string]repoInfo),\n\t}\n}\n\nfunc (r *repoInfoCache) put(repoURL string, info repoInfo) {\n\tr.mu.Lock()\n\tdefer r.mu.Unlock()\n\tr.cache[repoURL] = info\n}\n\nfunc (r *repoInfoCache) get(repoURL string) (repoInfo, bool) {\n\tr.mu.RLock()\n\tdefer r.mu.RUnlock()\n\n\tinfo, ok := r.cache[repoURL]\n\treturn info, ok\n}\n\ntype repoInfo struct {\n\towner      string\n\tname       string\n\tfullName   string\n\thasWiki    bool // the repo is _likely_ to have a wiki (see the comment on wikiIsReachable func).\n\tsize       int\n\tvisibility source_metadatapb.Visibility\n}\n\nfunc (s *Source) cacheRepoInfo(r *github.Repository) {\n\tinfo := repoInfo{\n\t\towner:    r.GetOwner().GetLogin(),\n\t\tname:     r.GetName(),\n\t\tfullName: r.GetFullName(),\n\t\thasWiki:  r.GetHasWiki(),\n\t\tsize:     r.GetSize(),\n\t}\n\tif r.GetPrivate() {\n\t\tinfo.visibility = source_metadatapb.Visibility_private\n\t} else {\n\t\tinfo.visibility = source_metadatapb.Visibility_public\n\t}\n\ts.repoInfoCache.put(r.GetCloneURL(), info)\n}\n\nfunc (s *Source) normalizeRepo(repo string) (string, error) {\n\t// If it's a full URL (has protocol), normalize it\n\tif regexp.MustCompile(`^[a-z]+://`).MatchString(repo) {\n\t\treturn giturl.NormalizeGithubRepo(repo)\n\t}\n\t// If it's a repository name (contains / but not http), convert to full URL first\n\tif strings.Contains(repo, \"/\") && !regexp.MustCompile(`^[a-z]+://`).MatchString(repo) {\n\t\tfullURL := \"https://github.com/\" + repo\n\t\treturn giturl.NormalizeGithubRepo(fullURL)\n\t}\n\n\treturn \"\", fmt.Errorf(\"no repositories found for %s\", repo)\n}\n"
  },
  {
    "path": "pkg/sources/gitlab/gitlab.go",
    "content": "package gitlab\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/feature\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/giturl\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/log\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sanitizer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git\"\n\n\tgogit \"github.com/go-git/go-git/v5\"\n\t\"github.com/gobwas/glob\"\n\tgitlab \"gitlab.com/gitlab-org/api/client-go\"\n\t\"golang.org/x/oauth2\"\n\t\"golang.org/x/sync/errgroup\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n)\n\nconst SourceType = sourcespb.SourceType_SOURCE_TYPE_GITLAB\n\n// This is the URL for gitlab hosted at gitlab.com\nconst gitlabBaseURL = \"https://gitlab.com/\"\n\ntype Source struct {\n\tname     string\n\tsourceID sources.SourceID\n\tjobID    sources.JobID\n\tverify   bool\n\n\tauthMethod string\n\tuser       string\n\tpassword   string\n\ttoken      string\n\turl        string\n\trepos      []string\n\tgroupIds   []string\n\n\t// These lists are checked both during enumeration and when ChunkUnit is called. This means that if they're modified\n\t// between enumeration and individual unit scans, units will be scanned only if they pass the filter during\n\t// enumeration and also if they pass the filter during unit scanning. This means that units can be \"removed\" from\n\t// the enumerated list post-enumeration by modifying the filters, but they can never be added post-enumeration.\n\tignoreRepos  []string\n\tincludeRepos []string\n\n\t// This is an experimental flag used to investigate some suspicious behavior we've seen with very large GitLab\n\t// organizations that have lots of group sharing.\n\tenumerateSharedProjects bool\n\n\tuseCustomContentWriter bool\n\tgit                    *git.Git\n\tscanOptions            *git.ScanOptions\n\n\tresumeInfoSlice []string\n\tresumeInfoMutex sync.Mutex\n\tsources.Progress\n\n\tjobPool *errgroup.Group\n\tsources.CommonSourceUnitUnmarshaller\n\n\tuseAuthInUrl bool\n\n\tclonePath string\n\tnoCleanup bool\n\n\tprintLegacyJSON bool\n\n\tprojectsPerPage int64\n\n\t// cache of repo URL to project info, used when generating metadata for chunks\n\trepoToProjCache repoToProjectCache\n}\n\n// WithCustomContentWriter sets the useCustomContentWriter flag on the source.\nfunc (s *Source) WithCustomContentWriter() { s.useCustomContentWriter = true }\n\n// Ensure the Source satisfies the interfaces at compile time.\nvar _ sources.Source = (*Source)(nil)\nvar _ sources.SourceUnitUnmarshaller = (*Source)(nil)\nvar _ sources.Validator = (*Source)(nil)\nvar _ sources.SourceUnitEnumChunker = (*Source)(nil)\n\n// Type returns the type of source.\n// It is used for matching source types in configuration and job input.\nfunc (s *Source) Type() sourcespb.SourceType {\n\treturn SourceType\n}\n\nfunc (s *Source) SourceID() sources.SourceID {\n\treturn s.sourceID\n}\n\nfunc (s *Source) JobID() sources.JobID {\n\treturn s.jobID\n}\n\n// globRepoFilter is a wrapper around cache.Cache that filters out repos\n// based on include and exclude globs.\ntype globRepoFilter struct {\n\tinclude, exclude []glob.Glob\n}\n\nfunc newGlobRepoFilter(include, exclude []string, onCompileErr func(err error, pattern string)) *globRepoFilter {\n\tincludeGlobs := make([]glob.Glob, 0, len(include))\n\texcludeGlobs := make([]glob.Glob, 0, len(exclude))\n\tfor _, ig := range include {\n\t\tg, err := glob.Compile(ig)\n\t\tif err != nil {\n\t\t\tonCompileErr(err, ig)\n\t\t\tcontinue\n\t\t}\n\t\tincludeGlobs = append(includeGlobs, g)\n\t}\n\tfor _, eg := range exclude {\n\t\tg, err := glob.Compile(eg)\n\t\tif err != nil {\n\t\t\tonCompileErr(err, eg)\n\t\t\tcontinue\n\t\t}\n\t\texcludeGlobs = append(excludeGlobs, g)\n\t}\n\treturn &globRepoFilter{include: includeGlobs, exclude: excludeGlobs}\n}\n\nfunc (c *globRepoFilter) ignoreRepo(s string) bool {\n\tfor _, g := range c.exclude {\n\t\tif g.Match(s) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (c *globRepoFilter) includeRepo(s string) bool {\n\tif len(c.include) == 0 {\n\t\treturn true\n\t}\n\n\tfor _, g := range c.include {\n\t\tif g.Match(s) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// Init returns an initialized Gitlab source.\nfunc (s *Source) Init(ctx context.Context, name string, jobId sources.JobID, sourceId sources.SourceID, verify bool, connection *anypb.Any, concurrency int) error {\n\ts.name = name\n\ts.sourceID = sourceId\n\ts.jobID = jobId\n\ts.verify = verify\n\ts.jobPool = &errgroup.Group{}\n\ts.jobPool.SetLimit(concurrency)\n\n\tif err := git.CmdCheck(); err != nil {\n\t\treturn err\n\t}\n\n\tvar conn sourcespb.GitLab\n\terr := anypb.UnmarshalTo(connection, &conn, proto.UnmarshalOptions{})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error unmarshalling connection: %w\", err)\n\t}\n\n\ts.repos = conn.GetRepositories()\n\ts.groupIds = conn.GetGroupIds()\n\ts.ignoreRepos = conn.GetIgnoreRepos()\n\ts.includeRepos = conn.GetIncludeRepos()\n\ts.enumerateSharedProjects = !conn.ExcludeProjectsSharedIntoGroups\n\ts.clonePath = conn.GetClonePath()\n\ts.noCleanup = conn.GetNoCleanup()\n\ts.printLegacyJSON = conn.GetPrintLegacyJson()\n\ts.projectsPerPage = feature.GitlabProjectsPerPage.Load()\n\n\tif s.projectsPerPage > 100 {\n\t\treturn fmt.Errorf(\"invalid config: maximum allowed projects per page for gitlab is 100\")\n\t}\n\n\t// configuration uses the inverse logic of the `useAuthInUrl` flag.\n\ts.useAuthInUrl = !conn.RemoveAuthInUrl\n\n\tctx.Logger().V(3).Info(\"setting ignore repos patterns\", \"patterns\", s.ignoreRepos)\n\tctx.Logger().V(3).Info(\"setting include repos patterns\", \"patterns\", s.includeRepos)\n\n\tswitch cred := conn.GetCredential().(type) {\n\tcase *sourcespb.GitLab_Token:\n\t\ts.authMethod = \"TOKEN\"\n\t\ts.token = cred.Token\n\t\tlog.RedactGlobally(s.token)\n\tcase *sourcespb.GitLab_Oauth:\n\t\ts.authMethod = \"OAUTH\"\n\t\ts.token = cred.Oauth.RefreshToken\n\t\tlog.RedactGlobally(s.token)\n\t\t// TODO: is it okay if there is no client id and secret? Might be an issue when marshalling config to proto\n\tcase *sourcespb.GitLab_BasicAuth:\n\t\ts.authMethod = \"BASIC_AUTH\"\n\t\ts.user = cred.BasicAuth.Username\n\t\ts.password = cred.BasicAuth.Password\n\t\t// We may need the password as a token if the user is using an access_token with basic auth.\n\t\ts.token = cred.BasicAuth.Password\n\t\tlog.RedactGlobally(cred.BasicAuth.Password)\n\tdefault:\n\t\treturn fmt.Errorf(\"invalid configuration given for source %q (%s)\", name, s.Type().String())\n\t}\n\n\ts.url, err = normalizeGitlabEndpoint(conn.Endpoint)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcfg := &git.Config{\n\t\tSourceName:   s.name,\n\t\tJobID:        s.jobID,\n\t\tSourceID:     s.sourceID,\n\t\tSourceType:   s.Type(),\n\t\tVerify:       s.verify,\n\t\tSkipBinaries: conn.GetSkipBinaries(),\n\t\tSkipArchives: conn.GetSkipArchives(),\n\t\tConcurrency:  concurrency,\n\t\tSourceMetadataFunc: func(file, email, commit, timestamp, repository, repositoryLocalPath string, line int64) *source_metadatapb.MetaData {\n\t\t\tgitlabMetadata := &source_metadatapb.Gitlab{\n\t\t\t\tCommit:              sanitizer.UTF8(commit),\n\t\t\t\tFile:                sanitizer.UTF8(file),\n\t\t\t\tEmail:               sanitizer.UTF8(email),\n\t\t\t\tRepository:          sanitizer.UTF8(repository),\n\t\t\t\tRepositoryLocalPath: sanitizer.UTF8(repositoryLocalPath),\n\t\t\t\tLink:                giturl.GenerateLink(repository, commit, file, line),\n\t\t\t\tTimestamp:           sanitizer.UTF8(timestamp),\n\t\t\t\tLine:                line,\n\t\t\t}\n\t\t\tproj, ok := s.repoToProjCache.get(repository)\n\t\t\tif ok {\n\t\t\t\tgitlabMetadata.ProjectId = int64(proj.id)\n\t\t\t\tgitlabMetadata.ProjectName = proj.name\n\t\t\t\tgitlabMetadata.ProjectOwner = proj.owner\n\t\t\t}\n\n\t\t\treturn &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Gitlab{\n\t\t\t\t\tGitlab: gitlabMetadata,\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t\tUseCustomContentWriter: s.useCustomContentWriter,\n\t\tAuthInUrl:              s.useAuthInUrl,\n\t}\n\ts.git = git.NewGit(cfg)\n\n\ts.repoToProjCache = repoToProjectCache{\n\t\tcache: make(map[string]*project),\n\t}\n\n\treturn nil\n}\n\n// Chunks emits chunks of bytes over a channel.\nfunc (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, targets ...sources.ChunkingTarget) error {\n\t// Start client.\n\tapiClient, err := s.newClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// If targets are provided, we're only scanning the data in those targets.\n\t// Otherwise, we're scanning all data.\n\t// This allows us to only scan the commit where a vulnerability was found.\n\tif len(targets) > 0 {\n\t\treturn s.scanTargets(ctx, apiClient, targets, chunksChan)\n\t}\n\n\tgitlabReposScanned.WithLabelValues(s.name).Set(0)\n\t// Get repo within target.\n\trepos, errs := normalizeRepos(s.repos)\n\tfor _, repoErr := range errs {\n\t\tctx.Logger().Info(\"error normalizing repo\", \"error\", repoErr)\n\t}\n\n\t// End early if we had errors getting specified repos but none were validated.\n\tif len(errs) > 0 && len(repos) == 0 {\n\t\treturn fmt.Errorf(\"all specified repos had validation issues, ending scan\")\n\t}\n\n\t// Get all repos if not specified.\n\tif len(repos) == 0 {\n\t\tctx.Logger().Info(\"no repositories configured, enumerating\")\n\t\tignoreRepo := buildIgnorer(s.includeRepos, s.ignoreRepos, func(err error, pattern string) {\n\t\t\tctx.Logger().Error(err, \"could not compile include/exclude repo glob\", \"glob\", pattern)\n\t\t})\n\t\treporter := sources.VisitorReporter{\n\t\t\tVisitUnit: func(ctx context.Context, unit sources.SourceUnit) error {\n\t\t\t\tid, _ := unit.SourceUnitID()\n\t\t\t\trepos = append(repos, id)\n\t\t\t\treturn ctx.Err()\n\t\t\t},\n\t\t}\n\n\t\tif err := s.listProjects(ctx, apiClient, ignoreRepo, reporter); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t} else {\n\t\tgitlabReposEnumerated.WithLabelValues(s.name).Set(float64(len(repos)))\n\t\t// ensure project details for specified repos are cached\n\t\t// this is required to populate metadata during chunking\n\t\tfor _, repo := range repos {\n\t\t\ts.ensureProjectInCache(ctx, repo)\n\t\t}\n\t}\n\n\ts.repos = repos\n\t// We must sort the repos so we can resume later if necessary.\n\tslices.Sort(s.repos)\n\n\treturn s.scanRepos(ctx, chunksChan)\n}\n\nfunc (s *Source) listProjects(ctx context.Context,\n\tapiClient *gitlab.Client,\n\tignoreProject func(string) bool,\n\tvisitor sources.UnitReporter) error {\n\tif len(s.groupIds) > 0 {\n\t\treturn s.getAllProjectReposInGroups(ctx, apiClient, ignoreProject, visitor)\n\t}\n\n\tif feature.UseSimplifiedGitlabEnumeration.Load() {\n\t\treturn s.getAllProjectReposV2(ctx, apiClient, ignoreProject, visitor)\n\t}\n\n\treturn s.getAllProjectRepos(ctx, apiClient, ignoreProject, visitor)\n}\n\nfunc (s *Source) scanTargets(ctx context.Context, client *gitlab.Client, targets []sources.ChunkingTarget, chunksChan chan *sources.Chunk) error {\n\tctx = context.WithValues(ctx, \"scan_type\", \"targeted\")\n\tfor _, tgt := range targets {\n\t\tif err := s.scanTarget(ctx, client, tgt, chunksChan); err != nil {\n\t\t\tctx.Logger().Error(err, \"error scanning target\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *Source) scanTarget(ctx context.Context, client *gitlab.Client, target sources.ChunkingTarget, chunksChan chan *sources.Chunk) error {\n\tmetaType, ok := target.QueryCriteria.GetData().(*source_metadatapb.MetaData_Gitlab)\n\tif !ok {\n\t\treturn fmt.Errorf(\"unable to cast metadata type for targeted scan\")\n\t}\n\tmeta := metaType.Gitlab\n\tprojID, sha := int(meta.GetProjectId()), meta.GetCommit()\n\tif projID == 0 || sha == \"\" {\n\t\treturn fmt.Errorf(\"project ID and commit SHA must be provided for targeted scan\")\n\t}\n\n\taCtx := context.WithValues(ctx, \"project_id\", projID, \"commit\", sha)\n\n\tdiffs, _, err := client.Commits.GetCommitDiff(projID, sha, new(gitlab.GetCommitDiffOptions), gitlab.WithContext(ctx))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error fetching diffs for commit %s: %w\", sha, err)\n\t}\n\n\tfor _, diff := range diffs {\n\t\tif diff.Diff == \"\" {\n\t\t\taCtx.Logger().V(4).Info(\"skipping empty diff\", \"file\", diff.NewPath)\n\t\t\tcontinue\n\t\t}\n\n\t\tchunk := &sources.Chunk{\n\t\t\tSourceType: s.Type(),\n\t\t\tSourceName: s.name,\n\t\t\tSourceID:   s.SourceID(),\n\t\t\tJobID:      s.JobID(),\n\t\t\tSecretID:   target.SecretID,\n\t\t\tData:       []byte(diff.Diff),\n\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Gitlab{Gitlab: meta},\n\t\t\t},\n\t\t\tSourceVerify: s.verify,\n\t\t}\n\n\t\tif err := common.CancellableWrite(ctx, chunksChan, chunk); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (s *Source) Validate(ctx context.Context) []error {\n\t// The client is only used to query Gitlab for a repo list - it's not used to actually clone anything. Thus, we\n\t// don't use it if there is a list of explicitly configured repos. However, constructing it validates that the\n\t// configured authentication method is sensible, so we'll do it here.\n\tapiClient, err := s.newClient()\n\tif err != nil {\n\t\treturn []error{err}\n\t}\n\n\t_, _, err = apiClient.Users.CurrentUser()\n\tif err != nil {\n\t\treturn []error{fmt.Errorf(\"gitlab authentication failed using method %v: %w\", s.authMethod, err)}\n\t}\n\n\texplicitlyConfiguredRepos, errs := normalizeRepos(s.repos)\n\n\tif len(explicitlyConfiguredRepos) > 0 {\n\t\tuser := s.user\n\t\tif user == \"\" {\n\t\t\tuser = \"placeholder\"\n\t\t}\n\n\t\t// We only check reachability for explicitly configured repositories. The purpose of source validation is to\n\t\t// help users validate their configuration files, and Gitlab telling us about repositories that it won't let us\n\t\t// access isn't a local configuration issue.\n\t\tfor _, r := range explicitlyConfiguredRepos {\n\t\t\tif err := git.PingRepoUsingToken(ctx, s.token, r, user); err != nil {\n\t\t\t\terr = fmt.Errorf(\"could not reach git repository %q: %w\", r, err)\n\t\t\t\terrs = append(errs, err)\n\t\t\t}\n\t\t}\n\n\t\tif len(s.ignoreRepos) > 0 {\n\t\t\terrs = append(\n\t\t\t\terrs,\n\t\t\t\tfmt.Errorf(\"both repositories and ignore patterns were explicitly configured; ignore patterns will not be used\"),\n\t\t\t)\n\t\t}\n\t}\n\n\tif len(explicitlyConfiguredRepos) > 0 || len(errs) > 0 {\n\t\treturn errs\n\t}\n\n\tignoreProject := buildIgnorer(s.includeRepos, s.ignoreRepos, func(err error, pattern string) {\n\t\terrs = append(errs, fmt.Errorf(\"could not compile include/exclude repo pattern %q: %w\", pattern, err))\n\t})\n\n\t// Query GitLab for the list of configured repos.\n\tvar repos []string\n\tvisitor := sources.VisitorReporter{\n\t\tVisitUnit: func(ctx context.Context, unit sources.SourceUnit) error {\n\t\t\tid, _ := unit.SourceUnitID()\n\t\t\trepos = append(repos, id)\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tif err := s.listProjects(ctx, apiClient, ignoreProject, visitor); err != nil {\n\t\terrs = append(errs, err)\n\t\treturn errs\n\t}\n\n\tif len(repos) == 0 {\n\t\terrs = append(errs, fmt.Errorf(\"ignore patterns excluded all projects\"))\n\t}\n\n\treturn errs\n}\n\nfunc (s *Source) newClient() (*gitlab.Client, error) {\n\t// Initialize a new api instance.\n\tswitch s.authMethod {\n\tcase \"OAUTH\":\n\t\tts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: s.token})\n\t\tapiClient, err := gitlab.NewAuthSourceClient(\n\t\t\tgitlab.OAuthTokenSource{TokenSource: ts},\n\t\t\tgitlab.WithBaseURL(s.url),\n\t\t\tgitlab.WithCustomRetryWaitMinMax(time.Second, 5*time.Second),\n\t\t\tgitlab.WithCustomRetryMax(3),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"could not create Gitlab OAUTH client for %q: %w\", s.url, err)\n\t\t}\n\t\treturn apiClient, nil\n\tcase \"BASIC_AUTH\":\n\t\tapiClient, err := gitlab.NewAuthSourceClient(\n\t\t\t&gitlab.PasswordCredentialsAuthSource{Username: s.user, Password: s.password},\n\t\t\tgitlab.WithBaseURL(s.url),\n\t\t\tgitlab.WithCustomRetryWaitMinMax(time.Second, 5*time.Second),\n\t\t\tgitlab.WithCustomRetryMax(3),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"could not create Gitlab BASICAUTH client for %q: %w\", s.url, err)\n\t\t}\n\t\t// If the user is using an access_token rather than a username/password, then basic auth\n\t\t// will not work. In this case, we test to see if basic auth would work, and if it does not,\n\t\t// we proceed with an OAuth client using the access_token (s.password) as the token.\n\t\t// At this point, s.token is already set to s.password\n\t\tif s.basicAuthSuccessful(apiClient) {\n\t\t\treturn apiClient, nil\n\t\t}\n\t\tfallthrough\n\tcase \"TOKEN\":\n\t\tts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: s.token})\n\t\tapiClient, err := gitlab.NewAuthSourceClient(\n\t\t\tgitlab.OAuthTokenSource{TokenSource: ts},\n\t\t\tgitlab.WithBaseURL(s.url),\n\t\t\tgitlab.WithCustomRetryWaitMinMax(time.Second, 5*time.Second),\n\t\t\tgitlab.WithCustomRetryMax(3),\n\t\t)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"could not create Gitlab TOKEN client for %q: %w\", s.url, err)\n\t\t}\n\t\treturn apiClient, nil\n\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid auth method %q\", s.authMethod)\n\t}\n}\n\nfunc (s *Source) basicAuthSuccessful(apiClient *gitlab.Client) bool {\n\tuser, resp, err := apiClient.Users.CurrentUser()\n\tif err != nil {\n\t\treturn false\n\t}\n\tif resp.StatusCode != 200 {\n\t\treturn false\n\t}\n\tif user != nil {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// getAllProjectRepos enumerates all GitLab projects using the provided API client.\n// The reporter is used to report the valid repository found for projects that are not ignored.\nfunc (s *Source) getAllProjectRepos(\n\tctx context.Context,\n\tapiClient *gitlab.Client,\n\tignoreRepo func(string) bool,\n\treporter sources.UnitReporter,\n) error {\n\tgitlabReposEnumerated.WithLabelValues(s.name).Set(0)\n\t// Projects without repo will get user projects, groups projects, and subgroup projects.\n\tuser, _, err := apiClient.Users.CurrentUser()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to authenticate using %s: %w\", s.authMethod, err)\n\t}\n\n\tuniqueProjects := make(map[int64]*gitlab.Project)\n\t// Record the projectsWithNamespace for logging.\n\tvar projectsWithNamespace []string\n\n\t// Used to filter out duplicate projects.\n\tprocessProjects := func(ctx context.Context, projList []*gitlab.Project) error {\n\t\tfor _, proj := range projList {\n\t\t\tctx := context.WithValues(ctx,\n\t\t\t\t\"project_id\", proj.ID,\n\t\t\t\t\"project_name\", proj.NameWithNamespace)\n\t\t\t// Skip projects we've already seen.\n\t\t\tif _, exists := uniqueProjects[proj.ID]; exists {\n\t\t\t\tctx.Logger().V(3).Info(\"skipping project\", \"reason\", \"ID already seen\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Skip projects configured to be ignored.\n\t\t\tif ignoreRepo(proj.PathWithNamespace) {\n\t\t\t\tctx.Logger().V(3).Info(\"skipping project\", \"reason\", \"ignored in config\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Record that we've seen this project.\n\t\t\tuniqueProjects[proj.ID] = proj\n\t\t\t// Report an error if we could not convert the project into a URL.\n\t\t\tif _, err := url.Parse(proj.HTTPURLToRepo); err != nil {\n\t\t\t\tctx.Logger().V(3).Info(\"skipping project\",\n\t\t\t\t\t\"reason\", \"URL parse failure\",\n\t\t\t\t\t\"url\", proj.HTTPURLToRepo,\n\t\t\t\t\t\"parse_error\", err)\n\n\t\t\t\terr = fmt.Errorf(\"could not parse url %q given by project: %w\", proj.HTTPURLToRepo, err)\n\t\t\t\tif err := reporter.UnitErr(ctx, err); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Report the unit.\n\t\t\tctx.Logger().V(3).Info(\"accepting project\")\n\t\t\ts.cacheGitlabProject(proj)\n\t\t\tunit := git.SourceUnit{Kind: git.UnitRepo, ID: proj.HTTPURLToRepo}\n\t\t\tgitlabReposEnumerated.WithLabelValues(s.name).Inc()\n\t\t\tprojectsWithNamespace = append(projectsWithNamespace, proj.NameWithNamespace)\n\t\t\tif err := reporter.UnitOk(ctx, unit); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\tconst (\n\t\torderBy = \"id\"\n\t)\n\t// Trufflehog default per page 100 unless set to other value through feature flag. If 0 provided in feature flag gitlab default it to 20\n\tlistOpts := gitlab.ListOptions{PerPage: s.projectsPerPage}\n\n\tprojectQueryOptions := &gitlab.ListProjectsOptions{OrderBy: gitlab.Ptr(orderBy), ListOptions: listOpts}\n\tfor {\n\t\tuserProjects, res, err := apiClient.Projects.ListUserProjects(user.ID, projectQueryOptions)\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"received error on listing user projects: %w\", err)\n\t\t\tif err := reporter.UnitErr(ctx, err); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tctx.Logger().V(3).Info(\"listed user projects\", \"count\", len(userProjects))\n\t\tif err := processProjects(ctx, userProjects); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tprojectQueryOptions.Page = res.NextPage\n\t\tif res.NextPage == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tlistGroupsOptions := gitlab.ListGroupsOptions{\n\t\tListOptions:  listOpts,\n\t\tAllAvailable: gitlab.Ptr(false), // This actually grabs public groups on public GitLab if set to true.\n\t\tTopLevelOnly: gitlab.Ptr(false),\n\t\tOwned:        gitlab.Ptr(false),\n\t}\n\n\tif s.url != gitlabBaseURL {\n\t\tlistGroupsOptions.AllAvailable = gitlab.Ptr(true)\n\t}\n\n\tctx.Logger().Info(\"beginning group enumeration\",\n\t\t\"list_options\", listOpts,\n\t\t\"all_available\", *listGroupsOptions.AllAvailable)\n\tgitlabGroupsEnumerated.WithLabelValues(s.name).Set(0)\n\n\tvar groups []*gitlab.Group\n\tfor {\n\t\tgroupList, res, err := apiClient.Groups.ListGroups(&listGroupsOptions)\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"received error on listing groups, you probably don't have permissions to do that: %w\", err)\n\t\t\tif err := reporter.UnitErr(ctx, err); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tctx.Logger().V(3).Info(\"listed groups\", \"count\", len(groupList))\n\t\tgroups = append(groups, groupList...)\n\t\tgitlabGroupsEnumerated.WithLabelValues(s.name).Add(float64(len(groupList)))\n\t\tlistGroupsOptions.Page = res.NextPage\n\t\tif res.NextPage == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tctx.Logger().Info(\"got groups\", \"group_count\", len(groups))\n\n\tfor _, group := range groups {\n\t\tctx := context.WithValue(ctx, \"group_id\", group.ID)\n\t\tlistGroupProjectOptions := &gitlab.ListGroupProjectsOptions{\n\t\t\tListOptions:      listOpts,\n\t\t\tOrderBy:          gitlab.Ptr(orderBy),\n\t\t\tIncludeSubGroups: gitlab.Ptr(true),\n\t\t\tWithShared:       gitlab.Ptr(s.enumerateSharedProjects),\n\t\t}\n\t\tfor {\n\t\t\tgrpPrjs, res, err := apiClient.Groups.ListGroupProjects(group.ID, listGroupProjectOptions)\n\t\t\tif err != nil {\n\t\t\t\terr = fmt.Errorf(\n\t\t\t\t\t\"received error on listing group projects for %q, you probably don't have permissions to do that: %w\",\n\t\t\t\t\tgroup.FullPath, err,\n\t\t\t\t)\n\t\t\t\tif err := reporter.UnitErr(ctx, err); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tctx.Logger().V(3).Info(\"listed group projects\", \"count\", len(grpPrjs))\n\t\t\tif err := processProjects(ctx, grpPrjs); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tlistGroupProjectOptions.Page = res.NextPage\n\t\t\tif res.NextPage == 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tctx.Logger().Info(\"Enumerated GitLab projects\", \"count\", len(projectsWithNamespace))\n\n\treturn nil\n}\n\n// getAllProjectReposV2 uses simplified logic to enumerate through all projects using list-all-projects API.\n// The reporter is used to report the valid repository found for projects that are not ignored.\nfunc (s *Source) getAllProjectReposV2(\n\tctx context.Context,\n\tapiClient *gitlab.Client,\n\tignoreRepo func(string) bool,\n\treporter sources.UnitReporter,\n) error {\n\tgitlabReposEnumerated.WithLabelValues(s.name).Set(0)\n\n\t// example: https://gitlab.com/gitlab-org/api/client-go/-/blob/main/examples/pagination.go#L55\n\tlistOpts := gitlab.ListOptions{\n\t\tOrderBy:    \"id\",\n\t\tPagination: \"keyset\", // https://docs.gitlab.com/api/rest/#keyset-based-pagination\n\t\t// Trufflehog default per page 100 unless set to other value through feature flag. If 0 provided in feature flag gitlab default it to 20\n\t\tPerPage: s.projectsPerPage,\n\t\tSort:    \"asc\",\n\t}\n\n\tprojectQueryOptions := &gitlab.ListProjectsOptions{\n\t\tListOptions: listOpts,\n\t\t// Return only limited fields for each project\n\t\tSimple: gitlab.Ptr(true),\n\t}\n\n\t// for gitlab.com instance, include only projects where the user is a member.\n\tif s.url == gitlabBaseURL {\n\t\tprojectQueryOptions.Membership = gitlab.Ptr(true)\n\t}\n\n\tctx.Logger().Info(\"starting projects enumeration\",\n\t\t\"list_options\", listOpts)\n\n\t// totalCount tracks the total number of projects processed by this enumeration.\n\t// It includes all projects fetched from the API, even those later skipped by ignore rules.\n\ttotalCount := 0\n\n\trequestOptions := []gitlab.RequestOptionFunc{gitlab.WithContext(ctx)}\n\n\t// Pagination loop: Continue fetching pages until the API indicates there are no more.\n\tfor {\n\t\t// Fetch a page of projects from the GitLab API using the current query options.\n\t\tprojects, resp, err := apiClient.Projects.ListProjects(projectQueryOptions, requestOptions...)\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"received error on listing projects, you might not have permissions to do that: %w\", err)\n\t\t\tif err := reporter.UnitErr(ctx, err); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// break on error as with error we will not have any response and no next page\n\t\t\tbreak\n\t\t}\n\n\t\t// Log the batch size for debugging and monitoring.\n\t\tctx.Logger().V(3).Info(\"listed projects batch\", \"batch_size\", len(projects), \"running_total\", totalCount)\n\t\t// Process each project in the current page.\n\t\tfor _, project := range projects {\n\t\t\tprojCtx := context.WithValues(ctx,\n\t\t\t\t\"project_id\", project.ID,\n\t\t\t\t\"project_name\", project.NameWithNamespace)\n\n\t\t\ttotalCount++\n\n\t\t\t// skip projects configured to be ignored.\n\t\t\tif ignoreRepo(project.PathWithNamespace) {\n\t\t\t\tprojCtx.Logger().V(3).Info(\"skipping project\", \"reason\", \"ignored in config\")\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// report an error if we could not convert the project into a URL.\n\t\t\tif _, err := url.Parse(project.HTTPURLToRepo); err != nil {\n\t\t\t\tprojCtx.Logger().V(3).Info(\"skipping project\",\n\t\t\t\t\t\"reason\", \"URL parse failure\",\n\t\t\t\t\t\"url\", project.HTTPURLToRepo,\n\t\t\t\t\t\"parse_error\", err)\n\n\t\t\t\terr = fmt.Errorf(\"could not parse url %q given by project: %w\", project.HTTPURLToRepo, err)\n\t\t\t\tif err := reporter.UnitErr(ctx, err); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// report the unit.\n\t\t\tprojCtx.Logger().V(3).Info(\"accepting project\")\n\n\t\t\ts.cacheGitlabProject(project)\n\t\t\tunit := git.SourceUnit{Kind: git.UnitRepo, ID: project.HTTPURLToRepo}\n\t\t\tgitlabReposEnumerated.WithLabelValues(s.name).Inc()\n\n\t\t\tif err := reporter.UnitOk(ctx, unit); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\t// if next page is empty, break the loop\n\t\tif resp == nil || resp.NextLink == \"\" {\n\t\t\t// No more pages to fetch. This is the normal loop exit condition.\n\t\t\t// It also acts as a safety stop if the current request failed.\n\t\t\tbreak\n\t\t}\n\t\t// Only update the token for the next page if we have a valid, non-empty link.\n\t\trequestOptions = []gitlab.RequestOptionFunc{\n\t\t\tgitlab.WithContext(ctx),\n\t\t\tgitlab.WithKeysetPaginationParameters(resp.NextLink),\n\t\t}\n\t}\n\n\tctx.Logger().Info(\"Enumerated GitLab projects\", \"count\", totalCount)\n\n\treturn nil\n}\n\n// getAllProjectReposInGroups fetches all projects in a GitLab group and its subgroups.\n// It uses the group projects API with include_subgroups=true parameter.\nfunc (s *Source) getAllProjectReposInGroups(\n\tctx context.Context,\n\tapiClient *gitlab.Client,\n\tignoreRepo func(string) bool,\n\treporter sources.UnitReporter,\n) error {\n\tgitlabReposEnumerated.WithLabelValues(s.name).Set(0)\n\tgitlabGroupsEnumerated.WithLabelValues(s.name).Set(float64(len(s.groupIds)))\n\n\tprocessedProjects := make(map[string]bool)\n\n\tvar projectsWithNamespace []string\n\tconst (\n\t\torderBy = \"id\"\n\t)\n\n\tlistOpts := gitlab.ListOptions{PerPage: s.projectsPerPage}\n\tprojectOpts := &gitlab.ListGroupProjectsOptions{\n\t\tListOptions:      listOpts,\n\t\tOrderBy:          gitlab.Ptr(orderBy),\n\t\tIncludeSubGroups: gitlab.Ptr(true),\n\t\tWithShared:       gitlab.Ptr(true),\n\t}\n\n\t// For non gitlab.com instances, you might want to adjust access levels\n\tif s.url != gitlabBaseURL {\n\t\tprojectOpts.MinAccessLevel = gitlab.Ptr(gitlab.GuestPermissions)\n\t}\n\n\tctx.Logger().Info(\"starting group projects enumeration\",\n\t\t\"group_ids\", s.groupIds,\n\t\t\"include_subgroups\", true,\n\t\t\"list_options\", listOpts)\n\n\tfor _, groupID := range s.groupIds {\n\t\tgroupCtx := context.WithValues(ctx, \"group_id\", groupID)\n\n\t\tprojectOpts.Page = 0\n\t\tgroupCtx.Logger().V(2).Info(\"processing group\", \"group_id\", groupID)\n\n\t\tfor {\n\t\t\tprojects, res, err := apiClient.Groups.ListGroupProjects(groupID, projectOpts)\n\t\t\tif err != nil {\n\t\t\t\terr = fmt.Errorf(\"received error on listing projects for group %s: %w\", groupID, err)\n\t\t\t\tif err := reporter.UnitErr(ctx, err); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tgroupCtx.Logger().V(3).Info(\"listed group projects\", \"count\", len(projects))\n\n\t\t\tfor _, proj := range projects {\n\t\t\t\tprojCtx := context.WithValues(ctx,\n\t\t\t\t\t\"project_id\", proj.ID,\n\t\t\t\t\t\"project_name\", proj.NameWithNamespace,\n\t\t\t\t\t\"group_id\", groupID)\n\n\t\t\t\tif processedProjects[proj.HTTPURLToRepo] {\n\t\t\t\t\tprojCtx.Logger().V(3).Info(\"skipping project\", \"reason\", \"already processed\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tprocessedProjects[proj.HTTPURLToRepo] = true\n\n\t\t\t\t// skip projects configured to be ignored.\n\t\t\t\tif ignoreRepo(proj.PathWithNamespace) {\n\t\t\t\t\tprojCtx.Logger().V(3).Info(\"skipping project\", \"reason\", \"ignored in config\")\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// report an error if we could not convert the project into a URL.\n\t\t\t\tif _, err := url.Parse(proj.HTTPURLToRepo); err != nil {\n\t\t\t\t\tprojCtx.Logger().V(3).Info(\"skipping project\",\n\t\t\t\t\t\t\"reason\", \"URL parse failure\",\n\t\t\t\t\t\t\"url\", proj.HTTPURLToRepo,\n\t\t\t\t\t\t\"parse_error\", err)\n\n\t\t\t\t\terr = fmt.Errorf(\"could not parse url %q given by project: %w\", proj.HTTPURLToRepo, err)\n\t\t\t\t\tif err := reporter.UnitErr(ctx, err); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\t// report the unit.\n\t\t\t\tprojCtx.Logger().V(3).Info(\"accepting project\")\n\n\t\t\t\ts.cacheGitlabProject(proj)\n\t\t\t\tunit := git.SourceUnit{Kind: git.UnitRepo, ID: proj.HTTPURLToRepo}\n\t\t\t\tgitlabReposEnumerated.WithLabelValues(s.name).Inc()\n\t\t\t\tprojectsWithNamespace = append(projectsWithNamespace, proj.NameWithNamespace)\n\n\t\t\t\tif err := reporter.UnitOk(ctx, unit); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// handle pagination.\n\t\t\tprojectOpts.Page = res.NextPage\n\t\t\tif res.NextPage == 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tctx.Logger().Info(\"Enumerated GitLab group projects\", \"count\", len(projectsWithNamespace))\n\n\treturn nil\n}\n\nfunc (s *Source) scanRepos(ctx context.Context, chunksChan chan *sources.Chunk) error {\n\t// If there is resume information available, limit this scan to only the repos that still need scanning.\n\treposToScan, progressIndexOffset := sources.FilterReposToResume(s.repos, s.GetProgress().EncodedResumeInfo)\n\tctx.Logger().V(2).Info(\"filtered repos to resume\", \"before\", len(s.repos), \"after\", len(reposToScan))\n\ts.repos = reposToScan\n\tscanErrs := sources.NewScanErrors()\n\n\tfor i, repo := range s.repos {\n\t\trepoURL := repo\n\t\ts.jobPool.Go(func() error {\n\t\t\tlogger := ctx.Logger().WithValues(\"repo\", repoURL)\n\t\t\tif common.IsDone(ctx) {\n\t\t\t\t// We are returning nil instead of the scanErrors slice here because\n\t\t\t\t// we don't want to mark this scan as errored if we cancelled it.\n\t\t\t\tlogger.V(2).Info(\"Skipping repo because context was cancelled\")\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tif len(repoURL) == 0 {\n\t\t\t\tlogger.V(2).Info(\"Skipping empty repo\")\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\ts.setProgressCompleteWithRepo(i, progressIndexOffset, repoURL)\n\t\t\t// Ensure the repo is removed from the resume info after being scanned.\n\t\t\tdefer func(s *Source) {\n\t\t\t\ts.resumeInfoMutex.Lock()\n\t\t\t\tdefer s.resumeInfoMutex.Unlock()\n\t\t\t\ts.resumeInfoSlice = sources.RemoveRepoFromResumeInfo(s.resumeInfoSlice, repoURL)\n\t\t\t}(s)\n\n\t\t\tvar path string\n\t\t\tvar repo *gogit.Repository\n\t\t\tvar err error\n\t\t\tif s.authMethod == \"UNAUTHENTICATED\" {\n\t\t\t\tpath, repo, err = git.CloneRepoUsingUnauthenticated(ctx, repoURL, s.clonePath)\n\t\t\t} else {\n\t\t\t\t// If a username is not provided we need to use a default one in order to clone a private repo.\n\t\t\t\t// Not setting \"placeholder\" as s.user on purpose in case any downstream services rely on a \"\" value for s.user.\n\t\t\t\tuser := s.user\n\t\t\t\tif user == \"\" {\n\t\t\t\t\tuser = \"placeholder\"\n\t\t\t\t}\n\n\t\t\t\tpath, repo, err = git.CloneRepoUsingToken(ctx, s.token, repoURL, s.clonePath, user, s.useAuthInUrl)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tscanErrs.Add(err)\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\t// remove the path only if it was created as a temporary path, or if it is a clone path and --no-cleanup is not set.\n\t\t\t// if legacy JSON is enabled, don't remove the directory because we need it for outputting legacy JSON.\n\t\t\tif !s.printLegacyJSON {\n\t\t\t\tif strings.HasPrefix(path, filepath.Join(os.TempDir(), \"trufflehog\")) || (!s.noCleanup && s.clonePath != \"\") {\n\t\t\t\t\tdefer os.RemoveAll(path)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlogger.V(2).Info(\"starting scan\", \"num\", i+1, \"total\", len(s.repos))\n\t\t\tif err = s.git.ScanRepo(ctx, repo, path, s.scanOptions, sources.ChanReporter{Ch: chunksChan}); err != nil {\n\t\t\t\tscanErrs.Add(err)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tgitlabReposScanned.WithLabelValues(s.name).Inc()\n\n\t\t\tlogger.V(2).Info(\"completed scan\", \"num\", i+1, \"total\", len(s.repos))\n\t\t\treturn nil\n\t\t})\n\t}\n\n\t_ = s.jobPool.Wait()\n\tif scanErrs.Count() > 0 {\n\t\tctx.Logger().V(2).Info(\"encountered errors while scanning\", \"count\", scanErrs.Count(), \"errors\", scanErrs)\n\t}\n\ts.SetProgressComplete(len(s.repos), len(s.repos), \"Completed Gitlab scan\", \"\")\n\n\treturn nil\n}\n\n// setProgressCompleteWithRepo calls the s.SetProgressComplete after safely setting up the encoded resume info string.\nfunc (s *Source) setProgressCompleteWithRepo(index int, offset int, repoURL string) {\n\ts.resumeInfoMutex.Lock()\n\tdefer s.resumeInfoMutex.Unlock()\n\n\t// Add the repoURL to the resume info slice.\n\ts.resumeInfoSlice = append(s.resumeInfoSlice, repoURL)\n\tslices.Sort(s.resumeInfoSlice)\n\n\t// Make the resume info string from the slice.\n\tencodedResumeInfo := sources.EncodeResumeInfo(s.resumeInfoSlice)\n\n\t// Add the offset to both the index and the repos to give the proper place and proper repo count.\n\ts.SetProgressComplete(index+offset, len(s.repos)+offset, fmt.Sprintf(\"Repo: %s\", repoURL), encodedResumeInfo)\n}\n\nfunc (s *Source) WithScanOptions(scanOptions *git.ScanOptions) {\n\ts.scanOptions = scanOptions\n}\n\nfunc buildIgnorer(include, exclude []string, onCompile func(err error, pattern string)) func(repo string) bool {\n\n\t// compile and load globRepoFilter\n\tglobRepoFilter := newGlobRepoFilter(include, exclude, onCompile)\n\n\tf := func(repo string) bool {\n\t\tif !globRepoFilter.includeRepo(repo) || globRepoFilter.ignoreRepo(repo) {\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\n\treturn f\n}\n\nfunc normalizeRepos(repos []string) ([]string, []error) {\n\t// Optimistically allocate space for all valid repositories.\n\tvalidRepos := make([]string, 0, len(repos))\n\tvar errs []error\n\tfor _, prj := range repos {\n\t\trepo, err := giturl.NormalizeGitlabRepo(prj)\n\t\tif err != nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"unable to normalize gitlab repo url %q: %w\", prj, err))\n\t\t\tcontinue\n\t\t}\n\n\t\tvalidRepos = append(validRepos, repo)\n\t}\n\treturn validRepos, errs\n}\n\n// normalizeGitlabEndpoint ensures that if an endpoint is going to gitlab.com, we use https://gitlab.com/ as the endpoint.\n// If we see the protocol is http, we error, because this shouldn't be used.\n// Otherwise, it ensures we are using https as our protocol, if none was provided.\nfunc normalizeGitlabEndpoint(gitlabEndpoint string) (string, error) {\n\tif gitlabEndpoint == \"\" {\n\t\treturn gitlabBaseURL, nil\n\t}\n\n\tgitlabURL, err := url.Parse(gitlabEndpoint)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// We probably didn't receive a URL with a scheme, which messed up the parsing.\n\tif gitlabURL.Host == \"\" {\n\t\tgitlabURL, err = url.Parse(\"https://\" + gitlabEndpoint)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\n\t// If the host is gitlab.com, this is the cloud version, which has only one valid endpoint.\n\tif gitlabURL.Host == \"gitlab.com\" {\n\t\treturn gitlabBaseURL, nil\n\t}\n\n\t// Beyond here, on-prem gitlab is being used, so we have to mostly leave things as-is.\n\n\tif gitlabURL.Scheme != \"https\" {\n\t\treturn \"\", fmt.Errorf(\"https was not used as URL scheme, but is required. Please use https\")\n\t}\n\n\t// The gitlab library wants trailing slashes.\n\tif !strings.HasSuffix(gitlabURL.Path, \"/\") {\n\t\tgitlabURL.Path = gitlabURL.Path + \"/\"\n\t}\n\n\treturn gitlabURL.String(), nil\n}\n\n// Enumerate reports all GitLab repositories to be scanned to the reporter. If\n// none are configured, it will find all repositories within all projects that\n// the configured user has access to, while respecting the configured ignore\n// rules.\nfunc (s *Source) Enumerate(ctx context.Context, reporter sources.UnitReporter) error {\n\t// Start client.\n\tapiClient, err := s.newClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Get repos within target.\n\trepos, errs := normalizeRepos(s.repos)\n\tfor _, repoErr := range errs {\n\t\tctx.Logger().Info(\"error normalizing repo\", \"error\", repoErr)\n\t\tif err := reporter.UnitErr(ctx, repoErr); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// End early if we had errors getting specified repos but none were validated.\n\tif len(errs) > 0 && len(repos) == 0 {\n\t\treturn fmt.Errorf(\"all configured repos had validation issues\")\n\t}\n\n\t// Report all repos if specified.\n\tif len(repos) > 0 {\n\t\tgitlabReposEnumerated.WithLabelValues(s.name).Set(0)\n\t\tfor _, repo := range repos {\n\t\t\tunit := git.SourceUnit{Kind: git.UnitRepo, ID: repo}\n\t\t\tif err := reporter.UnitOk(ctx, unit); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tgitlabReposEnumerated.WithLabelValues(s.name).Inc()\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Otherwise, enumerate all repos.\n\tignoreRepo := buildIgnorer(s.includeRepos, s.ignoreRepos, func(err error, pattern string) {\n\t\tctx.Logger().Error(err, \"could not compile include/exclude repo glob\", \"glob\", pattern)\n\t\t// TODO: Handle error returned from UnitErr.\n\t\t_ = reporter.UnitErr(ctx, fmt.Errorf(\"could not compile include/exclude repo glob: %w\", err))\n\t})\n\n\tif err := s.listProjects(ctx, apiClient, ignoreRepo, reporter); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// ChunkUnit downloads and reports chunks for the given GitLab repository unit.\nfunc (s *Source) ChunkUnit(ctx context.Context, unit sources.SourceUnit, reporter sources.ChunkReporter) error {\n\trepoURL, _ := unit.SourceUnitID()\n\n\tignoreRepo := buildIgnorer(s.includeRepos, s.ignoreRepos, func(err error, pattern string) {\n\t\tctx.Logger().Error(err, \"could not compile include/exclude repo glob\", \"glob\", pattern)\n\t})\n\tif ignoreRepo(repoURL) {\n\t\tctx.Logger().V(3).Info(\"skipping project\", \"reason\", \"ignored in config\")\n\t\treturn nil\n\t}\n\n\tvar path string\n\tvar repo *gogit.Repository\n\tvar err error\n\tif s.authMethod == \"UNAUTHENTICATED\" {\n\t\tpath, repo, err = git.CloneRepoUsingUnauthenticated(ctx, repoURL, s.clonePath)\n\t} else {\n\t\t// If a username is not provided we need to use a default one in order to clone a private repo.\n\t\t// Not setting \"placeholder\" as s.user on purpose in case any downstream services rely on a \"\" value for s.user.\n\t\tuser := s.user\n\t\tif user == \"\" {\n\t\t\tuser = \"placeholder\"\n\t\t}\n\n\t\tpath, repo, err = git.CloneRepoUsingToken(ctx, s.token, repoURL, s.clonePath, user, s.useAuthInUrl)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// remove the path only if it was created as a temporary path, or if it is a clone path and --no-cleanup is not set.\n\t// if legacy JSON is enabled, don't remove the directory because we need it for outputting legacy JSON.\n\tif !s.printLegacyJSON {\n\t\tif strings.HasPrefix(path, filepath.Join(os.TempDir(), \"trufflehog\")) || (!s.noCleanup && s.clonePath != \"\") {\n\t\t\tdefer os.RemoveAll(path)\n\t\t}\n\t}\n\n\t// ensure project details are cached\n\t// this is required to populate metadata during chunking\n\ts.ensureProjectInCache(ctx, repoURL)\n\n\treturn s.git.ScanRepo(ctx, repo, path, s.scanOptions, reporter)\n}\n\n// ensureProjectInCache checks if the project for the given repo URL is in the cache,\n// and if not, queries the GitLab API to fetch the project and adds it to the cache.\nfunc (s *Source) ensureProjectInCache(ctx context.Context, repoUrl string) {\n\t// check if project is already in cache\n\tif _, ok := s.repoToProjCache.get(repoUrl); ok {\n\t\treturn\n\t}\n\n\t// query project\n\tproj, err := s.getGitlabProject(ctx, repoUrl)\n\tif err != nil {\n\t\tctx.Logger().Error(err, \"could not fetch project for repo\", \"repo\", repoUrl)\n\t\treturn\n\t}\n\n\t// add to cache\n\ts.cacheGitlabProject(proj)\n}\n\nfunc (s *Source) getGitlabProject(ctx context.Context, repoUrl string) (*gitlab.Project, error) {\n\tapiClient, err := s.newClient()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not create api client: %w\", err)\n\t}\n\t// extract project path from repo URL\n\t// https://gitlab.com/testermctestface/testy.git => testermctestface/testy\n\turl, err := url.Parse(repoUrl)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not parse repo URL: %w\", err)\n\t}\n\trepoPath := strings.TrimPrefix(strings.TrimSuffix(url.Path, \".git\"), \"/\")\n\n\tproj, _, err := apiClient.Projects.GetProject(repoPath, nil, gitlab.WithContext(ctx))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not query project metadata: %w\", err)\n\t}\n\treturn proj, nil\n}\n\nfunc (s *Source) cacheGitlabProject(gitlabProj *gitlab.Project) {\n\tproj := &project{\n\t\tid:   gitlabProj.ID,\n\t\tname: gitlabProj.NameWithNamespace,\n\t}\n\tif gitlabProj.Owner != nil {\n\t\tproj.owner = gitlabProj.Owner.Email\n\t\tif proj.owner == \"\" {\n\t\t\tproj.owner = gitlabProj.Owner.Username\n\t\t}\n\t}\n\ts.repoToProjCache.set(gitlabProj.HTTPURLToRepo, proj)\n}\n"
  },
  {
    "path": "pkg/sources/gitlab/gitlab_integration_test.go",
    "content": "//go:build integration\n// +build integration\n\npackage gitlab\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/feature\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/credentialspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sourcestest\"\n)\n\nfunc TestSource_Scan(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\tsecret, err := common.GetTestSecret(ctx)\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\ttoken := secret.MustGetField(\"GITLAB_TOKEN\")\n\tbasicUser := secret.MustGetField(\"GITLAB_USER\")\n\tno2FaUser := secret.MustGetField(\"GITLAB_NO_2FA_USER\")\n\tno2FaPass := secret.MustGetField(\"GITLAB_NO_2FA_PASS\")\n\n\ttype init struct {\n\t\tname       string\n\t\tverify     bool\n\t\tconnection *sourcespb.GitLab\n\t}\n\ttests := []struct {\n\t\tname             string\n\t\tinit             init\n\t\twantChunk        *sources.Chunk\n\t\twantReposScanned int\n\t\twantErr          bool\n\t}{\n\t\t{\n\t\t\tname: \"token auth, enumerate repo, with explicit ignore\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source\",\n\t\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\t\tToken: token,\n\t\t\t\t\t},\n\t\t\t\t\tIgnoreRepos: []string{\"tes1188/learn-gitlab\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: &sources.Chunk{\n\t\t\t\tSourceType: sourcespb.SourceType_SOURCE_TYPE_GITLAB,\n\t\t\t\tSourceName: \"test source\",\n\t\t\t},\n\t\t\twantReposScanned: 5,\n\t\t},\n\t\t{\n\t\t\tname: \"token auth, enumerate repo, with glob ignore\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source\",\n\t\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\t\tToken: token,\n\t\t\t\t\t},\n\t\t\t\t\tIgnoreRepos: []string{\"tes1188/*-gitlab\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: &sources.Chunk{\n\t\t\t\tSourceType: sourcespb.SourceType_SOURCE_TYPE_GITLAB,\n\t\t\t\tSourceName: \"test source\",\n\t\t\t},\n\t\t\twantReposScanned: 5,\n\t\t},\n\t\t{\n\t\t\tname: \"token auth, scoped repo\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source scoped\",\n\t\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\t\tRepositories: []string{\"https://gitlab.com/testermctestface/testy.git\"},\n\t\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\t\tToken: token,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: &sources.Chunk{\n\t\t\t\tSourceType: sourcespb.SourceType_SOURCE_TYPE_GITLAB,\n\t\t\t\tSourceName: \"test source scoped\",\n\t\t\t},\n\t\t\twantReposScanned: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"basic auth, scoped repo\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source basic auth scoped\",\n\t\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\t\tRepositories: []string{\"https://gitlab.com/trufflesec-detectors/test-project.git\"},\n\n\t\t\t\t\tCredential: &sourcespb.GitLab_BasicAuth{\n\t\t\t\t\t\tBasicAuth: &credentialspb.BasicAuth{\n\t\t\t\t\t\t\tUsername: no2FaUser,\n\t\t\t\t\t\t\tPassword: no2FaPass,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: &sources.Chunk{\n\t\t\t\tSourceType: sourcespb.SourceType_SOURCE_TYPE_GITLAB,\n\t\t\t\tSourceName: \"test source basic auth scoped\",\n\t\t\t},\n\t\t\twantReposScanned: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"basic auth access token, scoped repo\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source basic auth access token scoped\",\n\t\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\t\tRepositories: []string{\"https://gitlab.com/testermctestface/testy.git\"},\n\t\t\t\t\tCredential: &sourcespb.GitLab_BasicAuth{\n\t\t\t\t\t\tBasicAuth: &credentialspb.BasicAuth{\n\t\t\t\t\t\t\tUsername: basicUser,\n\t\t\t\t\t\t\tPassword: token,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: &sources.Chunk{\n\t\t\t\tSourceType: sourcespb.SourceType_SOURCE_TYPE_GITLAB,\n\t\t\t\tSourceName: \"test source basic auth access token scoped\",\n\t\t\t},\n\t\t\twantReposScanned: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"token auth, group projects enumeration with include_subgroups\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source group enumeration\",\n\t\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\t\tToken: token,\n\t\t\t\t\t},\n\t\t\t\t\tGroupIds: []string{\"15013490\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: &sources.Chunk{\n\t\t\t\tSourceType: sourcespb.SourceType_SOURCE_TYPE_GITLAB,\n\t\t\t\tSourceName: \"test source group enumeration\",\n\t\t\t},\n\t\t\twantReposScanned: 5,\n\t\t},\n\t\t{\n\t\t\tname: \"token auth, group projects enumeration with include_subgroups and exclude repositories\",\n\t\t\tinit: init{\n\t\t\t\tname: \"test source group enumeration with exclude repos\",\n\t\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\t\tToken: token,\n\t\t\t\t\t},\n\t\t\t\t\tGroupIds:    []string{\"15013490\"},\n\t\t\t\t\tIgnoreRepos: []string{\"tes1188/test-user-count\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunk: &sources.Chunk{\n\t\t\t\tSourceType: sourcespb.SourceType_SOURCE_TYPE_GITLAB,\n\t\t\t\tSourceName: \"test source group enumeration with exclude repos\",\n\t\t\t},\n\t\t\twantReposScanned: 4,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Source{}\n\n\t\t\tconn, err := anypb.New(tt.init.connection)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\terr = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 10)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Source.Init() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tchunksCh := make(chan *sources.Chunk, 1)\n\t\t\tgo func() {\n\t\t\t\tdefer close(chunksCh)\n\t\t\t\terr = s.Chunks(ctx, chunksCh)\n\t\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\t\tt.Errorf(\"Source.Chunks() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}()\n\t\t\tvar chunkCnt int\n\t\t\t// Commits don't come in a deterministic order, so remove metadata comparison\n\t\t\tfor gotChunk := range chunksCh {\n\t\t\t\tchunkCnt++\n\t\t\t\tgotChunk.Data = nil\n\t\t\t\tgotChunk.SourceMetadata = nil\n\t\t\t\tif diff := pretty.Compare(gotChunk, tt.wantChunk); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"Source.Chunks() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tassert.Equal(t, tt.wantReposScanned, len(s.repos))\n\t\t\tif chunkCnt < 1 {\n\t\t\t\tt.Errorf(\"0 chunks scanned.\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSource_Validate(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\tsecret, err := common.GetTestSecret(ctx)\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\ttoken := secret.MustGetField(\"GITLAB_TOKEN\")\n\ttokenWrongScope := secret.MustGetField(\"GITLAB_TOKEN_WRONG_SCOPE\")\n\n\ttests := []struct {\n\t\tname         string\n\t\tconnection   *sourcespb.GitLab\n\t\twantErrCount int\n\t\twantErrs     []string\n\t}{\n\t\t{\n\t\t\tname: \"basic auth did not authenticate\",\n\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\tCredential: &sourcespb.GitLab_BasicAuth{\n\t\t\t\t\tBasicAuth: &credentialspb.BasicAuth{\n\t\t\t\t\t\tUsername: \"bad-user\",\n\t\t\t\t\t\tPassword: \"bad-password\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErrCount: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"token did not authenticate\",\n\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\tToken: \"bad-token\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErrCount: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"bad repo urls\",\n\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\tToken: token,\n\t\t\t\t},\n\t\t\t\tRepositories: []string{\n\t\t\t\t\t\"https://gitlab.com/testermctestface/testy\",  // valid\n\t\t\t\t\t\"https://gitlab.com/testermctestface/testy/\", // trailing slash\n\t\t\t\t\t\"ssh:git@gitlab.com/testermctestface/testy\",  // bad protocol\n\t\t\t\t\t\"https://gitlab.com\",                         // no path\n\t\t\t\t\t\"https://gitlab.com/\",                        // no org name\n\t\t\t\t\t\"https://gitlab.com//testy\",                  // no org name\n\t\t\t\t\t\"https://gitlab.com/testermctestface/\",       // no repo name\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErrCount: 6,\n\t\t},\n\t\t{\n\t\t\tname: \"token does not have permission to list projects\",\n\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\tToken: tokenWrongScope,\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErrCount: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"repositories and ignore globs both configured\",\n\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\tToken: token,\n\t\t\t\t},\n\t\t\t\tRepositories: []string{\n\t\t\t\t\t\"https://gitlab.com/testermctestface/testy\", // valid\n\t\t\t\t},\n\t\t\t\tIgnoreRepos: []string{\n\t\t\t\t\t\"tes1188/*-gitlab\",\n\t\t\t\t\t\"[\", // glob doesn't compile, but this won't be checked\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErrCount: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"could not compile ignore glob(s)\",\n\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\tToken: token,\n\t\t\t\t},\n\t\t\t\tIgnoreRepos: []string{\n\t\t\t\t\t\"tes1188/*-gitlab\",\n\t\t\t\t\t\"[\",    // glob doesn't compile\n\t\t\t\t\t\"[a-]\", // glob doesn't compile\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErrCount: 2,\n\t\t},\n\n\t\t{\n\t\t\tname: \"could not compile include glob(s)\",\n\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\tToken: token,\n\t\t\t\t},\n\t\t\t\tIncludeRepos: []string{\n\t\t\t\t\t\"tes1188/*-gitlab\",\n\t\t\t\t\t\"[\",    // glob doesn't compile\n\t\t\t\t\t\"[a-]\", // glob doesn't compile\n\t\t\t\t},\n\t\t\t\tIgnoreRepos: []string{\n\t\t\t\t\t\"[\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErrCount: 3,\n\t\t},\n\t\t{\n\t\t\tname: \"repositories do not exist or are not accessible\",\n\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\tToken: token,\n\t\t\t\t},\n\t\t\t\tRepositories: []string{\n\t\t\t\t\t\"https://gitlab.com/testermctestface/testy\",\n\t\t\t\t\t\"https://gitlab.com/testermctestface/doesn't-exist\",\n\t\t\t\t\t\"https://gitlab.com/testermctestface/also-doesn't-exist\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErrCount: 2,\n\t\t},\n\t\t{\n\t\t\tname: \"ignore globs exclude all repos\",\n\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\tToken: token,\n\t\t\t\t},\n\t\t\t\tIgnoreRepos: []string{\n\t\t\t\t\t\"*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErrCount: 1,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Source{}\n\n\t\t\tconn, err := anypb.New(tt.connection)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\terr = s.Init(ctx, tt.name, 0, 0, false, conn, 1)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Source.Init() error: %v\", err)\n\t\t\t}\n\n\t\t\terrs := s.Validate(ctx)\n\n\t\t\tassert.Equal(t, tt.wantErrCount, len(errs))\n\t\t})\n\t}\n}\n\nfunc TestSource_Chunks_TargetedScan(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\n\tsecret, err := common.GetTestSecret(ctx)\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\n\ttoken := secret.MustGetField(\"GITLAB_TOKEN\")\n\n\ttype init struct {\n\t\tname          string\n\t\tverify        bool\n\t\tconnection    *sourcespb.GitLab\n\t\tqueryCriteria *source_metadatapb.MetaData\n\t}\n\ttests := []struct {\n\t\tname       string\n\t\tinit       init\n\t\twantChunks int\n\t}{\n\t\t{\n\t\t\tname: \"targeted scan; single diff\",\n\t\t\tinit: init{\n\t\t\t\tconnection: &sourcespb.GitLab{Credential: &sourcespb.GitLab_Token{Token: token}},\n\t\t\t\tqueryCriteria: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Gitlab{\n\t\t\t\t\t\tGitlab: &source_metadatapb.Gitlab{\n\t\t\t\t\t\t\tRepository: \"https://gitlab.com/testermctestface/testy.git\",\n\t\t\t\t\t\t\tLink:       \"https://gitlab.com/testermctestface/testy/blob/30c407baee70d41d062114022a59ed8ee048880a/.gitlab-ci.yml#L1\",\n\t\t\t\t\t\t\tCommit:     \"30c407baee70d41d062114022a59ed8ee048880a\",\n\t\t\t\t\t\t\tProjectId:  32561068,\n\t\t\t\t\t\t\tFile:       \"keys\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunks: 1,\n\t\t},\n\t\t{\n\t\t\tname: \"targeted scan; multiple diffs\",\n\t\t\tinit: init{\n\t\t\t\tconnection: &sourcespb.GitLab{Credential: &sourcespb.GitLab_Token{Token: token}},\n\t\t\t\tqueryCriteria: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Gitlab{\n\t\t\t\t\t\tGitlab: &source_metadatapb.Gitlab{\n\t\t\t\t\t\t\tCommit:    \"b9a2fafeb0b978201e64f62efc9aa37c52a65045\",\n\t\t\t\t\t\t\tProjectId: 32561068,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunks: 2,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid query criteria, missing project ID\",\n\t\t\tinit: init{\n\t\t\t\tconnection: &sourcespb.GitLab{Credential: &sourcespb.GitLab_Token{Token: token}},\n\t\t\t\tqueryCriteria: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Gitlab{\n\t\t\t\t\t\tGitlab: &source_metadatapb.Gitlab{\n\t\t\t\t\t\t\tRepository: \"test_keys\",\n\t\t\t\t\t\t\tCommit:     \"fbc14303ffbf8fb1c2c1914e8dda7d0121633aca\",\n\t\t\t\t\t\t\tFile:       \"not-the-file\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunks: 0,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid query criteria, missing commit\",\n\t\t\tinit: init{\n\t\t\t\tname:       \"test source\",\n\t\t\t\tconnection: &sourcespb.GitLab{Credential: &sourcespb.GitLab_Token{Token: token}},\n\t\t\t\tqueryCriteria: &source_metadatapb.MetaData{\n\t\t\t\t\tData: &source_metadatapb.MetaData_Gitlab{\n\t\t\t\t\t\tGitlab: &source_metadatapb.Gitlab{\n\t\t\t\t\t\t\tRepository: \"test_keys\",\n\t\t\t\t\t\t\tProjectId:  32561068,\n\t\t\t\t\t\t\tFile:       \"not-the-file\",\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twantChunks: 0,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\ttt := tt\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Source{}\n\n\t\t\tconn, err := anypb.New(tt.init.connection)\n\t\t\tassert.NoError(t, err)\n\n\t\t\terr = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 8)\n\t\t\tassert.NoError(t, err)\n\n\t\t\tvar wg sync.WaitGroup\n\t\t\tchunksCh := make(chan *sources.Chunk, 1)\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer close(chunksCh)\n\t\t\t\tdefer wg.Done()\n\t\t\t\terr = s.Chunks(ctx, chunksCh, sources.ChunkingTarget{QueryCriteria: tt.init.queryCriteria})\n\t\t\t\tassert.NoError(t, err)\n\t\t\t}()\n\n\t\t\ti := 0\n\t\t\tfor range chunksCh {\n\t\t\t\ti++\n\t\t\t}\n\t\t\twg.Wait()\n\t\t\tassert.Equal(t, tt.wantChunks, i)\n\t\t})\n\t}\n}\n\nfunc TestSource_ChunkUnit_RepoFiltersRespected(t *testing.T) {\n\tctx := context.Background()\n\n\t// Arrange: Get test environment token\n\tsecret, err := common.GetTestSecret(ctx)\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\ttoken := secret.MustGetField(\"GITLAB_TOKEN\")\n\n\t// Arrange: Build a unit to scan\n\tunit := sources.CommonSourceUnit{\n\t\tKind: \"repo\",\n\t\tID:   \"https://gitlab.com/testermctestface/testy\",\n\t}\n\n\ttests := []struct {\n\t\tname          string\n\t\tincludeRepos  []string\n\t\tignoreRepos   []string\n\t\twantAnyChunks bool\n\t}{\n\t\t{\n\t\t\tname:          \"empty include, empty ignore\",\n\t\t\twantAnyChunks: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"unit matches include\",\n\t\t\tincludeRepos:  []string{\"https://gitlab.com/testermctestface/testy\"},\n\t\t\twantAnyChunks: true,\n\t\t},\n\t\t{\n\t\t\tname:          \"unit does not match include\",\n\t\t\tincludeRepos:  []string{\"https://gitlab.com/testermctestface/something-else\"},\n\t\t\twantAnyChunks: false,\n\t\t},\n\t\t{\n\t\t\tname:          \"unit matches ignore\",\n\t\t\tignoreRepos:   []string{\"https://gitlab.com/testermctestface/testy\"},\n\t\t\twantAnyChunks: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// Arrange: Create the connection\n\t\t\ttypedConn := &sourcespb.GitLab{\n\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\tToken: token,\n\t\t\t\t},\n\t\t\t\tIncludeRepos: tt.includeRepos,\n\t\t\t\tIgnoreRepos:  tt.ignoreRepos,\n\t\t\t}\n\t\t\tconn, err := anypb.New(typedConn)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Arrange: Instantiate and initialize the source\n\t\t\ts := &Source{}\n\t\t\trequire.NoError(t, s.Init(ctx, \"test source\", 1, 1, false, conn, 1))\n\n\t\t\t// Arrange: Build the chunk reporter\n\t\t\tchunksChan := make(chan *sources.Chunk, 1024)\n\t\t\tchunkReporter := sources.ChanReporter{Ch: chunksChan}\n\n\t\t\t// Act: Scan the unit\n\t\t\trequire.NoError(t, s.ChunkUnit(ctx, unit, chunkReporter))\n\n\t\t\t// Assert: Verify that chunk production was correct\n\t\t\tassert.Equal(t, tt.wantAnyChunks, len(chunksChan) > 0)\n\t\t})\n\t}\n}\n\nfunc TestSource_InclusionGlobbing(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\n\tsecret, err := common.GetTestSecret(ctx)\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\n\ttoken := secret.MustGetField(\"GITLAB_TOKEN\")\n\n\ttests := []struct {\n\t\tname             string\n\t\tconnection       *sourcespb.GitLab\n\t\twantReposScanned int\n\t\twantErrCount     int\n\t}{\n\t\t{\n\t\t\tname: \"Get all Repos\",\n\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\tToken: token,\n\t\t\t\t},\n\t\t\t\tIncludeRepos: []string{\"*\"},\n\t\t\t\tIgnoreRepos:  nil,\n\t\t\t},\n\t\t\twantReposScanned: 6,\n\t\t\twantErrCount:     0,\n\t\t},\n\t\t{\n\t\t\tname: \"Ignore testy repo, include all others\",\n\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\tToken: token,\n\t\t\t\t},\n\t\t\t\tIncludeRepos: []string{\"*\"},\n\t\t\t\tIgnoreRepos:  []string{\"*testy*\"},\n\t\t\t},\n\t\t\twantReposScanned: 5,\n\t\t\twantErrCount:     0,\n\t\t},\n\t\t{\n\t\t\tname: \"Ignore all repos\",\n\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\tToken: token,\n\t\t\t\t},\n\t\t\t\tIncludeRepos: nil,\n\t\t\t\tIgnoreRepos:  []string{\"*\"},\n\t\t\t},\n\t\t\twantReposScanned: 0,\n\t\t\twantErrCount:     0,\n\t\t},\n\t\t{\n\t\t\tname: \"Ignore all repos, but glob doesn't compile\",\n\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\tToken: token,\n\t\t\t\t},\n\t\t\t\tIncludeRepos: []string{\n\t\t\t\t\t\"[\",    // glob doesn't compile\n\t\t\t\t\t\"[a-]\", // glob doesn't compile\n\t\t\t\t},\n\t\t\t\tIgnoreRepos: []string{\n\t\t\t\t\t\"*\", // ignore all repos\n\t\t\t\t\t\"[\", // glob doesn't compile\n\t\t\t\t},\n\t\t\t},\n\t\t\twantReposScanned: 0,\n\t\t\twantErrCount:     3,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\n\t\t\tsrc := &Source{}\n\t\t\tconn, err := anypb.New(tt.connection)\n\t\t\tassert.NoError(t, err)\n\n\t\t\terr = src.Init(ctx, tt.name, 0, 0, false, conn, 1)\n\t\t\tassert.NoError(t, err)\n\n\t\t\t// Query GitLab for the list of configured repos.\n\t\t\tvar repos []string\n\t\t\tvisitor := sources.VisitorReporter{\n\t\t\t\tVisitUnit: func(ctx context.Context, unit sources.SourceUnit) error {\n\t\t\t\t\tid, _ := unit.SourceUnitID()\n\t\t\t\t\trepos = append(repos, id)\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t}\n\t\t\tapiClient, err := src.newClient()\n\t\t\tassert.NoError(t, err)\n\n\t\t\tvar errs []error\n\t\t\tignoreRepo := buildIgnorer(src.includeRepos, src.ignoreRepos, func(err error, pattern string) {\n\t\t\t\terrs = append(errs, err)\n\t\t\t})\n\t\t\terr = src.getAllProjectRepos(ctx, apiClient, ignoreRepo, visitor)\n\t\t\tassert.NoError(t, err)\n\n\t\t\tassert.Equal(t, tt.wantErrCount, len(errs))\n\t\t\tassert.Equal(t, tt.wantReposScanned, len(repos))\n\n\t\t})\n\t}\n}\n\nfunc TestSource_Chunks_ProjectDetailsInChunkMetadata(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*30)\n\tdefer cancel()\n\n\tsecret, err := common.GetTestSecret(ctx)\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\n\ttoken := secret.MustGetField(\"GITLAB_TOKEN\")\n\n\ttests := []struct {\n\t\tname       string\n\t\tconnection *sourcespb.GitLab\n\t}{\n\t\t{\n\t\t\tname: \"project details in chunk metadata - No repos configured\",\n\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\tToken: token,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"project details in chunk metadata - Repo configured\",\n\t\t\tconnection: &sourcespb.GitLab{\n\t\t\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\t\t\tToken: token,\n\t\t\t\t},\n\t\t\t\tRepositories: []string{\"https://gitlab.com/testermctestface/testy.git\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\n\t\t\ts := Source{}\n\n\t\t\tconn, err := anypb.New(tt.connection)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\terr = s.Init(ctx, tt.name, 0, 0, false, conn, 10)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Source.Init() error = %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tchunksCh := make(chan *sources.Chunk, 1)\n\t\t\tgo func() {\n\t\t\t\tdefer close(chunksCh)\n\t\t\t\terr = s.Chunks(context.Background(), chunksCh)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Source.Chunks() error = %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}()\n\t\t\tgotChunks := false\n\t\t\tfor gotChunk := range chunksCh {\n\t\t\t\tgotChunks = true\n\t\t\t\tmetadata := gotChunk.SourceMetadata.Data.(*source_metadatapb.MetaData_Gitlab)\n\t\t\t\tif metadata.Gitlab.ProjectId == 0 || metadata.Gitlab.ProjectName == \"\" {\n\t\t\t\t\tt.Errorf(\"Source.Chunks() missing project details in chunk metadata: %+v\", metadata.Gitlab)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !gotChunks {\n\t\t\t\tt.Errorf(\"0 chunks scanned.\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSource_Enumerate_ProjectDetailsInChunkMetadata(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*30)\n\tdefer cancel()\n\n\tsecret, err := common.GetTestSecret(ctx)\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\n\ttoken := secret.MustGetField(\"GITLAB_TOKEN\")\n\n\ts := Source{}\n\n\tconn, err := anypb.New(&sourcespb.GitLab{\n\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\tToken: token,\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = s.Init(ctx, \"project details in chunkmetadata\", 0, 0, false, conn, 10)\n\tif err != nil {\n\t\tt.Errorf(\"Source.Init() error = %v\", err)\n\t\treturn\n\t}\n\ttestReporter := sourcestest.TestReporter{}\n\terr = s.Enumerate(ctx, &testReporter)\n\tif err != nil {\n\t\tt.Errorf(\"Source.Chunks() error = %v\", err)\n\t\treturn\n\t}\n\tchunksCh := make(chan *sources.Chunk, 1)\n\tchanReporter := sources.ChanReporter{Ch: chunksCh}\n\t// Clear cache to force querying project details\n\tclear(s.repoToProjCache.cache)\n\tgo func() {\n\t\tdefer close(chunksCh)\n\t\tfor _, unit := range testReporter.Units {\n\t\t\terr := s.ChunkUnit(context.Background(), unit, chanReporter)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Source.ChunkUnit() error = %v\", err)\n\t\t\t}\n\t\t}\n\t}()\n\tgotChunks := false\n\tfor gotChunk := range chunksCh {\n\t\tgotChunks = true\n\t\tmetadata := gotChunk.SourceMetadata.Data.(*source_metadatapb.MetaData_Gitlab)\n\t\tif metadata.Gitlab.ProjectId == 0 || metadata.Gitlab.ProjectName == \"\" {\n\t\t\tt.Errorf(\"Source.Chunks() missing project details in chunk metadata: %+v\", metadata.Gitlab)\n\t\t}\n\t}\n\tif !gotChunks {\n\t\tt.Errorf(\"0 chunks scanned.\")\n\t}\n}\n\n// TestSource_Chunks_SimplifiedGitlabEnumeration enumerates GitLab projects\n// using a stored GitLab secret in GCP with the `UseSimplifiedGitlabEnumeration`\n// feature flag enabled. When enabled, the enumeration path is redirected to\n// `getAllProjectReposV2`, validating project listing via keyset pagination.\nfunc TestSource_Chunks_SimplifiedGitlabEnumeration(t *testing.T) {\n\t// Preserve and restore the feature flag to avoid cross-test contamination\n\tprev := feature.UseSimplifiedGitlabEnumeration.Load()\n\t// enable the simplified gitlab enumeration flag\n\tfeature.UseSimplifiedGitlabEnumeration.Store(true)\n\tdefer feature.UseSimplifiedGitlabEnumeration.Store(prev)\n\n\t// Create a bounded context for the entire test\n\tctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)\n\tdefer cancel()\n\n\t// Retrieve test secret containing the GitLab token\n\tsecret, err := common.GetTestSecret(ctx)\n\trequire.NoError(t, err, \"failed to access test secret\")\n\n\ttoken := secret.MustGetField(\"GITLAB_TOKEN\")\n\n\t// Initialize the GitLab source with token-based authentication\n\ts := Source{}\n\tconn, err := anypb.New(&sourcespb.GitLab{\n\t\tCredential: &sourcespb.GitLab_Token{\n\t\t\tToken: token,\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\n\terr = s.Init(ctx, \"enumerate gitlab projects with V2\", 0, 0, false, conn, 10)\n\trequire.NoError(t, err, \"failed during Source.Init\")\n\n\t// Enumerate GitLab projects\n\ttestReporter := sourcestest.TestReporter{}\n\terr = s.Enumerate(ctx, &testReporter)\n\trequire.NoError(t, err, \"enumeration should not fail\")\n\n\t// Ensure enumeration actually produced units\n\trequire.NotEmpty(t, testReporter.Units, \"enumeration returned no units\")\n\n\t// Clear project cache to force project-detail lookups during chunking\n\tclear(s.repoToProjCache.cache)\n\n\t// Channel-based reporter to capture emitted chunks\n\tchunksCh := make(chan *sources.Chunk, 1)\n\tchanReporter := sources.ChanReporter{Ch: chunksCh}\n\n\t// Chunk all enumerated units asynchronously\n\tgo func() {\n\t\tdefer close(chunksCh)\n\t\tfor _, unit := range testReporter.Units {\n\t\t\tif err := s.ChunkUnit(ctx, unit, chanReporter); err != nil {\n\t\t\t\tt.Errorf(\"Source.ChunkUnit() error = %v\", err)\n\t\t\t}\n\t\t}\n\t}()\n\n\t// Validate produced chunks and their GitLab metadata\n\tgotChunks := false\n\tfor chunk := range chunksCh {\n\t\tgotChunks = true\n\n\t\tmeta, ok := chunk.SourceMetadata.Data.(*source_metadatapb.MetaData_Gitlab)\n\t\trequire.True(t, ok, \"unexpected metadata type\")\n\n\t\tassert.NotZero(t, meta.Gitlab.ProjectId, \"missing project ID in chunk metadata\")\n\t\tassert.NotEmpty(t, meta.Gitlab.ProjectName, \"missing project name in chunk metadata\")\n\t}\n\n\t// Ensure at least one chunk was produced\n\tassert.True(t, gotChunks, \"expected at least one chunk, got zero\")\n}\n"
  },
  {
    "path": "pkg/sources/gitlab/gitlab_test.go",
    "content": "package gitlab\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"golang.org/x/sync/errgroup\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git\"\n)\n\nfunc Test_setProgressCompleteWithRepo_resumeInfo(t *testing.T) {\n\ttests := []struct {\n\t\tstartingResumeInfoSlice []string\n\t\trepoURL                 string\n\t\twantResumeInfoSlice     []string\n\t}{\n\t\t{\n\t\t\tstartingResumeInfoSlice: []string{},\n\t\t\trepoURL:                 \"a\",\n\t\t\twantResumeInfoSlice:     []string{\"a\"},\n\t\t},\n\t\t{\n\t\t\tstartingResumeInfoSlice: []string{\"b\"},\n\t\t\trepoURL:                 \"a\",\n\t\t\twantResumeInfoSlice:     []string{\"a\", \"b\"},\n\t\t},\n\t}\n\n\ts := &Source{repos: []string{}}\n\n\tfor _, tt := range tests {\n\t\ts.resumeInfoSlice = tt.startingResumeInfoSlice\n\t\ts.setProgressCompleteWithRepo(0, 0, tt.repoURL)\n\t\tif !reflect.DeepEqual(s.resumeInfoSlice, tt.wantResumeInfoSlice) {\n\t\t\tt.Errorf(\"s.setProgressCompleteWithRepo() got: %v, want: %v\", s.resumeInfoSlice, tt.wantResumeInfoSlice)\n\t\t}\n\t}\n}\n\nfunc Test_setProgressCompleteWithRepo_Progress(t *testing.T) {\n\trepos := []string{\"a\", \"b\", \"c\", \"d\", \"e\"}\n\ttests := map[string]struct {\n\t\trepos                 []string\n\t\tindex                 int\n\t\toffset                int\n\t\twantPercentComplete   int64\n\t\twantSectionsCompleted int32\n\t\twantSectionsRemaining int32\n\t}{\n\t\t\"starting from the beginning, no offset\": {\n\t\t\trepos:                 repos,\n\t\t\tindex:                 0,\n\t\t\toffset:                0,\n\t\t\twantPercentComplete:   0,\n\t\t\twantSectionsCompleted: 0,\n\t\t\twantSectionsRemaining: 5,\n\t\t},\n\t\t\"resume from the third, offset 2\": {\n\t\t\trepos:                 repos[2:],\n\t\t\tindex:                 0,\n\t\t\toffset:                2,\n\t\t\twantPercentComplete:   40,\n\t\t\twantSectionsCompleted: 2,\n\t\t\twantSectionsRemaining: 5,\n\t\t},\n\t\t\"resume from the third, on last repo, offset 2\": {\n\t\t\trepos:                 repos[2:],\n\t\t\tindex:                 2,\n\t\t\toffset:                2,\n\t\t\twantPercentComplete:   80,\n\t\t\twantSectionsCompleted: 4,\n\t\t\twantSectionsRemaining: 5,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\ts := &Source{\n\t\t\trepos: tt.repos,\n\t\t}\n\n\t\ts.setProgressCompleteWithRepo(tt.index, tt.offset, \"\")\n\t\tgotProgress := s.GetProgress()\n\t\tif gotProgress.PercentComplete != tt.wantPercentComplete {\n\t\t\tt.Errorf(\"s.setProgressCompleteWithRepo() PercentComplete got: %v want: %v\", gotProgress.PercentComplete, tt.wantPercentComplete)\n\t\t}\n\t\tif gotProgress.SectionsCompleted != tt.wantSectionsCompleted {\n\t\t\tt.Errorf(\"s.setProgressCompleteWithRepo() PercentComplete got: %v want: %v\", gotProgress.SectionsCompleted, tt.wantSectionsCompleted)\n\t\t}\n\t\tif gotProgress.SectionsRemaining != tt.wantSectionsRemaining {\n\t\t\tt.Errorf(\"s.setProgressCompleteWithRepo() PercentComplete got: %v want: %v\", gotProgress.SectionsRemaining, tt.wantSectionsRemaining)\n\t\t}\n\t}\n}\n\nfunc Test_scanRepos_SetProgressComplete(t *testing.T) {\n\ttestCases := []struct {\n\t\tname         string\n\t\trepos        []string\n\t\twantComplete bool\n\t\twantErr      bool\n\t}{\n\t\t{\n\t\t\tname:         \"no repos\",\n\t\t\twantComplete: true,\n\t\t},\n\t\t{\n\t\t\tname:         \"one valid repo\",\n\t\t\trepos:        []string{\"repo\"},\n\t\t\twantComplete: true,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tsrc := &Source{\n\t\t\t\trepos: tc.repos,\n\t\t\t}\n\t\t\tsrc.jobPool = &errgroup.Group{}\n\t\t\tsrc.scanOptions = &git.ScanOptions{}\n\n\t\t\t_ = src.scanRepos(context.Background(), nil)\n\t\t\tif !tc.wantErr {\n\t\t\t\tassert.Equal(t, \"\", src.GetProgress().EncodedResumeInfo)\n\t\t\t}\n\n\t\t\tgotComplete := src.GetProgress().PercentComplete == 100\n\t\t\tif gotComplete != tc.wantComplete {\n\t\t\t\tt.Errorf(\"got: %v, want: %v\", gotComplete, tc.wantComplete)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_normalizeGitlabEndpoint(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tinputEndpoint  string\n\t\toutputEndpoint string\n\t\twantErr        bool\n\t}{\n\t\t\"the cloud url should return the cloud url\": {\n\t\t\tinputEndpoint:  gitlabBaseURL,\n\t\t\toutputEndpoint: gitlabBaseURL,\n\t\t},\n\t\t\"empty string should return the cloud url\": {\n\t\t\tinputEndpoint:  \"\",\n\t\t\toutputEndpoint: gitlabBaseURL,\n\t\t},\n\t\t\"no scheme cloud url should return the cloud url\": {\n\t\t\tinputEndpoint:  \"gitlab.com\",\n\t\t\toutputEndpoint: gitlabBaseURL,\n\t\t},\n\t\t\"no scheme cloud url with trailing slash should return the cloud url\": {\n\t\t\tinputEndpoint:  \"gitlab.com/\",\n\t\t\toutputEndpoint: gitlabBaseURL,\n\t\t},\n\t\t\"http scheme cloud url with organization should return the cloud url\": {\n\t\t\tinputEndpoint:  \"http://gitlab.com/trufflesec\",\n\t\t\toutputEndpoint: gitlabBaseURL,\n\t\t},\n\t\t// On-prem endpoint testing.\n\t\t\"on-prem url should be unchanged\": {\n\t\t\tinputEndpoint:  \"https://gitlab.trufflesec.com/\",\n\t\t\toutputEndpoint: \"https://gitlab.trufflesec.com/\",\n\t\t},\n\t\t\"on-prem url without trailing slash should have trailing slash added\": {\n\t\t\tinputEndpoint:  \"https://gitlab.trufflesec.com\",\n\t\t\toutputEndpoint: \"https://gitlab.trufflesec.com/\",\n\t\t},\n\t\t\"on-prem url with http scheme should return an error\": {\n\t\t\tinputEndpoint: \"http://gitlab.trufflesec.com/\",\n\t\t\twantErr:       true,\n\t\t},\n\t\t\"on-prem with gitlab.com should not rewrite to the cloud url\": {\n\t\t\tinputEndpoint:  \"https://gitlab.com.trufflesec.com/\",\n\t\t\toutputEndpoint: \"https://gitlab.com.trufflesec.com/\",\n\t\t},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\toutput, err := normalizeGitlabEndpoint(tc.inputEndpoint)\n\t\t\tassert.Equal(t, tc.outputEndpoint, output)\n\t\t\tassert.Equal(t, tc.wantErr, err != nil)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/sources/gitlab/metrics.go",
    "content": "package gitlab\n\nimport (\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n)\n\nvar (\n\tgitlabGroupsEnumerated = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"gitlab_groups_enumerated\",\n\t\tHelp:      \"Total number of GitLab groups enumerated.\",\n\t},\n\t\t[]string{\"source_name\"})\n\n\tgitlabReposEnumerated = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"gitlab_repos_enumerated\",\n\t\tHelp:      \"Total number of Gitlab repositories enumerated.\",\n\t},\n\t\t[]string{\"source_name\"})\n\n\tgitlabReposScanned = promauto.NewGaugeVec(prometheus.GaugeOpts{\n\t\tNamespace: common.MetricsNamespace,\n\t\tSubsystem: common.MetricsSubsystem,\n\t\tName:      \"gitlab_repos_scanned\",\n\t\tHelp:      \"Total number of Gitlab repositories scanned.\",\n\t},\n\t\t[]string{\"source_name\"})\n)\n"
  },
  {
    "path": "pkg/sources/huggingface/client.go",
    "content": "package huggingface\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n)\n\n// Maps for API and HTML paths\nvar apiPaths = map[string]string{\n\tDATASET: DatasetsRoute,\n\tMODEL:   ModelsAPIRoute,\n\tSPACE:   SpacesRoute,\n}\n\nvar htmlPaths = map[string]string{\n\tDATASET: DatasetsRoute,\n\tMODEL:   \"\",\n\tSPACE:   SpacesRoute,\n}\n\ntype Author struct {\n\tUsername string `json:\"name\"`\n}\n\ntype Latest struct {\n\tRaw string `json:\"raw\"`\n}\n\ntype Data struct {\n\tLatest Latest `json:\"latest\"`\n}\n\ntype Event struct {\n\tType      string `json:\"type\"`\n\tAuthor    Author `json:\"author\"`\n\tCreatedAt string `json:\"createdAt\"`\n\tData      Data   `json:\"data\"`\n\tID        string `json:\"id\"`\n}\n\nfunc (e Event) GetAuthor() string {\n\treturn e.Author.Username\n}\n\nfunc (e Event) GetCreatedAt() string {\n\treturn e.CreatedAt\n}\n\nfunc (e Event) GetID() string {\n\treturn e.ID\n}\n\ntype RepoData struct {\n\tFullName     string `json:\"name\"`\n\tResourceType string `json:\"type\"`\n}\n\ntype Discussion struct {\n\tID        int      `json:\"num\"`\n\tIsPR      bool     `json:\"isPullRequest\"`\n\tCreatedAt string   `json:\"createdAt\"`\n\tTitle     string   `json:\"title\"`\n\tEvents    []Event  `json:\"events\"`\n\tRepo      RepoData `json:\"repo\"`\n}\n\nfunc (d Discussion) GetID() string {\n\treturn fmt.Sprint(d.ID)\n}\n\nfunc (d Discussion) GetTitle() string {\n\treturn d.Title\n}\n\nfunc (d Discussion) GetCreatedAt() string {\n\treturn d.CreatedAt\n}\n\nfunc (d Discussion) GetRepo() string {\n\treturn d.Repo.FullName\n}\n\n// GetDiscussionPath returns the path (ex: \"/models/user/repo/discussions/1\") for the discussion\nfunc (d Discussion) GetDiscussionPath() string {\n\tbasePath := fmt.Sprintf(\"%s/%s/%s\", d.GetRepo(), DiscussionsRoute, d.GetID())\n\tif d.Repo.ResourceType == \"model\" {\n\t\treturn basePath\n\t}\n\treturn fmt.Sprintf(\"%s/%s\", getResourceHTMLPath(d.Repo.ResourceType), basePath)\n}\n\n// GetGitPath returns the path (ex: \"/models/user/repo.git\") for the repo's git directory\nfunc (d Discussion) GetGitPath() string {\n\tbasePath := fmt.Sprintf(\"%s.git\", d.GetRepo())\n\tif d.Repo.ResourceType == \"model\" {\n\t\treturn basePath\n\t}\n\treturn fmt.Sprintf(\"%s/%s\", getResourceHTMLPath(d.Repo.ResourceType), basePath)\n}\n\ntype DiscussionList struct {\n\tDiscussions []Discussion `json:\"discussions\"`\n}\n\ntype Repo struct {\n\tIsPrivate bool   `json:\"private\"`\n\tOwner     string `json:\"author\"`\n\tRepoID    string `json:\"id\"`\n}\n\ntype HFClient struct {\n\tBaseURL    string\n\tAPIKey     string\n\tHTTPClient *http.Client\n}\n\n// NewHFClient creates a new HF client\nfunc NewHFClient(baseURL, apiKey string, timeout time.Duration) *HFClient {\n\treturn &HFClient{\n\t\tBaseURL: baseURL,\n\t\tAPIKey:  apiKey,\n\t\tHTTPClient: &http.Client{\n\t\t\tTimeout: timeout,\n\t\t},\n\t}\n}\n\n// get makes a GET request to the Hugging Face API\n// Note: not addressing rate limit, since it seems very permissive. (ex: \"If \\\n// your account suddenly sends 10k requests then you’re likely to receive 503\")\nfunc (c *HFClient) get(ctx context.Context, url string, target interface{}) error {\n\tctx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\tdefer cancel()\n\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", url, nil)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create HuggingFace API request: %w\", err)\n\t}\n\n\treq.Header.Set(\"Authorization\", \"Bearer \"+c.APIKey)\n\n\tresp, err := c.HTTPClient.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to make request to HuggingFace API: %w\", err)\n\t}\n\n\tif resp.StatusCode == http.StatusUnauthorized {\n\t\treturn errors.New(\"invalid API key\")\n\t}\n\n\tif resp.StatusCode == http.StatusForbidden {\n\t\treturn errors.New(\"access to this repo is restricted and you are not in the authorized list. Visit the repository to ask for access\")\n\t}\n\n\tdefer resp.Body.Close()\n\n\treturn json.NewDecoder(resp.Body).Decode(target)\n}\n\n// GetRepo retrieves repo from the Hugging Face API\nfunc (c *HFClient) GetRepo(ctx context.Context, repoName string, resourceType string) (Repo, error) {\n\tvar repo Repo\n\turl, err := buildAPIURL(c.BaseURL, resourceType, repoName)\n\tif err != nil {\n\t\treturn repo, err\n\t}\n\terr = c.get(ctx, url, &repo)\n\treturn repo, err\n}\n\n// ListDiscussions retrieves discussions from the Hugging Face API\nfunc (c *HFClient) ListDiscussions(ctx context.Context, repoInfo repoInfo) (DiscussionList, error) {\n\tvar discussions DiscussionList\n\tbaseURL, err := buildAPIURL(c.BaseURL, string(repoInfo.resourceType), repoInfo.fullName)\n\tif err != nil {\n\t\treturn discussions, err\n\t}\n\turl := fmt.Sprintf(\"%s/%s\", baseURL, DiscussionsRoute)\n\terr = c.get(ctx, url, &discussions)\n\treturn discussions, err\n}\n\nfunc (c *HFClient) GetDiscussionByID(ctx context.Context, repoInfo repoInfo, discussionID string) (Discussion, error) {\n\tvar discussion Discussion\n\tbaseURL, err := buildAPIURL(c.BaseURL, string(repoInfo.resourceType), repoInfo.fullName)\n\tif err != nil {\n\t\treturn discussion, err\n\t}\n\turl := fmt.Sprintf(\"%s/%s/%s\", baseURL, DiscussionsRoute, discussionID)\n\terr = c.get(ctx, url, &discussion)\n\treturn discussion, err\n}\n\n// ListReposByAuthor retrieves repos from the Hugging Face API by author (user or org)\n// Note: not addressing pagination b/c allow by default 1000 results, which should be enough for 99.99% of cases\nfunc (c *HFClient) ListReposByAuthor(ctx context.Context, resourceType string, author string) ([]Repo, error) {\n\tvar repos []Repo\n\turl := fmt.Sprintf(\"%s/%s/%s?limit=1000&author=%s\", c.BaseURL, APIRoute, getResourceAPIPath(resourceType), author)\n\terr := c.get(ctx, url, &repos)\n\treturn repos, err\n}\n\n// getResourceAPIPath returns the API path for the given resource type\nfunc getResourceAPIPath(resourceType string) string {\n\treturn apiPaths[resourceType]\n}\n\n// getResourceHTMLPath returns the HTML path for the given resource type\nfunc getResourceHTMLPath(resourceType string) string {\n\treturn htmlPaths[resourceType]\n}\n\nfunc buildAPIURL(endpoint string, resourceType string, repoName string) (string, error) {\n\tif endpoint == \"\" || resourceType == \"\" || repoName == \"\" {\n\t\treturn \"\", errors.New(\"endpoint, resourceType, and repoName must not be empty\")\n\t}\n\treturn fmt.Sprintf(\"%s/%s/%s/%s\", endpoint, APIRoute, getResourceAPIPath(resourceType), repoName), nil\n}\n"
  },
  {
    "path": "pkg/sources/huggingface/huggingface.go",
    "content": "package huggingface\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/gobwas/glob\"\n\t\"golang.org/x/sync/errgroup\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/cache/simple\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/giturl\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sanitizer\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git\"\n)\n\nconst (\n\tSourceType        = sourcespb.SourceType_SOURCE_TYPE_HUGGINGFACE\n\tDatasetsRoute     = \"datasets\"\n\tSpacesRoute       = \"spaces\"\n\tModelsAPIRoute    = \"models\"\n\tDiscussionsRoute  = \"discussions\"\n\tAPIRoute          = \"api\"\n\tDATASET           = \"dataset\"\n\tMODEL             = \"model\"\n\tSPACE             = \"space\"\n\tdefaultPagination = 100\n)\n\ntype resourceType string\n\ntype Source struct {\n\tname             string\n\thuggingfaceToken string\n\n\tsourceID               sources.SourceID\n\tjobID                  sources.JobID\n\tverify                 bool\n\tuseCustomContentWriter bool\n\torgsCache              cache.Cache[string]\n\tusersCache             cache.Cache[string]\n\n\tmodels   []string\n\tspaces   []string\n\tdatasets []string\n\n\tfilteredModelsCache   *filteredRepoCache\n\tfilteredSpacesCache   *filteredRepoCache\n\tfilteredDatasetsCache *filteredRepoCache\n\n\trepoInfoCache repoInfoCache\n\n\tgit *git.Git\n\n\tscanOptions *git.ScanOptions\n\n\tapiClient       *HFClient\n\tconn            *sourcespb.Huggingface\n\tjobPool         *errgroup.Group\n\tresumeInfoMutex sync.Mutex\n\tresumeInfoSlice []string\n\n\tskipAllModels      bool\n\tskipAllSpaces      bool\n\tskipAllDatasets    bool\n\tincludeDiscussions bool\n\tincludePrs         bool\n\n\tsources.Progress\n\tsources.CommonSourceUnitUnmarshaller\n}\n\n// Ensure the Source satisfies the interfaces at compile time\nvar _ sources.Source = (*Source)(nil)\nvar _ sources.SourceUnitUnmarshaller = (*Source)(nil)\n\n// WithCustomContentWriter sets the useCustomContentWriter flag on the source.\nfunc (s *Source) WithCustomContentWriter() { s.useCustomContentWriter = true }\n\n// Type returns the type of source.\n// It is used for matching source types in configuration and job input.\nfunc (s *Source) Type() sourcespb.SourceType {\n\treturn SourceType\n}\n\nfunc (s *Source) SourceID() sources.SourceID {\n\treturn s.sourceID\n}\n\nfunc (s *Source) JobID() sources.JobID {\n\treturn s.jobID\n}\n\n// filteredRepoCache is a wrapper around cache.Cache that filters out repos\n// based on include and exclude globs.\ntype filteredRepoCache struct {\n\tcache.Cache[string]\n\tinclude, exclude []glob.Glob\n}\n\nfunc (s *Source) newFilteredRepoCache(ctx context.Context, c cache.Cache[string], include, exclude []string) *filteredRepoCache {\n\tincludeGlobs := make([]glob.Glob, 0, len(include))\n\texcludeGlobs := make([]glob.Glob, 0, len(exclude))\n\tfor _, ig := range include {\n\t\tg, err := glob.Compile(ig)\n\t\tif err != nil {\n\t\t\tctx.Logger().V(1).Info(\"invalid include glob\", \"include_value\", ig, \"err\", err)\n\t\t\tcontinue\n\t\t}\n\t\tincludeGlobs = append(includeGlobs, g)\n\t}\n\tfor _, eg := range exclude {\n\t\tg, err := glob.Compile(eg)\n\t\tif err != nil {\n\t\t\tctx.Logger().V(1).Info(\"invalid exclude glob\", \"exclude_value\", eg, \"err\", err)\n\t\t\tcontinue\n\t\t}\n\t\texcludeGlobs = append(excludeGlobs, g)\n\t}\n\treturn &filteredRepoCache{Cache: c, include: includeGlobs, exclude: excludeGlobs}\n}\n\n// Set overrides the cache.Cache Set method to filter out repos based on\n// include and exclude globs.\nfunc (c *filteredRepoCache) Set(key, val string) {\n\tif c.ignoreRepo(key) {\n\t\treturn\n\t}\n\tif !c.includeRepo(key) {\n\t\treturn\n\t}\n\tc.Cache.Set(key, val)\n}\n\nfunc (c *filteredRepoCache) ignoreRepo(s string) bool {\n\tfor _, g := range c.exclude {\n\t\tif g.Match(s) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (c *filteredRepoCache) includeRepo(s string) bool {\n\tif len(c.include) == 0 {\n\t\treturn true\n\t}\n\n\tfor _, g := range c.include {\n\t\tif g.Match(s) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// Init returns an initialized HuggingFace source.\nfunc (s *Source) Init(ctx context.Context, name string, jobID sources.JobID, sourceID sources.SourceID, verify bool, connection *anypb.Any, concurrency int) error {\n\terr := git.CmdCheck()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.name = name\n\ts.sourceID = sourceID\n\ts.jobID = jobID\n\ts.verify = verify\n\ts.jobPool = &errgroup.Group{}\n\ts.jobPool.SetLimit(concurrency)\n\n\tvar conn sourcespb.Huggingface\n\terr = anypb.UnmarshalTo(connection, &conn, proto.UnmarshalOptions{})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error unmarshalling connection: %w\", err)\n\t}\n\ts.conn = &conn\n\n\ts.orgsCache = simple.NewCache[string]()\n\tfor _, org := range s.conn.Organizations {\n\t\ts.orgsCache.Set(org, org)\n\t}\n\n\ts.usersCache = simple.NewCache[string]()\n\tfor _, user := range s.conn.Users {\n\t\ts.usersCache.Set(user, user)\n\t}\n\n\t// Verify ignore and include models, spaces, and datasets are valid\n\t// this ensures that calling --org <org> --ignore-model <org/model> contains the proper\n\t// repo format of org/model. Otherwise, we would scan the entire org.\n\tif err := s.validateIgnoreIncludeRepos(); err != nil {\n\t\treturn err\n\t}\n\n\ts.filteredModelsCache = s.newFilteredRepoCache(ctx, simple.NewCache[string](),\n\t\tappend(s.conn.GetModels(), s.conn.GetIncludeModels()...),\n\t\ts.conn.GetIgnoreModels(),\n\t)\n\n\ts.filteredSpacesCache = s.newFilteredRepoCache(ctx, simple.NewCache[string](),\n\t\tappend(s.conn.GetSpaces(), s.conn.GetIncludeSpaces()...),\n\t\ts.conn.GetIgnoreSpaces(),\n\t)\n\n\ts.filteredDatasetsCache = s.newFilteredRepoCache(ctx, simple.NewCache[string](),\n\t\tappend(s.conn.GetDatasets(), s.conn.GetIncludeDatasets()...),\n\t\ts.conn.GetIgnoreDatasets(),\n\t)\n\n\ts.models = initializeRepos(s.filteredModelsCache, s.conn.Models, fmt.Sprintf(\"%s/%s.git\", s.conn.Endpoint, \"%s\"))\n\ts.spaces = initializeRepos(s.filteredSpacesCache, s.conn.Spaces, fmt.Sprintf(\"%s/%s/%s.git\", s.conn.Endpoint, SpacesRoute, \"%s\"))\n\ts.datasets = initializeRepos(s.filteredDatasetsCache, s.conn.Datasets, fmt.Sprintf(\"%s/%s/%s.git\", s.conn.Endpoint, DatasetsRoute, \"%s\"))\n\ts.repoInfoCache = newRepoInfoCache()\n\n\ts.includeDiscussions = s.conn.IncludeDiscussions\n\ts.includePrs = s.conn.IncludePrs\n\n\tcfg := &git.Config{\n\t\tSourceName:  s.name,\n\t\tJobID:       s.jobID,\n\t\tSourceID:    s.sourceID,\n\t\tSourceType:  s.Type(),\n\t\tVerify:      s.verify,\n\t\tConcurrency: concurrency,\n\t\tSourceMetadataFunc: func(file, email, commit, timestamp, repository, repositoryLocalPath string, line int64) *source_metadatapb.MetaData {\n\t\t\treturn &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Huggingface{\n\t\t\t\t\tHuggingface: &source_metadatapb.Huggingface{\n\t\t\t\t\t\tCommit:       sanitizer.UTF8(commit),\n\t\t\t\t\t\tFile:         sanitizer.UTF8(file),\n\t\t\t\t\t\tEmail:        sanitizer.UTF8(email),\n\t\t\t\t\t\tRepository:   sanitizer.UTF8(repository),\n\t\t\t\t\t\tLink:         giturl.GenerateLink(repository, commit, file, line),\n\t\t\t\t\t\tTimestamp:    sanitizer.UTF8(timestamp),\n\t\t\t\t\t\tLine:         line,\n\t\t\t\t\t\tVisibility:   s.visibilityOf(ctx, repository),\n\t\t\t\t\t\tResourceType: s.getResourceType(ctx, repository),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t\tUseCustomContentWriter: s.useCustomContentWriter,\n\t}\n\ts.git = git.NewGit(cfg)\n\n\ts.huggingfaceToken = s.conn.GetToken()\n\ts.apiClient = NewHFClient(s.conn.Endpoint, s.huggingfaceToken, 10*time.Second)\n\n\ts.skipAllModels = s.conn.SkipAllModels\n\ts.skipAllSpaces = s.conn.SkipAllSpaces\n\ts.skipAllDatasets = s.conn.SkipAllDatasets\n\n\treturn nil\n}\n\nfunc (s *Source) validateIgnoreIncludeRepos() error {\n\tif err := verifySlashSeparatedStrings(s.conn.IgnoreModels); err != nil {\n\t\treturn err\n\t}\n\tif err := verifySlashSeparatedStrings(s.conn.IncludeModels); err != nil {\n\t\treturn err\n\t}\n\tif err := verifySlashSeparatedStrings(s.conn.IgnoreSpaces); err != nil {\n\t\treturn err\n\t}\n\tif err := verifySlashSeparatedStrings(s.conn.IncludeSpaces); err != nil {\n\t\treturn err\n\t}\n\tif err := verifySlashSeparatedStrings(s.conn.IgnoreDatasets); err != nil {\n\t\treturn err\n\t}\n\tif err := verifySlashSeparatedStrings(s.conn.IncludeDatasets); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc verifySlashSeparatedStrings(s []string) error {\n\tfor _, str := range s {\n\t\tif !strings.Contains(str, \"/\") {\n\t\t\treturn fmt.Errorf(\"invalid owner/repo: %s\", str)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc initializeRepos(cache *filteredRepoCache, repos []string, urlPattern string) []string {\n\treturnRepos := make([]string, 0)\n\tfor _, repo := range repos {\n\t\tif !cache.ignoreRepo(repo) {\n\t\t\turl := fmt.Sprintf(urlPattern, repo)\n\t\t\tcache.Set(repo, url)\n\t\t\treturnRepos = append(returnRepos, repo)\n\t\t}\n\t}\n\treturn returnRepos\n}\n\nfunc (s *Source) getResourceType(ctx context.Context, repoURL string) string {\n\trepoInfo, ok := s.repoInfoCache.get(repoURL)\n\tif !ok {\n\t\t// This should never happen.\n\t\terr := fmt.Errorf(\"no repoInfo for URL: %s\", repoURL)\n\t\tctx.Logger().Error(err, \"failed to get repository resource type\")\n\t\treturn \"\"\n\t}\n\n\treturn string(repoInfo.resourceType)\n}\n\nfunc (s *Source) visibilityOf(ctx context.Context, repoURL string) source_metadatapb.Visibility {\n\trepoInfo, ok := s.repoInfoCache.get(repoURL)\n\tif !ok {\n\t\t// This should never happen.\n\t\terr := fmt.Errorf(\"no repoInfo for URL: %s\", repoURL)\n\t\tctx.Logger().Error(err, \"failed to get repository visibility\")\n\t\treturn source_metadatapb.Visibility_unknown\n\t}\n\n\treturn repoInfo.visibility\n}\n\n// Chunks emits chunks of bytes over a channel.\nfunc (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, targets ...sources.ChunkingTarget) error {\n\terr := s.enumerate(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn s.scan(ctx, chunksChan)\n}\n\nfunc (s *Source) enumerate(ctx context.Context) error {\n\ts.enumerateAuthors(ctx)\n\n\ts.models = make([]string, 0, s.filteredModelsCache.Count())\n\tfor _, repo := range s.filteredModelsCache.Keys() {\n\t\tif err := s.cacheRepoInfo(ctx, repo, MODEL, s.filteredModelsCache); err != nil {\n\t\t\tcontinue\n\t\t}\n\t}\n\n\ts.spaces = make([]string, 0, s.filteredSpacesCache.Count())\n\tfor _, repo := range s.filteredSpacesCache.Keys() {\n\t\tif err := s.cacheRepoInfo(ctx, repo, SPACE, s.filteredSpacesCache); err != nil {\n\t\t\tcontinue\n\t\t}\n\t}\n\n\ts.datasets = make([]string, 0, s.filteredDatasetsCache.Count())\n\tfor _, repo := range s.filteredDatasetsCache.Keys() {\n\t\tif err := s.cacheRepoInfo(ctx, repo, DATASET, s.filteredDatasetsCache); err != nil {\n\t\t\tcontinue\n\t\t}\n\t}\n\n\tctx.Logger().Info(\"Completed enumeration\", \"num_models\", len(s.models), \"num_spaces\", len(s.spaces), \"num_datasets\", len(s.datasets))\n\n\t// We must sort the repos so we can resume later if necessary.\n\tsort.Strings(s.models)\n\tsort.Strings(s.datasets)\n\tsort.Strings(s.spaces)\n\treturn nil\n}\n\nfunc (s *Source) cacheRepoInfo(ctx context.Context, repo string, repoType string, repoCache *filteredRepoCache) error {\n\trepoURL, _ := repoCache.Get(repo)\n\trepoCtx := context.WithValue(ctx, repoType, repoURL)\n\n\tif _, ok := s.repoInfoCache.get(repoURL); !ok {\n\t\trepoCtx.Logger().V(2).Info(\"Caching \" + repoType + \" info\")\n\t\trepo, err := s.apiClient.GetRepo(repoCtx, repo, repoType)\n\t\tif err != nil {\n\t\t\trepoCtx.Logger().Error(err, \"failed to fetch \"+repoType)\n\t\t\treturn err\n\t\t}\n\t\t// check if repo empty\n\t\tif repo.RepoID == \"\" {\n\t\t\trepoCtx.Logger().Error(fmt.Errorf(\"no repo found for repo\"), repoURL)\n\t\t\treturn nil\n\t\t}\n\t\ts.repoInfoCache.put(repoURL, repoInfo{\n\t\t\towner:        repo.Owner,\n\t\t\tname:         strings.Split(repo.RepoID, \"/\")[1],\n\t\t\tfullName:     repo.RepoID,\n\t\t\tvisibility:   getVisibility(repo.IsPrivate),\n\t\t\tresourceType: resourceType(repoType),\n\t\t})\n\t}\n\ts.updateRepoLists(repoURL, repoType)\n\treturn nil\n}\n\nfunc getVisibility(isPrivate bool) source_metadatapb.Visibility {\n\tif isPrivate {\n\t\treturn source_metadatapb.Visibility_private\n\t}\n\treturn source_metadatapb.Visibility_public\n}\n\nfunc (s *Source) updateRepoLists(repoURL string, repoType string) {\n\tswitch repoType {\n\tcase MODEL:\n\t\ts.models = append(s.models, repoURL)\n\tcase SPACE:\n\t\ts.spaces = append(s.spaces, repoURL)\n\tcase DATASET:\n\t\ts.datasets = append(s.datasets, repoURL)\n\t}\n}\n\nfunc (s *Source) fetchAndCacheRepos(ctx context.Context, resourceType string, org string) error {\n\tvar repos []Repo\n\tvar err error\n\tvar url string\n\tvar filteredCache *filteredRepoCache\n\tswitch resourceType {\n\tcase MODEL:\n\t\tfilteredCache = s.filteredModelsCache\n\t\turl = fmt.Sprintf(\"%s/%s.git\", s.conn.Endpoint, \"%s\")\n\t\trepos, err = s.apiClient.ListReposByAuthor(ctx, MODEL, org)\n\tcase SPACE:\n\t\tfilteredCache = s.filteredSpacesCache\n\t\turl = fmt.Sprintf(\"%s/%s/%s.git\", s.conn.Endpoint, SpacesRoute, \"%s\")\n\t\trepos, err = s.apiClient.ListReposByAuthor(ctx, SPACE, org)\n\tcase DATASET:\n\t\tfilteredCache = s.filteredDatasetsCache\n\t\turl = fmt.Sprintf(\"%s/%s/%s.git\", s.conn.Endpoint, DatasetsRoute, \"%s\")\n\t\trepos, err = s.apiClient.ListReposByAuthor(ctx, DATASET, org)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, repo := range repos {\n\t\trepoURL := fmt.Sprintf(url, repo.RepoID)\n\t\tfilteredCache.Set(repo.RepoID, repoURL)\n\t\tif err := s.cacheRepoInfo(ctx, repo.RepoID, resourceType, filteredCache); err != nil {\n\t\t\tcontinue\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Source) enumerateAuthors(ctx context.Context) {\n\tfor _, org := range s.orgsCache.Keys() {\n\t\torgCtx := context.WithValue(ctx, \"organization\", org)\n\t\tif !s.skipAllModels {\n\t\t\tif err := s.fetchAndCacheRepos(orgCtx, MODEL, org); err != nil {\n\t\t\t\torgCtx.Logger().Error(err, \"Failed to fetch models for organization\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tif !s.skipAllSpaces {\n\t\t\tif err := s.fetchAndCacheRepos(orgCtx, SPACE, org); err != nil {\n\t\t\t\torgCtx.Logger().Error(err, \"Failed to fetch spaces for organization\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tif !s.skipAllDatasets {\n\t\t\tif err := s.fetchAndCacheRepos(orgCtx, DATASET, org); err != nil {\n\t\t\t\torgCtx.Logger().Error(err, \"Failed to fetch datasets for organization\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n\tfor _, user := range s.usersCache.Keys() {\n\t\tuserCtx := context.WithValue(ctx, \"user\", user)\n\t\tif !s.skipAllModels {\n\t\t\tif err := s.fetchAndCacheRepos(userCtx, MODEL, user); err != nil {\n\t\t\t\tuserCtx.Logger().Error(err, \"Failed to fetch models for user\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tif !s.skipAllSpaces {\n\t\t\tif err := s.fetchAndCacheRepos(userCtx, SPACE, user); err != nil {\n\t\t\t\tuserCtx.Logger().Error(err, \"Failed to fetch spaces for user\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tif !s.skipAllDatasets {\n\t\t\tif err := s.fetchAndCacheRepos(userCtx, DATASET, user); err != nil {\n\t\t\t\tuserCtx.Logger().Error(err, \"Failed to fetch datasets for user\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (s *Source) scanRepos(ctx context.Context, chunksChan chan *sources.Chunk, resourceType string) error {\n\tvar scannedCount uint64 = 1\n\n\trepos := s.getReposListByType(resourceType)\n\n\tctx.Logger().V(2).Info(\"Found \"+resourceType+\" to scan\", \"count\", len(repos))\n\n\t// If there is resume information available, limit this scan to only the repos that still need scanning.\n\treposToScan, progressIndexOffset := sources.FilterReposToResume(repos, s.GetProgress().EncodedResumeInfo)\n\trepos = reposToScan\n\n\tscanErrs := sources.NewScanErrors()\n\n\tif s.scanOptions == nil {\n\t\ts.scanOptions = &git.ScanOptions{}\n\t}\n\n\tfor i, repoURL := range repos {\n\t\ts.jobPool.Go(func() error {\n\t\t\tif common.IsDone(ctx) {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\t// TODO: set progress complete is being called concurrently with i\n\t\t\ts.setProgressCompleteWithRepo(i, progressIndexOffset, repoURL, resourceType, repos)\n\t\t\t// Ensure the repo is removed from the resume info after being scanned.\n\t\t\tdefer func(s *Source, repoURL string) {\n\t\t\t\ts.resumeInfoMutex.Lock()\n\t\t\t\tdefer s.resumeInfoMutex.Unlock()\n\t\t\t\ts.resumeInfoSlice = sources.RemoveRepoFromResumeInfo(s.resumeInfoSlice, repoURL)\n\t\t\t}(s, repoURL)\n\n\t\t\t// Scan the repository\n\t\t\trepoInfo, ok := s.repoInfoCache.get(repoURL)\n\t\t\tif !ok {\n\t\t\t\t// This should never happen.\n\t\t\t\terr := fmt.Errorf(\"no repoInfo for URL: %s\", repoURL)\n\t\t\t\tctx.Logger().Error(err, \"failed to scan \"+resourceType)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trepoCtx := context.WithValues(ctx, resourceType, repoURL)\n\t\t\tduration, err := s.cloneAndScanRepo(repoCtx, repoURL, repoInfo, chunksChan)\n\t\t\tif err != nil {\n\t\t\t\tscanErrs.Add(err)\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\t// Scan discussions and PRs, if enabled.\n\t\t\tif s.includeDiscussions || s.includePrs {\n\t\t\t\tif err = s.scanDiscussions(repoCtx, repoInfo, chunksChan); err != nil {\n\t\t\t\t\tscanErrs.Add(fmt.Errorf(\"error scanning discussions/PRs in repo %s: %w\", repoURL, err))\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trepoCtx.Logger().V(2).Info(fmt.Sprintf(\"scanned %d/%d \"+resourceType+\"s\", scannedCount, len(s.models)), \"duration_seconds\", duration)\n\t\t\tatomic.AddUint64(&scannedCount, 1)\n\t\t\treturn nil\n\t\t})\n\t}\n\n\t_ = s.jobPool.Wait()\n\tif scanErrs.Count() > 0 {\n\t\tctx.Logger().V(0).Info(\"failed to scan some repositories\", \"error_count\", scanErrs.Count(), \"errors\", scanErrs.String())\n\t}\n\ts.SetProgressComplete(len(repos), len(repos), \"Completed HuggingFace \"+resourceType+\" scan\", \"\")\n\treturn nil\n}\n\nfunc (s *Source) getReposListByType(resourceType string) []string {\n\tswitch resourceType {\n\tcase MODEL:\n\t\treturn s.models\n\tcase SPACE:\n\t\treturn s.spaces\n\tcase DATASET:\n\t\treturn s.datasets\n\t}\n\treturn nil\n}\n\nfunc (s *Source) scan(ctx context.Context, chunksChan chan *sources.Chunk) error {\n\tif err := s.scanRepos(ctx, chunksChan, MODEL); err != nil {\n\t\treturn err\n\t}\n\tif err := s.scanRepos(ctx, chunksChan, SPACE); err != nil {\n\t\treturn err\n\t}\n\tif err := s.scanRepos(ctx, chunksChan, DATASET); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (s *Source) cloneAndScanRepo(ctx context.Context, repoURL string, repoInfo repoInfo, chunksChan chan *sources.Chunk) (time.Duration, error) {\n\tctx.Logger().V(2).Info(\"attempting to clone %s\", repoInfo.resourceType)\n\tpath, repo, err := s.cloneRepo(ctx, repoURL)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer os.RemoveAll(path)\n\n\tvar logger logr.Logger\n\tlogger.V(2).Info(\"scanning %s\", repoInfo.resourceType)\n\n\tstart := time.Now()\n\tif err = s.git.ScanRepo(ctx, repo, path, s.scanOptions, sources.ChanReporter{Ch: chunksChan}); err != nil {\n\t\treturn 0, fmt.Errorf(\"error scanning repo %s: %w\", repoURL, err)\n\t}\n\treturn time.Since(start), nil\n}\n\n// setProgressCompleteWithRepo calls the s.SetProgressComplete after safely setting up the encoded resume info string.\nfunc (s *Source) setProgressCompleteWithRepo(index int, offset int, repoURL string, resourceType string, repos []string) {\n\ts.resumeInfoMutex.Lock()\n\tdefer s.resumeInfoMutex.Unlock()\n\n\t// Add the repoURL to the resume info slice.\n\ts.resumeInfoSlice = append(s.resumeInfoSlice, repoURL)\n\tsort.Strings(s.resumeInfoSlice)\n\n\t// Make the resume info string from the slice.\n\tencodedResumeInfo := sources.EncodeResumeInfo(s.resumeInfoSlice)\n\ts.SetProgressComplete(index+offset, len(repos)+offset, fmt.Sprintf(\"%ss: %s\", resourceType, repoURL), encodedResumeInfo)\n}\n\nfunc (s *Source) scanDiscussions(ctx context.Context, repoInfo repoInfo, chunksChan chan *sources.Chunk) error {\n\tdiscussions, err := s.apiClient.ListDiscussions(ctx, repoInfo)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, discussion := range discussions.Discussions {\n\t\tif (discussion.IsPR && s.includePrs) || (!discussion.IsPR && s.includeDiscussions) {\n\t\t\td, err := s.apiClient.GetDiscussionByID(ctx, repoInfo, discussion.GetID())\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// Note: there is no discussion \"description\" or similar to chunk, only comments\n\t\t\tif err = s.chunkDiscussionComments(ctx, repoInfo, d, chunksChan); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Source) chunkDiscussionComments(ctx context.Context, repoInfo repoInfo, discussion Discussion, chunksChan chan *sources.Chunk) error {\n\tfor _, comment := range discussion.Events {\n\t\tchunk := &sources.Chunk{\n\t\t\tSourceName: s.name,\n\t\t\tSourceID:   s.SourceID(),\n\t\t\tJobID:      s.JobID(),\n\t\t\tSourceType: s.Type(),\n\t\t\tSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Huggingface{\n\t\t\t\t\tHuggingface: &source_metadatapb.Huggingface{\n\t\t\t\t\t\tLink:       sanitizer.UTF8(fmt.Sprintf(\"%s/%s#%s\", s.conn.Endpoint, discussion.GetDiscussionPath(), comment.GetID())),\n\t\t\t\t\t\tUsername:   sanitizer.UTF8(comment.GetAuthor()),\n\t\t\t\t\t\tRepository: sanitizer.UTF8(fmt.Sprintf(\"%s/%s\", s.conn.Endpoint, discussion.GetGitPath())),\n\t\t\t\t\t\tTimestamp:  sanitizer.UTF8(comment.GetCreatedAt()),\n\t\t\t\t\t\tVisibility: repoInfo.visibility,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tData:         []byte(comment.Data.Latest.Raw),\n\t\t\tSourceVerify: s.verify,\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\tcase chunksChan <- chunk:\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/sources/huggingface/huggingface_client_test.go",
    "content": "package huggingface\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"gopkg.in/h2non/gock.v1\"\n)\n\nconst (\n\tTEST_TOKEN = \"test token\"\n)\n\nfunc initTestClient() *HFClient {\n\treturn NewHFClient(\"https://huggingface.co\", TEST_TOKEN, 10*time.Second)\n}\n\nfunc TestGetRepo(t *testing.T) {\n\tresourceType := MODEL\n\trepoName := \"test-model\"\n\trepoOwner := \"test-author\"\n\tdefer gock.Off()\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/\"+APIRoute+\"/\"+getResourceAPIPath(resourceType)+\"/\"+repoName).\n\t\tMatchHeader(\"Authorization\", \"Bearer \"+TEST_TOKEN).\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      repoOwner + \"/\" + repoName,\n\t\t\t\"author\":  repoOwner,\n\t\t\t\"private\": true,\n\t\t})\n\n\tclient := initTestClient()\n\tmodel, err := client.GetRepo(context.Background(), repoName, resourceType)\n\n\tassert.Nil(t, err)\n\tassert.NotNil(t, model)\n\tassert.Equal(t, repoOwner+\"/\"+repoName, model.RepoID)\n\tassert.Equal(t, repoOwner, model.Owner)\n\tassert.Equal(t, true, model.IsPrivate)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestGetRepo_NotFound(t *testing.T) {\n\tresourceType := MODEL\n\trepoName := \"doesnotexist\"\n\tdefer gock.Off()\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/\"+APIRoute+\"/\"+getResourceAPIPath(resourceType)+\"/\"+repoName).\n\t\tMatchHeader(\"Authorization\", \"Bearer \"+TEST_TOKEN).\n\t\tReply(404).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"\",\n\t\t\t\"author\":  \"\",\n\t\t\t\"private\": false,\n\t\t})\n\n\tclient := initTestClient()\n\tmodel, err := client.GetRepo(context.Background(), repoName, resourceType)\n\n\tassert.Nil(t, err)\n\tassert.NotNil(t, model)\n\tassert.Equal(t, \"\", model.RepoID)\n\tassert.Equal(t, \"\", model.Owner)\n\tassert.Equal(t, false, model.IsPrivate)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestGetModel_Error(t *testing.T) {\n\tresourceType := MODEL\n\trepoName := \"doesnotexist\"\n\tdefer gock.Off()\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/\"+APIRoute+\"/\"+getResourceAPIPath(resourceType)+\"/\"+repoName).\n\t\tMatchHeader(\"Authorization\", \"Bearer \"+TEST_TOKEN).\n\t\tReply(500)\n\n\tclient := initTestClient()\n\tmodel, err := client.GetRepo(context.Background(), repoName, resourceType)\n\n\tassert.NotNil(t, err)\n\tassert.NotNil(t, model)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestListDiscussions(t *testing.T) {\n\trepoInfo := repoInfo{\n\t\tfullName:     \"test-author/test-model\",\n\t\tresourceType: MODEL,\n\t}\n\n\tjsonBlob := `{\n\t\t\"discussions\": [\n\t\t\t{\n\t\t\t\t\"num\": 2,\n\t\t\t\t\"author\": {\n\t\t\t\t\t\"avatarUrl\": \"/avatars/test.svg\",\n\t\t\t\t\t\"fullname\": \"TEST\",\n\t\t\t\t\t\"name\": \"test-author\",\n\t\t\t\t\t\"type\": \"user\",\n\t\t\t\t\t\"isPro\": false,\n\t\t\t\t\t\"isHf\": false,\n\t\t\t\t\t\"isMod\": false\n\t\t\t\t},\n\t\t\t\t\"repo\": {\n\t\t\t\t\t\"name\": \"test-author/test-model\",\n\t\t\t\t\t\"type\": \"model\"\n\t\t\t\t},\n\t\t\t\t\"title\": \"new PR\",\n\t\t\t\t\"status\": \"open\",\n\t\t\t\t\"createdAt\": \"2024-06-18T14:34:21.000Z\",\n\t\t\t\t\"isPullRequest\": true,\n\t\t\t\t\"numComments\": 2,\n\t\t\t\t\"pinned\": false\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"num\": 1,\n\t\t\t\t\"author\": {\n\t\t\t\t\t\"avatarUrl\": \"/avatars/test.svg\",\n\t\t\t\t\t\"fullname\": \"TEST\",\n\t\t\t\t\t\"name\": \"test-author\",\n\t\t\t\t\t\"type\": \"user\",\n\t\t\t\t\t\"isPro\": false,\n\t\t\t\t\t\"isHf\": false,\n\t\t\t\t\t\"isMod\": false\n\t\t\t\t},\n\t\t\t\t\"repo\": {\n\t\t\t\t\t\"name\": \"test-author/test-model\",\n\t\t\t\t\t\"type\": \"model\"\n\t\t\t\t},\n\t\t\t\t\"title\": \"secret in comment\",\n\t\t\t\t\"status\": \"closed\",\n\t\t\t\t\"createdAt\": \"2024-06-18T14:31:57.000Z\",\n\t\t\t\t\"isPullRequest\": false,\n\t\t\t\t\"numComments\": 2,\n\t\t\t\t\"pinned\": false\n\t\t\t}\n\t\t],\n\t\t\"count\": 2,\n\t\t\"start\": 0,\n\t\t\"numClosedDiscussions\": 1\n\t}`\n\n\tdefer gock.Off()\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/\"+APIRoute+\"/\"+getResourceAPIPath(string(repoInfo.resourceType))+\"/\"+repoInfo.fullName+\"/\"+DiscussionsRoute).\n\t\tMatchHeader(\"Authorization\", \"Bearer \"+TEST_TOKEN).\n\t\tReply(200).\n\t\tJSON(jsonBlob)\n\n\tclient := initTestClient()\n\tdiscussions, err := client.ListDiscussions(context.Background(), repoInfo)\n\n\tassert.Nil(t, err)\n\tassert.NotNil(t, discussions)\n\tassert.Equal(t, 2, len(discussions.Discussions))\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestListDiscussions_NotFound(t *testing.T) {\n\trepoInfo := repoInfo{\n\t\tfullName:     \"test-author/doesnotexist\",\n\t\tresourceType: MODEL,\n\t}\n\tdefer gock.Off()\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/\"+APIRoute+\"/\"+getResourceAPIPath(string(repoInfo.resourceType))+\"/\"+repoInfo.fullName+\"/\"+DiscussionsRoute).\n\t\tMatchHeader(\"Authorization\", \"Bearer \"+TEST_TOKEN).\n\t\tReply(404).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"discussions\": []map[string]interface{}{},\n\t\t})\n\n\tclient := initTestClient()\n\tdiscussions, err := client.ListDiscussions(context.Background(), repoInfo)\n\n\tassert.Nil(t, err)\n\tassert.NotNil(t, discussions)\n\tassert.Equal(t, 0, len(discussions.Discussions))\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestListDiscussions_Error(t *testing.T) {\n\trepoInfo := repoInfo{\n\t\tfullName:     \"test-author/doesnotexist\",\n\t\tresourceType: MODEL,\n\t}\n\tdefer gock.Off()\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/\"+APIRoute+\"/\"+getResourceAPIPath(string(repoInfo.resourceType))+\"/\"+repoInfo.fullName+\"/\"+DiscussionsRoute).\n\t\tMatchHeader(\"Authorization\", \"Bearer \"+TEST_TOKEN).\n\t\tReply(500)\n\n\tclient := initTestClient()\n\tdiscussions, err := client.ListDiscussions(context.Background(), repoInfo)\n\n\tassert.NotNil(t, err)\n\tassert.NotNil(t, discussions)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestGetDiscussionByID(t *testing.T) {\n\trepoInfo := repoInfo{\n\t\tfullName:     \"test-author/test-model\",\n\t\tresourceType: MODEL,\n\t}\n\tdiscussionID := \"1\"\n\n\tjsonBlob := `{\n\t\t\"author\": {\n\t\t\t\"avatarUrl\": \"/avatars/test.svg\",\n\t\t\t\"fullname\": \"TEST\",\n\t\t\t\"name\": \"test-author\",\n\t\t\t\"type\": \"user\",\n\t\t\t\"isPro\": false,\n\t\t\t\"isHf\": false,\n\t\t\t\"isMod\": false\n\t\t},\n\t\t\"num\": 1,\n\t\t\"repo\": {\n\t\t\t\"name\": \"test-author/test-model\",\n\t\t\t\"type\": \"model\"\n\t\t},\n\t\t\"title\": \"secret in initial\",\n\t\t\"status\": \"open\",\n\t\t\"createdAt\": \"2024-06-18T14:31:46.000Z\",\n\t\t\"events\": [\n\t\t\t{\n\t\t\t\t\"id\": \"525\",\n\t\t\t\t\"author\": {\n\t\t\t\t\t\"avatarUrl\": \"/avatars/test.svg\",\n\t\t\t\t\t\"fullname\": \"TEST\",\n\t\t\t\t\t\"name\": \"test-author\",\n\t\t\t\t\t\"type\": \"user\",\n\t\t\t\t\t\"isPro\": false,\n\t\t\t\t\t\"isHf\": false,\n\t\t\t\t\t\"isMod\": false,\n\t\t\t\t\t\"isOwner\": true,\n\t\t\t\t\t\"isOrgMember\": false\n\t\t\t\t},\n\t\t\t\t\"createdAt\": \"2024-06-18T14:31:46.000Z\",\n\t\t\t\t\"type\": \"comment\",\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"edited\": true,\n\t\t\t\t\t\"hidden\": false,\n\t\t\t\t\t\"latest\": {\n\t\t\t\t\t\t\"raw\": \"dd\",\n\t\t\t\t\t\t\"html\": \"<p>dd</p>\\n\",\n\t\t\t\t\t\t\"updatedAt\": \"2024-06-18T14:33:32.066Z\",\n\t\t\t\t\t\t\"author\": {\n\t\t\t\t\t\t\t\"avatarUrl\": \"/avatars/test.svg\",\n\t\t\t\t\t\t\t\"fullname\": \"TEST\",\n\t\t\t\t\t\t\t\"name\": \"test-author\",\n\t\t\t\t\t\t\t\"type\": \"user\",\n\t\t\t\t\t\t\t\"isPro\": false,\n\t\t\t\t\t\t\t\"isHf\": false,\n\t\t\t\t\t\t\t\"isMod\": false\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"numEdits\": 1,\n\t\t\t\t\t\"editors\": [\"trufflej\"],\n\t\t\t\t\t\"reactions\": [],\n\t\t\t\t\t\"identifiedLanguage\": {\n\t\t\t\t\t\t\"language\": \"en\",\n\t\t\t\t\t\t\"probability\": 0.40104949474334717\n\t\t\t\t\t},\n\t\t\t\t\t\"isReport\": false\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"id\": \"526\",\n\t\t\t\t\"author\": {\n\t\t\t\t\t\"avatarUrl\": \"/avatars/test.svg\",\n\t\t\t\t\t\"fullname\": \"TEST\",\n\t\t\t\t\t\"name\": \"test-author\",\n\t\t\t\t\t\"type\": \"user\",\n\t\t\t\t\t\"isPro\": false,\n\t\t\t\t\t\"isHf\": false,\n\t\t\t\t\t\"isMod\": false,\n\t\t\t\t\t\"isOwner\": true,\n\t\t\t\t\t\"isOrgMember\": false\n\t\t\t\t},\n\t\t\t\t\"createdAt\": \"2024-06-18T14:32:40.000Z\",\n\t\t\t\t\"type\": \"status-change\",\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"status\": \"closed\"\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"id\": \"527\",\n\t\t\t\t\"author\": {\n\t\t\t\t\t\"avatarUrl\": \"/avatars/test.svg\",\n\t\t\t\t\t\"fullname\": \"TEST\",\n\t\t\t\t\t\"name\": \"test-author\",\n\t\t\t\t\t\"type\": \"user\",\n\t\t\t\t\t\"isPro\": false,\n\t\t\t\t\t\"isHf\": false,\n\t\t\t\t\t\"isMod\": false,\n\t\t\t\t\t\"isOwner\": true,\n\t\t\t\t\t\"isOrgMember\": false\n\t\t\t\t},\n\t\t\t\t\"createdAt\": \"2024-06-18T14:33:27.000Z\",\n\t\t\t\t\"type\": \"status-change\",\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"status\": \"open\"\n\t\t\t\t}\n\t\t\t}\n\t\t],\n\t\t\"pinned\": false,\n\t\t\"locked\": false,\n\t\t\"isPullRequest\": false,\n\t\t\"isReport\": false\n\t}`\n\n\tdefer gock.Off()\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/\"+APIRoute+\"/\"+getResourceAPIPath(string(repoInfo.resourceType))+\"/\"+repoInfo.fullName+\"/\"+DiscussionsRoute+\"/\"+discussionID).\n\t\tMatchHeader(\"Authorization\", \"Bearer \"+TEST_TOKEN).\n\t\tReply(200).\n\t\tJSON(jsonBlob)\n\n\tclient := initTestClient()\n\tdiscussion, err := client.GetDiscussionByID(context.Background(), repoInfo, discussionID)\n\n\tassert.Nil(t, err)\n\tassert.NotNil(t, discussion)\n\tassert.Equal(t, discussionID, strconv.Itoa(discussion.ID))\n\tassert.Equal(t, 3, len(discussion.Events))\n\tassert.Equal(t, false, discussion.IsPR)\n\tassert.Equal(t, \"secret in initial\", discussion.Title)\n\tassert.Equal(t, repoInfo.fullName, discussion.Repo.FullName)\n\tassert.Equal(t, string(repoInfo.resourceType), discussion.Repo.ResourceType)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestGetDiscussionByID_NotFound(t *testing.T) {\n\trepoInfo := repoInfo{\n\t\tfullName:     \"test-author/test-model\",\n\t\tresourceType: MODEL,\n\t}\n\tdiscussionID := \"doesnotexist\"\n\tdefer gock.Off()\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/\"+APIRoute+\"/\"+getResourceAPIPath(string(repoInfo.resourceType))+\"/\"+repoInfo.fullName+\"/\"+DiscussionsRoute+\"/\"+discussionID).\n\t\tMatchHeader(\"Authorization\", \"Bearer \"+TEST_TOKEN).\n\t\tReply(404).\n\t\tJSON(map[string]interface{}{})\n\n\tclient := initTestClient()\n\tdiscussion, err := client.GetDiscussionByID(context.Background(), repoInfo, discussionID)\n\n\tassert.Nil(t, err)\n\tassert.NotNil(t, discussion)\n\tassert.Equal(t, 0, len(discussion.Events))\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestGetDiscussionByID_Error(t *testing.T) {\n\trepoInfo := repoInfo{\n\t\tfullName:     \"test-author/test-model\",\n\t\tresourceType: MODEL,\n\t}\n\tdiscussionID := \"doesnotexist\"\n\tdefer gock.Off()\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/\"+APIRoute+\"/\"+getResourceAPIPath(string(repoInfo.resourceType))+\"/\"+repoInfo.fullName+\"/\"+DiscussionsRoute+\"/\"+discussionID).\n\t\tMatchHeader(\"Authorization\", \"Bearer \"+TEST_TOKEN).\n\t\tReply(500)\n\n\tclient := initTestClient()\n\tdiscussion, err := client.GetDiscussionByID(context.Background(), repoInfo, discussionID)\n\n\tassert.NotNil(t, err)\n\tassert.NotNil(t, discussion)\n\tassert.Equal(t, \"\", discussion.Title)\n\tassert.Equal(t, 0, len(discussion.Events))\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestListReposByAuthor(t *testing.T) {\n\tresourceType := MODEL\n\tauthor := \"test-author\"\n\trepo := \"test-model\"\n\trepo2 := \"test-model2\"\n\n\tdefer gock.Off()\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(resourceType))).\n\t\tMatchParam(\"author\", author).\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tMatchHeader(\"Authorization\", \"Bearer \"+TEST_TOKEN).\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"1\",\n\t\t\t\t\"id\":      author + \"/\" + repo,\n\t\t\t\t\"modelId\": author + \"/\" + repo,\n\t\t\t\t\"private\": true,\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"_id\":     \"2\",\n\t\t\t\t\"id\":      author + \"/\" + repo2,\n\t\t\t\t\"modelId\": author + \"/\" + repo2,\n\t\t\t\t\"private\": false,\n\t\t\t},\n\t\t})\n\n\tfor _, mock := range gock.Pending() {\n\t\tfmt.Println(mock.Request().URLStruct.String())\n\t}\n\n\tclient := initTestClient()\n\trepos, err := client.ListReposByAuthor(context.Background(), resourceType, author)\n\tassert.Nil(t, err)\n\tassert.NotNil(t, repos)\n\tassert.Equal(t, 2, len(repos))\n\t// count of repos with private flag\n\tcountOfPrivateRepos := 0\n\tfor _, repo := range repos {\n\t\tif repo.IsPrivate {\n\t\t\tcountOfPrivateRepos++\n\t\t}\n\t}\n\tassert.Equal(t, 1, countOfPrivateRepos)\n\t// there is no author field in JSON, so assert repo.Owner is empty\n\tfor _, repo := range repos {\n\t\tassert.Equal(t, \"\", repo.Owner)\n\t}\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestListReposByAuthor_NotFound(t *testing.T) {\n\tresourceType := MODEL\n\tauthor := \"authordoesntexist\"\n\n\tdefer gock.Off()\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(resourceType))).\n\t\tMatchParam(\"author\", author).\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tMatchHeader(\"Authorization\", \"Bearer \"+TEST_TOKEN).\n\t\tReply(404).\n\t\tJSON([]map[string]interface{}{})\n\n\tclient := initTestClient()\n\trepos, err := client.ListReposByAuthor(context.Background(), resourceType, author)\n\n\tassert.Nil(t, err)\n\tassert.NotNil(t, repos)\n\tassert.Equal(t, 0, len(repos))\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestListReposByAuthor_Error(t *testing.T) {\n\tresourceType := MODEL\n\tauthor := \"doesnotexist\"\n\n\tdefer gock.Off()\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(resourceType))).\n\t\tMatchParam(\"author\", author).\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tMatchHeader(\"Authorization\", \"Bearer \"+TEST_TOKEN).\n\t\tReply(500)\n\n\tclient := initTestClient()\n\trepos, err := client.ListReposByAuthor(context.Background(), resourceType, author)\n\tassert.NotNil(t, err)\n\tassert.Nil(t, repos)\n\tassert.False(t, gock.HasUnmatchedRequest())\n\tassert.True(t, gock.IsDone())\n}\n\nfunc TestGetResourceAPIPath(t *testing.T) {\n\tassert.Equal(t, \"models\", getResourceAPIPath(MODEL))\n\tassert.Equal(t, \"datasets\", getResourceAPIPath(DATASET))\n\tassert.Equal(t, \"spaces\", getResourceAPIPath(SPACE))\n}\n\nfunc TestGetResourceHTMLPath(t *testing.T) {\n\tassert.Equal(t, \"\", getResourceHTMLPath(MODEL))\n\tassert.Equal(t, \"datasets\", getResourceHTMLPath(DATASET))\n\tassert.Equal(t, \"spaces\", getResourceHTMLPath(SPACE))\n}\n\nfunc TestBuildAPIURL_ValidInputs(t *testing.T) {\n\tendpoint := \"https://huggingface.co\"\n\tresourceType := MODEL\n\trepoName := \"test-repo\"\n\n\texpectedURL := \"https://huggingface.co/api/models/test-repo\"\n\n\turl, err := buildAPIURL(endpoint, resourceType, repoName)\n\n\tassert.Nil(t, err)\n\tassert.Equal(t, expectedURL, url)\n}\n\nfunc TestBuildAPIURL_EmptyEndpoint(t *testing.T) {\n\tendpoint := \"\"\n\tresourceType := MODEL\n\trepoName := \"test-repo\"\n\n\turl, err := buildAPIURL(endpoint, resourceType, repoName)\n\n\tassert.NotNil(t, err)\n\tassert.Equal(t, \"\", url)\n\tassert.Equal(t, \"endpoint, resourceType, and repoName must not be empty\", err.Error())\n}\n\nfunc TestBuildAPIURL_EmptyResourceType(t *testing.T) {\n\tendpoint := \"https://huggingface.co\"\n\tresourceType := \"\"\n\trepoName := \"test-repo\"\n\n\turl, err := buildAPIURL(endpoint, resourceType, repoName)\n\n\tassert.NotNil(t, err)\n\tassert.Equal(t, \"\", url)\n\tassert.Equal(t, \"endpoint, resourceType, and repoName must not be empty\", err.Error())\n}\n\nfunc TestBuildAPIURL_EmptyRepoName(t *testing.T) {\n\tendpoint := \"https://huggingface.co\"\n\tresourceType := \"model\"\n\trepoName := \"\"\n\n\turl, err := buildAPIURL(endpoint, resourceType, repoName)\n\n\tassert.NotNil(t, err)\n\tassert.Equal(t, \"\", url)\n\tassert.Equal(t, \"endpoint, resourceType, and repoName must not be empty\", err.Error())\n}\n\nfunc TestGetDiscussionPath_ModelResource(t *testing.T) {\n\tdiscussion := Discussion{\n\t\tRepo: RepoData{\n\t\t\tFullName:     \"test-author/test-model\",\n\t\t\tResourceType: \"model\",\n\t\t},\n\t\tID: 1,\n\t}\n\n\texpectedPath := \"test-author/test-model/discussions/1\"\n\n\tpath := discussion.GetDiscussionPath()\n\tassert.Equal(t, expectedPath, path)\n}\n\nfunc TestGetDiscussionPath_DatasetResource(t *testing.T) {\n\tdiscussion := Discussion{\n\t\tRepo: RepoData{\n\t\t\tFullName:     \"test-author/test-dataset\",\n\t\t\tResourceType: \"dataset\",\n\t\t},\n\t\tID: 1,\n\t}\n\n\texpectedPath := \"datasets/test-author/test-dataset/discussions/1\"\n\n\tpath := discussion.GetDiscussionPath()\n\tassert.Equal(t, expectedPath, path)\n}\n\nfunc TestGetDiscussionPath_SpaceResource(t *testing.T) {\n\tdiscussion := Discussion{\n\t\tRepo: RepoData{\n\t\t\tFullName:     \"test-author/test-space\",\n\t\t\tResourceType: \"space\",\n\t\t},\n\t\tID: 1,\n\t}\n\n\texpectedPath := \"spaces/test-author/test-space/discussions/1\"\n\n\tpath := discussion.GetDiscussionPath()\n\tassert.Equal(t, expectedPath, path)\n}\n\nfunc TestGetGitPath_ModelResource(t *testing.T) {\n\tdiscussion := Discussion{\n\t\tRepo: RepoData{\n\t\t\tFullName:     \"test-author/test-model\",\n\t\t\tResourceType: \"model\",\n\t\t},\n\t\tID: 1,\n\t}\n\n\texpectedPath := \"test-author/test-model.git\"\n\n\tpath := discussion.GetGitPath()\n\tassert.Equal(t, expectedPath, path)\n}\n\nfunc TestGetGitPath_DatasetResource(t *testing.T) {\n\tdiscussion := Discussion{\n\t\tRepo: RepoData{\n\t\t\tFullName:     \"test-author/test-dataset\",\n\t\t\tResourceType: \"dataset\",\n\t\t},\n\t\tID: 1,\n\t}\n\n\texpectedPath := \"datasets/test-author/test-dataset.git\"\n\n\tpath := discussion.GetGitPath()\n\tassert.Equal(t, expectedPath, path)\n}\n\nfunc TestGetGitPath_SpaceResource(t *testing.T) {\n\tdiscussion := Discussion{\n\t\tRepo: RepoData{\n\t\t\tFullName:     \"test-author/test-space\",\n\t\t\tResourceType: \"space\",\n\t\t},\n\t\tID: 1,\n\t}\n\n\texpectedPath := \"spaces/test-author/test-space.git\"\n\n\tpath := discussion.GetGitPath()\n\tassert.Equal(t, expectedPath, path)\n}\n"
  },
  {
    "path": "pkg/sources/huggingface/huggingface_test.go",
    "content": "package huggingface\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\t\"gopkg.in/h2non/gock.v1\"\n)\n\nfunc createTestSource(src *sourcespb.Huggingface) (*Source, *anypb.Any) {\n\ts := &Source{}\n\tconn, err := anypb.New(src)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn s, conn\n}\n\n// test include exclude ignore/include orgs, users\n\nfunc TestInit(t *testing.T) {\n\ts, conn := createTestSource(&sourcespb.Huggingface{\n\t\tEndpoint: \"https://huggingface.co\",\n\t\tCredential: &sourcespb.Huggingface_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tModels:         []string{\"user/model1\", \"user/model2\", \"user/ignorethismodel\"},\n\t\tIgnoreModels:   []string{\"user/ignorethismodel\"},\n\t\tSpaces:         []string{\"user/space1\", \"user/space2\", \"user/ignorethisspace\"},\n\t\tIgnoreSpaces:   []string{\"user/ignorethisspace\"},\n\t\tDatasets:       []string{\"user/dataset1\", \"user/dataset2\", \"user/ignorethisdataset\"},\n\t\tIgnoreDatasets: []string{\"user/ignorethisdataset\"},\n\t\tOrganizations:  []string{\"org1\", \"org2\"},\n\t\tUsers:          []string{\"user1\", \"user2\"},\n\t})\n\terr := s.Init(context.Background(), \"test - huggingface\", 0, 1337, false, conn, 1)\n\tassert.Nil(t, err)\n\n\tassert.ElementsMatch(t, []string{\"user/model1\", \"user/model2\"}, s.models)\n\tfor _, model := range s.models {\n\t\tmodelURL, _ := s.filteredModelsCache.Get(model)\n\t\tassert.Equal(t, modelURL, s.conn.Endpoint+\"/\"+model+\".git\")\n\t}\n\n\tassert.ElementsMatch(t, []string{\"user/space1\", \"user/space2\"}, s.spaces)\n\tfor _, space := range s.spaces {\n\t\tspaceURL, _ := s.filteredSpacesCache.Get(space)\n\t\tassert.Equal(t, spaceURL, s.conn.Endpoint+\"/\"+getResourceHTMLPath(SPACE)+\"/\"+space+\".git\")\n\t}\n\n\tassert.ElementsMatch(t, []string{\"user/dataset1\", \"user/dataset2\"}, s.datasets)\n\tfor _, dataset := range s.datasets {\n\t\tdatasetURL, _ := s.filteredDatasetsCache.Get(dataset)\n\t\tassert.Equal(t, datasetURL, s.conn.Endpoint+\"/\"+getResourceHTMLPath(DATASET)+\"/\"+dataset+\".git\")\n\t}\n\n\tassert.ElementsMatch(t, s.conn.Organizations, s.orgsCache.Keys())\n\tassert.ElementsMatch(t, s.conn.Users, s.usersCache.Keys())\n}\n\nfunc TestGetResourceType(t *testing.T) {\n\trepo := \"author/model1\"\n\ts, conn := createTestSource(&sourcespb.Huggingface{\n\t\tEndpoint: \"https://huggingface.co\",\n\t\tCredential: &sourcespb.Huggingface_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tModels: []string{repo},\n\t})\n\terr := s.Init(context.Background(), \"test - huggingface\", 0, 1337, false, conn, 1)\n\tassert.Nil(t, err)\n\n\tdefer gock.Off()\n\n\t// mock the request to the huggingface api\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/models/author/model1\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"author/model1\",\n\t\t\t\"author\":  \"author\",\n\t\t\t\"private\": true,\n\t\t})\n\n\terr = s.enumerate(context.Background())\n\tassert.Nil(t, err)\n\tassert.Equal(t, MODEL, s.getResourceType(context.Background(), (s.conn.Endpoint+\"/\"+repo+\".git\")))\n}\n\nfunc TestVisibilityOf(t *testing.T) {\n\trepo := \"author/model1\"\n\ts, conn := createTestSource(&sourcespb.Huggingface{\n\t\tEndpoint: \"https://huggingface.co\",\n\t\tCredential: &sourcespb.Huggingface_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tModels: []string{repo},\n\t})\n\terr := s.Init(context.Background(), \"test - huggingface\", 0, 1337, false, conn, 1)\n\tassert.Nil(t, err)\n\n\tdefer gock.Off()\n\n\t// mock the request to the huggingface api\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/models/author/model1\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"author/model1\",\n\t\t\t\"author\":  \"author\",\n\t\t\t\"private\": true,\n\t\t})\n\n\terr = s.enumerate(context.Background())\n\tassert.Nil(t, err)\n\tassert.Equal(t, source_metadatapb.Visibility(1), s.visibilityOf(context.Background(), (s.conn.Endpoint+\"/\"+repo+\".git\")))\n}\n\nfunc TestEnumerate(t *testing.T) {\n\ts, conn := createTestSource(&sourcespb.Huggingface{\n\t\tEndpoint: \"https://huggingface.co\",\n\t\tCredential: &sourcespb.Huggingface_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tModels:   []string{\"author/model1\"},\n\t\tDatasets: []string{\"author/dataset1\"},\n\t\tSpaces:   []string{\"author/space1\"},\n\t})\n\n\terr := s.Init(context.Background(), \"test - huggingface\", 0, 1337, false, conn, 1)\n\tassert.Nil(t, err)\n\n\tdefer gock.Off()\n\n\t// mock the request to the huggingface api\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/models/author/model1\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"author/model1\",\n\t\t\t\"author\":  \"author\",\n\t\t\t\"private\": true,\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/datasets/author/dataset1\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"author/dataset1\",\n\t\t\t\"author\":  \"author\",\n\t\t\t\"private\": false,\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/spaces/author/space1\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"author/space1\",\n\t\t\t\"author\":  \"author\",\n\t\t\t\"private\": false,\n\t\t})\n\n\terr = s.enumerate(context.Background())\n\tassert.Nil(t, err)\n\n\tmodelGitURL := \"https://huggingface.co/author/model1.git\"\n\tdatasetGitURL := \"https://huggingface.co/datasets/author/dataset1.git\"\n\tspaceGitURL := \"https://huggingface.co/spaces/author/space1.git\"\n\n\tassert.Equal(t, []string{modelGitURL}, s.models)\n\tassert.Equal(t, []string{datasetGitURL}, s.datasets)\n\tassert.Equal(t, []string{spaceGitURL}, s.spaces)\n\n\tr, _ := s.repoInfoCache.get(modelGitURL)\n\tassert.Equal(t, r, repoInfo{\n\t\tvisibility:   source_metadatapb.Visibility_private,\n\t\tresourceType: MODEL,\n\t\towner:        \"author\",\n\t\tname:         \"model1\",\n\t\tfullName:     \"author/model1\",\n\t})\n\n\tr, _ = s.repoInfoCache.get(datasetGitURL)\n\tassert.Equal(t, r, repoInfo{\n\t\tvisibility:   source_metadatapb.Visibility_public,\n\t\tresourceType: DATASET,\n\t\towner:        \"author\",\n\t\tname:         \"dataset1\",\n\t\tfullName:     \"author/dataset1\",\n\t})\n\n\tr, _ = s.repoInfoCache.get(spaceGitURL)\n\tassert.Equal(t, r, repoInfo{\n\t\tvisibility:   source_metadatapb.Visibility_public,\n\t\tresourceType: SPACE,\n\t\towner:        \"author\",\n\t\tname:         \"space1\",\n\t\tfullName:     \"author/space1\",\n\t})\n\n}\n\nfunc TestUpdateRepoList(t *testing.T) {\n\ts, conn := createTestSource(&sourcespb.Huggingface{\n\t\tEndpoint: \"https://huggingface.co\",\n\t\tCredential: &sourcespb.Huggingface_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t})\n\n\terr := s.Init(context.Background(), \"test - huggingface\", 0, 1337, false, conn, 1)\n\tassert.Nil(t, err)\n\n\tmodelGitURL := \"https://huggingface.co/author/model1.git\"\n\tdatasetGitURL := \"https://huggingface.co/datasets/author/dataset1.git\"\n\tspaceGitURL := \"https://huggingface.co/spaces/author/space1.git\"\n\n\ts.updateRepoLists(modelGitURL, MODEL)\n\ts.updateRepoLists(datasetGitURL, DATASET)\n\ts.updateRepoLists(spaceGitURL, SPACE)\n\n\tassert.Equal(t, []string{modelGitURL}, s.models)\n\tassert.Equal(t, []string{datasetGitURL}, s.datasets)\n\tassert.Equal(t, []string{spaceGitURL}, s.spaces)\n}\n\nfunc TestGetReposListByType(t *testing.T) {\n\ts, conn := createTestSource(&sourcespb.Huggingface{\n\t\tEndpoint: \"https://huggingface.co\",\n\t\tCredential: &sourcespb.Huggingface_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tModels:   []string{\"author/model1\", \"author/model2\"},\n\t\tDatasets: []string{\"author/dataset1\", \"author/dataset2\"},\n\t\tSpaces:   []string{\"author/space1\", \"author/space2\"},\n\t})\n\n\terr := s.Init(context.Background(), \"test - huggingface\", 0, 1337, false, conn, 1)\n\tassert.Nil(t, err)\n\n\tassert.Equal(t, s.getReposListByType(MODEL), s.models)\n\tassert.Equal(t, s.getReposListByType(DATASET), s.datasets)\n\tassert.Equal(t, s.getReposListByType(SPACE), s.spaces)\n}\n\nfunc TestGetVisibility(t *testing.T) {\n\tassert.Equal(t, source_metadatapb.Visibility(1), getVisibility(true))\n\tassert.Equal(t, source_metadatapb.Visibility(0), getVisibility(false))\n}\n\nfunc TestEnumerateAuthorsOrg(t *testing.T) {\n\ts, conn := createTestSource(&sourcespb.Huggingface{\n\t\tEndpoint: \"https://huggingface.co\",\n\t\tCredential: &sourcespb.Huggingface_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tOrganizations: []string{\"org\"},\n\t})\n\n\terr := s.Init(context.Background(), \"test - huggingface\", 0, 1337, false, conn, 1)\n\tassert.Nil(t, err)\n\n\tdefer gock.Off()\n\n\t// mock the request to the huggingface api\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(MODEL))).\n\t\tMatchParam(\"author\", \"org\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"1\",\n\t\t\t\t\"id\":      \"org/model\",\n\t\t\t\t\"modelId\": \"org/model\",\n\t\t\t\t\"private\": true,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/models/org/model\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"org/model\",\n\t\t\t\"author\":  \"org\",\n\t\t\t\"private\": true,\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(DATASET))).\n\t\tMatchParam(\"author\", \"org\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"3\",\n\t\t\t\t\"id\":      \"org/dataset\",\n\t\t\t\t\"modelId\": \"org/dataset\",\n\t\t\t\t\"private\": true,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/datasets/org/dataset\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"org/dataset\",\n\t\t\t\"author\":  \"org\",\n\t\t\t\"private\": true,\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(SPACE))).\n\t\tMatchParam(\"author\", \"org\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"5\",\n\t\t\t\t\"id\":      \"org/space\",\n\t\t\t\t\"modelId\": \"org/space\",\n\t\t\t\t\"private\": true,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/spaces/org/space\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"org/space\",\n\t\t\t\"author\":  \"org\",\n\t\t\t\"private\": true,\n\t\t})\n\n\terr = s.enumerate(context.Background())\n\tassert.Nil(t, err)\n\n\tmodelGitURL := \"https://huggingface.co/org/model.git\"\n\tdatasetGitURL := \"https://huggingface.co/datasets/org/dataset.git\"\n\tspaceGitURL := \"https://huggingface.co/spaces/org/space.git\"\n\n\tassert.Equal(t, []string{modelGitURL}, s.models)\n\tassert.Equal(t, []string{datasetGitURL}, s.datasets)\n\tassert.Equal(t, []string{spaceGitURL}, s.spaces)\n\n\tr, _ := s.repoInfoCache.get(modelGitURL)\n\tassert.Equal(t, r, repoInfo{\n\t\tvisibility:   source_metadatapb.Visibility_private,\n\t\tresourceType: MODEL,\n\t\towner:        \"org\",\n\t\tname:         \"model\",\n\t\tfullName:     \"org/model\",\n\t})\n\n\tr, _ = s.repoInfoCache.get(datasetGitURL)\n\tassert.Equal(t, r, repoInfo{\n\t\tvisibility:   source_metadatapb.Visibility_private,\n\t\tresourceType: DATASET,\n\t\towner:        \"org\",\n\t\tname:         \"dataset\",\n\t\tfullName:     \"org/dataset\",\n\t})\n\n\tr, _ = s.repoInfoCache.get(spaceGitURL)\n\tassert.Equal(t, r, repoInfo{\n\t\tvisibility:   source_metadatapb.Visibility_private,\n\t\tresourceType: SPACE,\n\t\towner:        \"org\",\n\t\tname:         \"space\",\n\t\tfullName:     \"org/space\",\n\t})\n}\n\nfunc TestEnumerateAuthorsOrgSkipAll(t *testing.T) {\n\ts, conn := createTestSource(&sourcespb.Huggingface{\n\t\tEndpoint: \"https://huggingface.co\",\n\t\tCredential: &sourcespb.Huggingface_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tOrganizations:   []string{\"org\"},\n\t\tSkipAllModels:   true,\n\t\tSkipAllDatasets: true,\n\t\tSkipAllSpaces:   true,\n\t})\n\n\terr := s.Init(context.Background(), \"test - huggingface\", 0, 1337, false, conn, 1)\n\tassert.Nil(t, err)\n\n\tdefer gock.Off()\n\n\t// mock the request to the huggingface api\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(MODEL))).\n\t\tMatchParam(\"author\", \"org\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"1\",\n\t\t\t\t\"id\":      \"org/model\",\n\t\t\t\t\"modelId\": \"org/model\",\n\t\t\t\t\"private\": true,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/models/org/model\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"org/model\",\n\t\t\t\"author\":  \"org\",\n\t\t\t\"private\": true,\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(DATASET))).\n\t\tMatchParam(\"author\", \"org\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"3\",\n\t\t\t\t\"id\":      \"org/dataset\",\n\t\t\t\t\"modelId\": \"org/dataset\",\n\t\t\t\t\"private\": true,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/datasets/org/dataset\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"org/dataset\",\n\t\t\t\"author\":  \"org\",\n\t\t\t\"private\": true,\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(SPACE))).\n\t\tMatchParam(\"author\", \"org\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"5\",\n\t\t\t\t\"id\":      \"org/space\",\n\t\t\t\t\"modelId\": \"org/space\",\n\t\t\t\t\"private\": true,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/spaces/org/space\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"org/space\",\n\t\t\t\"author\":  \"org\",\n\t\t\t\"private\": true,\n\t\t})\n\n\terr = s.enumerate(context.Background())\n\tassert.Nil(t, err)\n\n\tmodelGitURL := \"https://huggingface.co/org/model.git\"\n\tdatasetGitURL := \"https://huggingface.co/datasets/org/dataset.git\"\n\tspaceGitURL := \"https://huggingface.co/spaces/org/space.git\"\n\n\tassert.Equal(t, []string{}, s.models)\n\tassert.Equal(t, []string{}, s.datasets)\n\tassert.Equal(t, []string{}, s.spaces)\n\n\tr, _ := s.repoInfoCache.get(modelGitURL)\n\tassert.Equal(t, r, repoInfo{})\n\n\tr, _ = s.repoInfoCache.get(datasetGitURL)\n\tassert.Equal(t, r, repoInfo{})\n\n\tr, _ = s.repoInfoCache.get(spaceGitURL)\n\tassert.Equal(t, r, repoInfo{})\n}\n\nfunc TestEnumerateAuthorsOrgIgnores(t *testing.T) {\n\ts, conn := createTestSource(&sourcespb.Huggingface{\n\t\tEndpoint: \"https://huggingface.co\",\n\t\tCredential: &sourcespb.Huggingface_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tOrganizations:  []string{\"org\"},\n\t\tIgnoreModels:   []string{\"org/model\"},\n\t\tIgnoreDatasets: []string{\"org/dataset\"},\n\t\tIgnoreSpaces:   []string{\"org/space\"},\n\t})\n\n\terr := s.Init(context.Background(), \"test - huggingface\", 0, 1337, false, conn, 1)\n\tassert.Nil(t, err)\n\n\tdefer gock.Off()\n\n\t// mock the request to the huggingface api\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(MODEL))).\n\t\tMatchParam(\"author\", \"org\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"1\",\n\t\t\t\t\"id\":      \"org/model\",\n\t\t\t\t\"modelId\": \"org/model\",\n\t\t\t\t\"private\": true,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/models/org/model\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"org/model\",\n\t\t\t\"author\":  \"org\",\n\t\t\t\"private\": true,\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(DATASET))).\n\t\tMatchParam(\"author\", \"org\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"3\",\n\t\t\t\t\"id\":      \"org/dataset\",\n\t\t\t\t\"modelId\": \"org/dataset\",\n\t\t\t\t\"private\": true,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/datasets/org/dataset\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"org/dataset\",\n\t\t\t\"author\":  \"org\",\n\t\t\t\"private\": true,\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(SPACE))).\n\t\tMatchParam(\"author\", \"org\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"5\",\n\t\t\t\t\"id\":      \"org/space\",\n\t\t\t\t\"modelId\": \"org/space\",\n\t\t\t\t\"private\": true,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/spaces/org/space\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"org/space\",\n\t\t\t\"author\":  \"org\",\n\t\t\t\"private\": true,\n\t\t})\n\n\terr = s.enumerate(context.Background())\n\tassert.Nil(t, err)\n\n\tmodelGitURL := \"https://huggingface.co/org/model.git\"\n\tdatasetGitURL := \"https://huggingface.co/datasets/org/dataset.git\"\n\tspaceGitURL := \"https://huggingface.co/spaces/org/space.git\"\n\n\tassert.Equal(t, []string{}, s.models)\n\tassert.Equal(t, []string{}, s.datasets)\n\tassert.Equal(t, []string{}, s.spaces)\n\n\tr, _ := s.repoInfoCache.get(modelGitURL)\n\tassert.Equal(t, r, repoInfo{})\n\n\tr, _ = s.repoInfoCache.get(datasetGitURL)\n\tassert.Equal(t, r, repoInfo{})\n\n\tr, _ = s.repoInfoCache.get(spaceGitURL)\n\tassert.Equal(t, r, repoInfo{})\n}\n\nfunc TestEnumerateAuthorsUser(t *testing.T) {\n\ts, conn := createTestSource(&sourcespb.Huggingface{\n\t\tEndpoint: \"https://huggingface.co\",\n\t\tCredential: &sourcespb.Huggingface_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tUsers: []string{\"user\"},\n\t})\n\n\terr := s.Init(context.Background(), \"test - huggingface\", 0, 1337, false, conn, 1)\n\tassert.Nil(t, err)\n\n\tdefer gock.Off()\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(MODEL))).\n\t\tMatchParam(\"author\", \"user\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"2\",\n\t\t\t\t\"id\":      \"user/model\",\n\t\t\t\t\"modelId\": \"user/model\",\n\t\t\t\t\"private\": false,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/models/user/model\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"user/model\",\n\t\t\t\"author\":  \"user\",\n\t\t\t\"private\": false,\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(DATASET))).\n\t\tMatchParam(\"author\", \"user\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"4\",\n\t\t\t\t\"id\":      \"user/dataset\",\n\t\t\t\t\"modelId\": \"user/dataset\",\n\t\t\t\t\"private\": false,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/datasets/user/dataset\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"user/dataset\",\n\t\t\t\"author\":  \"user\",\n\t\t\t\"private\": false,\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(SPACE))).\n\t\tMatchParam(\"author\", \"user\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"6\",\n\t\t\t\t\"id\":      \"user/space\",\n\t\t\t\t\"modelId\": \"user/space\",\n\t\t\t\t\"private\": false,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/spaces/user/space\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"user/space\",\n\t\t\t\"author\":  \"user\",\n\t\t\t\"private\": false,\n\t\t})\n\n\terr = s.enumerate(context.Background())\n\tassert.Nil(t, err)\n\n\tmodelGitURL := \"https://huggingface.co/user/model.git\"\n\tdatasetGitURL := \"https://huggingface.co/datasets/user/dataset.git\"\n\tspaceGitURL := \"https://huggingface.co/spaces/user/space.git\"\n\n\tassert.Equal(t, []string{modelGitURL}, s.models)\n\tassert.Equal(t, []string{datasetGitURL}, s.datasets)\n\tassert.Equal(t, []string{spaceGitURL}, s.spaces)\n\n\tr, _ := s.repoInfoCache.get(modelGitURL)\n\tassert.Equal(t, r, repoInfo{\n\t\tvisibility:   source_metadatapb.Visibility_public,\n\t\tresourceType: MODEL,\n\t\towner:        \"user\",\n\t\tname:         \"model\",\n\t\tfullName:     \"user/model\",\n\t})\n\n\tr, _ = s.repoInfoCache.get(datasetGitURL)\n\tassert.Equal(t, r, repoInfo{\n\t\tvisibility:   source_metadatapb.Visibility_public,\n\t\tresourceType: DATASET,\n\t\towner:        \"user\",\n\t\tname:         \"dataset\",\n\t\tfullName:     \"user/dataset\",\n\t})\n\n\tr, _ = s.repoInfoCache.get(spaceGitURL)\n\tassert.Equal(t, r, repoInfo{\n\t\tvisibility:   source_metadatapb.Visibility_public,\n\t\tresourceType: SPACE,\n\t\towner:        \"user\",\n\t\tname:         \"space\",\n\t\tfullName:     \"user/space\",\n\t})\n}\n\nfunc TestEnumerateAuthorsUserSkipAll(t *testing.T) {\n\ts, conn := createTestSource(&sourcespb.Huggingface{\n\t\tEndpoint: \"https://huggingface.co\",\n\t\tCredential: &sourcespb.Huggingface_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tUsers:           []string{\"user\"},\n\t\tSkipAllModels:   true,\n\t\tSkipAllDatasets: true,\n\t\tSkipAllSpaces:   true,\n\t})\n\n\terr := s.Init(context.Background(), \"test - huggingface\", 0, 1337, false, conn, 1)\n\tassert.Nil(t, err)\n\n\tdefer gock.Off()\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(MODEL))).\n\t\tMatchParam(\"author\", \"user\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"2\",\n\t\t\t\t\"id\":      \"user/model\",\n\t\t\t\t\"modelId\": \"user/model\",\n\t\t\t\t\"private\": false,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/models/user/model\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"user/model\",\n\t\t\t\"author\":  \"user\",\n\t\t\t\"private\": false,\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(DATASET))).\n\t\tMatchParam(\"author\", \"user\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"4\",\n\t\t\t\t\"id\":      \"user/dataset\",\n\t\t\t\t\"modelId\": \"user/dataset\",\n\t\t\t\t\"private\": false,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/datasets/user/dataset\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"user/dataset\",\n\t\t\t\"author\":  \"user\",\n\t\t\t\"private\": false,\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(SPACE))).\n\t\tMatchParam(\"author\", \"user\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"6\",\n\t\t\t\t\"id\":      \"user/space\",\n\t\t\t\t\"modelId\": \"user/space\",\n\t\t\t\t\"private\": false,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/spaces/user/space\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"user/space\",\n\t\t\t\"author\":  \"user\",\n\t\t\t\"private\": false,\n\t\t})\n\n\terr = s.enumerate(context.Background())\n\tassert.Nil(t, err)\n\n\tmodelGitURL := \"https://huggingface.co/user/model.git\"\n\tdatasetGitURL := \"https://huggingface.co/datasets/user/dataset.git\"\n\tspaceGitURL := \"https://huggingface.co/spaces/user/space.git\"\n\n\tassert.Equal(t, []string{}, s.models)\n\tassert.Equal(t, []string{}, s.datasets)\n\tassert.Equal(t, []string{}, s.spaces)\n\n\tr, _ := s.repoInfoCache.get(modelGitURL)\n\tassert.Equal(t, r, repoInfo{})\n\n\tr, _ = s.repoInfoCache.get(datasetGitURL)\n\tassert.Equal(t, r, repoInfo{})\n\n\tr, _ = s.repoInfoCache.get(spaceGitURL)\n\tassert.Equal(t, r, repoInfo{})\n}\n\nfunc TestEnumerateAuthorsUserIgnores(t *testing.T) {\n\ts, conn := createTestSource(&sourcespb.Huggingface{\n\t\tEndpoint: \"https://huggingface.co\",\n\t\tCredential: &sourcespb.Huggingface_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tUsers:          []string{\"user\"},\n\t\tIgnoreModels:   []string{\"user/model\"},\n\t\tIgnoreDatasets: []string{\"user/dataset\"},\n\t\tIgnoreSpaces:   []string{\"user/space\"},\n\t})\n\n\terr := s.Init(context.Background(), \"test - huggingface\", 0, 1337, false, conn, 1)\n\tassert.Nil(t, err)\n\n\tdefer gock.Off()\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(MODEL))).\n\t\tMatchParam(\"author\", \"user\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"2\",\n\t\t\t\t\"id\":      \"user/model\",\n\t\t\t\t\"modelId\": \"user/model\",\n\t\t\t\t\"private\": false,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/models/user/model\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"user/model\",\n\t\t\t\"author\":  \"user\",\n\t\t\t\"private\": false,\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(DATASET))).\n\t\tMatchParam(\"author\", \"user\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"4\",\n\t\t\t\t\"id\":      \"user/dataset\",\n\t\t\t\t\"modelId\": \"user/dataset\",\n\t\t\t\t\"private\": false,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/datasets/user/dataset\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"user/dataset\",\n\t\t\t\"author\":  \"user\",\n\t\t\t\"private\": false,\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(fmt.Sprintf(\"/%s/%s\", APIRoute, getResourceAPIPath(SPACE))).\n\t\tMatchParam(\"author\", \"user\").\n\t\tMatchParam(\"limit\", \"1000\").\n\t\tReply(200).\n\t\tJSON([]map[string]interface{}{\n\t\t\t{\n\t\t\t\t\"_id\":     \"6\",\n\t\t\t\t\"id\":      \"user/space\",\n\t\t\t\t\"modelId\": \"user/space\",\n\t\t\t\t\"private\": false,\n\t\t\t},\n\t\t})\n\n\tgock.New(\"https://huggingface.co\").\n\t\tGet(\"/api/spaces/user/space\").\n\t\tReply(200).\n\t\tJSON(map[string]interface{}{\n\t\t\t\"id\":      \"user/space\",\n\t\t\t\"author\":  \"user\",\n\t\t\t\"private\": false,\n\t\t})\n\n\terr = s.enumerate(context.Background())\n\tassert.Nil(t, err)\n\n\tmodelGitURL := \"https://huggingface.co/user/model.git\"\n\tdatasetGitURL := \"https://huggingface.co/datasets/user/dataset.git\"\n\tspaceGitURL := \"https://huggingface.co/spaces/user/space.git\"\n\n\tassert.Equal(t, []string{}, s.models)\n\tassert.Equal(t, []string{}, s.datasets)\n\tassert.Equal(t, []string{}, s.spaces)\n\n\tr, _ := s.repoInfoCache.get(modelGitURL)\n\tassert.Equal(t, r, repoInfo{})\n\n\tr, _ = s.repoInfoCache.get(datasetGitURL)\n\tassert.Equal(t, r, repoInfo{})\n\n\tr, _ = s.repoInfoCache.get(spaceGitURL)\n\tassert.Equal(t, r, repoInfo{})\n}\n\nfunc TestVerifySlashSeparatedStrings(t *testing.T) {\n\tassert.Error(t, verifySlashSeparatedStrings([]string{\"orgmodel\"}))\n\tassert.NoError(t, verifySlashSeparatedStrings([]string{\"org/model\"}))\n\tassert.Error(t, verifySlashSeparatedStrings([]string{\"org/model\", \"orgmodel2\"}))\n\tassert.NoError(t, verifySlashSeparatedStrings([]string{\"org/model\", \"org/model2\"}))\n}\n\nfunc TestValidateIgnoreIncludeReposRepos(t *testing.T) {\n\ts, conn := createTestSource(&sourcespb.Huggingface{\n\t\tEndpoint: \"https://huggingface.co\",\n\t\tCredential: &sourcespb.Huggingface_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tOrganizations:  []string{\"org\"},\n\t\tIgnoreModels:   []string{\"orgmodel1\"},\n\t\tIgnoreDatasets: []string{\"org/dataset1\"},\n\t\tIgnoreSpaces:   []string{\"org/space1\"},\n\t})\n\n\terr := s.Init(context.Background(), \"test - huggingface\", 0, 1337, false, conn, 1)\n\tassert.NotNil(t, err)\n}\n\nfunc TestValidateIgnoreIncludeReposDatasets(t *testing.T) {\n\ts, conn := createTestSource(&sourcespb.Huggingface{\n\t\tEndpoint: \"https://huggingface.co\",\n\t\tCredential: &sourcespb.Huggingface_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tOrganizations:  []string{\"org\"},\n\t\tIgnoreModels:   []string{\"org/model1\"},\n\t\tIgnoreDatasets: []string{\"orgdataset1\"},\n\t\tIgnoreSpaces:   []string{\"org/space1\"},\n\t})\n\n\terr := s.Init(context.Background(), \"test - huggingface\", 0, 1337, false, conn, 1)\n\tassert.NotNil(t, err)\n}\n\nfunc TestValidateIgnoreIncludeReposSpaces(t *testing.T) {\n\ts, conn := createTestSource(&sourcespb.Huggingface{\n\t\tEndpoint: \"https://huggingface.co\",\n\t\tCredential: &sourcespb.Huggingface_Token{\n\t\t\tToken: \"super secret token\",\n\t\t},\n\t\tOrganizations:  []string{\"org\"},\n\t\tIgnoreModels:   []string{\"org/model1\"},\n\t\tIgnoreDatasets: []string{\"org/dataset1\"},\n\t\tIgnoreSpaces:   []string{\"orgspace1\"},\n\t})\n\n\terr := s.Init(context.Background(), \"test - huggingface\", 0, 1337, false, conn, 1)\n\tassert.NotNil(t, err)\n}\n\n// repeat this with all skip flags, and then include/ignore flags\n"
  },
  {
    "path": "pkg/sources/jenkins/jenkins_integration_test.go",
    "content": "//go:build localInfra\n// +build localInfra\n\npackage jenkins\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/context\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"google.golang.org/protobuf/types/known/anypb\"\n\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/common\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/credentialspb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb\"\n\t\"github.com/trufflesecurity/trufflehog/v3/pkg/sources\"\n)\n\n// Here’s how to test against Jenkins:\n\n// Forward port from thog-dev_us-central1_dev-c1:\n// kubectl --namespace jenkins port-forward svc/jenkins 8080:8080\n\n// go test -timeout 10s -tags localInfra -run '^TestSource_Scan$' github.com/trufflesecurity/thog/scanner/pkg/sources/jenkins\n\nfunc TestSource_Scan(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\n\tsecret, err := common.GetTestSecret(ctx)\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\tjenkinsToken := secret.MustGetField(\"JENKINS_TOKEN\")\n\n\ttype init struct {\n\t\tname       string\n\t\tverify     bool\n\t\tconnection *sourcespb.Jenkins\n\t}\n\ttests := []struct {\n\t\tname               string\n\t\tinit               init\n\t\twantSourceMetadata *source_metadatapb.MetaData\n\t\twantErr            bool\n\t}{\n\t\t{\n\t\t\tname: \"get a chunk\",\n\t\t\tinit: init{\n\t\t\t\tname: \"this repo\",\n\t\t\t\tconnection: &sourcespb.Jenkins{\n\t\t\t\t\tEndpoint: \"http://localhost:8080\",\n\t\t\t\t\tCredential: &sourcespb.Jenkins_BasicAuth{\n\t\t\t\t\t\tBasicAuth: &credentialspb.BasicAuth{\n\t\t\t\t\t\t\tUsername: \"admin\",\n\t\t\t\t\t\t\tPassword: jenkinsToken,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantSourceMetadata: &source_metadatapb.MetaData{\n\t\t\t\tData: &source_metadatapb.MetaData_Jenkins{Jenkins: &source_metadatapb.Jenkins{\n\t\t\t\t\tProjectName: \"within-1-subfolder\",\n\t\t\t\t\tBuildNumber: 1,\n\t\t\t\t\tLink:        \"http://localhost:8080/job/folder1/job/sub-folder1/job/within-1-subfolder/1/consoleText\",\n\t\t\t\t}},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Source{}\n\n\t\t\tconn, err := anypb.New(tt.init.connection)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\terr = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 5)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Source.Init() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tchunksCh := make(chan *sources.Chunk, 1)\n\t\t\t// TODO: this is kind of bad, if it errors right away we don't see it as a test failure.\n\t\t\t// Debugging this usually requires setting a breakpoint on L78 and running test w/ debug.\n\t\t\tgo func() {\n\t\t\t\terr = s.Chunks(ctx, chunksCh)\n\t\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\t\tt.Errorf(\"Source.Chunks() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tgotChunk := <-chunksCh\n\t\t\tif diff := pretty.Compare(gotChunk.SourceMetadata, tt.wantSourceMetadata); diff != \"\" {\n\t\t\t\tt.Errorf(\"Source.Chunks() %s diff: (-got +want)\\n%s\", tt.name, diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSource_ExpectBuilds(t *testing.T) {\n\tctx, cancel := context.WithTimeout(context.Background(), time.Second*5)\n\tdefer cancel()\n\n\tsecret, err := common.GetTestSecret(ctx)\n\tif err != nil {\n\t\tt.Fatal(fmt.Errorf(\"failed to access secret: %v\", err))\n\t}\n\tjenkinsToken := secret.MustGetField(\"JENKINS_TOKEN\")\n\n\ttype init struct {\n\t\tname       string\n\t\tverify     bool\n\t\tconnection *sourcespb.Jenkins\n\t}\n\ttests := []struct {\n\t\tname                string\n\t\tinit                init\n\t\twantSourceMetadatas map[string]bool\n\t\twantErr             bool\n\t}{\n\t\t{\n\t\t\tname: \"get a chunk\",\n\t\t\tinit: init{\n\t\t\t\tname: \"this repo\",\n\t\t\t\tconnection: &sourcespb.Jenkins{\n\t\t\t\t\tEndpoint: \"http://localhost:8080\",\n\t\t\t\t\tCredential: &sourcespb.Jenkins_BasicAuth{\n\t\t\t\t\t\tBasicAuth: &credentialspb.BasicAuth{\n\t\t\t\t\t\t\tUsername: \"admin\",\n\t\t\t\t\t\t\tPassword: jenkinsToken,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tverify: true,\n\t\t\t},\n\t\t\twantSourceMetadatas: map[string]bool{\n\t\t\t\t\"http://localhost:8080/job/hon-test/1/consoleText\":                  false,\n\t\t\t\t\"http://localhost:8080/job/steeeve-freestyle-project/6/consoleText\": false,\n\t\t\t\t\"http://localhost:8080/job/steeeve-freestyle-project/5/consoleText\": false,\n\t\t\t\t// Seems to 404? I assumed it would be there.\n\t\t\t\t// \"http://localhost:8080/job/steeeve-freestyle-project/4/consoleText\":           false,\n\t\t\t\t\"http://localhost:8080/job/steeeve-freestyle-project/3/consoleText\":                      false,\n\t\t\t\t\"http://localhost:8080/job/steeeve-freestyle-project/2/consoleText\":                      false,\n\t\t\t\t\"http://localhost:8080/job/steeeve-freestyle-project/1/consoleText\":                      false,\n\t\t\t\t\"http://localhost:8080/job/folder1/job/within-1-folder/1/consoleText\":                    false,\n\t\t\t\t\"http://localhost:8080/job/folder1/job/sub-folder1/job/within-1-subfolder/1/consoleText\": false,\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := Source{}\n\n\t\t\tconn, err := anypb.New(tt.init.connection)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\terr = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 5)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Fatalf(\"Source.Init() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\tchunksCh := make(chan *sources.Chunk, 100)\n\n\t\t\terr = s.Chunks(ctx, chunksCh)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Fatalf(\"Source.Chunks() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\tclose(chunksCh)\n\n\t\t\tfor gotChunk := range chunksCh {\n\t\t\t\tif _, ok := tt.wantSourceMetadatas[gotChunk.SourceMetadata.GetJenkins().Link]; !ok {\n\t\t\t\t\tt.Errorf(\"encountered unexpected build: %s\", gotChunk.SourceMetadata.GetJenkins().Link)\n\t\t\t\t}\n\t\t\t\ttt.wantSourceMetadatas[gotChunk.SourceMetadata.GetJenkins().Link] = true\n\t\t\t}\n\n\t\t\tfor k, found := range tt.wantSourceMetadatas {\n\t\t\t\tif !found {\n\t\t\t\t\tt.Errorf(\"did not encounter expected build: %s\", k)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  }
]